From 7d4fd77493a19b5d28e11c43257f28e61845ac8a Mon Sep 17 00:00:00 2001 From: Jens Kuerschner Date: Mon, 11 Mar 2024 09:50:37 +0100 Subject: [PATCH 1/2] fixes --- src/atcb-generate.js | 2 +- src/atcb-init.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/atcb-generate.js b/src/atcb-generate.js index 5568490b..12c316b2 100644 --- a/src/atcb-generate.js +++ b/src/atcb-generate.js @@ -701,7 +701,7 @@ function atcb_generate_date_button(data, parent, subEvent = 'all') { if (data.dates[`${subEvent}`].description !== '' && fullTimeInfo.length === 0 && (!data.recurrence || data.recurrence === '')) { const btnDescription = document.createElement('div'); btnDescription.classList.add('atcb-date-btn-content'); - btnDescription.textContent = data.dates[`${subEvent}`].description; + btnDescription.textContent = data.dates[`${subEvent}`].descriptionHtmlFree; btnDescription.style.cssText = 'overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;'; btnDetails.append(btnDescription); } else { diff --git a/src/atcb-init.js b/src/atcb-init.js index 549cb77d..11c51967 100644 --- a/src/atcb-init.js +++ b/src/atcb-init.js @@ -178,12 +178,13 @@ if (atcbIsBrowser()) { this.style.visibility = 'visible'; this.style.opacity = '1'; this.style.position = 'relative'; - return atcb_build_button(this.shadowRoot, this.data); + await atcb_build_button(this.shadowRoot, this.data); + return true; } catch (e) { if (this.debug) { atcb_render_debug_msg(this.shadowRoot, e); } - return; + return false; } } } From 7500a14a2f6d0f9973ae38b47b9fa48fa2926ccc Mon Sep 17 00:00:00 2001 From: Jens Kuerschner Date: Tue, 12 Mar 2024 09:17:21 +0100 Subject: [PATCH 2/2] fixes --- assets/css/atcb-3d.css | 2 +- assets/css/atcb-date.css | 2 +- assets/css/atcb-flat.css | 2 +- assets/css/atcb-neumorphism.css | 2 +- assets/css/atcb-round.css | 2 +- assets/css/atcb-text.css | 2 +- assets/css/atcb.css | 2 +- demo/components/footer.vue | 2 +- demo/nuxt.config.ts | 8 +- demo/pages/advanced-use.vue | 8 +- demo/public/atcb.css | 2 +- package-lock.json | 4 +- package.json | 2 +- src/atcb-control.js | 2 +- src/atcb-decorate.js | 2 +- src/atcb-event.js | 2 +- src/atcb-generate-pro.js | 2 +- src/atcb-generate-rich-data.js | 6 +- src/atcb-generate.js | 2 +- src/atcb-globals.js | 4 +- src/atcb-i18n.js | 2 +- src/atcb-init.js | 79 ++++---- src/atcb-links.js | 6 +- src/atcb-util.js | 6 +- src/atcb-validate.js | 310 +++++++++++++------------------- 25 files changed, 208 insertions(+), 255 deletions(-) diff --git a/assets/css/atcb-3d.css b/assets/css/atcb-3d.css index dfe4c289..c58a11e1 100644 --- a/assets/css/atcb-3d.css +++ b/assets/css/atcb-3d.css @@ -5,7 +5,7 @@ * * Style: 3D * - * Version: 2.6.1 + * Version: 2.6.2 * Creator: Jens Kuerschner (https://jekuer.com) * Project: https://github.com/add2cal/add-to-calendar-button * License: Elastic License 2.0 (ELv2) (https://github.com/add2cal/add-to-calendar-button/blob/main/LICENSE.txt) diff --git a/assets/css/atcb-date.css b/assets/css/atcb-date.css index 8244a99e..0c239410 100644 --- a/assets/css/atcb-date.css +++ b/assets/css/atcb-date.css @@ -5,7 +5,7 @@ * * Style: Date * - * Version: 2.6.1 + * Version: 2.6.2 * Creator: Jens Kuerschner (https://jekuer.com) * Project: https://github.com/add2cal/add-to-calendar-button * License: Elastic License 2.0 (ELv2) (https://github.com/add2cal/add-to-calendar-button/blob/main/LICENSE.txt) diff --git a/assets/css/atcb-flat.css b/assets/css/atcb-flat.css index bd74919f..70291a36 100644 --- a/assets/css/atcb-flat.css +++ b/assets/css/atcb-flat.css @@ -5,7 +5,7 @@ * * Style: Flat * - * Version: 2.6.1 + * Version: 2.6.2 * Creator: Jens Kuerschner (https://jekuer.com) * Project: https://github.com/add2cal/add-to-calendar-button * License: Elastic License 2.0 (ELv2) (https://github.com/add2cal/add-to-calendar-button/blob/main/LICENSE.txt) diff --git a/assets/css/atcb-neumorphism.css b/assets/css/atcb-neumorphism.css index de3bced5..821ea553 100644 --- a/assets/css/atcb-neumorphism.css +++ b/assets/css/atcb-neumorphism.css @@ -5,7 +5,7 @@ * * Style: Neumorphism * - * Version: 2.6.1 + * Version: 2.6.2 * Creator: Jens Kuerschner (https://jekuer.com) * Project: https://github.com/add2cal/add-to-calendar-button * License: Elastic License 2.0 (ELv2) (https://github.com/add2cal/add-to-calendar-button/blob/main/LICENSE.txt) diff --git a/assets/css/atcb-round.css b/assets/css/atcb-round.css index 39be17a8..1c4cf7a0 100644 --- a/assets/css/atcb-round.css +++ b/assets/css/atcb-round.css @@ -5,7 +5,7 @@ * * Style: Round * - * Version: 2.6.1 + * Version: 2.6.2 * Creator: Jens Kuerschner (https://jekuer.com) * Project: https://github.com/add2cal/add-to-calendar-button * License: Elastic License 2.0 (ELv2) (https://github.com/add2cal/add-to-calendar-button/blob/main/LICENSE.txt) diff --git a/assets/css/atcb-text.css b/assets/css/atcb-text.css index 3df05b25..ac8bedeb 100644 --- a/assets/css/atcb-text.css +++ b/assets/css/atcb-text.css @@ -5,7 +5,7 @@ * * Style: Text * - * Version: 2.6.1 + * Version: 2.6.2 * Creator: Jens Kuerschner (https://jekuer.com) * Project: https://github.com/add2cal/add-to-calendar-button * License: Elastic License 2.0 (ELv2) (https://github.com/add2cal/add-to-calendar-button/blob/main/LICENSE.txt) diff --git a/assets/css/atcb.css b/assets/css/atcb.css index 5f988a70..3c9adf96 100644 --- a/assets/css/atcb.css +++ b/assets/css/atcb.css @@ -5,7 +5,7 @@ * * Style: Default * - * Version: 2.6.1 + * Version: 2.6.2 * Creator: Jens Kuerschner (https://jekuer.com) * Project: https://github.com/add2cal/add-to-calendar-button * License: Elastic License 2.0 (ELv2) (https://github.com/add2cal/add-to-calendar-button/blob/main/LICENSE.txt) diff --git a/demo/components/footer.vue b/demo/components/footer.vue index 5a5cc6e9..6e4cfd6a 100644 --- a/demo/components/footer.vue +++ b/demo/components/footer.vue @@ -71,7 +71,7 @@ function topFunction() {
© {{new Date().getFullYear()}} - , Current Version: 2.6.1 + , Current Version: 2.6.2
- +
@@ -374,7 +374,7 @@ onUnmounted(() => {
- +
diff --git a/demo/public/atcb.css b/demo/public/atcb.css index 0cd84abb..9fd926a0 100644 --- a/demo/public/atcb.css +++ b/demo/public/atcb.css @@ -5,7 +5,7 @@ * * Style: Default * - * Version: 2.6.1 + * Version: 2.6.2 * Creator: Jens Kuerschner (https://jekuer.com) * Project: https://github.com/add2cal/add-to-calendar-button * License: Elastic License 2.0 (ELv2) (https://github.com/add2cal/add-to-calendar-button/blob/main/LICENSE.txt) diff --git a/package-lock.json b/package-lock.json index 80f3f924..73070186 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "add-to-calendar-button", - "version": "2.6.1", + "version": "2.6.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "add-to-calendar-button", - "version": "2.6.1", + "version": "2.6.2", "license": "ELv2", "dependencies": { "timezones-ical-library": "^1.8.2" diff --git a/package.json b/package.json index beb49ddf..1a02c48f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "add-to-calendar-button", - "version": "2.6.1", + "version": "2.6.2", "engines": { "node": ">=18.17.0", "npm": ">=9.6.7" diff --git a/src/atcb-control.js b/src/atcb-control.js index 62895bb2..e10e90fa 100644 --- a/src/atcb-control.js +++ b/src/atcb-control.js @@ -3,7 +3,7 @@ * Add to Calendar Button * ++++++++++++++++++++++ * - * Version: 2.6.1 + * Version: 2.6.2 * Creator: Jens Kuerschner (https://jekuer.com) * Project: https://github.com/add2cal/add-to-calendar-button * License: Elastic License 2.0 (ELv2) (https://github.com/add2cal/add-to-calendar-button/blob/main/LICENSE.txt) diff --git a/src/atcb-decorate.js b/src/atcb-decorate.js index 67448424..99a4f640 100644 --- a/src/atcb-decorate.js +++ b/src/atcb-decorate.js @@ -3,7 +3,7 @@ * Add to Calendar Button * ++++++++++++++++++++++ * - * Version: 2.6.1 + * Version: 2.6.2 * Creator: Jens Kuerschner (https://jekuer.com) * Project: https://github.com/add2cal/add-to-calendar-button * License: Elastic License 2.0 (ELv2) (https://github.com/add2cal/add-to-calendar-button/blob/main/LICENSE.txt) diff --git a/src/atcb-event.js b/src/atcb-event.js index b1dc0353..772de400 100644 --- a/src/atcb-event.js +++ b/src/atcb-event.js @@ -3,7 +3,7 @@ * Add to Calendar Button * ++++++++++++++++++++++ * - * Version: 2.6.1 + * Version: 2.6.2 * Creator: Jens Kuerschner (https://jekuer.com) * Project: https://github.com/add2cal/add-to-calendar-button * License: Elastic License 2.0 (ELv2) (https://github.com/add2cal/add-to-calendar-button/blob/main/LICENSE.txt) diff --git a/src/atcb-generate-pro.js b/src/atcb-generate-pro.js index 0d6a4dbd..a56e8962 100644 --- a/src/atcb-generate-pro.js +++ b/src/atcb-generate-pro.js @@ -3,7 +3,7 @@ * Add to Calendar Button * ++++++++++++++++++++++ * - * Version: 2.6.1 + * Version: 2.6.2 * Creator: Jens Kuerschner (https://jekuer.com) * Project: https://github.com/add2cal/add-to-calendar-button * License: Elastic License 2.0 (ELv2) (https://github.com/add2cal/add-to-calendar-button/blob/main/LICENSE.txt) diff --git a/src/atcb-generate-rich-data.js b/src/atcb-generate-rich-data.js index 37e350ec..9b7a1776 100644 --- a/src/atcb-generate-rich-data.js +++ b/src/atcb-generate-rich-data.js @@ -3,7 +3,7 @@ * Add to Calendar Button * ++++++++++++++++++++++ * - * Version: 2.6.1 + * Version: 2.6.2 * Creator: Jens Kuerschner (https://jekuer.com) * Project: https://github.com/add2cal/add-to-calendar-button * License: Elastic License 2.0 (ELv2) (https://github.com/add2cal/add-to-calendar-button/blob/main/LICENSE.txt) @@ -76,10 +76,6 @@ function atcb_generate_rich_data(data, parent) { } } } - } else { - imageData.push('"https://add-to-calendar-button.com/assets/img/1x1.png"'); - imageData.push('"https://add-to-calendar-button.com/assets/img/4x3.png"'); - imageData.push('"https://add-to-calendar-button.com/assets/img/16x9.png"'); } if (imageData.length > 0) { schemaContent.push('"image":[\r\n' + imageData.join(',\r\n') + ']'); diff --git a/src/atcb-generate.js b/src/atcb-generate.js index 12c316b2..cb24d495 100644 --- a/src/atcb-generate.js +++ b/src/atcb-generate.js @@ -3,7 +3,7 @@ * Add to Calendar Button * ++++++++++++++++++++++ * - * Version: 2.6.1 + * Version: 2.6.2 * Creator: Jens Kuerschner (https://jekuer.com) * Project: https://github.com/add2cal/add-to-calendar-button * License: Elastic License 2.0 (ELv2) (https://github.com/add2cal/add-to-calendar-button/blob/main/LICENSE.txt) diff --git a/src/atcb-globals.js b/src/atcb-globals.js index d8720aea..ee50a865 100644 --- a/src/atcb-globals.js +++ b/src/atcb-globals.js @@ -5,14 +5,14 @@ * Add to Calendar Button * ++++++++++++++++++++++ * - * Version: 2.6.1 + * Version: 2.6.2 * Creator: Jens Kuerschner (https://jekuer.com) * Project: https://github.com/add2cal/add-to-calendar-button * License: Elastic License 2.0 (ELv2) (https://github.com/add2cal/add-to-calendar-button/blob/main/LICENSE.txt) * Note: DO NOT REMOVE THE COPYRIGHT NOTICE ABOVE! * */ -const atcbVersion = '2.6.1'; +const atcbVersion = '2.6.2'; // DEFINING CSS const atcbCssTemplate = {}; diff --git a/src/atcb-i18n.js b/src/atcb-i18n.js index 718d00ae..e1a066e1 100644 --- a/src/atcb-i18n.js +++ b/src/atcb-i18n.js @@ -3,7 +3,7 @@ * Add to Calendar Button * ++++++++++++++++++++++ * - * Version: 2.6.1 + * Version: 2.6.2 * Creator: Jens Kuerschner (https://jekuer.com) * Project: https://github.com/add2cal/add-to-calendar-button * License: Elastic License 2.0 (ELv2) (https://github.com/add2cal/add-to-calendar-button/blob/main/LICENSE.txt) diff --git a/src/atcb-init.js b/src/atcb-init.js index 11c51967..0c2850e9 100644 --- a/src/atcb-init.js +++ b/src/atcb-init.js @@ -3,7 +3,7 @@ * Add to Calendar Button * ++++++++++++++++++++++ * - * Version: 2.6.1 + * Version: 2.6.2 * Creator: Jens Kuerschner (https://jekuer.com) * Project: https://github.com/add2cal/add-to-calendar-button * License: Elastic License 2.0 (ELv2) (https://github.com/add2cal/add-to-calendar-button/blob/main/LICENSE.txt) @@ -62,9 +62,10 @@ if (atcbIsBrowser()) { this.data.proKey = ''; // if no data yet, we try reading attributes or the innerHTML of the host element try { - this.data = atcb_process_inline_data(this, this.debug); + this.data = await atcb_process_inline_data(this, this.debug); } catch (e) { if (this.debug) { + console.error(e); atcb_render_debug_msg(this.shadowRoot, e); } this.loaded = true; @@ -130,9 +131,10 @@ if (atcbIsBrowser()) { } if (!this.data.name || this.data.name === '') { try { - this.data = atcb_process_inline_data(this, this.debug); + this.data = await atcb_process_inline_data(this, this.debug); } catch (e) { if (this.debug) { + console.error(e); atcb_render_debug_msg(this.shadowRoot, e); } return; @@ -182,6 +184,7 @@ if (atcbIsBrowser()) { return true; } catch (e) { if (this.debug) { + console.error(e.message ? e.message : e); atcb_render_debug_msg(this.shadowRoot, e); } return false; @@ -195,29 +198,29 @@ if (atcbIsBrowser()) { } // process inline data -function atcb_process_inline_data(el, debug = false) { - let data = atcb_read_attributes(el); - // if we receive no or not enough data that way, we try to get a potential JSON from the innerHTML - if (!atcb_check_required(data)) { - const slotInput = el.innerHTML; - const atcbJsonInput = (function () { - if (slotInput !== '') { - try { - return JSON.parse(atcb_secure_content(slotInput.replace(/(\\r\\n|\\n|\\r)/g, ''), false)); - } catch (e) { - throw new Error('Add to Calendar Button generation failed: JSON content provided, but badly formatted (in doubt, try some tool like https://jsonformatter.org/ to validate).\r\nError message: ' + e); - } - } - return null; - })(); - // abort on missing input data - if (!atcbJsonInput || (Array.isArray(atcbJsonInput) && atcbJsonInput.length === 0) || (typeof atcbJsonInput === 'object' && Object.keys(atcbJsonInput).length === 0)) { +async function atcb_process_inline_data(el, debug = false) { + let data; + try { + // Attempt to read attributes directly and validate + data = atcb_read_attributes(el); + await atcb_check_required(data); + } catch (e) { + // If the above fails, try to parse and validate JSON from innerHTML + const slotInput = el.innerHTML.trim(); + if (!slotInput) { + throw new Error('Add to Calendar Button generation failed: No data provided.'); + } + try { + const atcbJsonInput = JSON.parse(atcb_secure_content(slotInput.replace(/(\r\n|\n|\r)/g, ''), false)); + await atcb_check_required(atcbJsonInput); + data = atcbJsonInput; + } catch (jsonError) { + // Log detailed error for debugging if (debug) { - console.error(data.validationError); + console.error(jsonError); } throw new Error('Add to Calendar Button generation failed: no data provided or missing required fields - see console logs for details'); } - data = atcbJsonInput; } return data; } @@ -282,9 +285,10 @@ function atcb_read_attributes(el, params = atcbWcParams) { // build the button async function atcb_build_button(host, data) { - // Rewrite dynamic dates, standardize line breaks and transform urls in the description - data = await atcb_decorate_data(data); - if (atcb_validate(data)) { + try { + // Rewrite dynamic dates, standardize line breaks and transform urls in the description + data = await atcb_decorate_data(data); + await atcb_validate(data); const rootObj = host.querySelector('.atcb-initialized'); // ... and on success, load css and generate the button atcb_set_light_mode(host, data); @@ -312,11 +316,10 @@ async function atcb_build_button(host, data) { } // log event atcb_log_event('initialization', data.identifier, data.identifier); - } else if (data.debug) { - console.error(data.validationError); - throw new Error(data.validationError); + return true; + } catch (e) { + throw new Error(e.message); } - return true; } // destroy the button @@ -538,9 +541,13 @@ async function atcb_action(inputData, triggerElement, keyboardTrigger = false) { })(); // decorate & validate data data.debug = data.debug === 'true'; - if (!atcb_check_required(data)) { - console.error(data.validationError); - return; + try { + await atcb_check_required(data); + } catch (e) { + if (data.debug) { + console.error(e); + } + throw new Error('Add to Calendar Button generation failed: no data provided or missing required fields - see console logs for details'); } data = await atcb_decorate_data(data); let root = document.body; @@ -569,9 +576,11 @@ async function atcb_action(inputData, triggerElement, keyboardTrigger = false) { // if no button is defined, fallback to listStyle "modal" in any case data.listStyle = 'modal'; } - if (!atcb_validate(data)) { - console.error(data.validationError); - return; + try { + await atcb_validate(data); + } catch (e) { + console.error(e); + return false; } // determine whether we are looking for the 1-option case (also with buttonsList) const oneOption = (function () { diff --git a/src/atcb-links.js b/src/atcb-links.js index 61a51408..73de9fca 100644 --- a/src/atcb-links.js +++ b/src/atcb-links.js @@ -3,7 +3,7 @@ * Add to Calendar Button * ++++++++++++++++++++++ * - * Version: 2.6.1 + * Version: 2.6.2 * Creator: Jens Kuerschner (https://jekuer.com) * Project: https://github.com/add2cal/add-to-calendar-button * License: Elastic License 2.0 (ELv2) (https://github.com/add2cal/add-to-calendar-button/blob/main/LICENSE.txt) @@ -381,9 +381,9 @@ function atcb_generate_msteams(data, date, subEvent = 'all') { } let locationString = ''; if (date.location != null && date.location != '') { - locationString = encodeURIComponent(date.location); - urlParts.push('location=' + locationString); + locationString = date.location; locationString += ' // '; // preparing the workaround putting the location into the description, since the native field is not supported yet + urlParts.push('location=' + encodeURIComponent(locationString)); } if (date.descriptionHtmlFree && date.descriptionHtmlFree != '') { // using descriptionHtmlFree instead of description, since Teams does not support html tags diff --git a/src/atcb-util.js b/src/atcb-util.js index df2bfffe..df12dcc5 100644 --- a/src/atcb-util.js +++ b/src/atcb-util.js @@ -3,7 +3,7 @@ * Add to Calendar Button * ++++++++++++++++++++++ * - * Version: 2.6.1 + * Version: 2.6.2 * Creator: Jens Kuerschner (https://jekuer.com) * Project: https://github.com/add2cal/add-to-calendar-button * License: Elastic License 2.0 (ELv2) (https://github.com/add2cal/add-to-calendar-button/blob/main/LICENSE.txt) @@ -458,10 +458,10 @@ function atcb_rewrite_html_elements(content, clear = false, iCalBreaks = false) content = content.replace(/(\[br\s?\/?\]|\{br\s?\/?\}|(\[\/p\](?=.))|(\{\/p\}(?=.)))/gi, ' '); } // remove any pseudo elements - content = content.replace(/\[url\](.+)\[\/url\]/gi, (match, p1) => { + content = content.replace(/\[url\](.+?)\[\/url\]/gi, (match, p1) => { return p1.split('|')[0]; }); - content = content.replace(/\{url\}(.+)\{\/url\}/gi, (match, p1) => { + content = content.replace(/\{url\}(.+?)\{\/url\}/gi, (match, p1) => { return p1.split('|')[0]; }); content = content.replace(/\[(|\/)(hr|p|b|strong|u|i|em|li|ul|ol|h\d)\]/gi, ''); diff --git a/src/atcb-validate.js b/src/atcb-validate.js index 48304a7c..ad2989db 100644 --- a/src/atcb-validate.js +++ b/src/atcb-validate.js @@ -3,7 +3,7 @@ * Add to Calendar Button * ++++++++++++++++++++++ * - * Version: 2.6.1 + * Version: 2.6.2 * Creator: Jens Kuerschner (https://jekuer.com) * Project: https://github.com/add2cal/add-to-calendar-button * License: Elastic License 2.0 (ELv2) (https://github.com/add2cal/add-to-calendar-button/blob/main/LICENSE.txt) @@ -16,16 +16,12 @@ import { atcbOptions } from './atcb-globals.js'; import { atcb_secure_url, atcb_validEmail, atcb_generate_uuid } from './atcb-util.js'; // CHECK FOR REQUIRED FIELDS -function atcb_check_required(data) { - if (data.validationError) { - data.validationError = null; - } +async function atcb_check_required(data) { // in this first step, we only check for the bare minimum, so we can abort early on really broken setups. We will do further validation later. // check for min required data (without "options") // name is always required on top level (in the multi-date setup this would be the name of the event series) if (!data.name || data.name === '') { - data.validationError = 'Add to Calendar Button generation failed: required name information missing'; - return false; + throw new Error('Add to Calendar Button generation failed: required name information missing'); } // regarding event specifics, we start by checking for multi-date setups if (data.dates != null && data.dates.length > 0) { @@ -38,8 +34,7 @@ function atcb_check_required(data) { (!requiredMultiFieldFlex.includes(`${field}`) && (!data.dates[`${i}`][`${field}`] || data.dates[`${i}`][`${field}`] === '')) || (requiredMultiFieldFlex.includes(`${field}`) && (!data.dates[`${i}`][`${field}`] || data.dates[`${i}`][`${field}`] === '') && (!data[`${field}`] || data[`${field}`] === '')) ) { - data.validationError = 'Add to Calendar Button generation failed: required setting missing [dates array object #' + (i + 1) + '/' + data.dates.length + '] => [' + field + ']'; - return false; + throw new Error('Add to Calendar Button generation failed: required setting missing [dates array object #' + (i + 1) + '/' + data.dates.length + '] => [' + field + ']'); } } return true; @@ -48,8 +43,7 @@ function atcb_check_required(data) { const requiredSingleField = ['startDate']; return requiredSingleField.every(function (field) { if (!data[`${field}`] || data[`${field}`] === '') { - data.validationError = 'Add to Calendar Button generation failed: required setting missing [' + field + ']'; - return false; + throw new Error('Add to Calendar Button generation failed: required setting missing [' + field + ']'); } return true; }); @@ -57,28 +51,29 @@ function atcb_check_required(data) { } // VALIDATE THE INPUT DATA -function atcb_validate(data) { - if (data.validationError) { - data.validationError = null; - } +async function atcb_validate(data) { const msgPrefix = 'Add to Calendar Button generation (' + data.identifier + ')'; - if (!atcb_validate_icsFile(data, msgPrefix)) return false; - if (!atcb_validate_buttonStyle(data, msgPrefix)) return false; - if (!atcb_validate_subscribe(data, msgPrefix)) return false; - if (!atcb_validate_created(data, msgPrefix)) return false; - if (!atcb_validate_updated(data, msgPrefix)) return false; - if (!atcb_validate_options(data, msgPrefix)) return false; - if (!atcb_validate_date_blocks(data, msgPrefix)) return false; - if (!atcb_validate_rrule(data, msgPrefix)) return false; - if (data.recurrence_simplyfied) { - if (!atcb_validate_rrule_simplyfied(data, msgPrefix)) return false; + try { + await atcb_validate_icsFile(data, msgPrefix); + await atcb_validate_buttonStyle(data, msgPrefix); + await atcb_validate_subscribe(data, msgPrefix); + await atcb_validate_created(data, msgPrefix); + await atcb_validate_updated(data, msgPrefix); + await atcb_validate_options(data, msgPrefix); + await atcb_validate_date_blocks(data, msgPrefix); + await atcb_validate_rrule(data, msgPrefix); + if (data.recurrence_simplyfied) { + await atcb_validate_rrule_simplyfied(data, msgPrefix); + } + // on passing the validation, return true + return true; + } catch (e) { + throw new Error(e.message); } - // on passing the validation, return true - return true; } // validate explicit ics file -function atcb_validate_icsFile(data, msgPrefix, i = '', msgSuffix = '') { +async function atcb_validate_icsFile(data, msgPrefix, i = '', msgSuffix = '') { const icsFileStr = (function () { if (i !== '' && data.dates[`${i}`].icsFile) { return data.dates[`${i}`].icsFile; @@ -90,139 +85,126 @@ function atcb_validate_icsFile(data, msgPrefix, i = '', msgSuffix = '') { })(); if (icsFileStr !== '') { if (!atcb_secure_url(icsFileStr, false) || (!data.icsFile.startsWith('https://') && !data.icsFile.startsWith('http://'))) { - data.validationError = msgPrefix + ' failed: explicit ics file path not valid' + msgSuffix; - return false; + throw new Error(msgPrefix + ' failed: explicit ics file path not valid' + msgSuffix); } } return true; } // validate button style -function atcb_validate_buttonStyle(data, msgPrefix) { +async function atcb_validate_buttonStyle(data, msgPrefix) { const availableStyles = ['default', '3d', 'flat', 'round', 'neumorphism', 'text', 'date', 'custom', 'none']; if (!availableStyles.includes(data.buttonStyle)) { - data.validationError = msgPrefix + ' failed: provided buttonStyle invalid'; - return false; + throw new Error(msgPrefix + ' failed: provided buttonStyle invalid'); } if (data.customCss && data.customCss !== '' && (!atcb_secure_url(data.customCss, false) || !/\.css($|\?)/.test(data.customCss))) { - data.validationError = msgPrefix + ' failed: customCss provided, but no valid url'; - return false; + throw new Error(msgPrefix + ' failed: customCss provided, but no valid url'); } if ((!data.customCss || data.customCss === '') && data.buttonStyle === 'custom') { - data.validationError = msgPrefix + ' failed: buttonStyle "custom" selected, but no customCss file provided'; - return false; + throw new Error(msgPrefix + ' failed: buttonStyle "custom" selected, but no customCss file provided'); } if (data.rsvp && (data.buttonStyle === 'date' || data.buttonStyle === 'none')) { - data.validationError = msgPrefix + ' failed: buttonStyle ' + data.buttonStyle + ' is not compatible with the RSVP functionality'; - return false; + throw new Error(msgPrefix + ' failed: buttonStyle ' + data.buttonStyle + ' is not compatible with the RSVP functionality'); } return true; } // validate the subscription functionality (requires an explicit ics file) -function atcb_validate_subscribe(data, msgPrefix) { +async function atcb_validate_subscribe(data, msgPrefix) { if (data.subscribe === true && (!data.icsFile || data.icsFile === '')) { - data.validationError = msgPrefix + ' failed: a subscription calendar requires a valid explicit ics file as well'; - return false; + throw new Error(msgPrefix + ' failed: a subscription calendar requires a valid explicit ics file as well'); } return true; } // validate created input -function atcb_validate_created(data, msgPrefix) { +async function atcb_validate_created(data, msgPrefix) { if (!/^\d{8}T\d{6}Z$/.test(data.created)) { - data.validationError = msgPrefix + ' failed: created date format not valid. Needs to be a full ISO-8601 UTC date and time string, formatted YYYYMMDDTHHMMSSZ'; - return false; + throw new Error(msgPrefix + ' failed: created date format not valid. Needs to be a full ISO-8601 UTC date and time string, formatted YYYYMMDDTHHMMSSZ'); } return true; } // validate updated input -function atcb_validate_updated(data, msgPrefix) { +async function atcb_validate_updated(data, msgPrefix) { if (!/^\d{8}T\d{6}Z$/.test(data.updated)) { - data.validationError = msgPrefix + ' failed: updated date format not valid. Needs to be a full ISO-8601 UTC date and time string, formatted YYYYMMDDTHHMMSSZ'; - return false; + throw new Error(msgPrefix + ' failed: updated date format not valid. Needs to be a full ISO-8601 UTC date and time string, formatted YYYYMMDDTHHMMSSZ'); } return true; } // validate options -function atcb_validate_options(data, msgPrefix) { +async function atcb_validate_options(data, msgPrefix) { // we double-check whether options are valid - if ( - !data.options.every(function (option) { - if (!atcbOptions.includes(option)) { - data.validationError = msgPrefix + ' failed: invalid option [' + option + ']'; - return false; - } - return true; - }) - ) { - return false; - } - return true; + const isValid = data.options.every((option) => { + if (!atcbOptions.includes(option)) { + throw new Error(`${msgPrefix} failed: invalid option [${option}]`); + } + return true; + }); + return isValid; } // next goes for all date blocks -function atcb_validate_date_blocks(data, msgPrefix) { - for (let i = 0; i < data.dates.length; i++) { - const msgSuffix = (function () { - if (data.dates.length === 1) { - return ''; - } else { - return ' [dates array object #' + (i + 1) + '/' + data.dates.length + '] '; - } - })(); - if (!atcb_validate_icsFile(data, msgPrefix, i, msgSuffix)) return false; - if (!atcb_validate_status(data, msgPrefix, i, msgSuffix)) return false; - if (!atcb_validate_availability(data, msgPrefix, i, msgSuffix)) return false; - if (!atcb_validate_organizer(data, msgPrefix, i, msgSuffix)) return false; - if (!atcb_validate_attendee(data, msgPrefix, i, msgSuffix)) return false; - if (!atcb_validate_uid(data, msgPrefix, i, msgSuffix)) return false; - if (!atcb_validate_sequence(data, msgPrefix, i, msgSuffix)) return false; - if (!atcb_validate_timezone(data, msgPrefix, i, msgSuffix)) return false; - if (!atcb_validate_datetime(data, msgPrefix, i, msgSuffix)) return false; +async function atcb_validate_date_blocks(data, msgPrefix) { + try { + for (let i = 0; i < data.dates.length; i++) { + const msgSuffix = (function () { + if (data.dates.length === 1) { + return ''; + } else { + return ' [dates array object #' + (i + 1) + '/' + data.dates.length + '] '; + } + })(); + await atcb_validate_icsFile(data, msgPrefix, i, msgSuffix); + await atcb_validate_status(data, msgPrefix, i, msgSuffix); + await atcb_validate_availability(data, msgPrefix, i, msgSuffix); + await atcb_validate_organizer(data, msgPrefix, i, msgSuffix); + await atcb_validate_attendee(data, msgPrefix, i, msgSuffix); + await atcb_validate_uid(data, msgPrefix, i, msgSuffix); + await atcb_validate_sequence(data, msgPrefix, i, msgSuffix); + await atcb_validate_timezone(data, msgPrefix, i, msgSuffix); + await atcb_validate_datetime(data, msgPrefix, i, msgSuffix); + } + return true; + } catch (e) { + throw new Error(e.message); } - return true; } // validate status -function atcb_validate_status(data, msgPrefix, i, msgSuffix) { +async function atcb_validate_status(data, msgPrefix, i, msgSuffix) { if (data.dates[`${i}`].status !== 'TENTATIVE' && data.dates[`${i}`].status !== 'CONFIRMED' && data.dates[`${i}`].status !== 'CANCELLED') { - data.validationError = msgPrefix + ' failed: event status needs to be TENTATIVE, CONFIRMED, or CANCELLED' + msgSuffix; - return false; + throw new Error(msgPrefix + ' failed: event status needs to be TENTATIVE, CONFIRMED, or CANCELLED' + msgSuffix); } return true; } // validate availability -function atcb_validate_availability(data, msgPrefix, i, msgSuffix) { +async function atcb_validate_availability(data, msgPrefix, i, msgSuffix) { if (data.dates[`${i}`].availability && data.dates[`${i}`].availability !== '' && data.dates[`${i}`].availability !== 'free' && data.dates[`${i}`].availability !== 'busy') { - data.validationError = msgPrefix + ' failed: event availability needs to be "free" or "busy"' + msgSuffix; - return false; + throw new Error(msgPrefix + ' failed: event availability needs to be "free" or "busy"' + msgSuffix); } return true; } // validate organizer -function atcb_validate_organizer(data, msgPrefix, i, msgSuffix) { +async function atcb_validate_organizer(data, msgPrefix, i, msgSuffix) { if (data.dates[`${i}`].organizer && data.dates[`${i}`].organizer !== '') { const organizerParts = data.dates[`${i}`].organizer.split('|'); if (organizerParts.length !== 2 || organizerParts[0].length > 50 || organizerParts[1].length > 80 || !atcb_validEmail(organizerParts[1])) { - data.validationError = msgPrefix + ' failed: organizer needs to match the schema "NAME|EMAIL" with a valid email address' + msgSuffix; - return false; + throw new Error(msgPrefix + ' failed: organizer needs to match the schema "NAME|EMAIL" with a valid email address' + msgSuffix); } } return true; } // validate attendee -function atcb_validate_attendee(data, msgPrefix, i, msgSuffix) { +async function atcb_validate_attendee(data, msgPrefix, i, msgSuffix) { if (data.dates[`${i}`].attendee && data.dates[`${i}`].attendee !== '') { // when setting the attendee, an organizer needs to be set as well if (!data.dates[`${i}`].organizer || data.dates[`${i}`].organizer === '') { - data.validationError = msgPrefix + ' failed: if an attendee is set, you also need to set the organizer' + msgSuffix; - return false; + throw new Error(msgPrefix + ' failed: if an attendee is set, you also need to set the organizer' + msgSuffix); } // additionally, we check the same format as with the organizer or simple email (only 1 attendee possible) const attendeeParts = data.dates[`${i}`].attendee.split('|'); @@ -230,15 +212,14 @@ function atcb_validate_attendee(data, msgPrefix, i, msgSuffix) { return true; } if (attendeeParts.length !== 2 || attendeeParts[0].length > 50 || attendeeParts[1].length > 80 || !atcb_validEmail(attendeeParts[1])) { - data.validationError = msgPrefix + ' failed: attendee needs to be a valid email address or match the schema "NAME|EMAIL" with EMAIL being a valid email address' + msgSuffix; - return false; + throw new Error(msgPrefix + ' failed: attendee needs to be a valid email address or match the schema "NAME|EMAIL" with EMAIL being a valid email address' + msgSuffix); } } return true; } // validate UID -function atcb_validate_uid(data, msgPrefix, i, msgSuffix) { +async function atcb_validate_uid(data, msgPrefix, i, msgSuffix) { // must have less then 255 characters and only allowes for alpha characters, numbers, and dashes; see https://icalendar.org/New-Properties-for-iCalendar-RFC-7986/5-3-uid-property.html if (!/^(\w|-){1,254}$/.test(data.dates[`${i}`].uid)) { if (data.debug) { @@ -254,7 +235,7 @@ function atcb_validate_uid(data, msgPrefix, i, msgSuffix) { } // validate sequence number if given and set it 0 if not -function atcb_validate_sequence(data, msgPrefix, i, msgSuffix) { +async function atcb_validate_sequence(data, msgPrefix, i, msgSuffix) { if (data.dates[`${i}`].sequence && (data.dates[`${i}`].sequence < 0 || data.dates[`${i}`].sequence % 1 !== 0)) { if (data.debug) { console.log(msgPrefix + ': sequence needs to be a full number >= 0. Used the default 0 instead' + msgSuffix); @@ -265,131 +246,98 @@ function atcb_validate_sequence(data, msgPrefix, i, msgSuffix) { } // validate time zone -function atcb_validate_timezone(data, msgPrefix, i, msgSuffix) { +async function atcb_validate_timezone(data, msgPrefix, i, msgSuffix) { const validTimeZones = tzlib_get_timezones(); if (!validTimeZones.includes(data.dates[`${i}`].timeZone)) { - data.validationError = msgPrefix + ' failed: invalid time zone given' + msgSuffix; - return false; + throw new Error(msgPrefix + ' failed: invalid time zone given' + msgSuffix); } return true; } // validate date and time -function atcb_validate_datetime(data, msgPrefix, i, msgSuffix) { +async function atcb_validate_datetime(data, msgPrefix, i, msgSuffix) { + const selectedDate = data.dates[`${i}`]; const dates = ['startDate', 'endDate']; - const newDate = dates; - // testing for right format first - //mind that during decoration, we already cleaned up dates, so 2022-44-55 would be also valid, since it gets adjusted automatically. However, we have some pre-validation there too - if ( - !dates.every(function (date) { - if (data.dates[`${i}`][`${date}`].length !== 10) { - data.validationError = msgPrefix + ' failed: date misspelled [-> YYYY-MM-DD]' + msgSuffix; - return false; - } - const dateParts = data.dates[`${i}`][`${date}`].split('-'); - if (dateParts.length < 3 || dateParts.length > 3) { - data.validationError = msgPrefix + ' failed: date misspelled [' + date + ': ' + data.dates[`${i}`][`${date}`] + ']' + msgSuffix; - return false; - } - // setting date for further time validation - newDate[`${date}`] = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]); - return true; - }) - ) { - return false; - } + // Initialize newDate as an object to store the parsed dates + const newDate = {}; + // Testing for the right format first + // Mind that during decoration, we already cleaned up dates, so 2022-44-55 would be also valid, since it gets adjusted automatically. However, we have some pre-validation there too + dates.forEach((date) => { + const dateString = selectedDate[`${date}`]; + if (dateString.length !== 10) { + throw new Error(`${msgPrefix} failed: date misspelled [-> YYYY-MM-DD]${msgSuffix}`); + } + const dateParts = dateString.split('-'); + if (dateParts.length !== 3) { + throw new Error(`${msgPrefix} failed: date misspelled [${date}: ${dateString}]${msgSuffix}`); + } + // Setting date for further time validation + newDate[`${date}`] = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]); + }); const times = ['startTime', 'endTime']; - if ( - !times.every(function (time) { - if (data.dates[`${i}`][`${time}`]) { - if (data.dates[`${i}`][`${time}`].length !== 5) { - data.validationError = msgPrefix + ' failed: time misspelled [-> HH:MM]' + msgSuffix; - return false; - } - const timeParts = data.dates[`${i}`][`${time}`].split(':'); - // validate the time parts - if (timeParts.length < 2 || timeParts.length > 2) { - data.validationError = msgPrefix + ' failed: time misspelled [' + time + ': ' + data.dates[`${i}`][`${time}`] + ']' + msgSuffix; - return false; - } - if (timeParts[0] > 23) { - data.validationError = msgPrefix + ' failed: time misspelled - hours number too high [' + time + ': ' + timeParts[0] + ']' + msgSuffix; - return false; - } - if (timeParts[1] > 59) { - data.validationError = msgPrefix + ' failed: time misspelled - minutes number too high [' + time + ': ' + timeParts[1] + ']' + msgSuffix; - return false; - } - // update the date with the time for further validation steps - if (time === 'startTime') { - newDate.startDate = new Date(newDate.startDate.getTime() + timeParts[0] * 3600000 + timeParts[1] * 60000); - } - if (time === 'endTime') { - newDate.endDate = new Date(newDate.endDate.getTime() + timeParts[0] * 3600000 + timeParts[1] * 60000); - } + times.forEach((time) => { + const timeString = selectedDate[`${time}`]; + if (timeString) { + if (timeString.length !== 5) { + throw new Error(`${msgPrefix} failed: time misspelled [-> HH:MM]${msgSuffix}`); } - return true; - }) - ) { - return false; - } - if ((data.dates[`${i}`].startTime && !data.dates[`${i}`].endTime) || (!data.dates[`${i}`].startTime && data.dates[`${i}`].endTime)) { - data.validationError = msgPrefix + ' failed: if you set a starting or end time, the respective other one also needs to be defined' + msgSuffix; - return false; + const timeParts = timeString.split(':'); + // Validate the time parts + if (timeParts.length !== 2 || timeParts[0] > 23 || timeParts[1] > 59) { + throw new Error(`${msgPrefix} failed: time misspelled [${time}: ${timeString}]${msgSuffix}`); + } + // Update the date with the time for further validation steps + const dateKey = time === 'startTime' ? 'startDate' : 'endDate'; + newDate[`${dateKey}`] = new Date(newDate[`${dateKey}`].getTime() + parseInt(timeParts[0], 10) * 3600000 + parseInt(timeParts[1], 10) * 60000); + } + }); + // Validate start and end time presence + if ((selectedDate.startTime && !selectedDate.endTime) || (!selectedDate.startTime && selectedDate.endTime)) { + throw new Error(`${msgPrefix} failed: if you set a starting or end time, the respective other one also needs to be defined${msgSuffix}`); } - // validate whether end is not before start + // Validate whether end is not before start if (newDate.endDate < newDate.startDate) { - data.validationError = msgPrefix + ' failed: end date before start date' + msgSuffix; - return false; + throw new Error(`${msgPrefix} failed: end date before start date${msgSuffix}`); } return true; } // validate RRULE -function atcb_validate_rrule(data, msgPrefix) { +async function atcb_validate_rrule(data, msgPrefix) { // check for multi-date (which is not allowed) if (data.recurrence && data.recurrence !== '' && data.dates.length > 1) { - data.validationError = msgPrefix + ' failed: RRULE and multi-date set at the same time'; - return false; + throw new Error(msgPrefix + ' failed: RRULE and multi-date set at the same time'); } // validate any given RRULE if (data.recurrence && data.recurrence !== '' && !/^RRULE:[\w=;,:+-/\\]+$/i.test(data.recurrence)) { - data.validationError = msgPrefix + ' failed: RRULE data misspelled'; - return false; + throw new Error(msgPrefix + ' failed: RRULE data misspelled'); } return true; } // also validate the simplyfied recurrence settings (if provided), since any error there would be also hidden in the RRULE -function atcb_validate_rrule_simplyfied(data, msgPrefix) { +async function atcb_validate_rrule_simplyfied(data, msgPrefix) { if (data.recurrence_interval && (data.recurrence_interval < 1 || data.recurrence_interval % 1 !== 0)) { - data.validationError = msgPrefix + ' failed: recurrence data (interval) misspelled'; - return false; + throw new Error(msgPrefix + ' failed: recurrence data (interval) misspelled'); } if (data.recurrence_until && data.recurrence_until !== '' && !/^(\d|-|:)+$/i.test(data.recurrence_until)) { - data.validationError = msgPrefix + ' failed: recurrence data (until) misspelled'; - return false; + throw new Error(msgPrefix + ' failed: recurrence data (until) misspelled'); } if (data.recurrence_count && (data.recurrence_count < 1 || data.recurrence_count % 1 !== 0)) { - data.validationError = msgPrefix + ' failed: recurrence data (interval) misspelled'; - return false; + throw new Error(msgPrefix + ' failed: recurrence data (interval) misspelled'); } if (data.recurrence_byMonth && data.recurrence_byMonth !== '' && !/^(\d|,)+$/.test(data.recurrence_byMonth)) { - data.validationError = msgPrefix + ' failed: recurrence data (byMonth) misspelled'; - return false; + throw new Error(msgPrefix + ' failed: recurrence data (byMonth) misspelled'); } if (data.recurrence_byMonthDay && data.recurrence_byMonthDay !== '' && !/^(\d|,)+$/.test(data.recurrence_byMonthDay)) { - data.validationError = msgPrefix + ' failed: recurrence data (byMonthDay) misspelled'; - return false; + throw new Error(msgPrefix + ' failed: recurrence data (byMonthDay) misspelled'); } if (data.recurrence_byDay && data.recurrence_byDay !== '' && !/^(\d|-|MO|TU|WE|TH|FR|SA|SU|,)+$/im.test(data.recurrence_byDay)) { - data.validationError = msgPrefix + ' failed: recurrence data (byDay) misspelled'; - return false; + throw new Error(msgPrefix + ' failed: recurrence data (byDay) misspelled'); } if (data.recurrence_weekstart && data.recurrence_weekstart !== '' && !/^(MO|TU|WE|TH|FR|SA|SU)$/im.test(data.recurrence_weekstart)) { - data.validationError = msgPrefix + ' failed: recurrence data (weekstart) misspelled'; - return false; + throw new Error(msgPrefix + ' failed: recurrence data (weekstart) misspelled'); } return true; } -export { atcb_check_required, atcb_validate, atcb_validate_options }; +export { atcb_check_required, atcb_validate };