add-to-calendar-button
Advanced tools
Comparing version 1.8.10 to 1.9.0
@@ -6,13 +6,15 @@ /** | ||
*/ | ||
const atcbVersion = '1.8.10'; | ||
const atcbVersion = "1.9.0"; | ||
/* Creator: Jens Kuerschner (https://jenskuerschner.de) | ||
* Project: https://github.com/jekuer/add-to-calendar-button | ||
* License: MIT with “Commons Clause” License Condition v1.0 | ||
* | ||
* | ||
*/ | ||
const isBrowser = new Function("try { return this===window; }catch(e){ return false; }"); | ||
const isiOS = isBrowser() ? new Function("if ((/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)){ return true; }else{ return false; }") : new Function("return false;"); | ||
const isiOS = isBrowser() | ||
? new Function( | ||
"if ((/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)){ return true; }else{ return false; }" | ||
) | ||
: new Function("return false;"); | ||
@@ -25,11 +27,11 @@ // INITIALIZE THE SCRIPT AND FUNCTIONALITY | ||
// get all placeholders | ||
let atcButtons = document.querySelectorAll('.atcb'); | ||
const atcButtons = document.querySelectorAll(".atcb"); | ||
// if there are some, move on | ||
if (atcButtons.length > 0) { | ||
// get the amount of already initialized ones first | ||
let atcButtonsInitialized = document.querySelectorAll('.atcb_initialized'); | ||
const atcButtonsInitialized = document.querySelectorAll(".atcb_initialized"); | ||
// generate the buttons one by one | ||
for (let i = 0; i < atcButtons.length; i++) { | ||
// skip already initialized ones | ||
if (atcButtons[i].classList.contains('atcb_initialized')) { | ||
if (atcButtons[i].classList.contains("atcb_initialized")) { | ||
continue; | ||
@@ -39,15 +41,19 @@ } | ||
// check if schema.org markup is present | ||
let schema = atcButtons[i].querySelector('script'); | ||
const schema = atcButtons[i].querySelector("script"); | ||
// get their JSON content first | ||
if (schema && schema.innerHTML) { | ||
// get schema.org event markup and flatten the event block | ||
atcbConfig = JSON.parse(schema.innerHTML.replace(/(\r\n|\n|\r)/gm, "").replace(/(<(?!br)([^>]+)>)/gi, "")); // remove real code line breaks before parsing. Use <br> or \n explicitely in the description to create a line break. Also strip HTML tags (especially since stupid Safari adds stuff). | ||
atcbConfig = JSON.parse( | ||
schema.innerHTML.replace(/(\r\n|\n|\r)/g, "").replace(/(<(?!br)([^>]+)>)/gi, "") | ||
); // remove real code line breaks before parsing. Use <br> or \n explicitely in the description to create a line break. Also strip HTML tags (especially since stupid Safari adds stuff). | ||
atcbConfig = atcb_parse_schema_json(atcbConfig); | ||
// set flag to not delete HTML content later | ||
atcbConfig['deleteJSON'] = false; | ||
atcbConfig.deleteJSON = false; | ||
} else { | ||
// get JSON from HTML block | ||
atcbConfig = JSON.parse(atcButtons[i].innerHTML.replace(/(\r\n|\n|\r)/gm, "").replace(/(<(?!br)([^>]+)>)/gi, "")); // remove real code line breaks before parsing. Use <br> or \n explicitely in the description to create a line break. Also strip HTML tags (especially since stupid Safari adds stuff). | ||
atcbConfig = JSON.parse( | ||
atcButtons[i].innerHTML.replace(/(\r\n|\n|\r)/g, "").replace(/(<(?!br)([^>]+)>)/gi, "") | ||
); // remove real code line breaks before parsing. Use <br> or \n explicitely in the description to create a line break. Also strip HTML tags (especially since stupid Safari adds stuff). | ||
// set flag to delete HTML content later | ||
atcbConfig['deleteJSON'] = true; | ||
atcbConfig.deleteJSON = true; | ||
} | ||
@@ -70,34 +76,31 @@ // rewrite config for backwards compatibility - you can remove this, if you did not use this script before v1.4.0. | ||
// NORMALIZE AND PARSE JSON FROM SCHEMA.ORG MARKUP | ||
function atcb_parse_schema_json(atcbConfig) { | ||
try { | ||
Object.keys(atcbConfig['event']).forEach(key => { | ||
Object.keys(atcbConfig.event).forEach((key) => { | ||
// move entries one level up, but skip schema types | ||
if (key.charAt(0) !== '@') { | ||
atcbConfig[key] = atcbConfig['event'][key]; | ||
} | ||
if (key.charAt(0) !== "@") { | ||
atcbConfig[key] = atcbConfig.event[key]; | ||
} | ||
}); | ||
// drop the event block and return | ||
delete atcbConfig.event; | ||
} catch (err) { | ||
console.error( | ||
"add-to-calendar button problem: it seems like you use the schema.org style, but did not define it properly" | ||
); | ||
} | ||
catch(err) { | ||
console.error("add-to-calendar button problem: it seems like you use the schema.org style, but did not define it properly"); | ||
} | ||
return atcbConfig; | ||
} | ||
// BACKWARDS COMPATIBILITY REWRITE - you can remove this, if you did not use this script before v1.4.0. | ||
function atcb_patch_config(atcbConfig) { | ||
const keyChanges = { | ||
'title': 'name', | ||
'dateStart': 'startDate', | ||
'dateEnd': 'endDate', | ||
'timeStart': 'startTime', | ||
'timeEnd': 'endTime', | ||
title: "name", | ||
dateStart: "startDate", | ||
dateEnd: "endDate", | ||
timeStart: "startTime", | ||
timeEnd: "endTime", | ||
}; | ||
Object.keys(keyChanges).forEach(key => { | ||
Object.keys(keyChanges).forEach((key) => { | ||
if (atcbConfig[keyChanges[key]] == null && atcbConfig[key] != null) { | ||
@@ -110,4 +113,2 @@ atcbConfig[keyChanges[key]] = atcbConfig[key]; | ||
// CLEAN DATA BEFORE FURTHER VALIDATION (CONSIDERING SPECIAL RULES AND SCHEMES) | ||
@@ -118,5 +119,5 @@ function atcb_decorate_data(atcbConfig) { | ||
// calculate the real date values in case that there are some special rules included (e.g. adding days dynamically) | ||
atcbConfig['startDate'] = atcb_date_calculation(atcbConfig['startDate']); | ||
atcbConfig['endDate'] = atcb_date_calculation(atcbConfig['endDate']); | ||
atcbConfig.startDate = atcb_date_calculation(atcbConfig.startDate); | ||
atcbConfig.endDate = atcb_date_calculation(atcbConfig.endDate); | ||
// if no description or already decorated, return early | ||
@@ -128,14 +129,17 @@ if (!atcbConfig.description || atcbConfig.description_iCal) return atcbConfig; | ||
// standardize any line breaks in the description and transform URLs (but keep a clean copy without the URL magic for iCal) | ||
data.description = data.description.replace(/<br\s*\/?>/gmi, '\n'); | ||
data.description_iCal = data.description.replace('[url]','').replace('[/url]',''); | ||
data.description = data.description.replace(/\[url\](.*?)\[\/url\]/g, "<a href='$1' target='_blank' rel='noopener'>$1</a>"); | ||
return data | ||
data.description = data.description.replace(/<br\s*\/?>/gi, "\n"); | ||
data.description_iCal = data.description.replace(/\[url\]/g, "").replace(/\[\/url\]/g, ""); | ||
data.description = decodeURIComponent( | ||
data.description.replace( | ||
/\[url\]([\w&$+.,:;=~!*'?@#\s\-()|/^%]*)\[\/url\]/g, | ||
encodeURIComponent('<a href="$1" target="_blank" rel="noopener">$1</a>') | ||
) | ||
); | ||
return data; | ||
} | ||
// CHECK FOR REQUIRED FIELDS | ||
function atcb_check_required(data) { | ||
// check for at least 1 option | ||
if (data['options'] == null || data['options'].length < 1) { | ||
if (data.options == null || data.options.length < 1) { | ||
console.error("add-to-calendar button generation failed: no options set"); | ||
@@ -145,4 +149,4 @@ return false; | ||
// check for min required data (without "options") | ||
const requiredField = ['name', 'startDate', 'endDate'] | ||
return requiredField.every(function(field) { | ||
const requiredField = ["name", "startDate", "endDate"]; | ||
return requiredField.every(function (field) { | ||
if (data[field] == null || data[field] == "") { | ||
@@ -156,23 +160,21 @@ console.error("add-to-calendar button generation failed: required setting missing [" + field + "]"); | ||
// CALCULATE AND CLEAN UP THE ACTUAL DATES | ||
function atcb_date_cleanup(data) { | ||
// parse date+time format (default with Schema.org, but also an unofficial alternative to other implementation) | ||
const endpoints = ['start', 'end']; | ||
endpoints.forEach(function(point) { | ||
if (data[point + 'Date'] != null) { | ||
const endpoints = ["start", "end"]; | ||
endpoints.forEach(function (point) { | ||
if (data[point + "Date"] != null) { | ||
// remove any milliseconds information | ||
data[point + 'Date'] = data[point + 'Date'].replace(/\..../, '').replace('Z', ''); | ||
data[point + "Date"] = data[point + "Date"].replace(/\.\d{3}/, "").replace("Z", ""); | ||
// identify a possible time information within the date string | ||
let tmpSplitStartDate = data[point + 'Date'].split('T'); | ||
const tmpSplitStartDate = data[point + "Date"].split("T"); | ||
if (tmpSplitStartDate[1] != null) { | ||
data[point + 'Date'] = tmpSplitStartDate[0]; | ||
data[point + 'Time'] = tmpSplitStartDate[1]; | ||
data[point + "Date"] = tmpSplitStartDate[0]; | ||
data[point + "Time"] = tmpSplitStartDate[1]; | ||
} | ||
} | ||
// remove any seconds from time information | ||
if (data[point + 'Time'] != null && data[point + 'Time'].length == 8) { | ||
let timeStr = data[point + 'Time']; | ||
data[point + 'Time'] = timeStr.substring(0, timeStr.length - 3); | ||
if (data[point + "Time"] != null && data[point + "Time"].length === 8) { | ||
const timeStr = data[point + "Time"]; | ||
data[point + "Time"] = timeStr.substring(0, timeStr.length - 3); | ||
} | ||
@@ -185,8 +187,8 @@ }); | ||
// replace "today" with the current date first | ||
let today = new Date(); | ||
let todayString = (today.getUTCMonth() + 1) + '-' + today.getUTCDate() + '-' + today.getUTCFullYear(); | ||
dateString = dateString.replace(/today/ig, todayString); | ||
const today = new Date(); | ||
const todayString = today.getUTCMonth() + 1 + "-" + today.getUTCDate() + "-" + today.getUTCFullYear(); | ||
dateString = dateString.replace(/today/gi, todayString); | ||
// check for any dynamic additions and adjust | ||
const dateStringParts = dateString.split('+'); | ||
const dateParts = dateStringParts[0].split('-'); | ||
const dateStringParts = dateString.split("+"); | ||
const dateParts = dateStringParts[0].split("-"); | ||
let newDate = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]); | ||
@@ -200,79 +202,112 @@ if (dateParts[0].length < 4) { | ||
} | ||
return newDate.getFullYear() + '-' + (((newDate.getMonth() + 1) < 10 ? '0' : '') + (newDate.getMonth() + 1)) + '-' + (newDate.getDate() < 10 ? '0' : '') + newDate.getDate(); | ||
return ( | ||
newDate.getFullYear() + | ||
"-" + | ||
((newDate.getMonth() + 1 < 10 ? "0" : "") + (newDate.getMonth() + 1)) + | ||
"-" + | ||
(newDate.getDate() < 10 ? "0" : "") + | ||
newDate.getDate() | ||
); | ||
} | ||
// VALIDATE THE JSON DATA | ||
function atcb_validate(data) { | ||
// validate options | ||
const options = ['Apple', 'Google', 'iCal', 'Microsoft365', 'Outlook.com', 'MicrosoftTeams', 'Yahoo'] | ||
if (!data['options'].every(function(option) { | ||
let cleanOption = option.split('|'); | ||
if (!options.includes(cleanOption[0])) { | ||
console.error("add-to-calendar button generation failed: invalid option [" + cleanOption[0] + "]"); | ||
return false; | ||
} | ||
return true; | ||
})) { | ||
const options = ["Apple", "Google", "iCal", "Microsoft365", "Outlook.com", "MicrosoftTeams", "Yahoo"]; | ||
if ( | ||
!data.options.every(function (option) { | ||
const cleanOption = option.split("|"); | ||
if (!options.includes(cleanOption[0])) { | ||
console.error("add-to-calendar button generation failed: invalid option [" + cleanOption[0] + "]"); | ||
return false; | ||
} | ||
return true; | ||
}) | ||
) { | ||
return false; | ||
} | ||
// validate date | ||
const dates = ['startDate', 'endDate']; | ||
let newDate = dates; | ||
if (!dates.every(function(date) { | ||
if (data[date].length != 10) { | ||
console.error("add-to-calendar button generation failed: date misspelled [-> YYYY-MM-DD]"); | ||
return false; | ||
} | ||
const dateParts = data[date].split('-'); | ||
if (dateParts.length < 3 || dateParts.length > 3) { | ||
console.error("add-to-calendar button generation failed: date misspelled [" + date + ": " + data[date] + "]"); | ||
return false; | ||
} | ||
newDate[date] = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]); | ||
return true; | ||
})) { | ||
return false; | ||
} | ||
// validate time | ||
const times = ['startTime', 'endTime']; | ||
if (!times.every(function(time) { | ||
if (data[time] != null) { | ||
if (data[time].length != 5) { | ||
console.error("add-to-calendar button generation failed: time misspelled [-> HH:MM]"); | ||
const dates = ["startDate", "endDate"]; | ||
const newDate = dates; | ||
if ( | ||
!dates.every(function (date) { | ||
if (data[date].length !== 10) { | ||
console.error("add-to-calendar button generation failed: date misspelled [-> YYYY-MM-DD]"); | ||
return false; | ||
} | ||
const timeParts = data[time].split(':'); | ||
// validate the time parts | ||
if (timeParts.length < 2 || timeParts.length > 2) { | ||
console.error("add-to-calendar button generation failed: time misspelled [" + time + ": " + data[time] + "]"); | ||
const dateParts = data[date].split("-"); | ||
if (dateParts.length < 3 || dateParts.length > 3) { | ||
console.error( | ||
"add-to-calendar button generation failed: date misspelled [" + date + ": " + data[date] + "]" | ||
); | ||
return false; | ||
} | ||
if (timeParts[0] > 23) { | ||
console.error("add-to-calendar button generation failed: time misspelled - hours number too high [" + time + ": " + timeParts[0] + "]"); | ||
return false; | ||
newDate[date] = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]); | ||
return true; | ||
}) | ||
) { | ||
return false; | ||
} | ||
// validate time | ||
const times = ["startTime", "endTime"]; | ||
if ( | ||
!times.every(function (time) { | ||
if (data[time] != null) { | ||
if (data[time].length !== 5) { | ||
console.error("add-to-calendar button generation failed: time misspelled [-> HH:MM]"); | ||
return false; | ||
} | ||
const timeParts = data[time].split(":"); | ||
// validate the time parts | ||
if (timeParts.length < 2 || timeParts.length > 2) { | ||
console.error( | ||
"add-to-calendar button generation failed: time misspelled [" + time + ": " + data[time] + "]" | ||
); | ||
return false; | ||
} | ||
if (timeParts[0] > 23) { | ||
console.error( | ||
"add-to-calendar button generation failed: time misspelled - hours number too high [" + | ||
time + | ||
": " + | ||
timeParts[0] + | ||
"]" | ||
); | ||
return false; | ||
} | ||
if (timeParts[1] > 59) { | ||
console.error( | ||
"add-to-calendar button generation failed: time misspelled - minutes number too high [" + | ||
time + | ||
": " + | ||
timeParts[1] + | ||
"]" | ||
); | ||
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 | ||
); | ||
} | ||
} | ||
if (timeParts[1] > 59) { | ||
console.error("add-to-calendar button generation failed: time misspelled - minutes number too high [" + time + ": " + timeParts[1] + "]"); | ||
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)) | ||
} | ||
} | ||
return true; | ||
})) { | ||
return true; | ||
}) | ||
) { | ||
return false; | ||
} | ||
if ((data['startTime'] != null && data['endTime'] == null) || (data['startTime'] == null && data['endTime'] != null)) { | ||
console.error("add-to-calendar button generation failed: if you set a starting time, you also need to define an end time"); | ||
if ((data.startTime != null && data.endTime == null) || (data.startTime == null && data.endTime != null)) { | ||
console.error( | ||
"add-to-calendar button generation failed: if you set a starting time, you also need to define an end time" | ||
); | ||
return false; | ||
} | ||
// validate whether end is not before start | ||
if (newDate['endDate'] < newDate['startDate']) { | ||
if (newDate.endDate < newDate.startDate) { | ||
console.error("add-to-calendar button generation failed: end date before start date"); | ||
@@ -285,42 +320,60 @@ return false; | ||
// GENERATE THE ACTUAL BUTTON | ||
function atcb_generate_label(parent, icon = false, text) { | ||
// helper function to generate the labels for the button and list options | ||
if (icon) { | ||
const iconEl = document.createElement("span"); | ||
iconEl.classList.add("atcb_icon"); | ||
parent.appendChild(iconEl); | ||
} | ||
if (text != "") { | ||
const textEl = document.createElement("span"); | ||
textEl.classList.add("atcb_text"); | ||
textEl.textContent = text; | ||
parent.appendChild(textEl); | ||
} | ||
} | ||
// GENERATE THE ACTUAL BUTTON | ||
function atcb_generate(button, buttonId, data) { | ||
// clean the placeholder, if flagged that way | ||
if (data['deleteJSON']) { | ||
button.innerHTML = ''; | ||
if (data.deleteJSON) { | ||
button.innerHTML = ""; | ||
} | ||
// generate the wrapper div | ||
let buttonTriggerWrapper = document.createElement('div'); | ||
buttonTriggerWrapper.classList.add('atcb_button_wrapper'); | ||
const buttonTriggerWrapper = document.createElement("div"); | ||
buttonTriggerWrapper.classList.add("atcb_button_wrapper"); | ||
button.appendChild(buttonTriggerWrapper); | ||
// generate the button trigger div | ||
let buttonTrigger = document.createElement('button'); | ||
buttonTrigger.id = 'atcb_button_' + buttonId; | ||
buttonTrigger.classList.add('atcb_button'); | ||
buttonTrigger.setAttribute('type', 'button'); | ||
const buttonTrigger = document.createElement("button"); | ||
buttonTrigger.id = "atcb_button_" + buttonId; | ||
buttonTrigger.classList.add("atcb_button"); | ||
buttonTrigger.setAttribute("type", "button"); | ||
buttonTriggerWrapper.appendChild(buttonTrigger); | ||
buttonTrigger.innerHTML = '<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><path d="M81.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zm-3.65 76.03c1.83 0 3.32 1.49 3.32 3.32s-1.49 3.32-3.32 3.32l-12.95-.04-.04 12.93c0 1.83-1.49 3.32-3.32 3.32s-3.32-1.49-3.32-3.32l.04-12.94-12.93-.05c-1.83 0-3.32-1.49-3.32-3.32s1.49-3.32 3.32-3.32l12.94.04.04-12.93c0-1.83 1.49-3.32 3.32-3.32s3.32 1.49 3.32 3.32l-.04 12.95 12.94.04h0zM29.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zM6.4 45.32h110.08V21.47c0-.8-.33-1.53-.86-2.07-.53-.53-1.26-.86-2.07-.86H103c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h10.55c2.57 0 4.9 1.05 6.59 2.74s2.74 4.02 2.74 6.59v27.06 65.03c0 2.57-1.05 4.9-2.74 6.59s-4.02 2.74-6.59 2.74H9.33c-2.57 0-4.9-1.05-6.59-2.74-1.69-1.7-2.74-4.03-2.74-6.6V48.53 21.47c0-2.57 1.05-4.9 2.74-6.59s4.02-2.74 6.59-2.74H20.6c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H9.33c-.8 0-1.53.33-2.07.86-.53.53-.86 1.26-.86 2.07v23.85h0zm110.08 6.41H6.4v61.82c0 .8.33 1.53.86 2.07.53.53 1.26.86 2.07.86h104.22c.8 0 1.53-.33 2.07-.86.53-.53.86-1.26.86-2.07V51.73h0zM50.43 18.54c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h21.49c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H50.43h0z"/></svg></span>'; | ||
buttonTrigger.innerHTML += '<span class="atcb_text">' + (data['label'] || 'Add to Calendar') + '</span>'; | ||
// generate the icon and text within the button | ||
atcb_generate_label(buttonTrigger, true, data.label || "Add to Calendar"); | ||
buttonTrigger.firstChild.innerHTML = | ||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><path d="M81.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zm-3.65 76.03c1.83 0 3.32 1.49 3.32 3.32s-1.49 3.32-3.32 3.32l-12.95-.04-.04 12.93c0 1.83-1.49 3.32-3.32 3.32s-3.32-1.49-3.32-3.32l.04-12.94-12.93-.05c-1.83 0-3.32-1.49-3.32-3.32s1.49-3.32 3.32-3.32l12.94.04.04-12.93c0-1.83 1.49-3.32 3.32-3.32s3.32 1.49 3.32 3.32l-.04 12.95 12.94.04h0zM29.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zM6.4 45.32h110.08V21.47c0-.8-.33-1.53-.86-2.07-.53-.53-1.26-.86-2.07-.86H103c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h10.55c2.57 0 4.9 1.05 6.59 2.74s2.74 4.02 2.74 6.59v27.06 65.03c0 2.57-1.05 4.9-2.74 6.59s-4.02 2.74-6.59 2.74H9.33c-2.57 0-4.9-1.05-6.59-2.74-1.69-1.7-2.74-4.03-2.74-6.6V48.53 21.47c0-2.57 1.05-4.9 2.74-6.59s4.02-2.74 6.59-2.74H20.6c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H9.33c-.8 0-1.53.33-2.07.86-.53.53-.86 1.26-.86 2.07v23.85h0zm110.08 6.41H6.4v61.82c0 .8.33 1.53.86 2.07.53.53 1.26.86 2.07.86h104.22c.8 0 1.53-.33 2.07-.86.53-.53.86-1.26.86-2.07V51.73h0zM50.43 18.54c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h21.49c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H50.43h0z"/></svg>'; | ||
// set event listeners for the button trigger | ||
if (data['trigger'] == 'click') { | ||
buttonTrigger.addEventListener('mousedown', () => atcb_toggle(data, buttonTrigger, true, false)); | ||
if (data.trigger == "click") { | ||
buttonTrigger.addEventListener("mousedown", () => atcb_toggle(data, buttonTrigger, false, true)); | ||
} else { | ||
buttonTrigger.addEventListener('touchstart', () => atcb_toggle(data, buttonTrigger, true, false), {passive: true}); | ||
buttonTrigger.addEventListener('mouseenter', () => atcb_open(data, buttonTrigger, true, false)); | ||
buttonTrigger.addEventListener("touchstart", () => atcb_toggle(data, buttonTrigger, false, true), { | ||
passive: true, | ||
}); | ||
buttonTrigger.addEventListener("mouseenter", () => atcb_open(data, buttonTrigger, false, true)); | ||
} | ||
buttonTrigger.addEventListener('keydown', function(event) { // trigger click on enter as well | ||
if (event.key == 'Enter') { | ||
atcb_toggle(data, buttonTrigger, true); | ||
buttonTrigger.addEventListener("keydown", function (event) { | ||
// trigger click on enter as well | ||
if (event.key == "Enter") { | ||
atcb_toggle(data, buttonTrigger, true, true); | ||
} | ||
}); | ||
// update the placeholder class to prevent multiple initializations | ||
button.classList.remove('atcb'); | ||
button.classList.add('atcb_initialized'); | ||
button.classList.remove("atcb"); | ||
button.classList.add("atcb_initialized"); | ||
// show the placeholder div | ||
if (data['inline']) { | ||
button.style.display = 'inline-block'; | ||
if (data.inline) { | ||
button.style.display = "inline-block"; | ||
} else { | ||
button.style.display = 'block'; | ||
button.style.display = "block"; | ||
} | ||
@@ -331,11 +384,10 @@ // console log | ||
function atcb_generate_dropdown_list(data) { | ||
const optionsList = document.createElement('div'); | ||
optionsList.classList.add('atcb_list'); | ||
const optionsList = document.createElement("div"); | ||
optionsList.classList.add("atcb_list"); | ||
// generate the list items | ||
data['options'].forEach(function(option) { | ||
let optionParts = option.split('|'); | ||
let optionItem = document.createElement('div'); | ||
optionItem.classList.add('atcb_list_item'); | ||
data.options.forEach(function (option) { | ||
const optionParts = option.split("|"); | ||
const optionItem = document.createElement("div"); | ||
optionItem.classList.add("atcb_list_item"); | ||
optionItem.tabIndex = 0; | ||
@@ -345,7 +397,6 @@ optionsList.appendChild(optionItem); | ||
case "Apple": | ||
optionItem.innerHTML = '<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" viewBox="0 0 640 640"><path d="M494.782 340.02c-.803-81.025 66.084-119.907 69.072-121.832-37.595-54.993-96.167-62.552-117.037-63.402-49.843-5.032-97.242 29.362-122.565 29.362-25.253 0-64.277-28.607-105.604-27.85-54.32.803-104.4 31.594-132.403 80.245C29.81 334.457 71.81 479.58 126.816 558.976c26.87 38.882 58.914 82.56 100.997 81 40.512-1.594 55.843-26.244 104.848-26.244 48.993 0 62.753 26.245 105.64 25.406 43.606-.803 71.232-39.638 97.925-78.65 30.887-45.12 43.548-88.75 44.316-90.994-.969-.437-85.029-32.634-85.879-129.439l.118-.035zM414.23 102.178C436.553 75.095 451.636 37.5 447.514-.024c-32.162 1.311-71.163 21.437-94.253 48.485-20.729 24.012-38.836 62.28-33.993 99.036 35.918 2.8 72.591-18.248 94.926-45.272l.036-.047z"/></svg></span>'; | ||
optionItem.innerHTML += '<span class="atcb_text">'; | ||
optionItem.innerHTML += optionParts[1] || 'Apple'; | ||
optionItem.innerHTML += '</span>'; | ||
optionItem.addEventListener('click', function() { | ||
atcb_generate_label(optionItem, true, optionParts[1] || "Apple"); | ||
optionItem.firstChild.innerHTML = | ||
'<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" viewBox="0 0 640 640"><path d="M494.782 340.02c-.803-81.025 66.084-119.907 69.072-121.832-37.595-54.993-96.167-62.552-117.037-63.402-49.843-5.032-97.242 29.362-122.565 29.362-25.253 0-64.277-28.607-105.604-27.85-54.32.803-104.4 31.594-132.403 80.245C29.81 334.457 71.81 479.58 126.816 558.976c26.87 38.882 58.914 82.56 100.997 81 40.512-1.594 55.843-26.244 104.848-26.244 48.993 0 62.753 26.245 105.64 25.406 43.606-.803 71.232-39.638 97.925-78.65 30.887-45.12 43.548-88.75 44.316-90.994-.969-.437-85.029-32.634-85.879-129.439l.118-.035zM414.23 102.178C436.553 75.095 451.636 37.5 447.514-.024c-32.162 1.311-71.163 21.437-94.253 48.485-20.729 24.012-38.836 62.28-33.993 99.036 35.918 2.8 72.591-18.248 94.926-45.272l.036-.047z"/></svg>'; | ||
optionItem.addEventListener("click", function () { | ||
atcb_generate_ical(data); | ||
@@ -356,7 +407,6 @@ atcb_close(); | ||
case "Google": | ||
optionItem.innerHTML = '<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><path d="M93.78 29.1H29.1v64.68h64.68V29.1z" fill="#fff"/><path d="M93.78 122.88l29.1-29.1h-29.1v29.1z" fill="#f72a25"/><path d="M122.88 29.1h-29.1v64.68h29.1V29.1z" fill="#fbbc04"/><path d="M93.78 93.78H29.1v29.1h64.68v-29.1z" fill="#34a853"/><path d="M0 93.78v19.4c0 5.36 4.34 9.7 9.7 9.7h19.4v-29.1H0h0z" fill="#188038"/><path d="M122.88 29.1V9.7c0-5.36-4.34-9.7-9.7-9.7h-19.4v29.1h29.1 0z" fill="#1967d2"/><path d="M93.78 0H9.7C4.34 0 0 4.34 0 9.7v84.08h29.1V29.1h64.67V0h.01z" fill="#4285f4"/><path d="M42.37 79.27c-2.42-1.63-4.09-4.02-5-7.17l5.61-2.31c.51 1.94 1.4 3.44 2.67 4.51 1.26 1.07 2.8 1.59 4.59 1.59 1.84 0 3.41-.56 4.73-1.67 1.32-1.12 1.98-2.54 1.98-4.26 0-1.76-.7-3.2-2.09-4.32s-3.14-1.67-5.22-1.67H46.4v-5.55h2.91c1.79 0 3.31-.48 4.54-1.46 1.23-.97 1.84-2.3 1.84-3.99 0-1.5-.55-2.7-1.65-3.6s-2.49-1.35-4.18-1.35c-1.65 0-2.96.44-3.93 1.32s-1.7 2-2.12 3.24l-5.55-2.31c.74-2.09 2.09-3.93 4.07-5.52s4.51-2.39 7.58-2.39c2.27 0 4.32.44 6.13 1.32s3.23 2.1 4.26 3.65c1.03 1.56 1.54 3.31 1.54 5.25 0 1.98-.48 3.65-1.43 5.03-.95 1.37-2.13 2.43-3.52 3.16v.33c1.79.74 3.36 1.96 4.51 3.52 1.17 1.58 1.76 3.46 1.76 5.66s-.56 4.16-1.67 5.88c-1.12 1.72-2.66 3.08-4.62 4.07s-4.17 1.49-6.62 1.49c-2.84 0-5.46-.81-7.88-2.45h0 0zm34.46-27.84l-6.16 4.45-3.08-4.67 11.05-7.97h4.24v37.6h-6.05V51.43h0z" fill="#1a73e8"/></svg></span>'; | ||
optionItem.innerHTML += '<span class="atcb_text">'; | ||
optionItem.innerHTML += optionParts[1] || 'Google'; | ||
optionItem.innerHTML += '</span>'; | ||
optionItem.addEventListener('click', function() { | ||
atcb_generate_label(optionItem, true, optionParts[1] || "Google"); | ||
optionItem.firstChild.innerHTML = | ||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><path d="M93.78 29.1H29.1v64.68h64.68V29.1z" fill="#fff"/><path d="M93.78 122.88l29.1-29.1h-29.1v29.1z" fill="#f72a25"/><path d="M122.88 29.1h-29.1v64.68h29.1V29.1z" fill="#fbbc04"/><path d="M93.78 93.78H29.1v29.1h64.68v-29.1z" fill="#34a853"/><path d="M0 93.78v19.4c0 5.36 4.34 9.7 9.7 9.7h19.4v-29.1H0h0z" fill="#188038"/><path d="M122.88 29.1V9.7c0-5.36-4.34-9.7-9.7-9.7h-19.4v29.1h29.1 0z" fill="#1967d2"/><path d="M93.78 0H9.7C4.34 0 0 4.34 0 9.7v84.08h29.1V29.1h64.67V0h.01z" fill="#4285f4"/><path d="M42.37 79.27c-2.42-1.63-4.09-4.02-5-7.17l5.61-2.31c.51 1.94 1.4 3.44 2.67 4.51 1.26 1.07 2.8 1.59 4.59 1.59 1.84 0 3.41-.56 4.73-1.67 1.32-1.12 1.98-2.54 1.98-4.26 0-1.76-.7-3.2-2.09-4.32s-3.14-1.67-5.22-1.67H46.4v-5.55h2.91c1.79 0 3.31-.48 4.54-1.46 1.23-.97 1.84-2.3 1.84-3.99 0-1.5-.55-2.7-1.65-3.6s-2.49-1.35-4.18-1.35c-1.65 0-2.96.44-3.93 1.32s-1.7 2-2.12 3.24l-5.55-2.31c.74-2.09 2.09-3.93 4.07-5.52s4.51-2.39 7.58-2.39c2.27 0 4.32.44 6.13 1.32s3.23 2.1 4.26 3.65c1.03 1.56 1.54 3.31 1.54 5.25 0 1.98-.48 3.65-1.43 5.03-.95 1.37-2.13 2.43-3.52 3.16v.33c1.79.74 3.36 1.96 4.51 3.52 1.17 1.58 1.76 3.46 1.76 5.66s-.56 4.16-1.67 5.88c-1.12 1.72-2.66 3.08-4.62 4.07s-4.17 1.49-6.62 1.49c-2.84 0-5.46-.81-7.88-2.45h0 0zm34.46-27.84l-6.16 4.45-3.08-4.67 11.05-7.97h4.24v37.6h-6.05V51.43h0z" fill="#1a73e8"/></svg>'; | ||
optionItem.addEventListener("click", function () { | ||
atcb_generate_google(data); | ||
@@ -367,7 +417,6 @@ atcb_close(); | ||
case "iCal": | ||
optionItem.innerHTML = '<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><path d="M81.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zm-15.5 99.08c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2H81.9c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H66.11h0zM15.85 67.09c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H15.85h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H40.98h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2H81.9c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H66.11h0zm25.14 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H91.25h0zm-75.4 18.36c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H15.85h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H40.98h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2H81.9c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H66.11h0zm25.14 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H91.25h0zm-75.4 18.36c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H15.85h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H40.98h0zM29.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zM6.4 45.32h110.07V21.47c0-.8-.33-1.53-.86-2.07-.53-.53-1.26-.86-2.07-.86H103c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h10.55c2.57 0 4.9 1.05 6.59 2.74s2.74 4.02 2.74 6.59v27.06 65.03c0 2.57-1.05 4.9-2.74 6.59s-4.02 2.74-6.59 2.74H9.33c-2.57 0-4.9-1.05-6.59-2.74-1.69-1.7-2.74-4.03-2.74-6.6V48.52 21.47c0-2.57 1.05-4.9 2.74-6.59s4.02-2.74 6.59-2.74H20.6c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H9.33c-.8 0-1.53.33-2.07.86-.53.53-.86 1.26-.86 2.07v23.85h0zm110.08 6.41H6.4v61.82c0 .8.33 1.53.86 2.07.53.53 1.26.86 2.07.86h104.22c.8 0 1.53-.33 2.07-.86.53-.53.86-1.26.86-2.07V51.73h0zM50.43 18.54c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h21.49c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H50.43h0z"/></svg></span>'; | ||
optionItem.innerHTML += '<span class="atcb_text">'; | ||
optionItem.innerHTML += optionParts[1] || 'iCal File'; | ||
optionItem.innerHTML += '</span>'; | ||
optionItem.addEventListener('click', function() { | ||
atcb_generate_label(optionItem, true, optionParts[1] || "iCal File"); | ||
optionItem.firstChild.innerHTML = | ||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><path d="M81.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zm-15.5 99.08c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2H81.9c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H66.11h0zM15.85 67.09c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H15.85h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H40.98h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2H81.9c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H66.11h0zm25.14 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H91.25h0zm-75.4 18.36c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H15.85h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H40.98h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2H81.9c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H66.11h0zm25.14 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H91.25h0zm-75.4 18.36c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H15.85h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H40.98h0zM29.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zM6.4 45.32h110.07V21.47c0-.8-.33-1.53-.86-2.07-.53-.53-1.26-.86-2.07-.86H103c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h10.55c2.57 0 4.9 1.05 6.59 2.74s2.74 4.02 2.74 6.59v27.06 65.03c0 2.57-1.05 4.9-2.74 6.59s-4.02 2.74-6.59 2.74H9.33c-2.57 0-4.9-1.05-6.59-2.74-1.69-1.7-2.74-4.03-2.74-6.6V48.52 21.47c0-2.57 1.05-4.9 2.74-6.59s4.02-2.74 6.59-2.74H20.6c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H9.33c-.8 0-1.53.33-2.07.86-.53.53-.86 1.26-.86 2.07v23.85h0zm110.08 6.41H6.4v61.82c0 .8.33 1.53.86 2.07.53.53 1.26.86 2.07.86h104.22c.8 0 1.53-.33 2.07-.86.53-.53.86-1.26.86-2.07V51.73h0zM50.43 18.54c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h21.49c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H50.43h0z"/></svg>'; | ||
optionItem.addEventListener("click", function () { | ||
atcb_generate_ical(data); | ||
@@ -378,7 +427,6 @@ atcb_close(); | ||
case "MicrosoftTeams": | ||
optionItem.innerHTML = '<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 2228.833 2073.333"><g fill="#5059c9"><path d="M1554.637 777.5h575.713c54.391 0 98.483 44.092 98.483 98.483v524.398c0 199.901-162.051 361.952-361.952 361.952h0-1.711c-199.901.028-361.975-162-362.004-361.901v-.052-571.409c.001-28.427 23.045-51.471 51.471-51.471h0z"/><circle cx="1943.75" cy="440.583" r="233.25"/></g><g fill="#7b83eb"><circle cx="1218.083" cy="336.917" r="336.917"/><path d="M1667.323 777.5H717.01c-53.743 1.33-96.257 45.931-95.01 99.676v598.105c-7.505 322.519 247.657 590.16 570.167 598.053 322.51-7.893 577.671-275.534 570.167-598.053V877.176c1.245-53.745-41.268-98.346-95.011-99.676z"/></g><path opacity=".1" d="M1244 777.5v838.145c-.258 38.435-23.549 72.964-59.09 87.598-11.316 4.787-23.478 7.254-35.765 7.257H667.613c-6.738-17.105-12.958-34.21-18.142-51.833-18.144-59.477-27.402-121.307-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52H1244z"/><path opacity=".2" d="M1192.167 777.5v889.978a91.84 91.84 0 0 1-7.257 35.765c-14.634 35.541-49.163 58.833-87.598 59.09H691.975c-8.812-17.105-17.105-34.21-24.362-51.833s-12.958-34.21-18.142-51.833a631.28 631.28 0 0 1-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h475.313z"/><path opacity=".2" d="M1192.167 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855h-447.84A631.28 631.28 0 0 1 622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h475.312z"/><path opacity=".2" d="M1140.333 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855H649.472A631.28 631.28 0 0 1 622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h423.478z"/><path opacity=".1" d="M1244 509.522v163.275c-8.812.518-17.105 1.037-25.917 1.037s-17.105-.518-25.917-1.037c-17.496-1.161-34.848-3.937-51.833-8.293a336.92 336.92 0 0 1-233.25-198.003 288.02 288.02 0 0 1-16.587-51.833h258.648c52.305.198 94.657 42.549 94.856 94.854z"/><use xlink:href="#C" opacity=".2"/><use xlink:href="#C" opacity=".2"/><path opacity=".2" d="M1140.333 561.355v103.148A336.92 336.92 0 0 1 907.083 466.5h138.395c52.305.199 94.656 42.551 94.855 94.855z"/><linearGradient id="A" gradientUnits="userSpaceOnUse" x1="198.099" y1="392.261" x2="942.234" y2="1681.073"><stop offset="0" stop-color="#5a62c3"/><stop offset=".5" stop-color="#4d55bd"/><stop offset="1" stop-color="#3940ab"/></linearGradient><path fill="url(#A)" d="M95.01 466.5h950.312c52.473 0 95.01 42.538 95.01 95.01v950.312c0 52.473-42.538 95.01-95.01 95.01H95.01c-52.473 0-95.01-42.538-95.01-95.01V561.51c0-52.472 42.538-95.01 95.01-95.01z"/><path fill="#fff" d="M820.211,828.193H630.241v517.297H509.211V828.193H320.123V727.844h500.088V828.193z"/><defs ><path id="C" d="M1192.167 561.355v111.442c-17.496-1.161-34.848-3.937-51.833-8.293a336.92 336.92 0 0 1-233.25-198.003h190.228c52.304.198 94.656 42.55 94.855 94.854z"/></defs></svg></span>'; | ||
optionItem.innerHTML += '<span class="atcb_text">'; | ||
optionItem.innerHTML += optionParts[1] || 'Microsoft Teams'; | ||
optionItem.innerHTML += '</span>'; | ||
optionItem.addEventListener('click', function() { | ||
atcb_generate_label(optionItem, true, optionParts[1] || "Microsoft Teams"); | ||
optionItem.firstChild.innerHTML = | ||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 2228.833 2073.333"><g fill="#5059c9"><path d="M1554.637 777.5h575.713c54.391 0 98.483 44.092 98.483 98.483v524.398c0 199.901-162.051 361.952-361.952 361.952h0-1.711c-199.901.028-361.975-162-362.004-361.901v-.052-571.409c.001-28.427 23.045-51.471 51.471-51.471h0z"/><circle cx="1943.75" cy="440.583" r="233.25"/></g><g fill="#7b83eb"><circle cx="1218.083" cy="336.917" r="336.917"/><path d="M1667.323 777.5H717.01c-53.743 1.33-96.257 45.931-95.01 99.676v598.105c-7.505 322.519 247.657 590.16 570.167 598.053 322.51-7.893 577.671-275.534 570.167-598.053V877.176c1.245-53.745-41.268-98.346-95.011-99.676z"/></g><path opacity=".1" d="M1244 777.5v838.145c-.258 38.435-23.549 72.964-59.09 87.598-11.316 4.787-23.478 7.254-35.765 7.257H667.613c-6.738-17.105-12.958-34.21-18.142-51.833-18.144-59.477-27.402-121.307-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52H1244z"/><path opacity=".2" d="M1192.167 777.5v889.978a91.84 91.84 0 0 1-7.257 35.765c-14.634 35.541-49.163 58.833-87.598 59.09H691.975c-8.812-17.105-17.105-34.21-24.362-51.833s-12.958-34.21-18.142-51.833a631.28 631.28 0 0 1-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h475.313z"/><path opacity=".2" d="M1192.167 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855h-447.84A631.28 631.28 0 0 1 622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h475.312z"/><path opacity=".2" d="M1140.333 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855H649.472A631.28 631.28 0 0 1 622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h423.478z"/><path opacity=".1" d="M1244 509.522v163.275c-8.812.518-17.105 1.037-25.917 1.037s-17.105-.518-25.917-1.037c-17.496-1.161-34.848-3.937-51.833-8.293a336.92 336.92 0 0 1-233.25-198.003 288.02 288.02 0 0 1-16.587-51.833h258.648c52.305.198 94.657 42.549 94.856 94.854z"/><use xlink:href="#C" opacity=".2"/><use xlink:href="#C" opacity=".2"/><path opacity=".2" d="M1140.333 561.355v103.148A336.92 336.92 0 0 1 907.083 466.5h138.395c52.305.199 94.656 42.551 94.855 94.855z"/><linearGradient id="A" gradientUnits="userSpaceOnUse" x1="198.099" y1="392.261" x2="942.234" y2="1681.073"><stop offset="0" stop-color="#5a62c3"/><stop offset=".5" stop-color="#4d55bd"/><stop offset="1" stop-color="#3940ab"/></linearGradient><path fill="url(#A)" d="M95.01 466.5h950.312c52.473 0 95.01 42.538 95.01 95.01v950.312c0 52.473-42.538 95.01-95.01 95.01H95.01c-52.473 0-95.01-42.538-95.01-95.01V561.51c0-52.472 42.538-95.01 95.01-95.01z"/><path fill="#fff" d="M820.211,828.193H630.241v517.297H509.211V828.193H320.123V727.844h500.088V828.193z"/><defs ><path id="C" d="M1192.167 561.355v111.442c-17.496-1.161-34.848-3.937-51.833-8.293a336.92 336.92 0 0 1-233.25-198.003h190.228c52.304.198 94.656 42.55 94.855 94.854z"/></defs></svg>'; | ||
optionItem.addEventListener("click", function () { | ||
atcb_generate_teams(data); | ||
@@ -389,8 +437,7 @@ atcb_close(); | ||
case "Microsoft365": | ||
optionItem.innerHTML = '<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 278050 333334" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd"><path fill="#ea3e23" d="M278050 305556l-29-16V28627L178807 0 448 66971l-448 87 22 200227 60865-23821V80555l117920-28193-17 239519L122 267285l178668 65976v73l99231-27462v-316z"/></svg></span>'; | ||
optionItem.innerHTML += '<span class="atcb_text">'; | ||
optionItem.innerHTML += optionParts[1] || 'Microsoft 365'; | ||
optionItem.innerHTML += '</span>'; | ||
optionItem.addEventListener('click', function() { | ||
atcb_generate_microsoft(data, '365'); | ||
atcb_generate_label(optionItem, true, optionParts[1] || "Microsoft 365"); | ||
optionItem.firstChild.innerHTML = | ||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 278050 333334" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd"><path fill="#ea3e23" d="M278050 305556l-29-16V28627L178807 0 448 66971l-448 87 22 200227 60865-23821V80555l117920-28193-17 239519L122 267285l178668 65976v73l99231-27462v-316z"/></svg>'; | ||
optionItem.addEventListener("click", function () { | ||
atcb_generate_microsoft(data, "365"); | ||
atcb_close(); | ||
@@ -400,8 +447,7 @@ }); | ||
case "Outlook.com": | ||
optionItem.innerHTML = '<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.129793726981 0 33.251996719421 32" width="2500" height="2397"><path d="M28.596 2H11.404A1.404 1.404 0 0 0 10 3.404V5l9.69 3L30 5V3.404A1.404 1.404 0 0 0 28.596 2z" fill="#0364b8"/><path d="M31.65 17.405A11.341 11.341 0 0 0 32 16a.666.666 0 0 0-.333-.576l-.013-.008-.004-.002L20.812 9.24a1.499 1.499 0 0 0-1.479-.083 1.49 1.49 0 0 0-.145.082L8.35 15.415l-.004.002-.012.007A.666.666 0 0 0 8 16a11.344 11.344 0 0 0 .35 1.405l11.492 8.405z" fill="#0a2767"/><path d="M24 5h-7l-2.021 3L17 11l7 6h6v-6z" fill="#28a8ea"/><path d="M10 5h7v6h-7z" fill="#0078d4"/><path d="M24 5h6v6h-6z" fill="#50d9ff"/><path d="M24 17l-7-6h-7v6l7 6 10.832 1.768z" fill="#0364b8"/><path d="M17 11h7v6h-7z" fill="#0078d4"/><path d="M10 17h7v6h-7z" fill="#064a8c"/><path d="M24 17h6v6h-6z" fill="#0078d4"/><path d="M20.19 25.218l-11.793-8.6.495-.87 10.909 6.212a.528.528 0 0 0 .42-.012l10.933-6.23.496.869z" fill="#0a2767" opacity=".5"/><path d="M31.667 16.577l-.014.008-.003.002-10.838 6.174a1.497 1.497 0 0 1-1.46.091l3.774 5.061 8.254 1.797v.004A1.498 1.498 0 0 0 32 28.5V16a.666.666 0 0 1-.333.577z" fill="#1490df"/><path d="M32 28.5v-.738l-9.983-5.688-1.205.687a1.497 1.497 0 0 1-1.46.091l3.774 5.061 8.254 1.797v.004A1.498 1.498 0 0 0 32 28.5z" opacity=".05"/><path d="M31.95 28.883L21.007 22.65l-.195.11a1.497 1.497 0 0 1-1.46.092l3.774 5.061 8.254 1.797v.004a1.501 1.501 0 0 0 .57-.83z" opacity=".1"/><path d="M8.35 16.59v-.01h-.01l-.03-.02A.65.65 0 0 1 8 16v12.5A1.498 1.498 0 0 0 9.5 30h21a1.503 1.503 0 0 0 .37-.05.637.637 0 0 0 .18-.06.142.142 0 0 0 .06-.02 1.048 1.048 0 0 0 .23-.13c.02-.01.03-.01.04-.03z" fill="#28a8ea"/><path d="M18 24.667V8.333A1.337 1.337 0 0 0 16.667 7H10.03v7.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v10h8.667A1.337 1.337 0 0 0 18 24.667z" opacity=".1"/><path d="M17 25.667V9.333A1.337 1.337 0 0 0 15.667 8H10.03v6.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v11h7.667A1.337 1.337 0 0 0 17 25.667z" opacity=".2"/><path d="M17 23.667V9.333A1.337 1.337 0 0 0 15.667 8H10.03v6.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v9h7.667A1.337 1.337 0 0 0 17 23.667z" opacity=".2"/><path d="M16 23.667V9.333A1.337 1.337 0 0 0 14.667 8H10.03v6.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v9h6.667A1.337 1.337 0 0 0 16 23.667z" opacity=".2"/><path d="M1.333 8h13.334A1.333 1.333 0 0 1 16 9.333v13.334A1.333 1.333 0 0 1 14.667 24H1.333A1.333 1.333 0 0 1 0 22.667V9.333A1.333 1.333 0 0 1 1.333 8z" fill="#0078d4"/><path d="M3.867 13.468a4.181 4.181 0 0 1 1.642-1.814A4.965 4.965 0 0 1 8.119 11a4.617 4.617 0 0 1 2.413.62 4.14 4.14 0 0 1 1.598 1.733 5.597 5.597 0 0 1 .56 2.55 5.901 5.901 0 0 1-.577 2.666 4.239 4.239 0 0 1-1.645 1.794A4.8 4.8 0 0 1 7.963 21a4.729 4.729 0 0 1-2.468-.627 4.204 4.204 0 0 1-1.618-1.736 5.459 5.459 0 0 1-.567-2.519 6.055 6.055 0 0 1 .557-2.65zm1.75 4.258a2.716 2.716 0 0 0 .923 1.194 2.411 2.411 0 0 0 1.443.435 2.533 2.533 0 0 0 1.541-.449 2.603 2.603 0 0 0 .897-1.197 4.626 4.626 0 0 0 .286-1.665 5.063 5.063 0 0 0-.27-1.686 2.669 2.669 0 0 0-.866-1.24 2.387 2.387 0 0 0-1.527-.473 2.493 2.493 0 0 0-1.477.439 2.741 2.741 0 0 0-.944 1.203 4.776 4.776 0 0 0-.007 3.44z" fill="#fff"/></svg></span>'; | ||
optionItem.innerHTML += '<span class="atcb_text">'; | ||
optionItem.innerHTML += optionParts[1] || 'Outlook.com'; | ||
optionItem.innerHTML += '</span>'; | ||
optionItem.addEventListener('click', function() { | ||
atcb_generate_microsoft(data, 'outlook'); | ||
atcb_generate_label(optionItem, true, optionParts[1] || "Outlook.com"); | ||
optionItem.firstChild.innerHTML = | ||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.129793726981 0 33.251996719421 32" width="2500" height="2397"><path d="M28.596 2H11.404A1.404 1.404 0 0 0 10 3.404V5l9.69 3L30 5V3.404A1.404 1.404 0 0 0 28.596 2z" fill="#0364b8"/><path d="M31.65 17.405A11.341 11.341 0 0 0 32 16a.666.666 0 0 0-.333-.576l-.013-.008-.004-.002L20.812 9.24a1.499 1.499 0 0 0-1.479-.083 1.49 1.49 0 0 0-.145.082L8.35 15.415l-.004.002-.012.007A.666.666 0 0 0 8 16a11.344 11.344 0 0 0 .35 1.405l11.492 8.405z" fill="#0a2767"/><path d="M24 5h-7l-2.021 3L17 11l7 6h6v-6z" fill="#28a8ea"/><path d="M10 5h7v6h-7z" fill="#0078d4"/><path d="M24 5h6v6h-6z" fill="#50d9ff"/><path d="M24 17l-7-6h-7v6l7 6 10.832 1.768z" fill="#0364b8"/><path d="M17 11h7v6h-7z" fill="#0078d4"/><path d="M10 17h7v6h-7z" fill="#064a8c"/><path d="M24 17h6v6h-6z" fill="#0078d4"/><path d="M20.19 25.218l-11.793-8.6.495-.87 10.909 6.212a.528.528 0 0 0 .42-.012l10.933-6.23.496.869z" fill="#0a2767" opacity=".5"/><path d="M31.667 16.577l-.014.008-.003.002-10.838 6.174a1.497 1.497 0 0 1-1.46.091l3.774 5.061 8.254 1.797v.004A1.498 1.498 0 0 0 32 28.5V16a.666.666 0 0 1-.333.577z" fill="#1490df"/><path d="M32 28.5v-.738l-9.983-5.688-1.205.687a1.497 1.497 0 0 1-1.46.091l3.774 5.061 8.254 1.797v.004A1.498 1.498 0 0 0 32 28.5z" opacity=".05"/><path d="M31.95 28.883L21.007 22.65l-.195.11a1.497 1.497 0 0 1-1.46.092l3.774 5.061 8.254 1.797v.004a1.501 1.501 0 0 0 .57-.83z" opacity=".1"/><path d="M8.35 16.59v-.01h-.01l-.03-.02A.65.65 0 0 1 8 16v12.5A1.498 1.498 0 0 0 9.5 30h21a1.503 1.503 0 0 0 .37-.05.637.637 0 0 0 .18-.06.142.142 0 0 0 .06-.02 1.048 1.048 0 0 0 .23-.13c.02-.01.03-.01.04-.03z" fill="#28a8ea"/><path d="M18 24.667V8.333A1.337 1.337 0 0 0 16.667 7H10.03v7.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v10h8.667A1.337 1.337 0 0 0 18 24.667z" opacity=".1"/><path d="M17 25.667V9.333A1.337 1.337 0 0 0 15.667 8H10.03v6.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v11h7.667A1.337 1.337 0 0 0 17 25.667z" opacity=".2"/><path d="M17 23.667V9.333A1.337 1.337 0 0 0 15.667 8H10.03v6.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v9h7.667A1.337 1.337 0 0 0 17 23.667z" opacity=".2"/><path d="M16 23.667V9.333A1.337 1.337 0 0 0 14.667 8H10.03v6.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v9h6.667A1.337 1.337 0 0 0 16 23.667z" opacity=".2"/><path d="M1.333 8h13.334A1.333 1.333 0 0 1 16 9.333v13.334A1.333 1.333 0 0 1 14.667 24H1.333A1.333 1.333 0 0 1 0 22.667V9.333A1.333 1.333 0 0 1 1.333 8z" fill="#0078d4"/><path d="M3.867 13.468a4.181 4.181 0 0 1 1.642-1.814A4.965 4.965 0 0 1 8.119 11a4.617 4.617 0 0 1 2.413.62 4.14 4.14 0 0 1 1.598 1.733 5.597 5.597 0 0 1 .56 2.55 5.901 5.901 0 0 1-.577 2.666 4.239 4.239 0 0 1-1.645 1.794A4.8 4.8 0 0 1 7.963 21a4.729 4.729 0 0 1-2.468-.627 4.204 4.204 0 0 1-1.618-1.736 5.459 5.459 0 0 1-.567-2.519 6.055 6.055 0 0 1 .557-2.65zm1.75 4.258a2.716 2.716 0 0 0 .923 1.194 2.411 2.411 0 0 0 1.443.435 2.533 2.533 0 0 0 1.541-.449 2.603 2.603 0 0 0 .897-1.197 4.626 4.626 0 0 0 .286-1.665 5.063 5.063 0 0 0-.27-1.686 2.669 2.669 0 0 0-.866-1.24 2.387 2.387 0 0 0-1.527-.473 2.493 2.493 0 0 0-1.477.439 2.741 2.741 0 0 0-.944 1.203 4.776 4.776 0 0 0-.007 3.44z" fill="#fff"/></svg>'; | ||
optionItem.addEventListener("click", function () { | ||
atcb_generate_microsoft(data, "outlook"); | ||
atcb_close(); | ||
@@ -411,7 +457,6 @@ }); | ||
case "Yahoo": | ||
optionItem.innerHTML = '<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3386.34 3010.5" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd"><path d="M0 732.88h645.84l376.07 962.1 380.96-962.1h628.76l-946.8 2277.62H451.98l259.19-603.53L.02 732.88zm2763.84 768.75h-704.26L2684.65 0l701.69.03-622.5 1501.6zm-519.78 143.72c216.09 0 391.25 175.17 391.25 391.22 0 216.06-175.16 391.23-391.25 391.23-216.06 0-391.19-175.17-391.19-391.23 0-216.05 175.16-391.22 391.19-391.22z" fill="#5f01d1" fill-rule="nonzero"/></svg></span>'; | ||
optionItem.innerHTML += '<span class="atcb_text">'; | ||
optionItem.innerHTML += optionParts[1] || 'Yahoo'; | ||
optionItem.innerHTML += '</span>'; | ||
optionItem.addEventListener('click', function() { | ||
atcb_generate_label(optionItem, true, optionParts[1] || "Yahoo"); | ||
optionItem.firstChild.innerHTML = | ||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3386.34 3010.5" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd"><path d="M0 732.88h645.84l376.07 962.1 380.96-962.1h628.76l-946.8 2277.62H451.98l259.19-603.53L.02 732.88zm2763.84 768.75h-704.26L2684.65 0l701.69.03-622.5 1501.6zm-519.78 143.72c216.09 0 391.25 175.17 391.25 391.22 0 216.06-175.16 391.23-391.25 391.23-216.06 0-391.19-175.17-391.19-391.23 0-216.05 175.16-391.22 391.19-391.22z" fill="#5f01d1" fill-rule="nonzero"/></svg>'; | ||
optionItem.addEventListener("click", function () { | ||
atcb_generate_yahoo(data); | ||
@@ -422,4 +467,5 @@ atcb_close(); | ||
} | ||
optionItem.addEventListener('keydown', function(event) { // trigger click on enter as well | ||
if (event.key == 'Enter') { | ||
optionItem.addEventListener("keydown", function (event) { | ||
// trigger click on enter as well | ||
if (event.key == "Enter") { | ||
this.click(); | ||
@@ -433,21 +479,31 @@ } | ||
// create the background overlay, which also acts as trigger to close any dropdowns | ||
function atcb_generate_bg_overlay(data) { | ||
const bgOverlay = document.createElement('div'); | ||
bgOverlay.classList.add('atcb_bgoverlay'); | ||
const bgOverlay = document.createElement("div"); | ||
bgOverlay.classList.add("atcb_bgoverlay"); | ||
if (data.listStyle !== "modal") { | ||
bgOverlay.classList.add("atcb_animate_bg"); | ||
} | ||
bgOverlay.tabIndex = 0; | ||
bgOverlay.addEventListener('click', () => atcb_close(true)); | ||
bgOverlay.addEventListener("click", () => atcb_close(true)); | ||
let fingerMoved = false; | ||
bgOverlay.addEventListener('touchstart', () => fingerMoved = false, {passive: true}); | ||
bgOverlay.addEventListener('touchmove', () => fingerMoved = true, {passive: true}); | ||
bgOverlay.addEventListener('touchend', function() { | ||
if (fingerMoved == false) { | ||
atcb_close(true); | ||
} | ||
}, {passive: true}); | ||
bgOverlay.addEventListener('focus', () => atcb_close(false)); | ||
if (data['trigger'] !== 'click') { | ||
bgOverlay.addEventListener('mousemove', () => atcb_close(true)); | ||
} else { | ||
bgOverlay.classList.add('atcb_click'); | ||
bgOverlay.addEventListener("touchstart", () => (fingerMoved = false), { | ||
passive: true, | ||
}); | ||
bgOverlay.addEventListener("touchmove", () => (fingerMoved = true), { | ||
passive: true, | ||
}); | ||
bgOverlay.addEventListener( | ||
"touchend", | ||
function () { | ||
if (fingerMoved === false) { | ||
atcb_close(true); | ||
} | ||
}, | ||
{ passive: true } | ||
); | ||
bgOverlay.addEventListener("focus", () => atcb_close(false)); | ||
if (data.trigger !== "click") { | ||
bgOverlay.addEventListener("mousemove", () => atcb_close(true)); | ||
} else { | ||
bgOverlay.classList.add("atcb_click"); | ||
} | ||
@@ -457,11 +513,9 @@ return bgOverlay; | ||
// FUNCTIONS TO CONTROL THE INTERACTION | ||
function atcb_toggle(data, button, buttonGenerated, keyboardTrigger = true) { | ||
function atcb_toggle(data, button, keyboardTrigger = true, generatedButton = false) { | ||
// check for state and adjust accordingly | ||
if (button.classList.contains('atcb_active')) { | ||
if (button.classList.contains("atcb_active")) { | ||
atcb_close(); | ||
} else { | ||
atcb_open(data, button, buttonGenerated, keyboardTrigger); | ||
atcb_open(data, button, keyboardTrigger, generatedButton); | ||
} | ||
@@ -471,22 +525,30 @@ } | ||
// show the dropdown list + background overlay | ||
function atcb_open(data, button, buttonGenerated = false, keyboardTrigger = true) { | ||
function atcb_open(data, button, keyboardTrigger = true, generatedButton = false) { | ||
// abort early if an add-to-calendar dropdown already opened | ||
if (document.querySelector('.atcb_list')) return | ||
if (document.querySelector(".atcb_list")) return; | ||
// generate list | ||
const list = atcb_generate_dropdown_list(data, buttonGenerated); | ||
const list = atcb_generate_dropdown_list(data); | ||
// set list styles, set possible button to atcb_active and go for modal mode if no button is set | ||
// set list styles, set button to atcb_active and force modal listStyle if no button is set | ||
if (button) { | ||
button.classList.add('atcb_active'); | ||
const rect = button.getBoundingClientRect(); | ||
list.style.width = rect.width + 'px'; | ||
list.style.top = rect.bottom + window.scrollY -3 + 'px'; | ||
list.style.left = rect.left + 'px'; | ||
button.classList.add("atcb_active"); | ||
if (data.listStyle === "modal") { | ||
button.classList.add("atcb_modal_style"); | ||
list.classList.add("atcb_modal"); | ||
} else { | ||
const rect = button.getBoundingClientRect(); | ||
list.style.width = rect.width + "px"; | ||
list.style.top = rect.bottom + window.scrollY - 3 + "px"; | ||
list.style.left = rect.left + "px"; | ||
list.classList.add("atcb_dropdown"); | ||
if (generatedButton) { | ||
list.classList.add("atcb_generated_button"); // if the button has been generated by the script, we add some more specifics | ||
} | ||
} | ||
} else { | ||
list.classList.add('atcb_modal') | ||
// if no button is defined, fallback to listStyle "modal" | ||
data.listStyle = "modal"; | ||
list.classList.add("atcb_modal"); | ||
} | ||
if (buttonGenerated) { | ||
list.classList.add('atcb_generated_button'); | ||
} | ||
@@ -499,3 +561,4 @@ // add list to DOM | ||
// give keyboard focus to first item in list, if not blocked, because there is definitely no keyboard trigger | ||
// give keyboard focus to first item in list, if not blocked, because there is definitely no keyboard trigger. | ||
// Mintd that with custom atcb_action function, this might get triggered as well, if the custom function does not provide a proper value. | ||
if (keyboardTrigger) { | ||
@@ -507,126 +570,115 @@ list.firstChild.focus(); | ||
function atcb_close(blockFocus = false) { | ||
// 1. Focus triggering button (if not existing, try a custom element) | ||
// 1. Focus triggering button - especially relevant for keyboard navigation | ||
if (!blockFocus) { | ||
let newFocusEl = document.querySelector('.atcb_active'); | ||
let newFocusEl = document.querySelector(".atcb_active"); | ||
if (newFocusEl) { | ||
newFocusEl.focus(); | ||
} else { | ||
newFocusEl = document.querySelector('.atcb_customTrigger'); | ||
if (newFocusEl) { | ||
newFocusEl.focus(); | ||
} | ||
} | ||
} | ||
// 2. Inactivate all buttons | ||
Array.from(document.querySelectorAll('.atcb_active')).forEach(button => { | ||
button.classList.remove('atcb_active'); | ||
}) | ||
Array.from(document.querySelectorAll(".atcb_active")).forEach((button) => { | ||
button.classList.remove("atcb_active"); | ||
}); | ||
// 3. Remove dropdowns & bg overlays (should only be one of each) | ||
Array.from(document.querySelectorAll('.atcb_list')).concat( | ||
Array.from(document.querySelectorAll('.atcb_bgoverlay')) | ||
).forEach(el => el.remove()); | ||
Array.from(document.querySelectorAll(".atcb_list")) | ||
.concat(Array.from(document.querySelectorAll(".atcb_bgoverlay"))) | ||
.forEach((el) => el.remove()); | ||
} | ||
// prepare data when not using the init function | ||
function atcb_action(data, button) { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
function atcb_action(data, triggerElement, keyboardTrigger = true) { | ||
// validate & decorate data | ||
if (!atcb_check_required(data)) { | ||
throw new Error("data missing; see logs") | ||
throw new Error("data missing; see logs"); | ||
} | ||
data = atcb_decorate_data(data); | ||
if (!atcb_validate(data)) { | ||
throw new Error("Invalid data; see logs") | ||
throw new Error("Invalid data; see logs"); | ||
} | ||
atcb_open(data, button); | ||
atcb_open(data, triggerElement, keyboardTrigger); | ||
} | ||
// FUNCTION TO GENERATE THE GOOGLE URL | ||
function atcb_generate_google(data) { | ||
// base url | ||
let url = 'https://calendar.google.com/calendar/render?action=TEMPLATE'; | ||
let url = "https://calendar.google.com/calendar/render?action=TEMPLATE"; | ||
// generate and add date | ||
let formattedDate = atcb_generate_time(data, 'clean', 'google'); | ||
url += '&dates=' + formattedDate['start'] + '%2F' + formattedDate['end']; | ||
const formattedDate = atcb_generate_time(data, "clean", "google"); | ||
url += "&dates=" + formattedDate.start + "%2F" + formattedDate.end; | ||
// add details (if set) | ||
if (data['name'] != null && data['name'] != '') { | ||
url += '&text=' + encodeURIComponent(data['name']); | ||
if (data.name != null && data.name != "") { | ||
url += "&text=" + encodeURIComponent(data.name); | ||
} | ||
if (data['location'] != null && data['location'] != '') { | ||
url += '&location=' + encodeURIComponent(data['location']); | ||
if (data.location != null && data.location != "") { | ||
url += "&location=" + encodeURIComponent(data.location); | ||
// TODO: Find a better solution for the next temporary workaround. | ||
if (isiOS()) { // workaround to cover a bug, where, when using Google Calendar on an iPhone, the location is not recognized. So, for the moment, we simply add it to the description. | ||
if (data['description'] == null || data['description'] == '') { | ||
data['description'] = ''; | ||
if (isiOS()) { | ||
// workaround to cover a bug, where, when using Google Calendar on an iPhone, the location is not recognized. So, for the moment, we simply add it to the description. | ||
if (data.description == null || data.description == "") { | ||
data.description = ""; | ||
} else { | ||
data['description'] += '<br><br>'; | ||
data.description += "%0A%0A"; | ||
} | ||
data['description'] += '📍: ' + data['location']; | ||
data.description += "📍: " + data.location; | ||
} | ||
} | ||
if (data['description'] != null && data['description'] != '') { | ||
url += '&details=' + encodeURIComponent(data['description']); | ||
if (data.description != null && data.description != "") { | ||
url += "&details=" + encodeURIComponent(data.description); | ||
} | ||
window.open(url, '_blank').focus(); | ||
window.open(url, "_blank").focus(); | ||
} | ||
// FUNCTION TO GENERATE THE YAHOO URL | ||
function atcb_generate_yahoo(data) { | ||
// base url | ||
let url = 'https://calendar.yahoo.com/?v=60'; | ||
let url = "https://calendar.yahoo.com/?v=60"; | ||
// generate and add date | ||
let formattedDate = atcb_generate_time(data, 'clean'); | ||
url += '&st=' + formattedDate['start'] + '&et=' + formattedDate['end']; | ||
if (formattedDate['allday']) { | ||
url += '&dur=allday'; | ||
const formattedDate = atcb_generate_time(data, "clean"); | ||
url += "&st=" + formattedDate.start + "&et=" + formattedDate.end; | ||
if (formattedDate.allday) { | ||
url += "&dur=allday"; | ||
} | ||
// add details (if set) | ||
if (data['name'] != null && data['name'] != '') { | ||
url += '&title=' + encodeURIComponent(data['name']); | ||
if (data.name != null && data.name != "") { | ||
url += "&title=" + encodeURIComponent(data.name); | ||
} | ||
if (data['location'] != null && data['location'] != '') { | ||
url += '&in_loc=' + encodeURIComponent(data['location']); | ||
if (data.location != null && data.location != "") { | ||
url += "&in_loc=" + encodeURIComponent(data.location); | ||
} | ||
if (data['description'] != null && data['description'] != '') { | ||
url += '&desc=' + encodeURIComponent(data['description']); | ||
if (data.description != null && data.description != "") { | ||
url += "&desc=" + encodeURIComponent(data.description); | ||
} | ||
window.open(url, '_blank').focus(); | ||
window.open(url, "_blank").focus(); | ||
} | ||
// FUNCTION TO GENERATE THE MICROSOFT 365 OR OUTLOOK WEB URL | ||
function atcb_generate_microsoft(data, type = '365') { | ||
function atcb_generate_microsoft(data, type = "365") { | ||
// base url | ||
let url = 'https://'; | ||
if (type == 'outlook') { | ||
url += 'outlook.live.com'; | ||
let url = "https://"; | ||
if (type == "outlook") { | ||
url += "outlook.live.com"; | ||
} else { | ||
url += 'outlook.office.com'; | ||
url += "outlook.office.com"; | ||
} | ||
url += '/calendar/0/deeplink/compose?path=%2Fcalendar%2Faction%2Fcompose&rru=addevent'; | ||
url += "/calendar/0/deeplink/compose?path=%2Fcalendar%2Faction%2Fcompose&rru=addevent"; | ||
// generate and add date | ||
let formattedDate = atcb_generate_time(data, 'delimiters', 'microsoft'); | ||
url += '&startdt=' + formattedDate['start'] + '&enddt=' + formattedDate['end']; | ||
if (formattedDate['allday']) { | ||
url += '&allday=true'; | ||
const formattedDate = atcb_generate_time(data, "delimiters", "microsoft"); | ||
url += "&startdt=" + formattedDate.start + "&enddt=" + formattedDate.end; | ||
if (formattedDate.allday) { | ||
url += "&allday=true"; | ||
} | ||
// add details (if set) | ||
if (data['name'] != null && data['name'] != '') { | ||
url += '&subject=' + encodeURIComponent(data['name']); | ||
if (data.name != null && data.name != "") { | ||
url += "&subject=" + encodeURIComponent(data.name); | ||
} | ||
if (data['location'] != null && data['location'] != '') { | ||
url += '&location=' + encodeURIComponent(data['location']); | ||
if (data.location != null && data.location != "") { | ||
url += "&location=" + encodeURIComponent(data.location); | ||
} | ||
if (data['description'] != null && data['description'] != '') { | ||
url += '&body=' + encodeURIComponent(data['description'].replace(/\n/g, '<br>')); | ||
if (data.description != null && data.description != "") { | ||
url += "&body=" + encodeURIComponent(data.description.replace(/\n/g, "%0A")); | ||
} | ||
window.open(url, '_blank').focus(); | ||
window.open(url, "_blank").focus(); | ||
} | ||
// FUNCTION TO GENERATE THE MICROSOFT TEAMS URL | ||
@@ -637,67 +689,67 @@ // Mind that this is still in development mode by Microsoft! (https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/build-and-test/deep-links#deep-linking-to-the-scheduling-dialog) | ||
// base url | ||
let url = 'https://teams.microsoft.com/l/meeting/new?'; | ||
let url = "https://teams.microsoft.com/l/meeting/new?"; | ||
// generate and add date | ||
let formattedDate = atcb_generate_time(data, 'delimiters', 'microsoft'); | ||
url += '&startTime=' + formattedDate['start'] + '&endTime=' + formattedDate['end']; | ||
const formattedDate = atcb_generate_time(data, "delimiters", "microsoft"); | ||
url += "&startTime=" + formattedDate.start + "&endTime=" + formattedDate.end; | ||
// add details (if set) | ||
let locationString = ''; | ||
if (data['name'] != null && data['name'] != '') { | ||
url += '&subject=' + encodeURIComponent(data['name']); | ||
let locationString = ""; | ||
if (data.name != null && data.name != "") { | ||
url += "&subject=" + encodeURIComponent(data.name); | ||
} | ||
if (data['location'] != null && data['location'] != '') { | ||
locationString = encodeURIComponent(data['location']); | ||
url += '&location=' + locationString; | ||
locationString += ' // '; // preparing the workaround putting the location into the description, since the native field is not supported yet | ||
if (data.location != null && data.location != "") { | ||
locationString = encodeURIComponent(data.location); | ||
url += "&location=" + locationString; | ||
locationString += " // "; // preparing the workaround putting the location into the description, since the native field is not supported yet | ||
} | ||
if (data['description_iCal'] != null && data['description_iCal'] != '') { // using description_iCal instead of description, since Teams does not support html tags | ||
url += '&content=' + locationString + encodeURIComponent(data['description_iCal']); | ||
if (data.description_iCal != null && data.description_iCal != "") { | ||
// using description_iCal instead of description, since Teams does not support html tags | ||
url += "&content=" + locationString + encodeURIComponent(data.description_iCal); | ||
} | ||
window.open(url, '_blank').focus(); | ||
window.open(url, "_blank").focus(); | ||
} | ||
// FUNCTION TO GENERATE THE iCAL FILE (also for the Apple option) | ||
function atcb_generate_ical(data) { | ||
let now = new Date(); | ||
now = now.toISOString().replace(/\..../g, '').replace(/[^a-z0-9]/gi,''); | ||
let formattedDate = atcb_generate_time(data, 'clean', 'ical'); | ||
let timeslot = ''; | ||
if (formattedDate['allday']) { | ||
timeslot = ';VALUE=DATE'; | ||
now = now.toISOString(); | ||
const formattedDate = atcb_generate_time(data, "clean", "ical"); | ||
let timeslot = ""; | ||
if (formattedDate.allday) { | ||
timeslot = ";VALUE=DATE"; | ||
} | ||
let ics_lines = [ | ||
"BEGIN:VCALENDAR", | ||
"VERSION:2.0", | ||
"CALSCALE:GREGORIAN", | ||
"BEGIN:VEVENT", | ||
"DTSTAMP:" + formattedDate['start'], | ||
"DTSTART" + timeslot + ":" + formattedDate['start'], | ||
"DTEND" + timeslot + ":" + formattedDate['end'], | ||
"SUMMARY:" + data['name'] | ||
]; | ||
if (data['description_iCal'] != null && data['description_iCal'] != '') { | ||
ics_lines.push("DESCRIPTION:" + data['description_iCal'].replace(/\n/g, '\\n')); | ||
} | ||
if (data['location'] != null && data['location'] != '') { | ||
ics_lines.push("LOCATION:" + data['location']); | ||
} | ||
ics_lines.push( | ||
"STATUS:CONFIRMED", | ||
"LAST-MODIFIED:" + now, | ||
"SEQUENCE:0", | ||
"END:VEVENT", | ||
"END:VCALENDAR" | ||
); | ||
let dlurl = 'data:text/calendar;charset=utf-8,'+encodeURIComponent(ics_lines.join('\r\n')); | ||
const ics_lines = ["BEGIN:VCALENDAR", "VERSION:2.0"]; | ||
const corp = "github.com/jekuer/add-to-calendar-button"; | ||
ics_lines.push("PRODID:-// " + corp + " // atcb v" + atcbVersion + " //EN"); | ||
ics_lines.push("CALSCALE:GREGORIAN"); | ||
ics_lines.push("BEGIN:VEVENT"); | ||
ics_lines.push("UID:" + now + "@add-to-calendar-button"); | ||
ics_lines.push( | ||
"DTSTAMP:" + formattedDate.start, | ||
"DTSTART" + timeslot + ":" + formattedDate.start, | ||
"DTEND" + timeslot + ":" + formattedDate.end, | ||
"SUMMARY:" + data.name.replace(/.{65}/g, "$&" + "\r\n ") // making sure it does not exceed 75 characters per line | ||
); | ||
if (data.description_iCal != null && data.description_iCal != "") { | ||
ics_lines.push( | ||
"DESCRIPTION:" + data.description_iCal.replace(/\n/g, "\\n").replace(/.{60}/g, "$&" + "\r\n ") // adjusting for intended line breaks + making sure it does not exceed 75 characters per line | ||
); | ||
} | ||
if (data.location != null && data.location != "") { | ||
ics_lines.push("LOCATION:" + data.location); | ||
} | ||
now = now.replace(/\.\d{3}/g, "").replace(/[^a-z\d]/gi, ""); | ||
ics_lines.push("STATUS:CONFIRMED", "LAST-MODIFIED:" + now, "SEQUENCE:0", "END:VEVENT", "END:VCALENDAR"); | ||
const dlurl = "data:text/calendar;charset=utf-8," + encodeURIComponent(ics_lines.join("\r\n")); | ||
const filename = data.iCalFileName || "event-to-save-in-my-calendar"; | ||
try { | ||
if (!window.ActiveXObject) { | ||
let save = document.createElement('a'); | ||
const save = document.createElement("a"); | ||
save.href = dlurl; | ||
save.target = '_blank'; | ||
save.download = data['iCalFileName'] || 'event-to-save-in-my-calendar'; | ||
let evt = new MouseEvent('click', { | ||
'view': window, | ||
'bubbles': true, | ||
'cancelable': false | ||
save.target = "_blank"; | ||
save.download = filename; | ||
const evt = new MouseEvent("click", { | ||
view: window, | ||
button: 0, | ||
bubbles: true, | ||
cancelable: false, | ||
}); | ||
@@ -707,3 +759,10 @@ save.dispatchEvent(evt); | ||
} | ||
} catch(e) { | ||
// for IE < 11 (even no longer officially supported) | ||
else if (!!window.ActiveXObject && document.execCommand) { | ||
const _window = window.open(dlurl, "_blank"); | ||
_window.document.close(); | ||
_window.document.execCommand("SaveAs", true, filename || dlurl); | ||
_window.close(); | ||
} | ||
} catch (e) { | ||
console.error(e); | ||
@@ -713,57 +772,72 @@ } | ||
// SHARED FUNCTION TO GENERATE A TIME STRING | ||
function atcb_generate_time(data, style = 'delimiters', targetCal = 'general') { | ||
let startDate = data['startDate'].split('-'); | ||
let endDate = data['endDate'].split('-'); | ||
let start = ''; | ||
let end = ''; | ||
function atcb_generate_time(data, style = "delimiters", targetCal = "general") { | ||
const startDate = data.startDate.split("-"); | ||
const endDate = data.endDate.split("-"); | ||
let start = ""; | ||
let end = ""; | ||
let allday = false; | ||
if (data['startTime'] != null && data['endTime'] != null) { | ||
if (data.startTime != null && data.endTime != null) { | ||
// Adjust for timezone, if set (see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones for either the TZ name or the offset) | ||
if (data['timeZoneOffset'] != null && data['timeZoneOffset'] != '') { | ||
if (data.timeZoneOffset != null && data.timeZoneOffset != "") { | ||
// if we have a timezone offset given, consider it | ||
start = new Date( startDate[0] + '-' + startDate[1] + '-' + startDate[2] + 'T' + data['startTime'] + ':00.000' + data['timeZoneOffset'] ); | ||
end = new Date( endDate[0] + '-' + endDate[1] + '-' + endDate[2] + 'T' + data['endTime'] + ':00.000' + data['timeZoneOffset'] ); | ||
start = start.toISOString().replace('.000', ''); | ||
end = end.toISOString().replace('.000', ''); | ||
if (style == 'clean') { | ||
start = start.replace(/\-/g, '').replace(/\:/g, ''); | ||
end = end.replace(/\-/g, '').replace(/\:/g, ''); | ||
} | ||
start = new Date( | ||
startDate[0] + | ||
"-" + | ||
startDate[1] + | ||
"-" + | ||
startDate[2] + | ||
"T" + | ||
data.startTime + | ||
":00.000" + | ||
data.timeZoneOffset | ||
); | ||
end = new Date( | ||
endDate[0] + | ||
"-" + | ||
endDate[1] + | ||
"-" + | ||
endDate[2] + | ||
"T" + | ||
data.endTime + | ||
":00.000" + | ||
data.timeZoneOffset | ||
); | ||
} else { | ||
// if there is no offset, we prepare the time, assuming it is UTC formatted | ||
start = new Date( startDate[0] + '-' + startDate[1] + '-' + startDate[2] + 'T' + data['startTime'] + ':00.000+00:00' ); | ||
end = new Date( endDate[0] + '-' + endDate[1] + '-' + endDate[2] + 'T' + data['endTime'] + ':00.000+00:00' ); | ||
if (data['timeZone'] != null && data['timeZone'] != '') { | ||
start = new Date( | ||
startDate[0] + "-" + startDate[1] + "-" + startDate[2] + "T" + data.startTime + ":00.000+00:00" | ||
); | ||
end = new Date(endDate[0] + "-" + endDate[1] + "-" + endDate[2] + "T" + data.endTime + ":00.000+00:00"); | ||
if (data.timeZone != null && data.timeZone != "") { | ||
// if a timezone is given, we adjust dynamically with the modern toLocaleString function | ||
let utcDate = new Date(start.toLocaleString('en-US', { timeZone: "UTC" })); | ||
if (data['timeZone'] == 'currentBrowser') { | ||
data['timeZone'] = Intl.DateTimeFormat().resolvedOptions().timeZone; | ||
const utcDate = new Date(start.toLocaleString("en-US", { timeZone: "UTC" })); | ||
if (data.timeZone == "currentBrowser") { | ||
data.timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; | ||
} | ||
let tzDate = new Date(start.toLocaleString('en-US', { timeZone: data['timeZone'] })); | ||
let offset = utcDate.getTime() - tzDate.getTime(); | ||
start.setTime( start.getTime() + offset ); | ||
end.setTime( end.getTime() + offset ); | ||
const tzDate = new Date(start.toLocaleString("en-US", { timeZone: data.timeZone })); | ||
const offset = utcDate.getTime() - tzDate.getTime(); | ||
start.setTime(start.getTime() + offset); | ||
end.setTime(end.getTime() + offset); | ||
} | ||
start = start.toISOString().replace('.000', ''); | ||
end = end.toISOString().replace('.000', ''); | ||
if (style == 'clean') { | ||
start = start.replace(/\-/g, '').replace(/\:/g, ''); | ||
end = end.replace(/\-/g, '').replace(/\:/g, ''); | ||
} | ||
} | ||
} else { // would be an allday event then | ||
start = start.toISOString().replace(".000", ""); | ||
end = end.toISOString().replace(".000", ""); | ||
if (style == "clean") { | ||
start = start.replace(/-/g, "").replace(/:/g, ""); | ||
end = end.replace(/-/g, "").replace(/:/g, ""); | ||
} | ||
} else { | ||
// would be an allday event then | ||
allday = true; | ||
start = new Date(Date.UTC(startDate[0], startDate[1] - 1, startDate[2])); | ||
let breakStart = start.toISOString().replace(/T(.+)Z/g, ''); | ||
let breakStart = start.toISOString().replace(/T(.+)Z/g, ""); | ||
end = new Date(Date.UTC(endDate[0], endDate[1] - 1, endDate[2])); | ||
if (targetCal == 'google' || targetCal == 'microsoft' || targetCal == 'ical') { | ||
if (targetCal == "google" || targetCal == "microsoft" || targetCal == "ical") { | ||
end.setDate(end.getDate() + 1); // increment the day by 1 for Google Calendar, iCal and Outlook | ||
} | ||
let breakEnd = end.toISOString().replace(/T(.+)Z/g, ''); | ||
if (style == 'clean') { | ||
breakStart = breakStart.replace(/\-/g, ''); | ||
breakEnd = breakEnd.replace(/\-/g, ''); | ||
let breakEnd = end.toISOString().replace(/T(.+)Z/g, ""); | ||
if (style == "clean") { | ||
breakStart = breakStart.replace(/-/g, ""); | ||
breakEnd = breakEnd.replace(/-/g, ""); | ||
} | ||
@@ -773,3 +847,3 @@ start = breakStart; | ||
} | ||
let returnObject = {'start':start, 'end':end, 'allday':allday}; | ||
const returnObject = { start, end, allday }; | ||
return returnObject; | ||
@@ -780,19 +854,23 @@ } | ||
// Global listener to ESC key to close dropdown | ||
document.addEventListener('keydown', evt => { | ||
if (evt.key === 'Escape') { | ||
document.addEventListener("keydown", (evt) => { | ||
if (evt.key === "Escape") { | ||
atcb_close(); | ||
} | ||
}); | ||
// Global listener to any screen changes, where we need to close all buttons in order to prevent any bad renderings (more "expensive" alternative would be to re-render them) | ||
window.addEventListener('resize', () => { | ||
// Global listener to any screen changes | ||
// Closing all buttons in order to prevent any bad renderings (more "expensive" alternative would be to re-render them) | ||
// Checking for width change to prevent resize on mobile scroll, where a disappearing browser bar could trigger it | ||
let windowWidth = window.innerWidth; | ||
window.addEventListener("resize", () => { | ||
if (window.innerWidth != windowWidth) { | ||
windowWidth = window.innerWidth; | ||
atcb_close(true); | ||
} | ||
}); | ||
} | ||
// START INIT | ||
if (isBrowser()) { | ||
document.addEventListener('DOMContentLoaded', atcb_init, false); // init the magic as soon as the DOM has been loaded | ||
document.addEventListener("DOMContentLoaded", atcb_init, false); // init the magic as soon as the DOM has been loaded | ||
} | ||
// END INIT |
@@ -1,2 +0,2 @@ | ||
const atcbVersion="1.8.10",isBrowser=new Function("try { return this===window; }catch(e){ return false; }"),isiOS=isBrowser()?new Function("if ((/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)){ return true; }else{ return false; }"):new Function("return false;");function atcb_init(){console.log("add-to-calendar button initialized (version "+atcbVersion+")"),console.log("See https://github.com/jekuer/add-to-calendar-button for details");let a=document.querySelectorAll(".atcb");if(0<a.length){var c=document.querySelectorAll(".atcb_initialized");for(let n=0;n<a.length;n++)if(!a[n].classList.contains("atcb_initialized")){let e,t=a[n].querySelector("script");t&&t.innerHTML?(e=JSON.parse(t.innerHTML.replace(/(\r\n|\n|\r)/gm,"").replace(/(<(?!br)([^>]+)>)/gi,"")),e=atcb_parse_schema_json(e),e.deleteJSON=!1):(e=JSON.parse(a[n].innerHTML.replace(/(\r\n|\n|\r)/gm,"").replace(/(<(?!br)([^>]+)>)/gi,"")),e.deleteJSON=!0),e=atcb_patch_config(e),atcb_check_required(e)&&(e=atcb_decorate_data(e),atcb_validate(e)&&atcb_generate(a[n],n+c.length,e))}}}function atcb_parse_schema_json(t){try{Object.keys(t.event).forEach(e=>{"@"!==e.charAt(0)&&(t[e]=t.event[e])}),delete t.event}catch(e){console.error("add-to-calendar button problem: it seems like you use the schema.org style, but did not define it properly")}return t}function atcb_patch_config(t){const n={title:"name",dateStart:"startDate",dateEnd:"endDate",timeStart:"startTime",timeEnd:"endTime"};return Object.keys(n).forEach(e=>{null==t[n[e]]&&null!=t[e]&&(t[n[e]]=t[e])}),t}function atcb_decorate_data(e){if((e=atcb_date_cleanup(e)).startDate=atcb_date_calculation(e.startDate),e.endDate=atcb_date_calculation(e.endDate),!e.description||e.description_iCal)return e;const t=Object.assign({},e);return t.description=t.description.replace(/<br\s*\/?>/gim,"\n"),t.description_iCal=t.description.replace("[url]","").replace("[/url]",""),t.description=t.description.replace(/\[url\](.*?)\[\/url\]/g,"<a href='$1' target='_blank' rel='noopener'>$1</a>"),t}function atcb_check_required(t){if(null==t.options||t.options.length<1)return console.error("add-to-calendar button generation failed: no options set"),!1;return["name","startDate","endDate"].every(function(e){return null!=t[e]&&""!=t[e]||(console.error("add-to-calendar button generation failed: required setting missing ["+e+"]"),!1)})}function atcb_date_cleanup(n){return["start","end"].forEach(function(t){var e;if(null!=n[t+"Date"]&&(n[t+"Date"]=n[t+"Date"].replace(/\..../,"").replace("Z",""),null!=(e=n[t+"Date"].split("T"))[1]&&(n[t+"Date"]=e[0],n[t+"Time"]=e[1])),null!=n[t+"Time"]&&8==n[t+"Time"].length){let e=n[t+"Time"];n[t+"Time"]=e.substring(0,e.length-3)}}),n}function atcb_date_calculation(e){let t=new Date;var n=t.getUTCMonth()+1+"-"+t.getUTCDate()+"-"+t.getUTCFullYear();const a=(e=e.replace(/today/gi,n)).split("+");n=a[0].split("-");let c=new Date(n[0],n[1]-1,n[2]);return n[0].length<4&&(c=new Date(n[2],n[0]-1,n[1])),null!=a[1]&&0<a[1]&&c.setDate(c.getDate()+parseInt(a[1])),c.getFullYear()+"-"+(c.getMonth()+1<10?"0":"")+(c.getMonth()+1)+"-"+(c.getDate()<10?"0":"")+c.getDate()}function atcb_validate(n){const t=["Apple","Google","iCal","Microsoft365","Outlook.com","MicrosoftTeams","Yahoo"];if(!n.options.every(function(e){e=e.split("|");return!!t.includes(e[0])||(console.error("add-to-calendar button generation failed: invalid option ["+e[0]+"]"),!1)}))return!1;const e=["startDate","endDate"];let a=e;if(!e.every(function(e){if(10!=n[e].length)return console.error("add-to-calendar button generation failed: date misspelled [-> YYYY-MM-DD]"),!1;var t=n[e].split("-");return t.length<3||3<t.length?(console.error("add-to-calendar button generation failed: date misspelled ["+e+": "+n[e]+"]"),!1):(a[e]=new Date(t[0],t[1]-1,t[2]),!0)}))return!1;return!!["startTime","endTime"].every(function(e){if(null!=n[e]){if(5!=n[e].length)return console.error("add-to-calendar button generation failed: time misspelled [-> HH:MM]"),!1;var t=n[e].split(":");if(t.length<2||2<t.length)return console.error("add-to-calendar button generation failed: time misspelled ["+e+": "+n[e]+"]"),!1;if(23<t[0])return console.error("add-to-calendar button generation failed: time misspelled - hours number too high ["+e+": "+t[0]+"]"),!1;if(59<t[1])return console.error("add-to-calendar button generation failed: time misspelled - minutes number too high ["+e+": "+t[1]+"]"),!1;"startTime"==e&&(a.startDate=new Date(a.startDate.getTime()+36e5*t[0]+6e4*t[1])),"endTime"==e&&(a.endDate=new Date(a.endDate.getTime()+36e5*t[0]+6e4*t[1]))}return!0})&&(null!=n.startTime&&null==n.endTime||null==n.startTime&&null!=n.endTime?(console.error("add-to-calendar button generation failed: if you set a starting time, you also need to define an end time"),!1):!(a.endDate<a.startDate)||(console.error("add-to-calendar button generation failed: end date before start date"),!1))}function atcb_generate(e,t,n){n.deleteJSON&&(e.innerHTML="");let a=document.createElement("div"),c=(a.classList.add("atcb_button_wrapper"),e.appendChild(a),document.createElement("button"));c.id="atcb_button_"+t,c.classList.add("atcb_button"),c.setAttribute("type","button"),a.appendChild(c),c.innerHTML='<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><path d="M81.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zm-3.65 76.03c1.83 0 3.32 1.49 3.32 3.32s-1.49 3.32-3.32 3.32l-12.95-.04-.04 12.93c0 1.83-1.49 3.32-3.32 3.32s-3.32-1.49-3.32-3.32l.04-12.94-12.93-.05c-1.83 0-3.32-1.49-3.32-3.32s1.49-3.32 3.32-3.32l12.94.04.04-12.93c0-1.83 1.49-3.32 3.32-3.32s3.32 1.49 3.32 3.32l-.04 12.95 12.94.04h0zM29.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zM6.4 45.32h110.08V21.47c0-.8-.33-1.53-.86-2.07-.53-.53-1.26-.86-2.07-.86H103c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h10.55c2.57 0 4.9 1.05 6.59 2.74s2.74 4.02 2.74 6.59v27.06 65.03c0 2.57-1.05 4.9-2.74 6.59s-4.02 2.74-6.59 2.74H9.33c-2.57 0-4.9-1.05-6.59-2.74-1.69-1.7-2.74-4.03-2.74-6.6V48.53 21.47c0-2.57 1.05-4.9 2.74-6.59s4.02-2.74 6.59-2.74H20.6c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H9.33c-.8 0-1.53.33-2.07.86-.53.53-.86 1.26-.86 2.07v23.85h0zm110.08 6.41H6.4v61.82c0 .8.33 1.53.86 2.07.53.53 1.26.86 2.07.86h104.22c.8 0 1.53-.33 2.07-.86.53-.53.86-1.26.86-2.07V51.73h0zM50.43 18.54c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h21.49c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H50.43h0z"/></svg></span>',c.innerHTML+='<span class="atcb_text">'+(n.label||"Add to Calendar")+"</span>","click"==n.trigger?c.addEventListener("mousedown",()=>atcb_toggle(n,c,!0,!1)):(c.addEventListener("touchstart",()=>atcb_toggle(n,c,!0,!1),{passive:!0}),c.addEventListener("mouseenter",()=>atcb_open(n,c,!0,!1))),c.addEventListener("keydown",function(e){"Enter"==e.key&&atcb_toggle(n,c,!0)}),e.classList.remove("atcb"),e.classList.add("atcb_initialized"),n.inline?e.style.display="inline-block":e.style.display="block",console.log("add-to-calendar button #"+(t+1)+" created")}function atcb_generate_dropdown_list(a){const c=document.createElement("div");return c.classList.add("atcb_list"),a.options.forEach(function(e){var t=e.split("|");let n=document.createElement("div");switch(n.classList.add("atcb_list_item"),n.tabIndex=0,c.appendChild(n),t[0]){case"Apple":n.innerHTML='<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" viewBox="0 0 640 640"><path d="M494.782 340.02c-.803-81.025 66.084-119.907 69.072-121.832-37.595-54.993-96.167-62.552-117.037-63.402-49.843-5.032-97.242 29.362-122.565 29.362-25.253 0-64.277-28.607-105.604-27.85-54.32.803-104.4 31.594-132.403 80.245C29.81 334.457 71.81 479.58 126.816 558.976c26.87 38.882 58.914 82.56 100.997 81 40.512-1.594 55.843-26.244 104.848-26.244 48.993 0 62.753 26.245 105.64 25.406 43.606-.803 71.232-39.638 97.925-78.65 30.887-45.12 43.548-88.75 44.316-90.994-.969-.437-85.029-32.634-85.879-129.439l.118-.035zM414.23 102.178C436.553 75.095 451.636 37.5 447.514-.024c-32.162 1.311-71.163 21.437-94.253 48.485-20.729 24.012-38.836 62.28-33.993 99.036 35.918 2.8 72.591-18.248 94.926-45.272l.036-.047z"/></svg></span>',n.innerHTML+='<span class="atcb_text">',n.innerHTML+=t[1]||"Apple",n.innerHTML+="</span>",n.addEventListener("click",function(){atcb_generate_ical(a),atcb_close()});break;case"Google":n.innerHTML='<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><path d="M93.78 29.1H29.1v64.68h64.68V29.1z" fill="#fff"/><path d="M93.78 122.88l29.1-29.1h-29.1v29.1z" fill="#f72a25"/><path d="M122.88 29.1h-29.1v64.68h29.1V29.1z" fill="#fbbc04"/><path d="M93.78 93.78H29.1v29.1h64.68v-29.1z" fill="#34a853"/><path d="M0 93.78v19.4c0 5.36 4.34 9.7 9.7 9.7h19.4v-29.1H0h0z" fill="#188038"/><path d="M122.88 29.1V9.7c0-5.36-4.34-9.7-9.7-9.7h-19.4v29.1h29.1 0z" fill="#1967d2"/><path d="M93.78 0H9.7C4.34 0 0 4.34 0 9.7v84.08h29.1V29.1h64.67V0h.01z" fill="#4285f4"/><path d="M42.37 79.27c-2.42-1.63-4.09-4.02-5-7.17l5.61-2.31c.51 1.94 1.4 3.44 2.67 4.51 1.26 1.07 2.8 1.59 4.59 1.59 1.84 0 3.41-.56 4.73-1.67 1.32-1.12 1.98-2.54 1.98-4.26 0-1.76-.7-3.2-2.09-4.32s-3.14-1.67-5.22-1.67H46.4v-5.55h2.91c1.79 0 3.31-.48 4.54-1.46 1.23-.97 1.84-2.3 1.84-3.99 0-1.5-.55-2.7-1.65-3.6s-2.49-1.35-4.18-1.35c-1.65 0-2.96.44-3.93 1.32s-1.7 2-2.12 3.24l-5.55-2.31c.74-2.09 2.09-3.93 4.07-5.52s4.51-2.39 7.58-2.39c2.27 0 4.32.44 6.13 1.32s3.23 2.1 4.26 3.65c1.03 1.56 1.54 3.31 1.54 5.25 0 1.98-.48 3.65-1.43 5.03-.95 1.37-2.13 2.43-3.52 3.16v.33c1.79.74 3.36 1.96 4.51 3.52 1.17 1.58 1.76 3.46 1.76 5.66s-.56 4.16-1.67 5.88c-1.12 1.72-2.66 3.08-4.62 4.07s-4.17 1.49-6.62 1.49c-2.84 0-5.46-.81-7.88-2.45h0 0zm34.46-27.84l-6.16 4.45-3.08-4.67 11.05-7.97h4.24v37.6h-6.05V51.43h0z" fill="#1a73e8"/></svg></span>',n.innerHTML+='<span class="atcb_text">',n.innerHTML+=t[1]||"Google",n.innerHTML+="</span>",n.addEventListener("click",function(){atcb_generate_google(a),atcb_close()});break;case"iCal":n.innerHTML='<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><path d="M81.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zm-15.5 99.08c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2H81.9c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H66.11h0zM15.85 67.09c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H15.85h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H40.98h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2H81.9c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H66.11h0zm25.14 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H91.25h0zm-75.4 18.36c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H15.85h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H40.98h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2H81.9c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H66.11h0zm25.14 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H91.25h0zm-75.4 18.36c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H15.85h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H40.98h0zM29.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zM6.4 45.32h110.07V21.47c0-.8-.33-1.53-.86-2.07-.53-.53-1.26-.86-2.07-.86H103c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h10.55c2.57 0 4.9 1.05 6.59 2.74s2.74 4.02 2.74 6.59v27.06 65.03c0 2.57-1.05 4.9-2.74 6.59s-4.02 2.74-6.59 2.74H9.33c-2.57 0-4.9-1.05-6.59-2.74-1.69-1.7-2.74-4.03-2.74-6.6V48.52 21.47c0-2.57 1.05-4.9 2.74-6.59s4.02-2.74 6.59-2.74H20.6c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H9.33c-.8 0-1.53.33-2.07.86-.53.53-.86 1.26-.86 2.07v23.85h0zm110.08 6.41H6.4v61.82c0 .8.33 1.53.86 2.07.53.53 1.26.86 2.07.86h104.22c.8 0 1.53-.33 2.07-.86.53-.53.86-1.26.86-2.07V51.73h0zM50.43 18.54c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h21.49c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H50.43h0z"/></svg></span>',n.innerHTML+='<span class="atcb_text">',n.innerHTML+=t[1]||"iCal File",n.innerHTML+="</span>",n.addEventListener("click",function(){atcb_generate_ical(a),atcb_close()});break;case"MicrosoftTeams":n.innerHTML='<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 2228.833 2073.333"><g fill="#5059c9"><path d="M1554.637 777.5h575.713c54.391 0 98.483 44.092 98.483 98.483v524.398c0 199.901-162.051 361.952-361.952 361.952h0-1.711c-199.901.028-361.975-162-362.004-361.901v-.052-571.409c.001-28.427 23.045-51.471 51.471-51.471h0z"/><circle cx="1943.75" cy="440.583" r="233.25"/></g><g fill="#7b83eb"><circle cx="1218.083" cy="336.917" r="336.917"/><path d="M1667.323 777.5H717.01c-53.743 1.33-96.257 45.931-95.01 99.676v598.105c-7.505 322.519 247.657 590.16 570.167 598.053 322.51-7.893 577.671-275.534 570.167-598.053V877.176c1.245-53.745-41.268-98.346-95.011-99.676z"/></g><path opacity=".1" d="M1244 777.5v838.145c-.258 38.435-23.549 72.964-59.09 87.598-11.316 4.787-23.478 7.254-35.765 7.257H667.613c-6.738-17.105-12.958-34.21-18.142-51.833-18.144-59.477-27.402-121.307-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52H1244z"/><path opacity=".2" d="M1192.167 777.5v889.978a91.84 91.84 0 0 1-7.257 35.765c-14.634 35.541-49.163 58.833-87.598 59.09H691.975c-8.812-17.105-17.105-34.21-24.362-51.833s-12.958-34.21-18.142-51.833a631.28 631.28 0 0 1-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h475.313z"/><path opacity=".2" d="M1192.167 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855h-447.84A631.28 631.28 0 0 1 622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h475.312z"/><path opacity=".2" d="M1140.333 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855H649.472A631.28 631.28 0 0 1 622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h423.478z"/><path opacity=".1" d="M1244 509.522v163.275c-8.812.518-17.105 1.037-25.917 1.037s-17.105-.518-25.917-1.037c-17.496-1.161-34.848-3.937-51.833-8.293a336.92 336.92 0 0 1-233.25-198.003 288.02 288.02 0 0 1-16.587-51.833h258.648c52.305.198 94.657 42.549 94.856 94.854z"/><use xlink:href="#C" opacity=".2"/><use xlink:href="#C" opacity=".2"/><path opacity=".2" d="M1140.333 561.355v103.148A336.92 336.92 0 0 1 907.083 466.5h138.395c52.305.199 94.656 42.551 94.855 94.855z"/><linearGradient id="A" gradientUnits="userSpaceOnUse" x1="198.099" y1="392.261" x2="942.234" y2="1681.073"><stop offset="0" stop-color="#5a62c3"/><stop offset=".5" stop-color="#4d55bd"/><stop offset="1" stop-color="#3940ab"/></linearGradient><path fill="url(#A)" d="M95.01 466.5h950.312c52.473 0 95.01 42.538 95.01 95.01v950.312c0 52.473-42.538 95.01-95.01 95.01H95.01c-52.473 0-95.01-42.538-95.01-95.01V561.51c0-52.472 42.538-95.01 95.01-95.01z"/><path fill="#fff" d="M820.211,828.193H630.241v517.297H509.211V828.193H320.123V727.844h500.088V828.193z"/><defs ><path id="C" d="M1192.167 561.355v111.442c-17.496-1.161-34.848-3.937-51.833-8.293a336.92 336.92 0 0 1-233.25-198.003h190.228c52.304.198 94.656 42.55 94.855 94.854z"/></defs></svg></span>',n.innerHTML+='<span class="atcb_text">',n.innerHTML+=t[1]||"Microsoft Teams",n.innerHTML+="</span>",n.addEventListener("click",function(){atcb_generate_teams(a),atcb_close()});break;case"Microsoft365":n.innerHTML='<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 278050 333334" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd"><path fill="#ea3e23" d="M278050 305556l-29-16V28627L178807 0 448 66971l-448 87 22 200227 60865-23821V80555l117920-28193-17 239519L122 267285l178668 65976v73l99231-27462v-316z"/></svg></span>',n.innerHTML+='<span class="atcb_text">',n.innerHTML+=t[1]||"Microsoft 365",n.innerHTML+="</span>",n.addEventListener("click",function(){atcb_generate_microsoft(a,"365"),atcb_close()});break;case"Outlook.com":n.innerHTML='<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.129793726981 0 33.251996719421 32" width="2500" height="2397"><path d="M28.596 2H11.404A1.404 1.404 0 0 0 10 3.404V5l9.69 3L30 5V3.404A1.404 1.404 0 0 0 28.596 2z" fill="#0364b8"/><path d="M31.65 17.405A11.341 11.341 0 0 0 32 16a.666.666 0 0 0-.333-.576l-.013-.008-.004-.002L20.812 9.24a1.499 1.499 0 0 0-1.479-.083 1.49 1.49 0 0 0-.145.082L8.35 15.415l-.004.002-.012.007A.666.666 0 0 0 8 16a11.344 11.344 0 0 0 .35 1.405l11.492 8.405z" fill="#0a2767"/><path d="M24 5h-7l-2.021 3L17 11l7 6h6v-6z" fill="#28a8ea"/><path d="M10 5h7v6h-7z" fill="#0078d4"/><path d="M24 5h6v6h-6z" fill="#50d9ff"/><path d="M24 17l-7-6h-7v6l7 6 10.832 1.768z" fill="#0364b8"/><path d="M17 11h7v6h-7z" fill="#0078d4"/><path d="M10 17h7v6h-7z" fill="#064a8c"/><path d="M24 17h6v6h-6z" fill="#0078d4"/><path d="M20.19 25.218l-11.793-8.6.495-.87 10.909 6.212a.528.528 0 0 0 .42-.012l10.933-6.23.496.869z" fill="#0a2767" opacity=".5"/><path d="M31.667 16.577l-.014.008-.003.002-10.838 6.174a1.497 1.497 0 0 1-1.46.091l3.774 5.061 8.254 1.797v.004A1.498 1.498 0 0 0 32 28.5V16a.666.666 0 0 1-.333.577z" fill="#1490df"/><path d="M32 28.5v-.738l-9.983-5.688-1.205.687a1.497 1.497 0 0 1-1.46.091l3.774 5.061 8.254 1.797v.004A1.498 1.498 0 0 0 32 28.5z" opacity=".05"/><path d="M31.95 28.883L21.007 22.65l-.195.11a1.497 1.497 0 0 1-1.46.092l3.774 5.061 8.254 1.797v.004a1.501 1.501 0 0 0 .57-.83z" opacity=".1"/><path d="M8.35 16.59v-.01h-.01l-.03-.02A.65.65 0 0 1 8 16v12.5A1.498 1.498 0 0 0 9.5 30h21a1.503 1.503 0 0 0 .37-.05.637.637 0 0 0 .18-.06.142.142 0 0 0 .06-.02 1.048 1.048 0 0 0 .23-.13c.02-.01.03-.01.04-.03z" fill="#28a8ea"/><path d="M18 24.667V8.333A1.337 1.337 0 0 0 16.667 7H10.03v7.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v10h8.667A1.337 1.337 0 0 0 18 24.667z" opacity=".1"/><path d="M17 25.667V9.333A1.337 1.337 0 0 0 15.667 8H10.03v6.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v11h7.667A1.337 1.337 0 0 0 17 25.667z" opacity=".2"/><path d="M17 23.667V9.333A1.337 1.337 0 0 0 15.667 8H10.03v6.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v9h7.667A1.337 1.337 0 0 0 17 23.667z" opacity=".2"/><path d="M16 23.667V9.333A1.337 1.337 0 0 0 14.667 8H10.03v6.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v9h6.667A1.337 1.337 0 0 0 16 23.667z" opacity=".2"/><path d="M1.333 8h13.334A1.333 1.333 0 0 1 16 9.333v13.334A1.333 1.333 0 0 1 14.667 24H1.333A1.333 1.333 0 0 1 0 22.667V9.333A1.333 1.333 0 0 1 1.333 8z" fill="#0078d4"/><path d="M3.867 13.468a4.181 4.181 0 0 1 1.642-1.814A4.965 4.965 0 0 1 8.119 11a4.617 4.617 0 0 1 2.413.62 4.14 4.14 0 0 1 1.598 1.733 5.597 5.597 0 0 1 .56 2.55 5.901 5.901 0 0 1-.577 2.666 4.239 4.239 0 0 1-1.645 1.794A4.8 4.8 0 0 1 7.963 21a4.729 4.729 0 0 1-2.468-.627 4.204 4.204 0 0 1-1.618-1.736 5.459 5.459 0 0 1-.567-2.519 6.055 6.055 0 0 1 .557-2.65zm1.75 4.258a2.716 2.716 0 0 0 .923 1.194 2.411 2.411 0 0 0 1.443.435 2.533 2.533 0 0 0 1.541-.449 2.603 2.603 0 0 0 .897-1.197 4.626 4.626 0 0 0 .286-1.665 5.063 5.063 0 0 0-.27-1.686 2.669 2.669 0 0 0-.866-1.24 2.387 2.387 0 0 0-1.527-.473 2.493 2.493 0 0 0-1.477.439 2.741 2.741 0 0 0-.944 1.203 4.776 4.776 0 0 0-.007 3.44z" fill="#fff"/></svg></span>',n.innerHTML+='<span class="atcb_text">',n.innerHTML+=t[1]||"Outlook.com",n.innerHTML+="</span>",n.addEventListener("click",function(){atcb_generate_microsoft(a,"outlook"),atcb_close()});break;case"Yahoo":n.innerHTML='<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3386.34 3010.5" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd"><path d="M0 732.88h645.84l376.07 962.1 380.96-962.1h628.76l-946.8 2277.62H451.98l259.19-603.53L.02 732.88zm2763.84 768.75h-704.26L2684.65 0l701.69.03-622.5 1501.6zm-519.78 143.72c216.09 0 391.25 175.17 391.25 391.22 0 216.06-175.16 391.23-391.25 391.23-216.06 0-391.19-175.17-391.19-391.23 0-216.05 175.16-391.22 391.19-391.22z" fill="#5f01d1" fill-rule="nonzero"/></svg></span>',n.innerHTML+='<span class="atcb_text">',n.innerHTML+=t[1]||"Yahoo",n.innerHTML+="</span>",n.addEventListener("click",function(){atcb_generate_yahoo(a),atcb_close()})}n.addEventListener("keydown",function(e){"Enter"==e.key&&this.click()})}),c}function atcb_generate_bg_overlay(e){const t=document.createElement("div");t.classList.add("atcb_bgoverlay"),t.tabIndex=0,t.addEventListener("click",()=>atcb_close(!0));let n=!1;return t.addEventListener("touchstart",()=>n=!1,{passive:!0}),t.addEventListener("touchmove",()=>n=!0,{passive:!0}),t.addEventListener("touchend",function(){0==n&&atcb_close(!0)},{passive:!0}),t.addEventListener("focus",()=>atcb_close(!1)),"click"!==e.trigger?t.addEventListener("mousemove",()=>atcb_close(!0)):t.classList.add("atcb_click"),t}function atcb_toggle(e,t,n,a=!0){t.classList.contains("atcb_active")?atcb_close():atcb_open(e,t,n,a)}function atcb_open(e,t,n=!1,a=!0){if(!document.querySelector(".atcb_list")){const c=atcb_generate_dropdown_list(e,n);t?(t.classList.add("atcb_active"),t=t.getBoundingClientRect(),c.style.width=t.width+"px",c.style.top=t.bottom+window.scrollY-3+"px",c.style.left=t.left+"px"):c.classList.add("atcb_modal"),n&&c.classList.add("atcb_generated_button"),document.body.appendChild(c),document.body.appendChild(atcb_generate_bg_overlay(e)),a&&c.firstChild.focus()}}function atcb_close(e=!1){if(!e){let e=document.querySelector(".atcb_active");e?e.focus():(e=document.querySelector(".atcb_customTrigger"),e&&e.focus())}Array.from(document.querySelectorAll(".atcb_active")).forEach(e=>{e.classList.remove("atcb_active")}),Array.from(document.querySelectorAll(".atcb_list")).concat(Array.from(document.querySelectorAll(".atcb_bgoverlay"))).forEach(e=>e.remove())}function atcb_action(e,t){if(!atcb_check_required(e))throw new Error("data missing; see logs");if(!atcb_validate(e=atcb_decorate_data(e)))throw new Error("Invalid data; see logs");atcb_open(e,t)}function atcb_generate_google(e){let t="https://calendar.google.com/calendar/render?action=TEMPLATE";var n=atcb_generate_time(e,"clean","google");t+="&dates="+n.start+"%2F"+n.end,null!=e.name&&""!=e.name&&(t+="&text="+encodeURIComponent(e.name)),null!=e.location&&""!=e.location&&(t+="&location="+encodeURIComponent(e.location),isiOS()&&(null==e.description||""==e.description?e.description="":e.description+="<br><br>",e.description+="📍: "+e.location)),null!=e.description&&""!=e.description&&(t+="&details="+encodeURIComponent(e.description)),window.open(t,"_blank").focus()}function atcb_generate_yahoo(e){let t="https://calendar.yahoo.com/?v=60";var n=atcb_generate_time(e,"clean");t+="&st="+n.start+"&et="+n.end,n.allday&&(t+="&dur=allday"),null!=e.name&&""!=e.name&&(t+="&title="+encodeURIComponent(e.name)),null!=e.location&&""!=e.location&&(t+="&in_loc="+encodeURIComponent(e.location)),null!=e.description&&""!=e.description&&(t+="&desc="+encodeURIComponent(e.description)),window.open(t,"_blank").focus()}function atcb_generate_microsoft(e,t="365"){let n="https://";n+="outlook"==t?"outlook.live.com":"outlook.office.com",n+="/calendar/0/deeplink/compose?path=%2Fcalendar%2Faction%2Fcompose&rru=addevent";t=atcb_generate_time(e,"delimiters","microsoft");n+="&startdt="+t.start+"&enddt="+t.end,t.allday&&(n+="&allday=true"),null!=e.name&&""!=e.name&&(n+="&subject="+encodeURIComponent(e.name)),null!=e.location&&""!=e.location&&(n+="&location="+encodeURIComponent(e.location)),null!=e.description&&""!=e.description&&(n+="&body="+encodeURIComponent(e.description.replace(/\n/g,"<br>"))),window.open(n,"_blank").focus()}function atcb_generate_teams(e){let t="https://teams.microsoft.com/l/meeting/new?";var n=atcb_generate_time(e,"delimiters","microsoft");t+="&startTime="+n.start+"&endTime="+n.end;let a="";null!=e.name&&""!=e.name&&(t+="&subject="+encodeURIComponent(e.name)),null!=e.location&&""!=e.location&&(a=encodeURIComponent(e.location),t+="&location="+a,a+=" // "),null!=e.description_iCal&&""!=e.description_iCal&&(t+="&content="+a+encodeURIComponent(e.description_iCal)),window.open(t,"_blank").focus()}function atcb_generate_ical(t){let e=new Date;e=e.toISOString().replace(/\..../g,"").replace(/[^a-z0-9]/gi,"");var n=atcb_generate_time(t,"clean","ical");let a="",c=(n.allday&&(a=";VALUE=DATE"),["BEGIN:VCALENDAR","VERSION:2.0","CALSCALE:GREGORIAN","BEGIN:VEVENT","DTSTAMP:"+n.start,"DTSTART"+a+":"+n.start,"DTEND"+a+":"+n.end,"SUMMARY:"+t.name]);null!=t.description_iCal&&""!=t.description_iCal&&c.push("DESCRIPTION:"+t.description_iCal.replace(/\n/g,"\\n")),null!=t.location&&""!=t.location&&c.push("LOCATION:"+t.location),c.push("STATUS:CONFIRMED","LAST-MODIFIED:"+e,"SEQUENCE:0","END:VEVENT","END:VCALENDAR");n="data:text/calendar;charset=utf-8,"+encodeURIComponent(c.join("\r\n"));try{if(!window.ActiveXObject){let e=document.createElement("a");e.href=n,e.target="_blank",e.download=t.iCalFileName||"event-to-save-in-my-calendar";var o=new MouseEvent("click",{view:window,bubbles:!0,cancelable:!1});e.dispatchEvent(o),(window.URL||window.webkitURL).revokeObjectURL(e.href)}}catch(e){console.error(e)}}function atcb_generate_time(n,a="delimiters",c="general"){var o=n.startDate.split("-"),i=n.endDate.split("-");let l="",r="",s=!1;if(null!=n.startTime&&null!=n.endTime)if(null!=n.timeZoneOffset&&""!=n.timeZoneOffset)l=new Date(o[0]+"-"+o[1]+"-"+o[2]+"T"+n.startTime+":00.000"+n.timeZoneOffset),r=new Date(i[0]+"-"+i[1]+"-"+i[2]+"T"+n.endTime+":00.000"+n.timeZoneOffset),l=l.toISOString().replace(".000",""),r=r.toISOString().replace(".000",""),"clean"==a&&(l=l.replace(/\-/g,"").replace(/\:/g,""),r=r.replace(/\-/g,"").replace(/\:/g,""));else{if(l=new Date(o[0]+"-"+o[1]+"-"+o[2]+"T"+n.startTime+":00.000+00:00"),r=new Date(i[0]+"-"+i[1]+"-"+i[2]+"T"+n.endTime+":00.000+00:00"),null!=n.timeZone&&""!=n.timeZone){let e=new Date(l.toLocaleString("en-US",{timeZone:"UTC"})),t=("currentBrowser"==n.timeZone&&(n.timeZone=Intl.DateTimeFormat().resolvedOptions().timeZone),new Date(l.toLocaleString("en-US",{timeZone:n.timeZone})));n=e.getTime()-t.getTime();l.setTime(l.getTime()+n),r.setTime(r.getTime()+n)}l=l.toISOString().replace(".000",""),r=r.toISOString().replace(".000",""),"clean"==a&&(l=l.replace(/\-/g,"").replace(/\:/g,""),r=r.replace(/\-/g,"").replace(/\:/g,""))}else{s=!0,l=new Date(Date.UTC(o[0],o[1]-1,o[2]));let e=l.toISOString().replace(/T(.+)Z/g,""),t=(r=new Date(Date.UTC(i[0],i[1]-1,i[2])),"google"!=c&&"microsoft"!=c&&"ical"!=c||r.setDate(r.getDate()+1),r.toISOString().replace(/T(.+)Z/g,""));"clean"==a&&(e=e.replace(/\-/g,""),t=t.replace(/\-/g,"")),l=e,r=t}return{start:l,end:r,allday:s}}isBrowser()&&(document.addEventListener("keydown",e=>{"Escape"===e.key&&atcb_close()}),window.addEventListener("resize",()=>{atcb_close(!0)})),isBrowser()&&document.addEventListener("DOMContentLoaded",atcb_init,!1); | ||
const atcbVersion="1.9.0",isBrowser=new Function("try { return this===window; }catch(e){ return false; }"),isiOS=isBrowser()?new Function("if ((/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)){ return true; }else{ return false; }"):new Function("return false;");function atcb_init(){console.log("add-to-calendar button initialized (version "+atcbVersion+")"),console.log("See https://github.com/jekuer/add-to-calendar-button for details");const a=document.querySelectorAll(".atcb");if(0<a.length){var n=document.querySelectorAll(".atcb_initialized");for(let t=0;t<a.length;t++)if(!a[t].classList.contains("atcb_initialized")){let e;const c=a[t].querySelector("script");c&&c.innerHTML?(e=JSON.parse(c.innerHTML.replace(/(\r\n|\n|\r)/g,"").replace(/(<(?!br)([^>]+)>)/gi,"")),e=atcb_parse_schema_json(e),e.deleteJSON=!1):(e=JSON.parse(a[t].innerHTML.replace(/(\r\n|\n|\r)/g,"").replace(/(<(?!br)([^>]+)>)/gi,"")),e.deleteJSON=!0),e=atcb_patch_config(e),atcb_check_required(e)&&(e=atcb_decorate_data(e),atcb_validate(e)&&atcb_generate(a[t],t+n.length,e))}}}function atcb_parse_schema_json(t){try{Object.keys(t.event).forEach(e=>{"@"!==e.charAt(0)&&(t[e]=t.event[e])}),delete t.event}catch(e){console.error("add-to-calendar button problem: it seems like you use the schema.org style, but did not define it properly")}return t}function atcb_patch_config(t){const a={title:"name",dateStart:"startDate",dateEnd:"endDate",timeStart:"startTime",timeEnd:"endTime"};return Object.keys(a).forEach(e=>{null==t[a[e]]&&null!=t[e]&&(t[a[e]]=t[e])}),t}function atcb_decorate_data(e){if((e=atcb_date_cleanup(e)).startDate=atcb_date_calculation(e.startDate),e.endDate=atcb_date_calculation(e.endDate),!e.description||e.description_iCal)return e;const t=Object.assign({},e);return t.description=t.description.replace(/<br\s*\/?>/gi,"\n"),t.description_iCal=t.description.replace(/\[url\]/g,"").replace(/\[\/url\]/g,""),t.description=decodeURIComponent(t.description.replace(/\[url\]([\w&$+.,:;=~!*'?@#\s\-()|/^%]*)\[\/url\]/g,encodeURIComponent('<a href="$1" target="_blank" rel="noopener">$1</a>'))),t}function atcb_check_required(t){if(null==t.options||t.options.length<1)return console.error("add-to-calendar button generation failed: no options set"),!1;return["name","startDate","endDate"].every(function(e){return null!=t[e]&&""!=t[e]||(console.error("add-to-calendar button generation failed: required setting missing ["+e+"]"),!1)})}function atcb_date_cleanup(n){return["start","end"].forEach(function(e){var t;if(null!=n[e+"Date"]&&(n[e+"Date"]=n[e+"Date"].replace(/\.\d{3}/,"").replace("Z",""),null!=(t=n[e+"Date"].split("T"))[1]&&(n[e+"Date"]=t[0],n[e+"Time"]=t[1])),null!=n[e+"Time"]&&8===n[e+"Time"].length){const a=n[e+"Time"];n[e+"Time"]=a.substring(0,a.length-3)}}),n}function atcb_date_calculation(e){const t=new Date;var a=t.getUTCMonth()+1+"-"+t.getUTCDate()+"-"+t.getUTCFullYear();const n=(e=e.replace(/today/gi,a)).split("+");a=n[0].split("-");let c=new Date(a[0],a[1]-1,a[2]);return a[0].length<4&&(c=new Date(a[2],a[0]-1,a[1])),null!=n[1]&&0<n[1]&&c.setDate(c.getDate()+parseInt(n[1])),c.getFullYear()+"-"+(c.getMonth()+1<10?"0":"")+(c.getMonth()+1)+"-"+(c.getDate()<10?"0":"")+c.getDate()}function atcb_validate(a){const t=["Apple","Google","iCal","Microsoft365","Outlook.com","MicrosoftTeams","Yahoo"];if(!a.options.every(function(e){e=e.split("|");return!!t.includes(e[0])||(console.error("add-to-calendar button generation failed: invalid option ["+e[0]+"]"),!1)}))return!1;const e=["startDate","endDate"],n=e;if(!e.every(function(e){if(10!==a[e].length)return console.error("add-to-calendar button generation failed: date misspelled [-> YYYY-MM-DD]"),!1;var t=a[e].split("-");return t.length<3||3<t.length?(console.error("add-to-calendar button generation failed: date misspelled ["+e+": "+a[e]+"]"),!1):(n[e]=new Date(t[0],t[1]-1,t[2]),!0)}))return!1;return!!["startTime","endTime"].every(function(e){if(null!=a[e]){if(5!==a[e].length)return console.error("add-to-calendar button generation failed: time misspelled [-> HH:MM]"),!1;var t=a[e].split(":");if(t.length<2||2<t.length)return console.error("add-to-calendar button generation failed: time misspelled ["+e+": "+a[e]+"]"),!1;if(23<t[0])return console.error("add-to-calendar button generation failed: time misspelled - hours number too high ["+e+": "+t[0]+"]"),!1;if(59<t[1])return console.error("add-to-calendar button generation failed: time misspelled - minutes number too high ["+e+": "+t[1]+"]"),!1;"startTime"==e&&(n.startDate=new Date(n.startDate.getTime()+36e5*t[0]+6e4*t[1])),"endTime"==e&&(n.endDate=new Date(n.endDate.getTime()+36e5*t[0]+6e4*t[1]))}return!0})&&(null!=a.startTime&&null==a.endTime||null==a.startTime&&null!=a.endTime?(console.error("add-to-calendar button generation failed: if you set a starting time, you also need to define an end time"),!1):!(n.endDate<n.startDate)||(console.error("add-to-calendar button generation failed: end date before start date"),!1))}function atcb_generate_label(e,t=!1,a){if(t){const n=document.createElement("span");n.classList.add("atcb_icon"),e.appendChild(n)}if(""!=a){const c=document.createElement("span");c.classList.add("atcb_text"),c.textContent=a,e.appendChild(c)}}function atcb_generate(e,t,a){a.deleteJSON&&(e.innerHTML="");const n=document.createElement("div"),c=(n.classList.add("atcb_button_wrapper"),e.appendChild(n),document.createElement("button"));c.id="atcb_button_"+t,c.classList.add("atcb_button"),c.setAttribute("type","button"),n.appendChild(c),atcb_generate_label(c,!0,a.label||"Add to Calendar"),c.firstChild.innerHTML='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><path d="M81.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zm-3.65 76.03c1.83 0 3.32 1.49 3.32 3.32s-1.49 3.32-3.32 3.32l-12.95-.04-.04 12.93c0 1.83-1.49 3.32-3.32 3.32s-3.32-1.49-3.32-3.32l.04-12.94-12.93-.05c-1.83 0-3.32-1.49-3.32-3.32s1.49-3.32 3.32-3.32l12.94.04.04-12.93c0-1.83 1.49-3.32 3.32-3.32s3.32 1.49 3.32 3.32l-.04 12.95 12.94.04h0zM29.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zM6.4 45.32h110.08V21.47c0-.8-.33-1.53-.86-2.07-.53-.53-1.26-.86-2.07-.86H103c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h10.55c2.57 0 4.9 1.05 6.59 2.74s2.74 4.02 2.74 6.59v27.06 65.03c0 2.57-1.05 4.9-2.74 6.59s-4.02 2.74-6.59 2.74H9.33c-2.57 0-4.9-1.05-6.59-2.74-1.69-1.7-2.74-4.03-2.74-6.6V48.53 21.47c0-2.57 1.05-4.9 2.74-6.59s4.02-2.74 6.59-2.74H20.6c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H9.33c-.8 0-1.53.33-2.07.86-.53.53-.86 1.26-.86 2.07v23.85h0zm110.08 6.41H6.4v61.82c0 .8.33 1.53.86 2.07.53.53 1.26.86 2.07.86h104.22c.8 0 1.53-.33 2.07-.86.53-.53.86-1.26.86-2.07V51.73h0zM50.43 18.54c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h21.49c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H50.43h0z"/></svg>',"click"==a.trigger?c.addEventListener("mousedown",()=>atcb_toggle(a,c,!1,!0)):(c.addEventListener("touchstart",()=>atcb_toggle(a,c,!1,!0),{passive:!0}),c.addEventListener("mouseenter",()=>atcb_open(a,c,!1,!0))),c.addEventListener("keydown",function(e){"Enter"==e.key&&atcb_toggle(a,c,!0,!0)}),e.classList.remove("atcb"),e.classList.add("atcb_initialized"),a.inline?e.style.display="inline-block":e.style.display="block",console.log("add-to-calendar button #"+(t+1)+" created")}function atcb_generate_dropdown_list(n){const c=document.createElement("div");return c.classList.add("atcb_list"),n.options.forEach(function(e){var t=e.split("|");const a=document.createElement("div");switch(a.classList.add("atcb_list_item"),a.tabIndex=0,c.appendChild(a),t[0]){case"Apple":atcb_generate_label(a,!0,t[1]||"Apple"),a.firstChild.innerHTML='<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" viewBox="0 0 640 640"><path d="M494.782 340.02c-.803-81.025 66.084-119.907 69.072-121.832-37.595-54.993-96.167-62.552-117.037-63.402-49.843-5.032-97.242 29.362-122.565 29.362-25.253 0-64.277-28.607-105.604-27.85-54.32.803-104.4 31.594-132.403 80.245C29.81 334.457 71.81 479.58 126.816 558.976c26.87 38.882 58.914 82.56 100.997 81 40.512-1.594 55.843-26.244 104.848-26.244 48.993 0 62.753 26.245 105.64 25.406 43.606-.803 71.232-39.638 97.925-78.65 30.887-45.12 43.548-88.75 44.316-90.994-.969-.437-85.029-32.634-85.879-129.439l.118-.035zM414.23 102.178C436.553 75.095 451.636 37.5 447.514-.024c-32.162 1.311-71.163 21.437-94.253 48.485-20.729 24.012-38.836 62.28-33.993 99.036 35.918 2.8 72.591-18.248 94.926-45.272l.036-.047z"/></svg>',a.addEventListener("click",function(){atcb_generate_ical(n),atcb_close()});break;case"Google":atcb_generate_label(a,!0,t[1]||"Google"),a.firstChild.innerHTML='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><path d="M93.78 29.1H29.1v64.68h64.68V29.1z" fill="#fff"/><path d="M93.78 122.88l29.1-29.1h-29.1v29.1z" fill="#f72a25"/><path d="M122.88 29.1h-29.1v64.68h29.1V29.1z" fill="#fbbc04"/><path d="M93.78 93.78H29.1v29.1h64.68v-29.1z" fill="#34a853"/><path d="M0 93.78v19.4c0 5.36 4.34 9.7 9.7 9.7h19.4v-29.1H0h0z" fill="#188038"/><path d="M122.88 29.1V9.7c0-5.36-4.34-9.7-9.7-9.7h-19.4v29.1h29.1 0z" fill="#1967d2"/><path d="M93.78 0H9.7C4.34 0 0 4.34 0 9.7v84.08h29.1V29.1h64.67V0h.01z" fill="#4285f4"/><path d="M42.37 79.27c-2.42-1.63-4.09-4.02-5-7.17l5.61-2.31c.51 1.94 1.4 3.44 2.67 4.51 1.26 1.07 2.8 1.59 4.59 1.59 1.84 0 3.41-.56 4.73-1.67 1.32-1.12 1.98-2.54 1.98-4.26 0-1.76-.7-3.2-2.09-4.32s-3.14-1.67-5.22-1.67H46.4v-5.55h2.91c1.79 0 3.31-.48 4.54-1.46 1.23-.97 1.84-2.3 1.84-3.99 0-1.5-.55-2.7-1.65-3.6s-2.49-1.35-4.18-1.35c-1.65 0-2.96.44-3.93 1.32s-1.7 2-2.12 3.24l-5.55-2.31c.74-2.09 2.09-3.93 4.07-5.52s4.51-2.39 7.58-2.39c2.27 0 4.32.44 6.13 1.32s3.23 2.1 4.26 3.65c1.03 1.56 1.54 3.31 1.54 5.25 0 1.98-.48 3.65-1.43 5.03-.95 1.37-2.13 2.43-3.52 3.16v.33c1.79.74 3.36 1.96 4.51 3.52 1.17 1.58 1.76 3.46 1.76 5.66s-.56 4.16-1.67 5.88c-1.12 1.72-2.66 3.08-4.62 4.07s-4.17 1.49-6.62 1.49c-2.84 0-5.46-.81-7.88-2.45h0 0zm34.46-27.84l-6.16 4.45-3.08-4.67 11.05-7.97h4.24v37.6h-6.05V51.43h0z" fill="#1a73e8"/></svg>',a.addEventListener("click",function(){atcb_generate_google(n),atcb_close()});break;case"iCal":atcb_generate_label(a,!0,t[1]||"iCal File"),a.firstChild.innerHTML='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><path d="M81.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zm-15.5 99.08c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2H81.9c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H66.11h0zM15.85 67.09c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H15.85h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H40.98h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2H81.9c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H66.11h0zm25.14 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H91.25h0zm-75.4 18.36c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H15.85h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H40.98h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2H81.9c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H66.11h0zm25.14 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H91.25h0zm-75.4 18.36c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H15.85h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H40.98h0zM29.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zM6.4 45.32h110.07V21.47c0-.8-.33-1.53-.86-2.07-.53-.53-1.26-.86-2.07-.86H103c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h10.55c2.57 0 4.9 1.05 6.59 2.74s2.74 4.02 2.74 6.59v27.06 65.03c0 2.57-1.05 4.9-2.74 6.59s-4.02 2.74-6.59 2.74H9.33c-2.57 0-4.9-1.05-6.59-2.74-1.69-1.7-2.74-4.03-2.74-6.6V48.52 21.47c0-2.57 1.05-4.9 2.74-6.59s4.02-2.74 6.59-2.74H20.6c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H9.33c-.8 0-1.53.33-2.07.86-.53.53-.86 1.26-.86 2.07v23.85h0zm110.08 6.41H6.4v61.82c0 .8.33 1.53.86 2.07.53.53 1.26.86 2.07.86h104.22c.8 0 1.53-.33 2.07-.86.53-.53.86-1.26.86-2.07V51.73h0zM50.43 18.54c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h21.49c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H50.43h0z"/></svg>',a.addEventListener("click",function(){atcb_generate_ical(n),atcb_close()});break;case"MicrosoftTeams":atcb_generate_label(a,!0,t[1]||"Microsoft Teams"),a.firstChild.innerHTML='<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 2228.833 2073.333"><g fill="#5059c9"><path d="M1554.637 777.5h575.713c54.391 0 98.483 44.092 98.483 98.483v524.398c0 199.901-162.051 361.952-361.952 361.952h0-1.711c-199.901.028-361.975-162-362.004-361.901v-.052-571.409c.001-28.427 23.045-51.471 51.471-51.471h0z"/><circle cx="1943.75" cy="440.583" r="233.25"/></g><g fill="#7b83eb"><circle cx="1218.083" cy="336.917" r="336.917"/><path d="M1667.323 777.5H717.01c-53.743 1.33-96.257 45.931-95.01 99.676v598.105c-7.505 322.519 247.657 590.16 570.167 598.053 322.51-7.893 577.671-275.534 570.167-598.053V877.176c1.245-53.745-41.268-98.346-95.011-99.676z"/></g><path opacity=".1" d="M1244 777.5v838.145c-.258 38.435-23.549 72.964-59.09 87.598-11.316 4.787-23.478 7.254-35.765 7.257H667.613c-6.738-17.105-12.958-34.21-18.142-51.833-18.144-59.477-27.402-121.307-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52H1244z"/><path opacity=".2" d="M1192.167 777.5v889.978a91.84 91.84 0 0 1-7.257 35.765c-14.634 35.541-49.163 58.833-87.598 59.09H691.975c-8.812-17.105-17.105-34.21-24.362-51.833s-12.958-34.21-18.142-51.833a631.28 631.28 0 0 1-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h475.313z"/><path opacity=".2" d="M1192.167 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855h-447.84A631.28 631.28 0 0 1 622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h475.312z"/><path opacity=".2" d="M1140.333 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855H649.472A631.28 631.28 0 0 1 622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h423.478z"/><path opacity=".1" d="M1244 509.522v163.275c-8.812.518-17.105 1.037-25.917 1.037s-17.105-.518-25.917-1.037c-17.496-1.161-34.848-3.937-51.833-8.293a336.92 336.92 0 0 1-233.25-198.003 288.02 288.02 0 0 1-16.587-51.833h258.648c52.305.198 94.657 42.549 94.856 94.854z"/><use xlink:href="#C" opacity=".2"/><use xlink:href="#C" opacity=".2"/><path opacity=".2" d="M1140.333 561.355v103.148A336.92 336.92 0 0 1 907.083 466.5h138.395c52.305.199 94.656 42.551 94.855 94.855z"/><linearGradient id="A" gradientUnits="userSpaceOnUse" x1="198.099" y1="392.261" x2="942.234" y2="1681.073"><stop offset="0" stop-color="#5a62c3"/><stop offset=".5" stop-color="#4d55bd"/><stop offset="1" stop-color="#3940ab"/></linearGradient><path fill="url(#A)" d="M95.01 466.5h950.312c52.473 0 95.01 42.538 95.01 95.01v950.312c0 52.473-42.538 95.01-95.01 95.01H95.01c-52.473 0-95.01-42.538-95.01-95.01V561.51c0-52.472 42.538-95.01 95.01-95.01z"/><path fill="#fff" d="M820.211,828.193H630.241v517.297H509.211V828.193H320.123V727.844h500.088V828.193z"/><defs ><path id="C" d="M1192.167 561.355v111.442c-17.496-1.161-34.848-3.937-51.833-8.293a336.92 336.92 0 0 1-233.25-198.003h190.228c52.304.198 94.656 42.55 94.855 94.854z"/></defs></svg>',a.addEventListener("click",function(){atcb_generate_teams(n),atcb_close()});break;case"Microsoft365":atcb_generate_label(a,!0,t[1]||"Microsoft 365"),a.firstChild.innerHTML='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 278050 333334" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd"><path fill="#ea3e23" d="M278050 305556l-29-16V28627L178807 0 448 66971l-448 87 22 200227 60865-23821V80555l117920-28193-17 239519L122 267285l178668 65976v73l99231-27462v-316z"/></svg>',a.addEventListener("click",function(){atcb_generate_microsoft(n,"365"),atcb_close()});break;case"Outlook.com":atcb_generate_label(a,!0,t[1]||"Outlook.com"),a.firstChild.innerHTML='<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.129793726981 0 33.251996719421 32" width="2500" height="2397"><path d="M28.596 2H11.404A1.404 1.404 0 0 0 10 3.404V5l9.69 3L30 5V3.404A1.404 1.404 0 0 0 28.596 2z" fill="#0364b8"/><path d="M31.65 17.405A11.341 11.341 0 0 0 32 16a.666.666 0 0 0-.333-.576l-.013-.008-.004-.002L20.812 9.24a1.499 1.499 0 0 0-1.479-.083 1.49 1.49 0 0 0-.145.082L8.35 15.415l-.004.002-.012.007A.666.666 0 0 0 8 16a11.344 11.344 0 0 0 .35 1.405l11.492 8.405z" fill="#0a2767"/><path d="M24 5h-7l-2.021 3L17 11l7 6h6v-6z" fill="#28a8ea"/><path d="M10 5h7v6h-7z" fill="#0078d4"/><path d="M24 5h6v6h-6z" fill="#50d9ff"/><path d="M24 17l-7-6h-7v6l7 6 10.832 1.768z" fill="#0364b8"/><path d="M17 11h7v6h-7z" fill="#0078d4"/><path d="M10 17h7v6h-7z" fill="#064a8c"/><path d="M24 17h6v6h-6z" fill="#0078d4"/><path d="M20.19 25.218l-11.793-8.6.495-.87 10.909 6.212a.528.528 0 0 0 .42-.012l10.933-6.23.496.869z" fill="#0a2767" opacity=".5"/><path d="M31.667 16.577l-.014.008-.003.002-10.838 6.174a1.497 1.497 0 0 1-1.46.091l3.774 5.061 8.254 1.797v.004A1.498 1.498 0 0 0 32 28.5V16a.666.666 0 0 1-.333.577z" fill="#1490df"/><path d="M32 28.5v-.738l-9.983-5.688-1.205.687a1.497 1.497 0 0 1-1.46.091l3.774 5.061 8.254 1.797v.004A1.498 1.498 0 0 0 32 28.5z" opacity=".05"/><path d="M31.95 28.883L21.007 22.65l-.195.11a1.497 1.497 0 0 1-1.46.092l3.774 5.061 8.254 1.797v.004a1.501 1.501 0 0 0 .57-.83z" opacity=".1"/><path d="M8.35 16.59v-.01h-.01l-.03-.02A.65.65 0 0 1 8 16v12.5A1.498 1.498 0 0 0 9.5 30h21a1.503 1.503 0 0 0 .37-.05.637.637 0 0 0 .18-.06.142.142 0 0 0 .06-.02 1.048 1.048 0 0 0 .23-.13c.02-.01.03-.01.04-.03z" fill="#28a8ea"/><path d="M18 24.667V8.333A1.337 1.337 0 0 0 16.667 7H10.03v7.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v10h8.667A1.337 1.337 0 0 0 18 24.667z" opacity=".1"/><path d="M17 25.667V9.333A1.337 1.337 0 0 0 15.667 8H10.03v6.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v11h7.667A1.337 1.337 0 0 0 17 25.667z" opacity=".2"/><path d="M17 23.667V9.333A1.337 1.337 0 0 0 15.667 8H10.03v6.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v9h7.667A1.337 1.337 0 0 0 17 23.667z" opacity=".2"/><path d="M16 23.667V9.333A1.337 1.337 0 0 0 14.667 8H10.03v6.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v9h6.667A1.337 1.337 0 0 0 16 23.667z" opacity=".2"/><path d="M1.333 8h13.334A1.333 1.333 0 0 1 16 9.333v13.334A1.333 1.333 0 0 1 14.667 24H1.333A1.333 1.333 0 0 1 0 22.667V9.333A1.333 1.333 0 0 1 1.333 8z" fill="#0078d4"/><path d="M3.867 13.468a4.181 4.181 0 0 1 1.642-1.814A4.965 4.965 0 0 1 8.119 11a4.617 4.617 0 0 1 2.413.62 4.14 4.14 0 0 1 1.598 1.733 5.597 5.597 0 0 1 .56 2.55 5.901 5.901 0 0 1-.577 2.666 4.239 4.239 0 0 1-1.645 1.794A4.8 4.8 0 0 1 7.963 21a4.729 4.729 0 0 1-2.468-.627 4.204 4.204 0 0 1-1.618-1.736 5.459 5.459 0 0 1-.567-2.519 6.055 6.055 0 0 1 .557-2.65zm1.75 4.258a2.716 2.716 0 0 0 .923 1.194 2.411 2.411 0 0 0 1.443.435 2.533 2.533 0 0 0 1.541-.449 2.603 2.603 0 0 0 .897-1.197 4.626 4.626 0 0 0 .286-1.665 5.063 5.063 0 0 0-.27-1.686 2.669 2.669 0 0 0-.866-1.24 2.387 2.387 0 0 0-1.527-.473 2.493 2.493 0 0 0-1.477.439 2.741 2.741 0 0 0-.944 1.203 4.776 4.776 0 0 0-.007 3.44z" fill="#fff"/></svg>',a.addEventListener("click",function(){atcb_generate_microsoft(n,"outlook"),atcb_close()});break;case"Yahoo":atcb_generate_label(a,!0,t[1]||"Yahoo"),a.firstChild.innerHTML='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3386.34 3010.5" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd"><path d="M0 732.88h645.84l376.07 962.1 380.96-962.1h628.76l-946.8 2277.62H451.98l259.19-603.53L.02 732.88zm2763.84 768.75h-704.26L2684.65 0l701.69.03-622.5 1501.6zm-519.78 143.72c216.09 0 391.25 175.17 391.25 391.22 0 216.06-175.16 391.23-391.25 391.23-216.06 0-391.19-175.17-391.19-391.23 0-216.05 175.16-391.22 391.19-391.22z" fill="#5f01d1" fill-rule="nonzero"/></svg>',a.addEventListener("click",function(){atcb_generate_yahoo(n),atcb_close()})}a.addEventListener("keydown",function(e){"Enter"==e.key&&this.click()})}),c}function atcb_generate_bg_overlay(e){const t=document.createElement("div");t.classList.add("atcb_bgoverlay"),"modal"!==e.listStyle&&t.classList.add("atcb_animate_bg"),t.tabIndex=0,t.addEventListener("click",()=>atcb_close(!0));let a=!1;return t.addEventListener("touchstart",()=>a=!1,{passive:!0}),t.addEventListener("touchmove",()=>a=!0,{passive:!0}),t.addEventListener("touchend",function(){!1===a&&atcb_close(!0)},{passive:!0}),t.addEventListener("focus",()=>atcb_close(!1)),"click"!==e.trigger?t.addEventListener("mousemove",()=>atcb_close(!0)):t.classList.add("atcb_click"),t}function atcb_toggle(e,t,a=!0,n=!1){t.classList.contains("atcb_active")?atcb_close():atcb_open(e,t,a,n)}function atcb_open(e,t,a=!0,n=!1){if(!document.querySelector(".atcb_list")){const c=atcb_generate_dropdown_list(e);t?(t.classList.add("atcb_active"),"modal"===e.listStyle?(t.classList.add("atcb_modal_style"),c.classList.add("atcb_modal")):(t=t.getBoundingClientRect(),c.style.width=t.width+"px",c.style.top=t.bottom+window.scrollY-3+"px",c.style.left=t.left+"px",c.classList.add("atcb_dropdown"),n&&c.classList.add("atcb_generated_button"))):(e.listStyle="modal",c.classList.add("atcb_modal")),document.body.appendChild(c),document.body.appendChild(atcb_generate_bg_overlay(e)),a&&c.firstChild.focus()}}function atcb_close(e=!1){if(!e){let e=document.querySelector(".atcb_active");e&&e.focus()}Array.from(document.querySelectorAll(".atcb_active")).forEach(e=>{e.classList.remove("atcb_active")}),Array.from(document.querySelectorAll(".atcb_list")).concat(Array.from(document.querySelectorAll(".atcb_bgoverlay"))).forEach(e=>e.remove())}function atcb_action(e,t,a=!0){if(!atcb_check_required(e))throw new Error("data missing; see logs");if(!atcb_validate(e=atcb_decorate_data(e)))throw new Error("Invalid data; see logs");atcb_open(e,t,a)}function atcb_generate_google(e){let t="https://calendar.google.com/calendar/render?action=TEMPLATE";var a=atcb_generate_time(e,"clean","google");t+="&dates="+a.start+"%2F"+a.end,null!=e.name&&""!=e.name&&(t+="&text="+encodeURIComponent(e.name)),null!=e.location&&""!=e.location&&(t+="&location="+encodeURIComponent(e.location),isiOS()&&(null==e.description||""==e.description?e.description="":e.description+="%0A%0A",e.description+="📍: "+e.location)),null!=e.description&&""!=e.description&&(t+="&details="+encodeURIComponent(e.description)),window.open(t,"_blank").focus()}function atcb_generate_yahoo(e){let t="https://calendar.yahoo.com/?v=60";var a=atcb_generate_time(e,"clean");t+="&st="+a.start+"&et="+a.end,a.allday&&(t+="&dur=allday"),null!=e.name&&""!=e.name&&(t+="&title="+encodeURIComponent(e.name)),null!=e.location&&""!=e.location&&(t+="&in_loc="+encodeURIComponent(e.location)),null!=e.description&&""!=e.description&&(t+="&desc="+encodeURIComponent(e.description)),window.open(t,"_blank").focus()}function atcb_generate_microsoft(e,t="365"){let a="https://";a+="outlook"==t?"outlook.live.com":"outlook.office.com",a+="/calendar/0/deeplink/compose?path=%2Fcalendar%2Faction%2Fcompose&rru=addevent";t=atcb_generate_time(e,"delimiters","microsoft");a+="&startdt="+t.start+"&enddt="+t.end,t.allday&&(a+="&allday=true"),null!=e.name&&""!=e.name&&(a+="&subject="+encodeURIComponent(e.name)),null!=e.location&&""!=e.location&&(a+="&location="+encodeURIComponent(e.location)),null!=e.description&&""!=e.description&&(a+="&body="+encodeURIComponent(e.description.replace(/\n/g,"%0A"))),window.open(a,"_blank").focus()}function atcb_generate_teams(e){let t="https://teams.microsoft.com/l/meeting/new?";var a=atcb_generate_time(e,"delimiters","microsoft");t+="&startTime="+a.start+"&endTime="+a.end;let n="";null!=e.name&&""!=e.name&&(t+="&subject="+encodeURIComponent(e.name)),null!=e.location&&""!=e.location&&(n=encodeURIComponent(e.location),t+="&location="+n,n+=" // "),null!=e.description_iCal&&""!=e.description_iCal&&(t+="&content="+n+encodeURIComponent(e.description_iCal)),window.open(t,"_blank").focus()}function atcb_generate_ical(e){let t=new Date;t=t.toISOString();var a=atcb_generate_time(e,"clean","ical");let n="";a.allday&&(n=";VALUE=DATE");const c=["BEGIN:VCALENDAR","VERSION:2.0"];c.push("PRODID:-// github.com/jekuer/add-to-calendar-button // atcb v"+atcbVersion+" //EN"),c.push("CALSCALE:GREGORIAN"),c.push("BEGIN:VEVENT"),c.push("UID:"+t+"@add-to-calendar-button"),c.push("DTSTAMP:"+a.start,"DTSTART"+n+":"+a.start,"DTEND"+n+":"+a.end,"SUMMARY:"+e.name.replace(/.{65}/g,"$&\r\n ")),null!=e.description_iCal&&""!=e.description_iCal&&c.push("DESCRIPTION:"+e.description_iCal.replace(/\n/g,"\\n").replace(/.{60}/g,"$&\r\n ")),null!=e.location&&""!=e.location&&c.push("LOCATION:"+e.location),t=t.replace(/\.\d{3}/g,"").replace(/[^a-z\d]/gi,""),c.push("STATUS:CONFIRMED","LAST-MODIFIED:"+t,"SEQUENCE:0","END:VEVENT","END:VCALENDAR");a="data:text/calendar;charset=utf-8,"+encodeURIComponent(c.join("\r\n")),e=e.iCalFileName||"event-to-save-in-my-calendar";try{if(window.ActiveXObject){if(window.ActiveXObject&&document.execCommand){const i=window.open(a,"_blank");i.document.close(),i.document.execCommand("SaveAs",!0,e||a),i.close()}}else{const l=document.createElement("a");l.href=a,l.target="_blank",l.download=e;var o=new MouseEvent("click",{view:window,button:0,bubbles:!0,cancelable:!1});l.dispatchEvent(o),(window.URL||window.webkitURL).revokeObjectURL(l.href)}}catch(e){console.error(e)}}function atcb_generate_time(e,a="delimiters",n="general"){var c=e.startDate.split("-"),o=e.endDate.split("-");let i="",l="",r=!1;if(null!=e.startTime&&null!=e.endTime){if(null!=e.timeZoneOffset&&""!=e.timeZoneOffset)i=new Date(c[0]+"-"+c[1]+"-"+c[2]+"T"+e.startTime+":00.000"+e.timeZoneOffset),l=new Date(o[0]+"-"+o[1]+"-"+o[2]+"T"+e.endTime+":00.000"+e.timeZoneOffset);else if(i=new Date(c[0]+"-"+c[1]+"-"+c[2]+"T"+e.startTime+":00.000+00:00"),l=new Date(o[0]+"-"+o[1]+"-"+o[2]+"T"+e.endTime+":00.000+00:00"),null!=e.timeZone&&""!=e.timeZone){const t=new Date(i.toLocaleString("en-US",{timeZone:"UTC"})),s=("currentBrowser"==e.timeZone&&(e.timeZone=Intl.DateTimeFormat().resolvedOptions().timeZone),new Date(i.toLocaleString("en-US",{timeZone:e.timeZone})));e=t.getTime()-s.getTime();i.setTime(i.getTime()+e),l.setTime(l.getTime()+e)}i=i.toISOString().replace(".000",""),l=l.toISOString().replace(".000",""),"clean"==a&&(i=i.replace(/-/g,"").replace(/:/g,""),l=l.replace(/-/g,"").replace(/:/g,""))}else{r=!0,i=new Date(Date.UTC(c[0],c[1]-1,c[2]));let e=i.toISOString().replace(/T(.+)Z/g,""),t=(l=new Date(Date.UTC(o[0],o[1]-1,o[2])),"google"!=n&&"microsoft"!=n&&"ical"!=n||l.setDate(l.getDate()+1),l.toISOString().replace(/T(.+)Z/g,""));"clean"==a&&(e=e.replace(/-/g,""),t=t.replace(/-/g,"")),i=e,l=t}return{start:i,end:l,allday:r}}if(isBrowser()){document.addEventListener("keydown",e=>{"Escape"===e.key&&atcb_close()});let e=window.innerWidth;window.addEventListener("resize",()=>{window.innerWidth!=e&&(e=window.innerWidth,atcb_close(!0))})}isBrowser()&&document.addEventListener("DOMContentLoaded",atcb_init,!1); | ||
//# sourceMappingURL=atcb.min.js.map |
@@ -1,2 +0,2 @@ | ||
const initCodeDelimiter = /\/\/ START INIT[\s\S]*?\/\/ END INIT/gm | ||
const initCodeDelimiter = /\/\/ START INIT[\s\S]*?\/\/ END INIT/g; | ||
@@ -6,78 +6,94 @@ function process(content, exportPhrase) { | ||
} | ||
module.exports = function(grunt) { | ||
module.exports = function (grunt) { | ||
// The config. | ||
grunt.initConfig({ | ||
version: { // update version. Either use via `npm run release --set=version::patch`, `npm run release --set=version::minor`, `npm run release --set=version::major`, or `npm run release --set=version::x.x.x` (with x.x.x being the exact version number) | ||
// update version. Either use via `npm run release -- patch` (default), `npm run release -- minor`, `npm run release -- major`, or `npm run release -- x.x.x` (with x.x.x being the exact version number) | ||
version: { | ||
package: { | ||
src: ['package.json'] | ||
src: ["package.json"], | ||
}, | ||
demoHtml: { | ||
options: { | ||
prefix: '.(tinyVersion">v|.\?v=)' | ||
prefix: '.(tinyVersion">v|.?v=)', | ||
}, | ||
src: ['index.html'] | ||
src: ["index.html"], | ||
}, | ||
js: { | ||
options: { | ||
prefix: 'Version.=..' | ||
prefix: "Version.=..", | ||
}, | ||
src: ['assets/js/atcb.js'] | ||
src: ["assets/js/atcb.js"], | ||
}, | ||
css: { | ||
options: { | ||
prefix: 'Version:.' | ||
prefix: "Version:.", | ||
}, | ||
src: ['assets/css/atcb.css'] | ||
src: ["assets/css/atcb.css"], | ||
}, | ||
README: { | ||
options: { | ||
prefix: "add-to-calendar-button@", | ||
}, | ||
src: ["README.md"], | ||
}, | ||
}, | ||
clean: { // cleans old built files | ||
oldBuildFiles: ['assets/js/atcb.min.js', 'assets/js/atcb.min.js.map', 'assets/css/atcb.min.css', 'assets/css/atcb.min.css.map', 'npm_dist/'] | ||
// cleans old built files | ||
clean: { | ||
oldBuildFiles: [ | ||
"assets/js/atcb.min.js", | ||
"assets/js/atcb.min.js.map", | ||
"assets/css/atcb.min.css", | ||
"assets/css/atcb.min.css.map", | ||
"npm_dist/cjs/*.js", | ||
"npm_dist/mjs/", | ||
], | ||
}, | ||
copy: { // creates the source files for the npm versionm supporting CommonJS and ES Module (https://www.sensedeep.com/blog/posts/2021/how-to-create-single-source-npm-module.html) | ||
// creates the source files for the npm versionm supporting CommonJS and ES Module (https://www.sensedeep.com/blog/posts/2021/how-to-create-single-source-npm-module.html) | ||
copy: { | ||
mjs_dist: { | ||
src: 'assets/js/atcb.js', | ||
dest: 'npm_dist/mjs/index.js', | ||
options: { process: content => process(content, "export") } | ||
src: "assets/js/atcb.js", | ||
dest: "npm_dist/mjs/index.js", | ||
options: { process: (content) => process(content, "export") }, | ||
}, | ||
cjs_dist: { | ||
src: 'assets/js/atcb.js', | ||
dest: 'npm_dist/cjs/index.js', | ||
options: { process: content => process(content, "module.exports =") } | ||
} | ||
src: "assets/js/atcb.js", | ||
dest: "npm_dist/cjs/index.js", | ||
options: { process: (content) => process(content, "module.exports =") }, | ||
}, | ||
}, | ||
uglify: { // minifies the main js file | ||
// minifies the main js file | ||
uglify: { | ||
options: { | ||
compress: true, | ||
mangle: true, | ||
sourceMap: true | ||
sourceMap: true, | ||
}, | ||
newBuild: { | ||
files: { | ||
'assets/js/atcb.min.js': ['assets/js/atcb.js'] | ||
} | ||
} | ||
"assets/js/atcb.min.js": ["assets/js/atcb.js"], | ||
}, | ||
}, | ||
}, | ||
cssmin: { // minifies the main css file | ||
// minifies the main css file | ||
cssmin: { | ||
options: { | ||
sourceMap: true | ||
sourceMap: true, | ||
}, | ||
newBuild: { | ||
files: { | ||
'assets/css/atcb.min.css': ['assets/css/atcb.css'] | ||
} | ||
} | ||
} | ||
"assets/css/atcb.min.css": ["assets/css/atcb.css"], | ||
}, | ||
}, | ||
}, | ||
}); | ||
// Load the plugins. | ||
grunt.loadNpmTasks('grunt-contrib-copy'); | ||
grunt.loadNpmTasks('grunt-contrib-clean'); | ||
grunt.loadNpmTasks('grunt-contrib-uglify'); | ||
grunt.loadNpmTasks('grunt-contrib-cssmin'); | ||
grunt.loadNpmTasks('grunt-version'); | ||
grunt.loadNpmTasks("grunt-contrib-copy"); | ||
grunt.loadNpmTasks("grunt-contrib-clean"); | ||
grunt.loadNpmTasks("grunt-contrib-uglify"); | ||
grunt.loadNpmTasks("grunt-contrib-cssmin"); | ||
grunt.loadNpmTasks("grunt-version"); | ||
// Register task(s). | ||
grunt.registerTask('default', ['clean', 'copy', 'uglify', 'cssmin']); | ||
}; | ||
grunt.registerTask("default", ["clean", "copy", "uglify", "cssmin"]); | ||
}; |
@@ -1,6 +0,6 @@ | ||
declare module 'add-to-calendar-button' { | ||
declare module "add-to-calendar-button" { | ||
export type ISO8601Date = string; | ||
export type ISO8601Time = string; | ||
export function atcb_init(): any; | ||
export function atcb_action ( | ||
export function atcb_init(): void; | ||
export function atcb_action( | ||
config: { | ||
@@ -14,16 +14,12 @@ name?: string; | ||
location?: string; | ||
options: ("Apple" | "Google" | "iCal" | "Microsoft365" | "MicrosoftTeams" | "Outlook.com" | "Yahoo")[], | ||
options: ("Apple" | "Google" | "iCal" | "Microsoft365" | "MicrosoftTeams" | "Outlook.com" | "Yahoo")[]; | ||
timeZone?: string; | ||
timeZoneOffset?: string; | ||
iCalFileName?: string; | ||
/** | ||
* By default, the dropdown list will be opened on hover and closed as | ||
* soon as you mouse over the background overlay. If you want it to | ||
* instead close only when thebackground overlay is clicked, set this | ||
* to `"click"`. | ||
*/ | ||
trigger?: "click"; | ||
trigger?: "hover" | "click"; | ||
listStyle?: "dropdown" | "modal"; | ||
}, | ||
placeBelow?: HTMLElement | ||
triggerElement?: HTMLElement, | ||
keyboardTrigger?: boolean | ||
): void; | ||
} | ||
} |
@@ -24,3 +24,3 @@ “Commons Clause” License Condition v1.0 | ||
Licensor: Jens Kürschner + all contributors | ||
Licensor: Jens Kuerschner (www.jenskuerschner.de) | ||
@@ -33,3 +33,3 @@ | ||
Copyright (c) 2021 Jens Kürschner + all contributors | ||
Copyright (c) Jens Kuerschner (www.jenskuerschner.de) | ||
@@ -36,0 +36,0 @@ Permission is hereby granted, free of charge, to any person obtaining a copy |
@@ -6,13 +6,15 @@ /** | ||
*/ | ||
const atcbVersion = '1.8.10'; | ||
const atcbVersion = "1.9.0"; | ||
/* Creator: Jens Kuerschner (https://jenskuerschner.de) | ||
* Project: https://github.com/jekuer/add-to-calendar-button | ||
* License: MIT with “Commons Clause” License Condition v1.0 | ||
* | ||
* | ||
*/ | ||
const isBrowser = new Function("try { return this===window; }catch(e){ return false; }"); | ||
const isiOS = isBrowser() ? new Function("if ((/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)){ return true; }else{ return false; }") : new Function("return false;"); | ||
const isiOS = isBrowser() | ||
? new Function( | ||
"if ((/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)){ return true; }else{ return false; }" | ||
) | ||
: new Function("return false;"); | ||
@@ -25,11 +27,11 @@ // INITIALIZE THE SCRIPT AND FUNCTIONALITY | ||
// get all placeholders | ||
let atcButtons = document.querySelectorAll('.atcb'); | ||
const atcButtons = document.querySelectorAll(".atcb"); | ||
// if there are some, move on | ||
if (atcButtons.length > 0) { | ||
// get the amount of already initialized ones first | ||
let atcButtonsInitialized = document.querySelectorAll('.atcb_initialized'); | ||
const atcButtonsInitialized = document.querySelectorAll(".atcb_initialized"); | ||
// generate the buttons one by one | ||
for (let i = 0; i < atcButtons.length; i++) { | ||
// skip already initialized ones | ||
if (atcButtons[i].classList.contains('atcb_initialized')) { | ||
if (atcButtons[i].classList.contains("atcb_initialized")) { | ||
continue; | ||
@@ -39,15 +41,19 @@ } | ||
// check if schema.org markup is present | ||
let schema = atcButtons[i].querySelector('script'); | ||
const schema = atcButtons[i].querySelector("script"); | ||
// get their JSON content first | ||
if (schema && schema.innerHTML) { | ||
// get schema.org event markup and flatten the event block | ||
atcbConfig = JSON.parse(schema.innerHTML.replace(/(\r\n|\n|\r)/gm, "").replace(/(<(?!br)([^>]+)>)/gi, "")); // remove real code line breaks before parsing. Use <br> or \n explicitely in the description to create a line break. Also strip HTML tags (especially since stupid Safari adds stuff). | ||
atcbConfig = JSON.parse( | ||
schema.innerHTML.replace(/(\r\n|\n|\r)/g, "").replace(/(<(?!br)([^>]+)>)/gi, "") | ||
); // remove real code line breaks before parsing. Use <br> or \n explicitely in the description to create a line break. Also strip HTML tags (especially since stupid Safari adds stuff). | ||
atcbConfig = atcb_parse_schema_json(atcbConfig); | ||
// set flag to not delete HTML content later | ||
atcbConfig['deleteJSON'] = false; | ||
atcbConfig.deleteJSON = false; | ||
} else { | ||
// get JSON from HTML block | ||
atcbConfig = JSON.parse(atcButtons[i].innerHTML.replace(/(\r\n|\n|\r)/gm, "").replace(/(<(?!br)([^>]+)>)/gi, "")); // remove real code line breaks before parsing. Use <br> or \n explicitely in the description to create a line break. Also strip HTML tags (especially since stupid Safari adds stuff). | ||
atcbConfig = JSON.parse( | ||
atcButtons[i].innerHTML.replace(/(\r\n|\n|\r)/g, "").replace(/(<(?!br)([^>]+)>)/gi, "") | ||
); // remove real code line breaks before parsing. Use <br> or \n explicitely in the description to create a line break. Also strip HTML tags (especially since stupid Safari adds stuff). | ||
// set flag to delete HTML content later | ||
atcbConfig['deleteJSON'] = true; | ||
atcbConfig.deleteJSON = true; | ||
} | ||
@@ -70,34 +76,31 @@ // rewrite config for backwards compatibility - you can remove this, if you did not use this script before v1.4.0. | ||
// NORMALIZE AND PARSE JSON FROM SCHEMA.ORG MARKUP | ||
function atcb_parse_schema_json(atcbConfig) { | ||
try { | ||
Object.keys(atcbConfig['event']).forEach(key => { | ||
Object.keys(atcbConfig.event).forEach((key) => { | ||
// move entries one level up, but skip schema types | ||
if (key.charAt(0) !== '@') { | ||
atcbConfig[key] = atcbConfig['event'][key]; | ||
} | ||
if (key.charAt(0) !== "@") { | ||
atcbConfig[key] = atcbConfig.event[key]; | ||
} | ||
}); | ||
// drop the event block and return | ||
delete atcbConfig.event; | ||
} catch (err) { | ||
console.error( | ||
"add-to-calendar button problem: it seems like you use the schema.org style, but did not define it properly" | ||
); | ||
} | ||
catch(err) { | ||
console.error("add-to-calendar button problem: it seems like you use the schema.org style, but did not define it properly"); | ||
} | ||
return atcbConfig; | ||
} | ||
// BACKWARDS COMPATIBILITY REWRITE - you can remove this, if you did not use this script before v1.4.0. | ||
function atcb_patch_config(atcbConfig) { | ||
const keyChanges = { | ||
'title': 'name', | ||
'dateStart': 'startDate', | ||
'dateEnd': 'endDate', | ||
'timeStart': 'startTime', | ||
'timeEnd': 'endTime', | ||
title: "name", | ||
dateStart: "startDate", | ||
dateEnd: "endDate", | ||
timeStart: "startTime", | ||
timeEnd: "endTime", | ||
}; | ||
Object.keys(keyChanges).forEach(key => { | ||
Object.keys(keyChanges).forEach((key) => { | ||
if (atcbConfig[keyChanges[key]] == null && atcbConfig[key] != null) { | ||
@@ -110,4 +113,2 @@ atcbConfig[keyChanges[key]] = atcbConfig[key]; | ||
// CLEAN DATA BEFORE FURTHER VALIDATION (CONSIDERING SPECIAL RULES AND SCHEMES) | ||
@@ -118,5 +119,5 @@ function atcb_decorate_data(atcbConfig) { | ||
// calculate the real date values in case that there are some special rules included (e.g. adding days dynamically) | ||
atcbConfig['startDate'] = atcb_date_calculation(atcbConfig['startDate']); | ||
atcbConfig['endDate'] = atcb_date_calculation(atcbConfig['endDate']); | ||
atcbConfig.startDate = atcb_date_calculation(atcbConfig.startDate); | ||
atcbConfig.endDate = atcb_date_calculation(atcbConfig.endDate); | ||
// if no description or already decorated, return early | ||
@@ -128,14 +129,17 @@ if (!atcbConfig.description || atcbConfig.description_iCal) return atcbConfig; | ||
// standardize any line breaks in the description and transform URLs (but keep a clean copy without the URL magic for iCal) | ||
data.description = data.description.replace(/<br\s*\/?>/gmi, '\n'); | ||
data.description_iCal = data.description.replace('[url]','').replace('[/url]',''); | ||
data.description = data.description.replace(/\[url\](.*?)\[\/url\]/g, "<a href='$1' target='_blank' rel='noopener'>$1</a>"); | ||
return data | ||
data.description = data.description.replace(/<br\s*\/?>/gi, "\n"); | ||
data.description_iCal = data.description.replace(/\[url\]/g, "").replace(/\[\/url\]/g, ""); | ||
data.description = decodeURIComponent( | ||
data.description.replace( | ||
/\[url\]([\w&$+.,:;=~!*'?@#\s\-()|/^%]*)\[\/url\]/g, | ||
encodeURIComponent('<a href="$1" target="_blank" rel="noopener">$1</a>') | ||
) | ||
); | ||
return data; | ||
} | ||
// CHECK FOR REQUIRED FIELDS | ||
function atcb_check_required(data) { | ||
// check for at least 1 option | ||
if (data['options'] == null || data['options'].length < 1) { | ||
if (data.options == null || data.options.length < 1) { | ||
console.error("add-to-calendar button generation failed: no options set"); | ||
@@ -145,4 +149,4 @@ return false; | ||
// check for min required data (without "options") | ||
const requiredField = ['name', 'startDate', 'endDate'] | ||
return requiredField.every(function(field) { | ||
const requiredField = ["name", "startDate", "endDate"]; | ||
return requiredField.every(function (field) { | ||
if (data[field] == null || data[field] == "") { | ||
@@ -156,23 +160,21 @@ console.error("add-to-calendar button generation failed: required setting missing [" + field + "]"); | ||
// CALCULATE AND CLEAN UP THE ACTUAL DATES | ||
function atcb_date_cleanup(data) { | ||
// parse date+time format (default with Schema.org, but also an unofficial alternative to other implementation) | ||
const endpoints = ['start', 'end']; | ||
endpoints.forEach(function(point) { | ||
if (data[point + 'Date'] != null) { | ||
const endpoints = ["start", "end"]; | ||
endpoints.forEach(function (point) { | ||
if (data[point + "Date"] != null) { | ||
// remove any milliseconds information | ||
data[point + 'Date'] = data[point + 'Date'].replace(/\..../, '').replace('Z', ''); | ||
data[point + "Date"] = data[point + "Date"].replace(/\.\d{3}/, "").replace("Z", ""); | ||
// identify a possible time information within the date string | ||
let tmpSplitStartDate = data[point + 'Date'].split('T'); | ||
const tmpSplitStartDate = data[point + "Date"].split("T"); | ||
if (tmpSplitStartDate[1] != null) { | ||
data[point + 'Date'] = tmpSplitStartDate[0]; | ||
data[point + 'Time'] = tmpSplitStartDate[1]; | ||
data[point + "Date"] = tmpSplitStartDate[0]; | ||
data[point + "Time"] = tmpSplitStartDate[1]; | ||
} | ||
} | ||
// remove any seconds from time information | ||
if (data[point + 'Time'] != null && data[point + 'Time'].length == 8) { | ||
let timeStr = data[point + 'Time']; | ||
data[point + 'Time'] = timeStr.substring(0, timeStr.length - 3); | ||
if (data[point + "Time"] != null && data[point + "Time"].length === 8) { | ||
const timeStr = data[point + "Time"]; | ||
data[point + "Time"] = timeStr.substring(0, timeStr.length - 3); | ||
} | ||
@@ -185,8 +187,8 @@ }); | ||
// replace "today" with the current date first | ||
let today = new Date(); | ||
let todayString = (today.getUTCMonth() + 1) + '-' + today.getUTCDate() + '-' + today.getUTCFullYear(); | ||
dateString = dateString.replace(/today/ig, todayString); | ||
const today = new Date(); | ||
const todayString = today.getUTCMonth() + 1 + "-" + today.getUTCDate() + "-" + today.getUTCFullYear(); | ||
dateString = dateString.replace(/today/gi, todayString); | ||
// check for any dynamic additions and adjust | ||
const dateStringParts = dateString.split('+'); | ||
const dateParts = dateStringParts[0].split('-'); | ||
const dateStringParts = dateString.split("+"); | ||
const dateParts = dateStringParts[0].split("-"); | ||
let newDate = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]); | ||
@@ -200,79 +202,112 @@ if (dateParts[0].length < 4) { | ||
} | ||
return newDate.getFullYear() + '-' + (((newDate.getMonth() + 1) < 10 ? '0' : '') + (newDate.getMonth() + 1)) + '-' + (newDate.getDate() < 10 ? '0' : '') + newDate.getDate(); | ||
return ( | ||
newDate.getFullYear() + | ||
"-" + | ||
((newDate.getMonth() + 1 < 10 ? "0" : "") + (newDate.getMonth() + 1)) + | ||
"-" + | ||
(newDate.getDate() < 10 ? "0" : "") + | ||
newDate.getDate() | ||
); | ||
} | ||
// VALIDATE THE JSON DATA | ||
function atcb_validate(data) { | ||
// validate options | ||
const options = ['Apple', 'Google', 'iCal', 'Microsoft365', 'Outlook.com', 'MicrosoftTeams', 'Yahoo'] | ||
if (!data['options'].every(function(option) { | ||
let cleanOption = option.split('|'); | ||
if (!options.includes(cleanOption[0])) { | ||
console.error("add-to-calendar button generation failed: invalid option [" + cleanOption[0] + "]"); | ||
return false; | ||
} | ||
return true; | ||
})) { | ||
const options = ["Apple", "Google", "iCal", "Microsoft365", "Outlook.com", "MicrosoftTeams", "Yahoo"]; | ||
if ( | ||
!data.options.every(function (option) { | ||
const cleanOption = option.split("|"); | ||
if (!options.includes(cleanOption[0])) { | ||
console.error("add-to-calendar button generation failed: invalid option [" + cleanOption[0] + "]"); | ||
return false; | ||
} | ||
return true; | ||
}) | ||
) { | ||
return false; | ||
} | ||
// validate date | ||
const dates = ['startDate', 'endDate']; | ||
let newDate = dates; | ||
if (!dates.every(function(date) { | ||
if (data[date].length != 10) { | ||
console.error("add-to-calendar button generation failed: date misspelled [-> YYYY-MM-DD]"); | ||
return false; | ||
} | ||
const dateParts = data[date].split('-'); | ||
if (dateParts.length < 3 || dateParts.length > 3) { | ||
console.error("add-to-calendar button generation failed: date misspelled [" + date + ": " + data[date] + "]"); | ||
return false; | ||
} | ||
newDate[date] = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]); | ||
return true; | ||
})) { | ||
return false; | ||
} | ||
// validate time | ||
const times = ['startTime', 'endTime']; | ||
if (!times.every(function(time) { | ||
if (data[time] != null) { | ||
if (data[time].length != 5) { | ||
console.error("add-to-calendar button generation failed: time misspelled [-> HH:MM]"); | ||
const dates = ["startDate", "endDate"]; | ||
const newDate = dates; | ||
if ( | ||
!dates.every(function (date) { | ||
if (data[date].length !== 10) { | ||
console.error("add-to-calendar button generation failed: date misspelled [-> YYYY-MM-DD]"); | ||
return false; | ||
} | ||
const timeParts = data[time].split(':'); | ||
// validate the time parts | ||
if (timeParts.length < 2 || timeParts.length > 2) { | ||
console.error("add-to-calendar button generation failed: time misspelled [" + time + ": " + data[time] + "]"); | ||
const dateParts = data[date].split("-"); | ||
if (dateParts.length < 3 || dateParts.length > 3) { | ||
console.error( | ||
"add-to-calendar button generation failed: date misspelled [" + date + ": " + data[date] + "]" | ||
); | ||
return false; | ||
} | ||
if (timeParts[0] > 23) { | ||
console.error("add-to-calendar button generation failed: time misspelled - hours number too high [" + time + ": " + timeParts[0] + "]"); | ||
return false; | ||
newDate[date] = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]); | ||
return true; | ||
}) | ||
) { | ||
return false; | ||
} | ||
// validate time | ||
const times = ["startTime", "endTime"]; | ||
if ( | ||
!times.every(function (time) { | ||
if (data[time] != null) { | ||
if (data[time].length !== 5) { | ||
console.error("add-to-calendar button generation failed: time misspelled [-> HH:MM]"); | ||
return false; | ||
} | ||
const timeParts = data[time].split(":"); | ||
// validate the time parts | ||
if (timeParts.length < 2 || timeParts.length > 2) { | ||
console.error( | ||
"add-to-calendar button generation failed: time misspelled [" + time + ": " + data[time] + "]" | ||
); | ||
return false; | ||
} | ||
if (timeParts[0] > 23) { | ||
console.error( | ||
"add-to-calendar button generation failed: time misspelled - hours number too high [" + | ||
time + | ||
": " + | ||
timeParts[0] + | ||
"]" | ||
); | ||
return false; | ||
} | ||
if (timeParts[1] > 59) { | ||
console.error( | ||
"add-to-calendar button generation failed: time misspelled - minutes number too high [" + | ||
time + | ||
": " + | ||
timeParts[1] + | ||
"]" | ||
); | ||
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 | ||
); | ||
} | ||
} | ||
if (timeParts[1] > 59) { | ||
console.error("add-to-calendar button generation failed: time misspelled - minutes number too high [" + time + ": " + timeParts[1] + "]"); | ||
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)) | ||
} | ||
} | ||
return true; | ||
})) { | ||
return true; | ||
}) | ||
) { | ||
return false; | ||
} | ||
if ((data['startTime'] != null && data['endTime'] == null) || (data['startTime'] == null && data['endTime'] != null)) { | ||
console.error("add-to-calendar button generation failed: if you set a starting time, you also need to define an end time"); | ||
if ((data.startTime != null && data.endTime == null) || (data.startTime == null && data.endTime != null)) { | ||
console.error( | ||
"add-to-calendar button generation failed: if you set a starting time, you also need to define an end time" | ||
); | ||
return false; | ||
} | ||
// validate whether end is not before start | ||
if (newDate['endDate'] < newDate['startDate']) { | ||
if (newDate.endDate < newDate.startDate) { | ||
console.error("add-to-calendar button generation failed: end date before start date"); | ||
@@ -285,42 +320,60 @@ return false; | ||
// GENERATE THE ACTUAL BUTTON | ||
function atcb_generate_label(parent, icon = false, text) { | ||
// helper function to generate the labels for the button and list options | ||
if (icon) { | ||
const iconEl = document.createElement("span"); | ||
iconEl.classList.add("atcb_icon"); | ||
parent.appendChild(iconEl); | ||
} | ||
if (text != "") { | ||
const textEl = document.createElement("span"); | ||
textEl.classList.add("atcb_text"); | ||
textEl.textContent = text; | ||
parent.appendChild(textEl); | ||
} | ||
} | ||
// GENERATE THE ACTUAL BUTTON | ||
function atcb_generate(button, buttonId, data) { | ||
// clean the placeholder, if flagged that way | ||
if (data['deleteJSON']) { | ||
button.innerHTML = ''; | ||
if (data.deleteJSON) { | ||
button.innerHTML = ""; | ||
} | ||
// generate the wrapper div | ||
let buttonTriggerWrapper = document.createElement('div'); | ||
buttonTriggerWrapper.classList.add('atcb_button_wrapper'); | ||
const buttonTriggerWrapper = document.createElement("div"); | ||
buttonTriggerWrapper.classList.add("atcb_button_wrapper"); | ||
button.appendChild(buttonTriggerWrapper); | ||
// generate the button trigger div | ||
let buttonTrigger = document.createElement('button'); | ||
buttonTrigger.id = 'atcb_button_' + buttonId; | ||
buttonTrigger.classList.add('atcb_button'); | ||
buttonTrigger.setAttribute('type', 'button'); | ||
const buttonTrigger = document.createElement("button"); | ||
buttonTrigger.id = "atcb_button_" + buttonId; | ||
buttonTrigger.classList.add("atcb_button"); | ||
buttonTrigger.setAttribute("type", "button"); | ||
buttonTriggerWrapper.appendChild(buttonTrigger); | ||
buttonTrigger.innerHTML = '<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><path d="M81.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zm-3.65 76.03c1.83 0 3.32 1.49 3.32 3.32s-1.49 3.32-3.32 3.32l-12.95-.04-.04 12.93c0 1.83-1.49 3.32-3.32 3.32s-3.32-1.49-3.32-3.32l.04-12.94-12.93-.05c-1.83 0-3.32-1.49-3.32-3.32s1.49-3.32 3.32-3.32l12.94.04.04-12.93c0-1.83 1.49-3.32 3.32-3.32s3.32 1.49 3.32 3.32l-.04 12.95 12.94.04h0zM29.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zM6.4 45.32h110.08V21.47c0-.8-.33-1.53-.86-2.07-.53-.53-1.26-.86-2.07-.86H103c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h10.55c2.57 0 4.9 1.05 6.59 2.74s2.74 4.02 2.74 6.59v27.06 65.03c0 2.57-1.05 4.9-2.74 6.59s-4.02 2.74-6.59 2.74H9.33c-2.57 0-4.9-1.05-6.59-2.74-1.69-1.7-2.74-4.03-2.74-6.6V48.53 21.47c0-2.57 1.05-4.9 2.74-6.59s4.02-2.74 6.59-2.74H20.6c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H9.33c-.8 0-1.53.33-2.07.86-.53.53-.86 1.26-.86 2.07v23.85h0zm110.08 6.41H6.4v61.82c0 .8.33 1.53.86 2.07.53.53 1.26.86 2.07.86h104.22c.8 0 1.53-.33 2.07-.86.53-.53.86-1.26.86-2.07V51.73h0zM50.43 18.54c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h21.49c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H50.43h0z"/></svg></span>'; | ||
buttonTrigger.innerHTML += '<span class="atcb_text">' + (data['label'] || 'Add to Calendar') + '</span>'; | ||
// generate the icon and text within the button | ||
atcb_generate_label(buttonTrigger, true, data.label || "Add to Calendar"); | ||
buttonTrigger.firstChild.innerHTML = | ||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><path d="M81.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zm-3.65 76.03c1.83 0 3.32 1.49 3.32 3.32s-1.49 3.32-3.32 3.32l-12.95-.04-.04 12.93c0 1.83-1.49 3.32-3.32 3.32s-3.32-1.49-3.32-3.32l.04-12.94-12.93-.05c-1.83 0-3.32-1.49-3.32-3.32s1.49-3.32 3.32-3.32l12.94.04.04-12.93c0-1.83 1.49-3.32 3.32-3.32s3.32 1.49 3.32 3.32l-.04 12.95 12.94.04h0zM29.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zM6.4 45.32h110.08V21.47c0-.8-.33-1.53-.86-2.07-.53-.53-1.26-.86-2.07-.86H103c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h10.55c2.57 0 4.9 1.05 6.59 2.74s2.74 4.02 2.74 6.59v27.06 65.03c0 2.57-1.05 4.9-2.74 6.59s-4.02 2.74-6.59 2.74H9.33c-2.57 0-4.9-1.05-6.59-2.74-1.69-1.7-2.74-4.03-2.74-6.6V48.53 21.47c0-2.57 1.05-4.9 2.74-6.59s4.02-2.74 6.59-2.74H20.6c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H9.33c-.8 0-1.53.33-2.07.86-.53.53-.86 1.26-.86 2.07v23.85h0zm110.08 6.41H6.4v61.82c0 .8.33 1.53.86 2.07.53.53 1.26.86 2.07.86h104.22c.8 0 1.53-.33 2.07-.86.53-.53.86-1.26.86-2.07V51.73h0zM50.43 18.54c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h21.49c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H50.43h0z"/></svg>'; | ||
// set event listeners for the button trigger | ||
if (data['trigger'] == 'click') { | ||
buttonTrigger.addEventListener('mousedown', () => atcb_toggle(data, buttonTrigger, true, false)); | ||
if (data.trigger == "click") { | ||
buttonTrigger.addEventListener("mousedown", () => atcb_toggle(data, buttonTrigger, false, true)); | ||
} else { | ||
buttonTrigger.addEventListener('touchstart', () => atcb_toggle(data, buttonTrigger, true, false), {passive: true}); | ||
buttonTrigger.addEventListener('mouseenter', () => atcb_open(data, buttonTrigger, true, false)); | ||
buttonTrigger.addEventListener("touchstart", () => atcb_toggle(data, buttonTrigger, false, true), { | ||
passive: true, | ||
}); | ||
buttonTrigger.addEventListener("mouseenter", () => atcb_open(data, buttonTrigger, false, true)); | ||
} | ||
buttonTrigger.addEventListener('keydown', function(event) { // trigger click on enter as well | ||
if (event.key == 'Enter') { | ||
atcb_toggle(data, buttonTrigger, true); | ||
buttonTrigger.addEventListener("keydown", function (event) { | ||
// trigger click on enter as well | ||
if (event.key == "Enter") { | ||
atcb_toggle(data, buttonTrigger, true, true); | ||
} | ||
}); | ||
// update the placeholder class to prevent multiple initializations | ||
button.classList.remove('atcb'); | ||
button.classList.add('atcb_initialized'); | ||
button.classList.remove("atcb"); | ||
button.classList.add("atcb_initialized"); | ||
// show the placeholder div | ||
if (data['inline']) { | ||
button.style.display = 'inline-block'; | ||
if (data.inline) { | ||
button.style.display = "inline-block"; | ||
} else { | ||
button.style.display = 'block'; | ||
button.style.display = "block"; | ||
} | ||
@@ -331,11 +384,10 @@ // console log | ||
function atcb_generate_dropdown_list(data) { | ||
const optionsList = document.createElement('div'); | ||
optionsList.classList.add('atcb_list'); | ||
const optionsList = document.createElement("div"); | ||
optionsList.classList.add("atcb_list"); | ||
// generate the list items | ||
data['options'].forEach(function(option) { | ||
let optionParts = option.split('|'); | ||
let optionItem = document.createElement('div'); | ||
optionItem.classList.add('atcb_list_item'); | ||
data.options.forEach(function (option) { | ||
const optionParts = option.split("|"); | ||
const optionItem = document.createElement("div"); | ||
optionItem.classList.add("atcb_list_item"); | ||
optionItem.tabIndex = 0; | ||
@@ -345,7 +397,6 @@ optionsList.appendChild(optionItem); | ||
case "Apple": | ||
optionItem.innerHTML = '<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" viewBox="0 0 640 640"><path d="M494.782 340.02c-.803-81.025 66.084-119.907 69.072-121.832-37.595-54.993-96.167-62.552-117.037-63.402-49.843-5.032-97.242 29.362-122.565 29.362-25.253 0-64.277-28.607-105.604-27.85-54.32.803-104.4 31.594-132.403 80.245C29.81 334.457 71.81 479.58 126.816 558.976c26.87 38.882 58.914 82.56 100.997 81 40.512-1.594 55.843-26.244 104.848-26.244 48.993 0 62.753 26.245 105.64 25.406 43.606-.803 71.232-39.638 97.925-78.65 30.887-45.12 43.548-88.75 44.316-90.994-.969-.437-85.029-32.634-85.879-129.439l.118-.035zM414.23 102.178C436.553 75.095 451.636 37.5 447.514-.024c-32.162 1.311-71.163 21.437-94.253 48.485-20.729 24.012-38.836 62.28-33.993 99.036 35.918 2.8 72.591-18.248 94.926-45.272l.036-.047z"/></svg></span>'; | ||
optionItem.innerHTML += '<span class="atcb_text">'; | ||
optionItem.innerHTML += optionParts[1] || 'Apple'; | ||
optionItem.innerHTML += '</span>'; | ||
optionItem.addEventListener('click', function() { | ||
atcb_generate_label(optionItem, true, optionParts[1] || "Apple"); | ||
optionItem.firstChild.innerHTML = | ||
'<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" viewBox="0 0 640 640"><path d="M494.782 340.02c-.803-81.025 66.084-119.907 69.072-121.832-37.595-54.993-96.167-62.552-117.037-63.402-49.843-5.032-97.242 29.362-122.565 29.362-25.253 0-64.277-28.607-105.604-27.85-54.32.803-104.4 31.594-132.403 80.245C29.81 334.457 71.81 479.58 126.816 558.976c26.87 38.882 58.914 82.56 100.997 81 40.512-1.594 55.843-26.244 104.848-26.244 48.993 0 62.753 26.245 105.64 25.406 43.606-.803 71.232-39.638 97.925-78.65 30.887-45.12 43.548-88.75 44.316-90.994-.969-.437-85.029-32.634-85.879-129.439l.118-.035zM414.23 102.178C436.553 75.095 451.636 37.5 447.514-.024c-32.162 1.311-71.163 21.437-94.253 48.485-20.729 24.012-38.836 62.28-33.993 99.036 35.918 2.8 72.591-18.248 94.926-45.272l.036-.047z"/></svg>'; | ||
optionItem.addEventListener("click", function () { | ||
atcb_generate_ical(data); | ||
@@ -356,7 +407,6 @@ atcb_close(); | ||
case "Google": | ||
optionItem.innerHTML = '<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><path d="M93.78 29.1H29.1v64.68h64.68V29.1z" fill="#fff"/><path d="M93.78 122.88l29.1-29.1h-29.1v29.1z" fill="#f72a25"/><path d="M122.88 29.1h-29.1v64.68h29.1V29.1z" fill="#fbbc04"/><path d="M93.78 93.78H29.1v29.1h64.68v-29.1z" fill="#34a853"/><path d="M0 93.78v19.4c0 5.36 4.34 9.7 9.7 9.7h19.4v-29.1H0h0z" fill="#188038"/><path d="M122.88 29.1V9.7c0-5.36-4.34-9.7-9.7-9.7h-19.4v29.1h29.1 0z" fill="#1967d2"/><path d="M93.78 0H9.7C4.34 0 0 4.34 0 9.7v84.08h29.1V29.1h64.67V0h.01z" fill="#4285f4"/><path d="M42.37 79.27c-2.42-1.63-4.09-4.02-5-7.17l5.61-2.31c.51 1.94 1.4 3.44 2.67 4.51 1.26 1.07 2.8 1.59 4.59 1.59 1.84 0 3.41-.56 4.73-1.67 1.32-1.12 1.98-2.54 1.98-4.26 0-1.76-.7-3.2-2.09-4.32s-3.14-1.67-5.22-1.67H46.4v-5.55h2.91c1.79 0 3.31-.48 4.54-1.46 1.23-.97 1.84-2.3 1.84-3.99 0-1.5-.55-2.7-1.65-3.6s-2.49-1.35-4.18-1.35c-1.65 0-2.96.44-3.93 1.32s-1.7 2-2.12 3.24l-5.55-2.31c.74-2.09 2.09-3.93 4.07-5.52s4.51-2.39 7.58-2.39c2.27 0 4.32.44 6.13 1.32s3.23 2.1 4.26 3.65c1.03 1.56 1.54 3.31 1.54 5.25 0 1.98-.48 3.65-1.43 5.03-.95 1.37-2.13 2.43-3.52 3.16v.33c1.79.74 3.36 1.96 4.51 3.52 1.17 1.58 1.76 3.46 1.76 5.66s-.56 4.16-1.67 5.88c-1.12 1.72-2.66 3.08-4.62 4.07s-4.17 1.49-6.62 1.49c-2.84 0-5.46-.81-7.88-2.45h0 0zm34.46-27.84l-6.16 4.45-3.08-4.67 11.05-7.97h4.24v37.6h-6.05V51.43h0z" fill="#1a73e8"/></svg></span>'; | ||
optionItem.innerHTML += '<span class="atcb_text">'; | ||
optionItem.innerHTML += optionParts[1] || 'Google'; | ||
optionItem.innerHTML += '</span>'; | ||
optionItem.addEventListener('click', function() { | ||
atcb_generate_label(optionItem, true, optionParts[1] || "Google"); | ||
optionItem.firstChild.innerHTML = | ||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><path d="M93.78 29.1H29.1v64.68h64.68V29.1z" fill="#fff"/><path d="M93.78 122.88l29.1-29.1h-29.1v29.1z" fill="#f72a25"/><path d="M122.88 29.1h-29.1v64.68h29.1V29.1z" fill="#fbbc04"/><path d="M93.78 93.78H29.1v29.1h64.68v-29.1z" fill="#34a853"/><path d="M0 93.78v19.4c0 5.36 4.34 9.7 9.7 9.7h19.4v-29.1H0h0z" fill="#188038"/><path d="M122.88 29.1V9.7c0-5.36-4.34-9.7-9.7-9.7h-19.4v29.1h29.1 0z" fill="#1967d2"/><path d="M93.78 0H9.7C4.34 0 0 4.34 0 9.7v84.08h29.1V29.1h64.67V0h.01z" fill="#4285f4"/><path d="M42.37 79.27c-2.42-1.63-4.09-4.02-5-7.17l5.61-2.31c.51 1.94 1.4 3.44 2.67 4.51 1.26 1.07 2.8 1.59 4.59 1.59 1.84 0 3.41-.56 4.73-1.67 1.32-1.12 1.98-2.54 1.98-4.26 0-1.76-.7-3.2-2.09-4.32s-3.14-1.67-5.22-1.67H46.4v-5.55h2.91c1.79 0 3.31-.48 4.54-1.46 1.23-.97 1.84-2.3 1.84-3.99 0-1.5-.55-2.7-1.65-3.6s-2.49-1.35-4.18-1.35c-1.65 0-2.96.44-3.93 1.32s-1.7 2-2.12 3.24l-5.55-2.31c.74-2.09 2.09-3.93 4.07-5.52s4.51-2.39 7.58-2.39c2.27 0 4.32.44 6.13 1.32s3.23 2.1 4.26 3.65c1.03 1.56 1.54 3.31 1.54 5.25 0 1.98-.48 3.65-1.43 5.03-.95 1.37-2.13 2.43-3.52 3.16v.33c1.79.74 3.36 1.96 4.51 3.52 1.17 1.58 1.76 3.46 1.76 5.66s-.56 4.16-1.67 5.88c-1.12 1.72-2.66 3.08-4.62 4.07s-4.17 1.49-6.62 1.49c-2.84 0-5.46-.81-7.88-2.45h0 0zm34.46-27.84l-6.16 4.45-3.08-4.67 11.05-7.97h4.24v37.6h-6.05V51.43h0z" fill="#1a73e8"/></svg>'; | ||
optionItem.addEventListener("click", function () { | ||
atcb_generate_google(data); | ||
@@ -367,7 +417,6 @@ atcb_close(); | ||
case "iCal": | ||
optionItem.innerHTML = '<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><path d="M81.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zm-15.5 99.08c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2H81.9c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H66.11h0zM15.85 67.09c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H15.85h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H40.98h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2H81.9c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H66.11h0zm25.14 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H91.25h0zm-75.4 18.36c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H15.85h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H40.98h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2H81.9c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H66.11h0zm25.14 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H91.25h0zm-75.4 18.36c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H15.85h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H40.98h0zM29.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zM6.4 45.32h110.07V21.47c0-.8-.33-1.53-.86-2.07-.53-.53-1.26-.86-2.07-.86H103c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h10.55c2.57 0 4.9 1.05 6.59 2.74s2.74 4.02 2.74 6.59v27.06 65.03c0 2.57-1.05 4.9-2.74 6.59s-4.02 2.74-6.59 2.74H9.33c-2.57 0-4.9-1.05-6.59-2.74-1.69-1.7-2.74-4.03-2.74-6.6V48.52 21.47c0-2.57 1.05-4.9 2.74-6.59s4.02-2.74 6.59-2.74H20.6c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H9.33c-.8 0-1.53.33-2.07.86-.53.53-.86 1.26-.86 2.07v23.85h0zm110.08 6.41H6.4v61.82c0 .8.33 1.53.86 2.07.53.53 1.26.86 2.07.86h104.22c.8 0 1.53-.33 2.07-.86.53-.53.86-1.26.86-2.07V51.73h0zM50.43 18.54c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h21.49c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H50.43h0z"/></svg></span>'; | ||
optionItem.innerHTML += '<span class="atcb_text">'; | ||
optionItem.innerHTML += optionParts[1] || 'iCal File'; | ||
optionItem.innerHTML += '</span>'; | ||
optionItem.addEventListener('click', function() { | ||
atcb_generate_label(optionItem, true, optionParts[1] || "iCal File"); | ||
optionItem.firstChild.innerHTML = | ||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><path d="M81.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zm-15.5 99.08c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2H81.9c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H66.11h0zM15.85 67.09c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H15.85h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H40.98h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2H81.9c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H66.11h0zm25.14 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H91.25h0zm-75.4 18.36c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H15.85h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H40.98h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2H81.9c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H66.11h0zm25.14 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H91.25h0zm-75.4 18.36c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H15.85h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H40.98h0zM29.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zM6.4 45.32h110.07V21.47c0-.8-.33-1.53-.86-2.07-.53-.53-1.26-.86-2.07-.86H103c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h10.55c2.57 0 4.9 1.05 6.59 2.74s2.74 4.02 2.74 6.59v27.06 65.03c0 2.57-1.05 4.9-2.74 6.59s-4.02 2.74-6.59 2.74H9.33c-2.57 0-4.9-1.05-6.59-2.74-1.69-1.7-2.74-4.03-2.74-6.6V48.52 21.47c0-2.57 1.05-4.9 2.74-6.59s4.02-2.74 6.59-2.74H20.6c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H9.33c-.8 0-1.53.33-2.07.86-.53.53-.86 1.26-.86 2.07v23.85h0zm110.08 6.41H6.4v61.82c0 .8.33 1.53.86 2.07.53.53 1.26.86 2.07.86h104.22c.8 0 1.53-.33 2.07-.86.53-.53.86-1.26.86-2.07V51.73h0zM50.43 18.54c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h21.49c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H50.43h0z"/></svg>'; | ||
optionItem.addEventListener("click", function () { | ||
atcb_generate_ical(data); | ||
@@ -378,7 +427,6 @@ atcb_close(); | ||
case "MicrosoftTeams": | ||
optionItem.innerHTML = '<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 2228.833 2073.333"><g fill="#5059c9"><path d="M1554.637 777.5h575.713c54.391 0 98.483 44.092 98.483 98.483v524.398c0 199.901-162.051 361.952-361.952 361.952h0-1.711c-199.901.028-361.975-162-362.004-361.901v-.052-571.409c.001-28.427 23.045-51.471 51.471-51.471h0z"/><circle cx="1943.75" cy="440.583" r="233.25"/></g><g fill="#7b83eb"><circle cx="1218.083" cy="336.917" r="336.917"/><path d="M1667.323 777.5H717.01c-53.743 1.33-96.257 45.931-95.01 99.676v598.105c-7.505 322.519 247.657 590.16 570.167 598.053 322.51-7.893 577.671-275.534 570.167-598.053V877.176c1.245-53.745-41.268-98.346-95.011-99.676z"/></g><path opacity=".1" d="M1244 777.5v838.145c-.258 38.435-23.549 72.964-59.09 87.598-11.316 4.787-23.478 7.254-35.765 7.257H667.613c-6.738-17.105-12.958-34.21-18.142-51.833-18.144-59.477-27.402-121.307-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52H1244z"/><path opacity=".2" d="M1192.167 777.5v889.978a91.84 91.84 0 0 1-7.257 35.765c-14.634 35.541-49.163 58.833-87.598 59.09H691.975c-8.812-17.105-17.105-34.21-24.362-51.833s-12.958-34.21-18.142-51.833a631.28 631.28 0 0 1-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h475.313z"/><path opacity=".2" d="M1192.167 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855h-447.84A631.28 631.28 0 0 1 622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h475.312z"/><path opacity=".2" d="M1140.333 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855H649.472A631.28 631.28 0 0 1 622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h423.478z"/><path opacity=".1" d="M1244 509.522v163.275c-8.812.518-17.105 1.037-25.917 1.037s-17.105-.518-25.917-1.037c-17.496-1.161-34.848-3.937-51.833-8.293a336.92 336.92 0 0 1-233.25-198.003 288.02 288.02 0 0 1-16.587-51.833h258.648c52.305.198 94.657 42.549 94.856 94.854z"/><use xlink:href="#C" opacity=".2"/><use xlink:href="#C" opacity=".2"/><path opacity=".2" d="M1140.333 561.355v103.148A336.92 336.92 0 0 1 907.083 466.5h138.395c52.305.199 94.656 42.551 94.855 94.855z"/><linearGradient id="A" gradientUnits="userSpaceOnUse" x1="198.099" y1="392.261" x2="942.234" y2="1681.073"><stop offset="0" stop-color="#5a62c3"/><stop offset=".5" stop-color="#4d55bd"/><stop offset="1" stop-color="#3940ab"/></linearGradient><path fill="url(#A)" d="M95.01 466.5h950.312c52.473 0 95.01 42.538 95.01 95.01v950.312c0 52.473-42.538 95.01-95.01 95.01H95.01c-52.473 0-95.01-42.538-95.01-95.01V561.51c0-52.472 42.538-95.01 95.01-95.01z"/><path fill="#fff" d="M820.211,828.193H630.241v517.297H509.211V828.193H320.123V727.844h500.088V828.193z"/><defs ><path id="C" d="M1192.167 561.355v111.442c-17.496-1.161-34.848-3.937-51.833-8.293a336.92 336.92 0 0 1-233.25-198.003h190.228c52.304.198 94.656 42.55 94.855 94.854z"/></defs></svg></span>'; | ||
optionItem.innerHTML += '<span class="atcb_text">'; | ||
optionItem.innerHTML += optionParts[1] || 'Microsoft Teams'; | ||
optionItem.innerHTML += '</span>'; | ||
optionItem.addEventListener('click', function() { | ||
atcb_generate_label(optionItem, true, optionParts[1] || "Microsoft Teams"); | ||
optionItem.firstChild.innerHTML = | ||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 2228.833 2073.333"><g fill="#5059c9"><path d="M1554.637 777.5h575.713c54.391 0 98.483 44.092 98.483 98.483v524.398c0 199.901-162.051 361.952-361.952 361.952h0-1.711c-199.901.028-361.975-162-362.004-361.901v-.052-571.409c.001-28.427 23.045-51.471 51.471-51.471h0z"/><circle cx="1943.75" cy="440.583" r="233.25"/></g><g fill="#7b83eb"><circle cx="1218.083" cy="336.917" r="336.917"/><path d="M1667.323 777.5H717.01c-53.743 1.33-96.257 45.931-95.01 99.676v598.105c-7.505 322.519 247.657 590.16 570.167 598.053 322.51-7.893 577.671-275.534 570.167-598.053V877.176c1.245-53.745-41.268-98.346-95.011-99.676z"/></g><path opacity=".1" d="M1244 777.5v838.145c-.258 38.435-23.549 72.964-59.09 87.598-11.316 4.787-23.478 7.254-35.765 7.257H667.613c-6.738-17.105-12.958-34.21-18.142-51.833-18.144-59.477-27.402-121.307-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52H1244z"/><path opacity=".2" d="M1192.167 777.5v889.978a91.84 91.84 0 0 1-7.257 35.765c-14.634 35.541-49.163 58.833-87.598 59.09H691.975c-8.812-17.105-17.105-34.21-24.362-51.833s-12.958-34.21-18.142-51.833a631.28 631.28 0 0 1-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h475.313z"/><path opacity=".2" d="M1192.167 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855h-447.84A631.28 631.28 0 0 1 622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h475.312z"/><path opacity=".2" d="M1140.333 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855H649.472A631.28 631.28 0 0 1 622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h423.478z"/><path opacity=".1" d="M1244 509.522v163.275c-8.812.518-17.105 1.037-25.917 1.037s-17.105-.518-25.917-1.037c-17.496-1.161-34.848-3.937-51.833-8.293a336.92 336.92 0 0 1-233.25-198.003 288.02 288.02 0 0 1-16.587-51.833h258.648c52.305.198 94.657 42.549 94.856 94.854z"/><use xlink:href="#C" opacity=".2"/><use xlink:href="#C" opacity=".2"/><path opacity=".2" d="M1140.333 561.355v103.148A336.92 336.92 0 0 1 907.083 466.5h138.395c52.305.199 94.656 42.551 94.855 94.855z"/><linearGradient id="A" gradientUnits="userSpaceOnUse" x1="198.099" y1="392.261" x2="942.234" y2="1681.073"><stop offset="0" stop-color="#5a62c3"/><stop offset=".5" stop-color="#4d55bd"/><stop offset="1" stop-color="#3940ab"/></linearGradient><path fill="url(#A)" d="M95.01 466.5h950.312c52.473 0 95.01 42.538 95.01 95.01v950.312c0 52.473-42.538 95.01-95.01 95.01H95.01c-52.473 0-95.01-42.538-95.01-95.01V561.51c0-52.472 42.538-95.01 95.01-95.01z"/><path fill="#fff" d="M820.211,828.193H630.241v517.297H509.211V828.193H320.123V727.844h500.088V828.193z"/><defs ><path id="C" d="M1192.167 561.355v111.442c-17.496-1.161-34.848-3.937-51.833-8.293a336.92 336.92 0 0 1-233.25-198.003h190.228c52.304.198 94.656 42.55 94.855 94.854z"/></defs></svg>'; | ||
optionItem.addEventListener("click", function () { | ||
atcb_generate_teams(data); | ||
@@ -389,8 +437,7 @@ atcb_close(); | ||
case "Microsoft365": | ||
optionItem.innerHTML = '<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 278050 333334" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd"><path fill="#ea3e23" d="M278050 305556l-29-16V28627L178807 0 448 66971l-448 87 22 200227 60865-23821V80555l117920-28193-17 239519L122 267285l178668 65976v73l99231-27462v-316z"/></svg></span>'; | ||
optionItem.innerHTML += '<span class="atcb_text">'; | ||
optionItem.innerHTML += optionParts[1] || 'Microsoft 365'; | ||
optionItem.innerHTML += '</span>'; | ||
optionItem.addEventListener('click', function() { | ||
atcb_generate_microsoft(data, '365'); | ||
atcb_generate_label(optionItem, true, optionParts[1] || "Microsoft 365"); | ||
optionItem.firstChild.innerHTML = | ||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 278050 333334" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd"><path fill="#ea3e23" d="M278050 305556l-29-16V28627L178807 0 448 66971l-448 87 22 200227 60865-23821V80555l117920-28193-17 239519L122 267285l178668 65976v73l99231-27462v-316z"/></svg>'; | ||
optionItem.addEventListener("click", function () { | ||
atcb_generate_microsoft(data, "365"); | ||
atcb_close(); | ||
@@ -400,8 +447,7 @@ }); | ||
case "Outlook.com": | ||
optionItem.innerHTML = '<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.129793726981 0 33.251996719421 32" width="2500" height="2397"><path d="M28.596 2H11.404A1.404 1.404 0 0 0 10 3.404V5l9.69 3L30 5V3.404A1.404 1.404 0 0 0 28.596 2z" fill="#0364b8"/><path d="M31.65 17.405A11.341 11.341 0 0 0 32 16a.666.666 0 0 0-.333-.576l-.013-.008-.004-.002L20.812 9.24a1.499 1.499 0 0 0-1.479-.083 1.49 1.49 0 0 0-.145.082L8.35 15.415l-.004.002-.012.007A.666.666 0 0 0 8 16a11.344 11.344 0 0 0 .35 1.405l11.492 8.405z" fill="#0a2767"/><path d="M24 5h-7l-2.021 3L17 11l7 6h6v-6z" fill="#28a8ea"/><path d="M10 5h7v6h-7z" fill="#0078d4"/><path d="M24 5h6v6h-6z" fill="#50d9ff"/><path d="M24 17l-7-6h-7v6l7 6 10.832 1.768z" fill="#0364b8"/><path d="M17 11h7v6h-7z" fill="#0078d4"/><path d="M10 17h7v6h-7z" fill="#064a8c"/><path d="M24 17h6v6h-6z" fill="#0078d4"/><path d="M20.19 25.218l-11.793-8.6.495-.87 10.909 6.212a.528.528 0 0 0 .42-.012l10.933-6.23.496.869z" fill="#0a2767" opacity=".5"/><path d="M31.667 16.577l-.014.008-.003.002-10.838 6.174a1.497 1.497 0 0 1-1.46.091l3.774 5.061 8.254 1.797v.004A1.498 1.498 0 0 0 32 28.5V16a.666.666 0 0 1-.333.577z" fill="#1490df"/><path d="M32 28.5v-.738l-9.983-5.688-1.205.687a1.497 1.497 0 0 1-1.46.091l3.774 5.061 8.254 1.797v.004A1.498 1.498 0 0 0 32 28.5z" opacity=".05"/><path d="M31.95 28.883L21.007 22.65l-.195.11a1.497 1.497 0 0 1-1.46.092l3.774 5.061 8.254 1.797v.004a1.501 1.501 0 0 0 .57-.83z" opacity=".1"/><path d="M8.35 16.59v-.01h-.01l-.03-.02A.65.65 0 0 1 8 16v12.5A1.498 1.498 0 0 0 9.5 30h21a1.503 1.503 0 0 0 .37-.05.637.637 0 0 0 .18-.06.142.142 0 0 0 .06-.02 1.048 1.048 0 0 0 .23-.13c.02-.01.03-.01.04-.03z" fill="#28a8ea"/><path d="M18 24.667V8.333A1.337 1.337 0 0 0 16.667 7H10.03v7.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v10h8.667A1.337 1.337 0 0 0 18 24.667z" opacity=".1"/><path d="M17 25.667V9.333A1.337 1.337 0 0 0 15.667 8H10.03v6.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v11h7.667A1.337 1.337 0 0 0 17 25.667z" opacity=".2"/><path d="M17 23.667V9.333A1.337 1.337 0 0 0 15.667 8H10.03v6.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v9h7.667A1.337 1.337 0 0 0 17 23.667z" opacity=".2"/><path d="M16 23.667V9.333A1.337 1.337 0 0 0 14.667 8H10.03v6.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v9h6.667A1.337 1.337 0 0 0 16 23.667z" opacity=".2"/><path d="M1.333 8h13.334A1.333 1.333 0 0 1 16 9.333v13.334A1.333 1.333 0 0 1 14.667 24H1.333A1.333 1.333 0 0 1 0 22.667V9.333A1.333 1.333 0 0 1 1.333 8z" fill="#0078d4"/><path d="M3.867 13.468a4.181 4.181 0 0 1 1.642-1.814A4.965 4.965 0 0 1 8.119 11a4.617 4.617 0 0 1 2.413.62 4.14 4.14 0 0 1 1.598 1.733 5.597 5.597 0 0 1 .56 2.55 5.901 5.901 0 0 1-.577 2.666 4.239 4.239 0 0 1-1.645 1.794A4.8 4.8 0 0 1 7.963 21a4.729 4.729 0 0 1-2.468-.627 4.204 4.204 0 0 1-1.618-1.736 5.459 5.459 0 0 1-.567-2.519 6.055 6.055 0 0 1 .557-2.65zm1.75 4.258a2.716 2.716 0 0 0 .923 1.194 2.411 2.411 0 0 0 1.443.435 2.533 2.533 0 0 0 1.541-.449 2.603 2.603 0 0 0 .897-1.197 4.626 4.626 0 0 0 .286-1.665 5.063 5.063 0 0 0-.27-1.686 2.669 2.669 0 0 0-.866-1.24 2.387 2.387 0 0 0-1.527-.473 2.493 2.493 0 0 0-1.477.439 2.741 2.741 0 0 0-.944 1.203 4.776 4.776 0 0 0-.007 3.44z" fill="#fff"/></svg></span>'; | ||
optionItem.innerHTML += '<span class="atcb_text">'; | ||
optionItem.innerHTML += optionParts[1] || 'Outlook.com'; | ||
optionItem.innerHTML += '</span>'; | ||
optionItem.addEventListener('click', function() { | ||
atcb_generate_microsoft(data, 'outlook'); | ||
atcb_generate_label(optionItem, true, optionParts[1] || "Outlook.com"); | ||
optionItem.firstChild.innerHTML = | ||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.129793726981 0 33.251996719421 32" width="2500" height="2397"><path d="M28.596 2H11.404A1.404 1.404 0 0 0 10 3.404V5l9.69 3L30 5V3.404A1.404 1.404 0 0 0 28.596 2z" fill="#0364b8"/><path d="M31.65 17.405A11.341 11.341 0 0 0 32 16a.666.666 0 0 0-.333-.576l-.013-.008-.004-.002L20.812 9.24a1.499 1.499 0 0 0-1.479-.083 1.49 1.49 0 0 0-.145.082L8.35 15.415l-.004.002-.012.007A.666.666 0 0 0 8 16a11.344 11.344 0 0 0 .35 1.405l11.492 8.405z" fill="#0a2767"/><path d="M24 5h-7l-2.021 3L17 11l7 6h6v-6z" fill="#28a8ea"/><path d="M10 5h7v6h-7z" fill="#0078d4"/><path d="M24 5h6v6h-6z" fill="#50d9ff"/><path d="M24 17l-7-6h-7v6l7 6 10.832 1.768z" fill="#0364b8"/><path d="M17 11h7v6h-7z" fill="#0078d4"/><path d="M10 17h7v6h-7z" fill="#064a8c"/><path d="M24 17h6v6h-6z" fill="#0078d4"/><path d="M20.19 25.218l-11.793-8.6.495-.87 10.909 6.212a.528.528 0 0 0 .42-.012l10.933-6.23.496.869z" fill="#0a2767" opacity=".5"/><path d="M31.667 16.577l-.014.008-.003.002-10.838 6.174a1.497 1.497 0 0 1-1.46.091l3.774 5.061 8.254 1.797v.004A1.498 1.498 0 0 0 32 28.5V16a.666.666 0 0 1-.333.577z" fill="#1490df"/><path d="M32 28.5v-.738l-9.983-5.688-1.205.687a1.497 1.497 0 0 1-1.46.091l3.774 5.061 8.254 1.797v.004A1.498 1.498 0 0 0 32 28.5z" opacity=".05"/><path d="M31.95 28.883L21.007 22.65l-.195.11a1.497 1.497 0 0 1-1.46.092l3.774 5.061 8.254 1.797v.004a1.501 1.501 0 0 0 .57-.83z" opacity=".1"/><path d="M8.35 16.59v-.01h-.01l-.03-.02A.65.65 0 0 1 8 16v12.5A1.498 1.498 0 0 0 9.5 30h21a1.503 1.503 0 0 0 .37-.05.637.637 0 0 0 .18-.06.142.142 0 0 0 .06-.02 1.048 1.048 0 0 0 .23-.13c.02-.01.03-.01.04-.03z" fill="#28a8ea"/><path d="M18 24.667V8.333A1.337 1.337 0 0 0 16.667 7H10.03v7.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v10h8.667A1.337 1.337 0 0 0 18 24.667z" opacity=".1"/><path d="M17 25.667V9.333A1.337 1.337 0 0 0 15.667 8H10.03v6.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v11h7.667A1.337 1.337 0 0 0 17 25.667z" opacity=".2"/><path d="M17 23.667V9.333A1.337 1.337 0 0 0 15.667 8H10.03v6.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v9h7.667A1.337 1.337 0 0 0 17 23.667z" opacity=".2"/><path d="M16 23.667V9.333A1.337 1.337 0 0 0 14.667 8H10.03v6.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v9h6.667A1.337 1.337 0 0 0 16 23.667z" opacity=".2"/><path d="M1.333 8h13.334A1.333 1.333 0 0 1 16 9.333v13.334A1.333 1.333 0 0 1 14.667 24H1.333A1.333 1.333 0 0 1 0 22.667V9.333A1.333 1.333 0 0 1 1.333 8z" fill="#0078d4"/><path d="M3.867 13.468a4.181 4.181 0 0 1 1.642-1.814A4.965 4.965 0 0 1 8.119 11a4.617 4.617 0 0 1 2.413.62 4.14 4.14 0 0 1 1.598 1.733 5.597 5.597 0 0 1 .56 2.55 5.901 5.901 0 0 1-.577 2.666 4.239 4.239 0 0 1-1.645 1.794A4.8 4.8 0 0 1 7.963 21a4.729 4.729 0 0 1-2.468-.627 4.204 4.204 0 0 1-1.618-1.736 5.459 5.459 0 0 1-.567-2.519 6.055 6.055 0 0 1 .557-2.65zm1.75 4.258a2.716 2.716 0 0 0 .923 1.194 2.411 2.411 0 0 0 1.443.435 2.533 2.533 0 0 0 1.541-.449 2.603 2.603 0 0 0 .897-1.197 4.626 4.626 0 0 0 .286-1.665 5.063 5.063 0 0 0-.27-1.686 2.669 2.669 0 0 0-.866-1.24 2.387 2.387 0 0 0-1.527-.473 2.493 2.493 0 0 0-1.477.439 2.741 2.741 0 0 0-.944 1.203 4.776 4.776 0 0 0-.007 3.44z" fill="#fff"/></svg>'; | ||
optionItem.addEventListener("click", function () { | ||
atcb_generate_microsoft(data, "outlook"); | ||
atcb_close(); | ||
@@ -411,7 +457,6 @@ }); | ||
case "Yahoo": | ||
optionItem.innerHTML = '<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3386.34 3010.5" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd"><path d="M0 732.88h645.84l376.07 962.1 380.96-962.1h628.76l-946.8 2277.62H451.98l259.19-603.53L.02 732.88zm2763.84 768.75h-704.26L2684.65 0l701.69.03-622.5 1501.6zm-519.78 143.72c216.09 0 391.25 175.17 391.25 391.22 0 216.06-175.16 391.23-391.25 391.23-216.06 0-391.19-175.17-391.19-391.23 0-216.05 175.16-391.22 391.19-391.22z" fill="#5f01d1" fill-rule="nonzero"/></svg></span>'; | ||
optionItem.innerHTML += '<span class="atcb_text">'; | ||
optionItem.innerHTML += optionParts[1] || 'Yahoo'; | ||
optionItem.innerHTML += '</span>'; | ||
optionItem.addEventListener('click', function() { | ||
atcb_generate_label(optionItem, true, optionParts[1] || "Yahoo"); | ||
optionItem.firstChild.innerHTML = | ||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3386.34 3010.5" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd"><path d="M0 732.88h645.84l376.07 962.1 380.96-962.1h628.76l-946.8 2277.62H451.98l259.19-603.53L.02 732.88zm2763.84 768.75h-704.26L2684.65 0l701.69.03-622.5 1501.6zm-519.78 143.72c216.09 0 391.25 175.17 391.25 391.22 0 216.06-175.16 391.23-391.25 391.23-216.06 0-391.19-175.17-391.19-391.23 0-216.05 175.16-391.22 391.19-391.22z" fill="#5f01d1" fill-rule="nonzero"/></svg>'; | ||
optionItem.addEventListener("click", function () { | ||
atcb_generate_yahoo(data); | ||
@@ -422,4 +467,5 @@ atcb_close(); | ||
} | ||
optionItem.addEventListener('keydown', function(event) { // trigger click on enter as well | ||
if (event.key == 'Enter') { | ||
optionItem.addEventListener("keydown", function (event) { | ||
// trigger click on enter as well | ||
if (event.key == "Enter") { | ||
this.click(); | ||
@@ -433,21 +479,31 @@ } | ||
// create the background overlay, which also acts as trigger to close any dropdowns | ||
function atcb_generate_bg_overlay(data) { | ||
const bgOverlay = document.createElement('div'); | ||
bgOverlay.classList.add('atcb_bgoverlay'); | ||
const bgOverlay = document.createElement("div"); | ||
bgOverlay.classList.add("atcb_bgoverlay"); | ||
if (data.listStyle !== "modal") { | ||
bgOverlay.classList.add("atcb_animate_bg"); | ||
} | ||
bgOverlay.tabIndex = 0; | ||
bgOverlay.addEventListener('click', () => atcb_close(true)); | ||
bgOverlay.addEventListener("click", () => atcb_close(true)); | ||
let fingerMoved = false; | ||
bgOverlay.addEventListener('touchstart', () => fingerMoved = false, {passive: true}); | ||
bgOverlay.addEventListener('touchmove', () => fingerMoved = true, {passive: true}); | ||
bgOverlay.addEventListener('touchend', function() { | ||
if (fingerMoved == false) { | ||
atcb_close(true); | ||
} | ||
}, {passive: true}); | ||
bgOverlay.addEventListener('focus', () => atcb_close(false)); | ||
if (data['trigger'] !== 'click') { | ||
bgOverlay.addEventListener('mousemove', () => atcb_close(true)); | ||
} else { | ||
bgOverlay.classList.add('atcb_click'); | ||
bgOverlay.addEventListener("touchstart", () => (fingerMoved = false), { | ||
passive: true, | ||
}); | ||
bgOverlay.addEventListener("touchmove", () => (fingerMoved = true), { | ||
passive: true, | ||
}); | ||
bgOverlay.addEventListener( | ||
"touchend", | ||
function () { | ||
if (fingerMoved === false) { | ||
atcb_close(true); | ||
} | ||
}, | ||
{ passive: true } | ||
); | ||
bgOverlay.addEventListener("focus", () => atcb_close(false)); | ||
if (data.trigger !== "click") { | ||
bgOverlay.addEventListener("mousemove", () => atcb_close(true)); | ||
} else { | ||
bgOverlay.classList.add("atcb_click"); | ||
} | ||
@@ -457,11 +513,9 @@ return bgOverlay; | ||
// FUNCTIONS TO CONTROL THE INTERACTION | ||
function atcb_toggle(data, button, buttonGenerated, keyboardTrigger = true) { | ||
function atcb_toggle(data, button, keyboardTrigger = true, generatedButton = false) { | ||
// check for state and adjust accordingly | ||
if (button.classList.contains('atcb_active')) { | ||
if (button.classList.contains("atcb_active")) { | ||
atcb_close(); | ||
} else { | ||
atcb_open(data, button, buttonGenerated, keyboardTrigger); | ||
atcb_open(data, button, keyboardTrigger, generatedButton); | ||
} | ||
@@ -471,22 +525,30 @@ } | ||
// show the dropdown list + background overlay | ||
function atcb_open(data, button, buttonGenerated = false, keyboardTrigger = true) { | ||
function atcb_open(data, button, keyboardTrigger = true, generatedButton = false) { | ||
// abort early if an add-to-calendar dropdown already opened | ||
if (document.querySelector('.atcb_list')) return | ||
if (document.querySelector(".atcb_list")) return; | ||
// generate list | ||
const list = atcb_generate_dropdown_list(data, buttonGenerated); | ||
const list = atcb_generate_dropdown_list(data); | ||
// set list styles, set possible button to atcb_active and go for modal mode if no button is set | ||
// set list styles, set button to atcb_active and force modal listStyle if no button is set | ||
if (button) { | ||
button.classList.add('atcb_active'); | ||
const rect = button.getBoundingClientRect(); | ||
list.style.width = rect.width + 'px'; | ||
list.style.top = rect.bottom + window.scrollY -3 + 'px'; | ||
list.style.left = rect.left + 'px'; | ||
button.classList.add("atcb_active"); | ||
if (data.listStyle === "modal") { | ||
button.classList.add("atcb_modal_style"); | ||
list.classList.add("atcb_modal"); | ||
} else { | ||
const rect = button.getBoundingClientRect(); | ||
list.style.width = rect.width + "px"; | ||
list.style.top = rect.bottom + window.scrollY - 3 + "px"; | ||
list.style.left = rect.left + "px"; | ||
list.classList.add("atcb_dropdown"); | ||
if (generatedButton) { | ||
list.classList.add("atcb_generated_button"); // if the button has been generated by the script, we add some more specifics | ||
} | ||
} | ||
} else { | ||
list.classList.add('atcb_modal') | ||
// if no button is defined, fallback to listStyle "modal" | ||
data.listStyle = "modal"; | ||
list.classList.add("atcb_modal"); | ||
} | ||
if (buttonGenerated) { | ||
list.classList.add('atcb_generated_button'); | ||
} | ||
@@ -499,3 +561,4 @@ // add list to DOM | ||
// give keyboard focus to first item in list, if not blocked, because there is definitely no keyboard trigger | ||
// give keyboard focus to first item in list, if not blocked, because there is definitely no keyboard trigger. | ||
// Mintd that with custom atcb_action function, this might get triggered as well, if the custom function does not provide a proper value. | ||
if (keyboardTrigger) { | ||
@@ -507,126 +570,115 @@ list.firstChild.focus(); | ||
function atcb_close(blockFocus = false) { | ||
// 1. Focus triggering button (if not existing, try a custom element) | ||
// 1. Focus triggering button - especially relevant for keyboard navigation | ||
if (!blockFocus) { | ||
let newFocusEl = document.querySelector('.atcb_active'); | ||
let newFocusEl = document.querySelector(".atcb_active"); | ||
if (newFocusEl) { | ||
newFocusEl.focus(); | ||
} else { | ||
newFocusEl = document.querySelector('.atcb_customTrigger'); | ||
if (newFocusEl) { | ||
newFocusEl.focus(); | ||
} | ||
} | ||
} | ||
// 2. Inactivate all buttons | ||
Array.from(document.querySelectorAll('.atcb_active')).forEach(button => { | ||
button.classList.remove('atcb_active'); | ||
}) | ||
Array.from(document.querySelectorAll(".atcb_active")).forEach((button) => { | ||
button.classList.remove("atcb_active"); | ||
}); | ||
// 3. Remove dropdowns & bg overlays (should only be one of each) | ||
Array.from(document.querySelectorAll('.atcb_list')).concat( | ||
Array.from(document.querySelectorAll('.atcb_bgoverlay')) | ||
).forEach(el => el.remove()); | ||
Array.from(document.querySelectorAll(".atcb_list")) | ||
.concat(Array.from(document.querySelectorAll(".atcb_bgoverlay"))) | ||
.forEach((el) => el.remove()); | ||
} | ||
// prepare data when not using the init function | ||
function atcb_action(data, button) { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
function atcb_action(data, triggerElement, keyboardTrigger = true) { | ||
// validate & decorate data | ||
if (!atcb_check_required(data)) { | ||
throw new Error("data missing; see logs") | ||
throw new Error("data missing; see logs"); | ||
} | ||
data = atcb_decorate_data(data); | ||
if (!atcb_validate(data)) { | ||
throw new Error("Invalid data; see logs") | ||
throw new Error("Invalid data; see logs"); | ||
} | ||
atcb_open(data, button); | ||
atcb_open(data, triggerElement, keyboardTrigger); | ||
} | ||
// FUNCTION TO GENERATE THE GOOGLE URL | ||
function atcb_generate_google(data) { | ||
// base url | ||
let url = 'https://calendar.google.com/calendar/render?action=TEMPLATE'; | ||
let url = "https://calendar.google.com/calendar/render?action=TEMPLATE"; | ||
// generate and add date | ||
let formattedDate = atcb_generate_time(data, 'clean', 'google'); | ||
url += '&dates=' + formattedDate['start'] + '%2F' + formattedDate['end']; | ||
const formattedDate = atcb_generate_time(data, "clean", "google"); | ||
url += "&dates=" + formattedDate.start + "%2F" + formattedDate.end; | ||
// add details (if set) | ||
if (data['name'] != null && data['name'] != '') { | ||
url += '&text=' + encodeURIComponent(data['name']); | ||
if (data.name != null && data.name != "") { | ||
url += "&text=" + encodeURIComponent(data.name); | ||
} | ||
if (data['location'] != null && data['location'] != '') { | ||
url += '&location=' + encodeURIComponent(data['location']); | ||
if (data.location != null && data.location != "") { | ||
url += "&location=" + encodeURIComponent(data.location); | ||
// TODO: Find a better solution for the next temporary workaround. | ||
if (isiOS()) { // workaround to cover a bug, where, when using Google Calendar on an iPhone, the location is not recognized. So, for the moment, we simply add it to the description. | ||
if (data['description'] == null || data['description'] == '') { | ||
data['description'] = ''; | ||
if (isiOS()) { | ||
// workaround to cover a bug, where, when using Google Calendar on an iPhone, the location is not recognized. So, for the moment, we simply add it to the description. | ||
if (data.description == null || data.description == "") { | ||
data.description = ""; | ||
} else { | ||
data['description'] += '<br><br>'; | ||
data.description += "%0A%0A"; | ||
} | ||
data['description'] += '📍: ' + data['location']; | ||
data.description += "📍: " + data.location; | ||
} | ||
} | ||
if (data['description'] != null && data['description'] != '') { | ||
url += '&details=' + encodeURIComponent(data['description']); | ||
if (data.description != null && data.description != "") { | ||
url += "&details=" + encodeURIComponent(data.description); | ||
} | ||
window.open(url, '_blank').focus(); | ||
window.open(url, "_blank").focus(); | ||
} | ||
// FUNCTION TO GENERATE THE YAHOO URL | ||
function atcb_generate_yahoo(data) { | ||
// base url | ||
let url = 'https://calendar.yahoo.com/?v=60'; | ||
let url = "https://calendar.yahoo.com/?v=60"; | ||
// generate and add date | ||
let formattedDate = atcb_generate_time(data, 'clean'); | ||
url += '&st=' + formattedDate['start'] + '&et=' + formattedDate['end']; | ||
if (formattedDate['allday']) { | ||
url += '&dur=allday'; | ||
const formattedDate = atcb_generate_time(data, "clean"); | ||
url += "&st=" + formattedDate.start + "&et=" + formattedDate.end; | ||
if (formattedDate.allday) { | ||
url += "&dur=allday"; | ||
} | ||
// add details (if set) | ||
if (data['name'] != null && data['name'] != '') { | ||
url += '&title=' + encodeURIComponent(data['name']); | ||
if (data.name != null && data.name != "") { | ||
url += "&title=" + encodeURIComponent(data.name); | ||
} | ||
if (data['location'] != null && data['location'] != '') { | ||
url += '&in_loc=' + encodeURIComponent(data['location']); | ||
if (data.location != null && data.location != "") { | ||
url += "&in_loc=" + encodeURIComponent(data.location); | ||
} | ||
if (data['description'] != null && data['description'] != '') { | ||
url += '&desc=' + encodeURIComponent(data['description']); | ||
if (data.description != null && data.description != "") { | ||
url += "&desc=" + encodeURIComponent(data.description); | ||
} | ||
window.open(url, '_blank').focus(); | ||
window.open(url, "_blank").focus(); | ||
} | ||
// FUNCTION TO GENERATE THE MICROSOFT 365 OR OUTLOOK WEB URL | ||
function atcb_generate_microsoft(data, type = '365') { | ||
function atcb_generate_microsoft(data, type = "365") { | ||
// base url | ||
let url = 'https://'; | ||
if (type == 'outlook') { | ||
url += 'outlook.live.com'; | ||
let url = "https://"; | ||
if (type == "outlook") { | ||
url += "outlook.live.com"; | ||
} else { | ||
url += 'outlook.office.com'; | ||
url += "outlook.office.com"; | ||
} | ||
url += '/calendar/0/deeplink/compose?path=%2Fcalendar%2Faction%2Fcompose&rru=addevent'; | ||
url += "/calendar/0/deeplink/compose?path=%2Fcalendar%2Faction%2Fcompose&rru=addevent"; | ||
// generate and add date | ||
let formattedDate = atcb_generate_time(data, 'delimiters', 'microsoft'); | ||
url += '&startdt=' + formattedDate['start'] + '&enddt=' + formattedDate['end']; | ||
if (formattedDate['allday']) { | ||
url += '&allday=true'; | ||
const formattedDate = atcb_generate_time(data, "delimiters", "microsoft"); | ||
url += "&startdt=" + formattedDate.start + "&enddt=" + formattedDate.end; | ||
if (formattedDate.allday) { | ||
url += "&allday=true"; | ||
} | ||
// add details (if set) | ||
if (data['name'] != null && data['name'] != '') { | ||
url += '&subject=' + encodeURIComponent(data['name']); | ||
if (data.name != null && data.name != "") { | ||
url += "&subject=" + encodeURIComponent(data.name); | ||
} | ||
if (data['location'] != null && data['location'] != '') { | ||
url += '&location=' + encodeURIComponent(data['location']); | ||
if (data.location != null && data.location != "") { | ||
url += "&location=" + encodeURIComponent(data.location); | ||
} | ||
if (data['description'] != null && data['description'] != '') { | ||
url += '&body=' + encodeURIComponent(data['description'].replace(/\n/g, '<br>')); | ||
if (data.description != null && data.description != "") { | ||
url += "&body=" + encodeURIComponent(data.description.replace(/\n/g, "%0A")); | ||
} | ||
window.open(url, '_blank').focus(); | ||
window.open(url, "_blank").focus(); | ||
} | ||
// FUNCTION TO GENERATE THE MICROSOFT TEAMS URL | ||
@@ -637,67 +689,67 @@ // Mind that this is still in development mode by Microsoft! (https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/build-and-test/deep-links#deep-linking-to-the-scheduling-dialog) | ||
// base url | ||
let url = 'https://teams.microsoft.com/l/meeting/new?'; | ||
let url = "https://teams.microsoft.com/l/meeting/new?"; | ||
// generate and add date | ||
let formattedDate = atcb_generate_time(data, 'delimiters', 'microsoft'); | ||
url += '&startTime=' + formattedDate['start'] + '&endTime=' + formattedDate['end']; | ||
const formattedDate = atcb_generate_time(data, "delimiters", "microsoft"); | ||
url += "&startTime=" + formattedDate.start + "&endTime=" + formattedDate.end; | ||
// add details (if set) | ||
let locationString = ''; | ||
if (data['name'] != null && data['name'] != '') { | ||
url += '&subject=' + encodeURIComponent(data['name']); | ||
let locationString = ""; | ||
if (data.name != null && data.name != "") { | ||
url += "&subject=" + encodeURIComponent(data.name); | ||
} | ||
if (data['location'] != null && data['location'] != '') { | ||
locationString = encodeURIComponent(data['location']); | ||
url += '&location=' + locationString; | ||
locationString += ' // '; // preparing the workaround putting the location into the description, since the native field is not supported yet | ||
if (data.location != null && data.location != "") { | ||
locationString = encodeURIComponent(data.location); | ||
url += "&location=" + locationString; | ||
locationString += " // "; // preparing the workaround putting the location into the description, since the native field is not supported yet | ||
} | ||
if (data['description_iCal'] != null && data['description_iCal'] != '') { // using description_iCal instead of description, since Teams does not support html tags | ||
url += '&content=' + locationString + encodeURIComponent(data['description_iCal']); | ||
if (data.description_iCal != null && data.description_iCal != "") { | ||
// using description_iCal instead of description, since Teams does not support html tags | ||
url += "&content=" + locationString + encodeURIComponent(data.description_iCal); | ||
} | ||
window.open(url, '_blank').focus(); | ||
window.open(url, "_blank").focus(); | ||
} | ||
// FUNCTION TO GENERATE THE iCAL FILE (also for the Apple option) | ||
function atcb_generate_ical(data) { | ||
let now = new Date(); | ||
now = now.toISOString().replace(/\..../g, '').replace(/[^a-z0-9]/gi,''); | ||
let formattedDate = atcb_generate_time(data, 'clean', 'ical'); | ||
let timeslot = ''; | ||
if (formattedDate['allday']) { | ||
timeslot = ';VALUE=DATE'; | ||
now = now.toISOString(); | ||
const formattedDate = atcb_generate_time(data, "clean", "ical"); | ||
let timeslot = ""; | ||
if (formattedDate.allday) { | ||
timeslot = ";VALUE=DATE"; | ||
} | ||
let ics_lines = [ | ||
"BEGIN:VCALENDAR", | ||
"VERSION:2.0", | ||
"CALSCALE:GREGORIAN", | ||
"BEGIN:VEVENT", | ||
"DTSTAMP:" + formattedDate['start'], | ||
"DTSTART" + timeslot + ":" + formattedDate['start'], | ||
"DTEND" + timeslot + ":" + formattedDate['end'], | ||
"SUMMARY:" + data['name'] | ||
]; | ||
if (data['description_iCal'] != null && data['description_iCal'] != '') { | ||
ics_lines.push("DESCRIPTION:" + data['description_iCal'].replace(/\n/g, '\\n')); | ||
} | ||
if (data['location'] != null && data['location'] != '') { | ||
ics_lines.push("LOCATION:" + data['location']); | ||
} | ||
ics_lines.push( | ||
"STATUS:CONFIRMED", | ||
"LAST-MODIFIED:" + now, | ||
"SEQUENCE:0", | ||
"END:VEVENT", | ||
"END:VCALENDAR" | ||
); | ||
let dlurl = 'data:text/calendar;charset=utf-8,'+encodeURIComponent(ics_lines.join('\r\n')); | ||
const ics_lines = ["BEGIN:VCALENDAR", "VERSION:2.0"]; | ||
const corp = "github.com/jekuer/add-to-calendar-button"; | ||
ics_lines.push("PRODID:-// " + corp + " // atcb v" + atcbVersion + " //EN"); | ||
ics_lines.push("CALSCALE:GREGORIAN"); | ||
ics_lines.push("BEGIN:VEVENT"); | ||
ics_lines.push("UID:" + now + "@add-to-calendar-button"); | ||
ics_lines.push( | ||
"DTSTAMP:" + formattedDate.start, | ||
"DTSTART" + timeslot + ":" + formattedDate.start, | ||
"DTEND" + timeslot + ":" + formattedDate.end, | ||
"SUMMARY:" + data.name.replace(/.{65}/g, "$&" + "\r\n ") // making sure it does not exceed 75 characters per line | ||
); | ||
if (data.description_iCal != null && data.description_iCal != "") { | ||
ics_lines.push( | ||
"DESCRIPTION:" + data.description_iCal.replace(/\n/g, "\\n").replace(/.{60}/g, "$&" + "\r\n ") // adjusting for intended line breaks + making sure it does not exceed 75 characters per line | ||
); | ||
} | ||
if (data.location != null && data.location != "") { | ||
ics_lines.push("LOCATION:" + data.location); | ||
} | ||
now = now.replace(/\.\d{3}/g, "").replace(/[^a-z\d]/gi, ""); | ||
ics_lines.push("STATUS:CONFIRMED", "LAST-MODIFIED:" + now, "SEQUENCE:0", "END:VEVENT", "END:VCALENDAR"); | ||
const dlurl = "data:text/calendar;charset=utf-8," + encodeURIComponent(ics_lines.join("\r\n")); | ||
const filename = data.iCalFileName || "event-to-save-in-my-calendar"; | ||
try { | ||
if (!window.ActiveXObject) { | ||
let save = document.createElement('a'); | ||
const save = document.createElement("a"); | ||
save.href = dlurl; | ||
save.target = '_blank'; | ||
save.download = data['iCalFileName'] || 'event-to-save-in-my-calendar'; | ||
let evt = new MouseEvent('click', { | ||
'view': window, | ||
'bubbles': true, | ||
'cancelable': false | ||
save.target = "_blank"; | ||
save.download = filename; | ||
const evt = new MouseEvent("click", { | ||
view: window, | ||
button: 0, | ||
bubbles: true, | ||
cancelable: false, | ||
}); | ||
@@ -707,3 +759,10 @@ save.dispatchEvent(evt); | ||
} | ||
} catch(e) { | ||
// for IE < 11 (even no longer officially supported) | ||
else if (!!window.ActiveXObject && document.execCommand) { | ||
const _window = window.open(dlurl, "_blank"); | ||
_window.document.close(); | ||
_window.document.execCommand("SaveAs", true, filename || dlurl); | ||
_window.close(); | ||
} | ||
} catch (e) { | ||
console.error(e); | ||
@@ -713,57 +772,72 @@ } | ||
// SHARED FUNCTION TO GENERATE A TIME STRING | ||
function atcb_generate_time(data, style = 'delimiters', targetCal = 'general') { | ||
let startDate = data['startDate'].split('-'); | ||
let endDate = data['endDate'].split('-'); | ||
let start = ''; | ||
let end = ''; | ||
function atcb_generate_time(data, style = "delimiters", targetCal = "general") { | ||
const startDate = data.startDate.split("-"); | ||
const endDate = data.endDate.split("-"); | ||
let start = ""; | ||
let end = ""; | ||
let allday = false; | ||
if (data['startTime'] != null && data['endTime'] != null) { | ||
if (data.startTime != null && data.endTime != null) { | ||
// Adjust for timezone, if set (see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones for either the TZ name or the offset) | ||
if (data['timeZoneOffset'] != null && data['timeZoneOffset'] != '') { | ||
if (data.timeZoneOffset != null && data.timeZoneOffset != "") { | ||
// if we have a timezone offset given, consider it | ||
start = new Date( startDate[0] + '-' + startDate[1] + '-' + startDate[2] + 'T' + data['startTime'] + ':00.000' + data['timeZoneOffset'] ); | ||
end = new Date( endDate[0] + '-' + endDate[1] + '-' + endDate[2] + 'T' + data['endTime'] + ':00.000' + data['timeZoneOffset'] ); | ||
start = start.toISOString().replace('.000', ''); | ||
end = end.toISOString().replace('.000', ''); | ||
if (style == 'clean') { | ||
start = start.replace(/\-/g, '').replace(/\:/g, ''); | ||
end = end.replace(/\-/g, '').replace(/\:/g, ''); | ||
} | ||
start = new Date( | ||
startDate[0] + | ||
"-" + | ||
startDate[1] + | ||
"-" + | ||
startDate[2] + | ||
"T" + | ||
data.startTime + | ||
":00.000" + | ||
data.timeZoneOffset | ||
); | ||
end = new Date( | ||
endDate[0] + | ||
"-" + | ||
endDate[1] + | ||
"-" + | ||
endDate[2] + | ||
"T" + | ||
data.endTime + | ||
":00.000" + | ||
data.timeZoneOffset | ||
); | ||
} else { | ||
// if there is no offset, we prepare the time, assuming it is UTC formatted | ||
start = new Date( startDate[0] + '-' + startDate[1] + '-' + startDate[2] + 'T' + data['startTime'] + ':00.000+00:00' ); | ||
end = new Date( endDate[0] + '-' + endDate[1] + '-' + endDate[2] + 'T' + data['endTime'] + ':00.000+00:00' ); | ||
if (data['timeZone'] != null && data['timeZone'] != '') { | ||
start = new Date( | ||
startDate[0] + "-" + startDate[1] + "-" + startDate[2] + "T" + data.startTime + ":00.000+00:00" | ||
); | ||
end = new Date(endDate[0] + "-" + endDate[1] + "-" + endDate[2] + "T" + data.endTime + ":00.000+00:00"); | ||
if (data.timeZone != null && data.timeZone != "") { | ||
// if a timezone is given, we adjust dynamically with the modern toLocaleString function | ||
let utcDate = new Date(start.toLocaleString('en-US', { timeZone: "UTC" })); | ||
if (data['timeZone'] == 'currentBrowser') { | ||
data['timeZone'] = Intl.DateTimeFormat().resolvedOptions().timeZone; | ||
const utcDate = new Date(start.toLocaleString("en-US", { timeZone: "UTC" })); | ||
if (data.timeZone == "currentBrowser") { | ||
data.timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; | ||
} | ||
let tzDate = new Date(start.toLocaleString('en-US', { timeZone: data['timeZone'] })); | ||
let offset = utcDate.getTime() - tzDate.getTime(); | ||
start.setTime( start.getTime() + offset ); | ||
end.setTime( end.getTime() + offset ); | ||
const tzDate = new Date(start.toLocaleString("en-US", { timeZone: data.timeZone })); | ||
const offset = utcDate.getTime() - tzDate.getTime(); | ||
start.setTime(start.getTime() + offset); | ||
end.setTime(end.getTime() + offset); | ||
} | ||
start = start.toISOString().replace('.000', ''); | ||
end = end.toISOString().replace('.000', ''); | ||
if (style == 'clean') { | ||
start = start.replace(/\-/g, '').replace(/\:/g, ''); | ||
end = end.replace(/\-/g, '').replace(/\:/g, ''); | ||
} | ||
} | ||
} else { // would be an allday event then | ||
start = start.toISOString().replace(".000", ""); | ||
end = end.toISOString().replace(".000", ""); | ||
if (style == "clean") { | ||
start = start.replace(/-/g, "").replace(/:/g, ""); | ||
end = end.replace(/-/g, "").replace(/:/g, ""); | ||
} | ||
} else { | ||
// would be an allday event then | ||
allday = true; | ||
start = new Date(Date.UTC(startDate[0], startDate[1] - 1, startDate[2])); | ||
let breakStart = start.toISOString().replace(/T(.+)Z/g, ''); | ||
let breakStart = start.toISOString().replace(/T(.+)Z/g, ""); | ||
end = new Date(Date.UTC(endDate[0], endDate[1] - 1, endDate[2])); | ||
if (targetCal == 'google' || targetCal == 'microsoft' || targetCal == 'ical') { | ||
if (targetCal == "google" || targetCal == "microsoft" || targetCal == "ical") { | ||
end.setDate(end.getDate() + 1); // increment the day by 1 for Google Calendar, iCal and Outlook | ||
} | ||
let breakEnd = end.toISOString().replace(/T(.+)Z/g, ''); | ||
if (style == 'clean') { | ||
breakStart = breakStart.replace(/\-/g, ''); | ||
breakEnd = breakEnd.replace(/\-/g, ''); | ||
let breakEnd = end.toISOString().replace(/T(.+)Z/g, ""); | ||
if (style == "clean") { | ||
breakStart = breakStart.replace(/-/g, ""); | ||
breakEnd = breakEnd.replace(/-/g, ""); | ||
} | ||
@@ -773,3 +847,3 @@ start = breakStart; | ||
} | ||
let returnObject = {'start':start, 'end':end, 'allday':allday}; | ||
const returnObject = { start, end, allday }; | ||
return returnObject; | ||
@@ -780,15 +854,19 @@ } | ||
// Global listener to ESC key to close dropdown | ||
document.addEventListener('keydown', evt => { | ||
if (evt.key === 'Escape') { | ||
document.addEventListener("keydown", (evt) => { | ||
if (evt.key === "Escape") { | ||
atcb_close(); | ||
} | ||
}); | ||
// Global listener to any screen changes, where we need to close all buttons in order to prevent any bad renderings (more "expensive" alternative would be to re-render them) | ||
window.addEventListener('resize', () => { | ||
// Global listener to any screen changes | ||
// Closing all buttons in order to prevent any bad renderings (more "expensive" alternative would be to re-render them) | ||
// Checking for width change to prevent resize on mobile scroll, where a disappearing browser bar could trigger it | ||
let windowWidth = window.innerWidth; | ||
window.addEventListener("resize", () => { | ||
if (window.innerWidth != windowWidth) { | ||
windowWidth = window.innerWidth; | ||
atcb_close(true); | ||
} | ||
}); | ||
} | ||
module.exports = { atcb_action, atcb_init }; |
@@ -6,13 +6,15 @@ /** | ||
*/ | ||
const atcbVersion = '1.8.10'; | ||
const atcbVersion = "1.9.0"; | ||
/* Creator: Jens Kuerschner (https://jenskuerschner.de) | ||
* Project: https://github.com/jekuer/add-to-calendar-button | ||
* License: MIT with “Commons Clause” License Condition v1.0 | ||
* | ||
* | ||
*/ | ||
const isBrowser = new Function("try { return this===window; }catch(e){ return false; }"); | ||
const isiOS = isBrowser() ? new Function("if ((/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)){ return true; }else{ return false; }") : new Function("return false;"); | ||
const isiOS = isBrowser() | ||
? new Function( | ||
"if ((/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)){ return true; }else{ return false; }" | ||
) | ||
: new Function("return false;"); | ||
@@ -25,11 +27,11 @@ // INITIALIZE THE SCRIPT AND FUNCTIONALITY | ||
// get all placeholders | ||
let atcButtons = document.querySelectorAll('.atcb'); | ||
const atcButtons = document.querySelectorAll(".atcb"); | ||
// if there are some, move on | ||
if (atcButtons.length > 0) { | ||
// get the amount of already initialized ones first | ||
let atcButtonsInitialized = document.querySelectorAll('.atcb_initialized'); | ||
const atcButtonsInitialized = document.querySelectorAll(".atcb_initialized"); | ||
// generate the buttons one by one | ||
for (let i = 0; i < atcButtons.length; i++) { | ||
// skip already initialized ones | ||
if (atcButtons[i].classList.contains('atcb_initialized')) { | ||
if (atcButtons[i].classList.contains("atcb_initialized")) { | ||
continue; | ||
@@ -39,15 +41,19 @@ } | ||
// check if schema.org markup is present | ||
let schema = atcButtons[i].querySelector('script'); | ||
const schema = atcButtons[i].querySelector("script"); | ||
// get their JSON content first | ||
if (schema && schema.innerHTML) { | ||
// get schema.org event markup and flatten the event block | ||
atcbConfig = JSON.parse(schema.innerHTML.replace(/(\r\n|\n|\r)/gm, "").replace(/(<(?!br)([^>]+)>)/gi, "")); // remove real code line breaks before parsing. Use <br> or \n explicitely in the description to create a line break. Also strip HTML tags (especially since stupid Safari adds stuff). | ||
atcbConfig = JSON.parse( | ||
schema.innerHTML.replace(/(\r\n|\n|\r)/g, "").replace(/(<(?!br)([^>]+)>)/gi, "") | ||
); // remove real code line breaks before parsing. Use <br> or \n explicitely in the description to create a line break. Also strip HTML tags (especially since stupid Safari adds stuff). | ||
atcbConfig = atcb_parse_schema_json(atcbConfig); | ||
// set flag to not delete HTML content later | ||
atcbConfig['deleteJSON'] = false; | ||
atcbConfig.deleteJSON = false; | ||
} else { | ||
// get JSON from HTML block | ||
atcbConfig = JSON.parse(atcButtons[i].innerHTML.replace(/(\r\n|\n|\r)/gm, "").replace(/(<(?!br)([^>]+)>)/gi, "")); // remove real code line breaks before parsing. Use <br> or \n explicitely in the description to create a line break. Also strip HTML tags (especially since stupid Safari adds stuff). | ||
atcbConfig = JSON.parse( | ||
atcButtons[i].innerHTML.replace(/(\r\n|\n|\r)/g, "").replace(/(<(?!br)([^>]+)>)/gi, "") | ||
); // remove real code line breaks before parsing. Use <br> or \n explicitely in the description to create a line break. Also strip HTML tags (especially since stupid Safari adds stuff). | ||
// set flag to delete HTML content later | ||
atcbConfig['deleteJSON'] = true; | ||
atcbConfig.deleteJSON = true; | ||
} | ||
@@ -70,34 +76,31 @@ // rewrite config for backwards compatibility - you can remove this, if you did not use this script before v1.4.0. | ||
// NORMALIZE AND PARSE JSON FROM SCHEMA.ORG MARKUP | ||
function atcb_parse_schema_json(atcbConfig) { | ||
try { | ||
Object.keys(atcbConfig['event']).forEach(key => { | ||
Object.keys(atcbConfig.event).forEach((key) => { | ||
// move entries one level up, but skip schema types | ||
if (key.charAt(0) !== '@') { | ||
atcbConfig[key] = atcbConfig['event'][key]; | ||
} | ||
if (key.charAt(0) !== "@") { | ||
atcbConfig[key] = atcbConfig.event[key]; | ||
} | ||
}); | ||
// drop the event block and return | ||
delete atcbConfig.event; | ||
} catch (err) { | ||
console.error( | ||
"add-to-calendar button problem: it seems like you use the schema.org style, but did not define it properly" | ||
); | ||
} | ||
catch(err) { | ||
console.error("add-to-calendar button problem: it seems like you use the schema.org style, but did not define it properly"); | ||
} | ||
return atcbConfig; | ||
} | ||
// BACKWARDS COMPATIBILITY REWRITE - you can remove this, if you did not use this script before v1.4.0. | ||
function atcb_patch_config(atcbConfig) { | ||
const keyChanges = { | ||
'title': 'name', | ||
'dateStart': 'startDate', | ||
'dateEnd': 'endDate', | ||
'timeStart': 'startTime', | ||
'timeEnd': 'endTime', | ||
title: "name", | ||
dateStart: "startDate", | ||
dateEnd: "endDate", | ||
timeStart: "startTime", | ||
timeEnd: "endTime", | ||
}; | ||
Object.keys(keyChanges).forEach(key => { | ||
Object.keys(keyChanges).forEach((key) => { | ||
if (atcbConfig[keyChanges[key]] == null && atcbConfig[key] != null) { | ||
@@ -110,4 +113,2 @@ atcbConfig[keyChanges[key]] = atcbConfig[key]; | ||
// CLEAN DATA BEFORE FURTHER VALIDATION (CONSIDERING SPECIAL RULES AND SCHEMES) | ||
@@ -118,5 +119,5 @@ function atcb_decorate_data(atcbConfig) { | ||
// calculate the real date values in case that there are some special rules included (e.g. adding days dynamically) | ||
atcbConfig['startDate'] = atcb_date_calculation(atcbConfig['startDate']); | ||
atcbConfig['endDate'] = atcb_date_calculation(atcbConfig['endDate']); | ||
atcbConfig.startDate = atcb_date_calculation(atcbConfig.startDate); | ||
atcbConfig.endDate = atcb_date_calculation(atcbConfig.endDate); | ||
// if no description or already decorated, return early | ||
@@ -128,14 +129,17 @@ if (!atcbConfig.description || atcbConfig.description_iCal) return atcbConfig; | ||
// standardize any line breaks in the description and transform URLs (but keep a clean copy without the URL magic for iCal) | ||
data.description = data.description.replace(/<br\s*\/?>/gmi, '\n'); | ||
data.description_iCal = data.description.replace('[url]','').replace('[/url]',''); | ||
data.description = data.description.replace(/\[url\](.*?)\[\/url\]/g, "<a href='$1' target='_blank' rel='noopener'>$1</a>"); | ||
return data | ||
data.description = data.description.replace(/<br\s*\/?>/gi, "\n"); | ||
data.description_iCal = data.description.replace(/\[url\]/g, "").replace(/\[\/url\]/g, ""); | ||
data.description = decodeURIComponent( | ||
data.description.replace( | ||
/\[url\]([\w&$+.,:;=~!*'?@#\s\-()|/^%]*)\[\/url\]/g, | ||
encodeURIComponent('<a href="$1" target="_blank" rel="noopener">$1</a>') | ||
) | ||
); | ||
return data; | ||
} | ||
// CHECK FOR REQUIRED FIELDS | ||
function atcb_check_required(data) { | ||
// check for at least 1 option | ||
if (data['options'] == null || data['options'].length < 1) { | ||
if (data.options == null || data.options.length < 1) { | ||
console.error("add-to-calendar button generation failed: no options set"); | ||
@@ -145,4 +149,4 @@ return false; | ||
// check for min required data (without "options") | ||
const requiredField = ['name', 'startDate', 'endDate'] | ||
return requiredField.every(function(field) { | ||
const requiredField = ["name", "startDate", "endDate"]; | ||
return requiredField.every(function (field) { | ||
if (data[field] == null || data[field] == "") { | ||
@@ -156,23 +160,21 @@ console.error("add-to-calendar button generation failed: required setting missing [" + field + "]"); | ||
// CALCULATE AND CLEAN UP THE ACTUAL DATES | ||
function atcb_date_cleanup(data) { | ||
// parse date+time format (default with Schema.org, but also an unofficial alternative to other implementation) | ||
const endpoints = ['start', 'end']; | ||
endpoints.forEach(function(point) { | ||
if (data[point + 'Date'] != null) { | ||
const endpoints = ["start", "end"]; | ||
endpoints.forEach(function (point) { | ||
if (data[point + "Date"] != null) { | ||
// remove any milliseconds information | ||
data[point + 'Date'] = data[point + 'Date'].replace(/\..../, '').replace('Z', ''); | ||
data[point + "Date"] = data[point + "Date"].replace(/\.\d{3}/, "").replace("Z", ""); | ||
// identify a possible time information within the date string | ||
let tmpSplitStartDate = data[point + 'Date'].split('T'); | ||
const tmpSplitStartDate = data[point + "Date"].split("T"); | ||
if (tmpSplitStartDate[1] != null) { | ||
data[point + 'Date'] = tmpSplitStartDate[0]; | ||
data[point + 'Time'] = tmpSplitStartDate[1]; | ||
data[point + "Date"] = tmpSplitStartDate[0]; | ||
data[point + "Time"] = tmpSplitStartDate[1]; | ||
} | ||
} | ||
// remove any seconds from time information | ||
if (data[point + 'Time'] != null && data[point + 'Time'].length == 8) { | ||
let timeStr = data[point + 'Time']; | ||
data[point + 'Time'] = timeStr.substring(0, timeStr.length - 3); | ||
if (data[point + "Time"] != null && data[point + "Time"].length === 8) { | ||
const timeStr = data[point + "Time"]; | ||
data[point + "Time"] = timeStr.substring(0, timeStr.length - 3); | ||
} | ||
@@ -185,8 +187,8 @@ }); | ||
// replace "today" with the current date first | ||
let today = new Date(); | ||
let todayString = (today.getUTCMonth() + 1) + '-' + today.getUTCDate() + '-' + today.getUTCFullYear(); | ||
dateString = dateString.replace(/today/ig, todayString); | ||
const today = new Date(); | ||
const todayString = today.getUTCMonth() + 1 + "-" + today.getUTCDate() + "-" + today.getUTCFullYear(); | ||
dateString = dateString.replace(/today/gi, todayString); | ||
// check for any dynamic additions and adjust | ||
const dateStringParts = dateString.split('+'); | ||
const dateParts = dateStringParts[0].split('-'); | ||
const dateStringParts = dateString.split("+"); | ||
const dateParts = dateStringParts[0].split("-"); | ||
let newDate = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]); | ||
@@ -200,79 +202,112 @@ if (dateParts[0].length < 4) { | ||
} | ||
return newDate.getFullYear() + '-' + (((newDate.getMonth() + 1) < 10 ? '0' : '') + (newDate.getMonth() + 1)) + '-' + (newDate.getDate() < 10 ? '0' : '') + newDate.getDate(); | ||
return ( | ||
newDate.getFullYear() + | ||
"-" + | ||
((newDate.getMonth() + 1 < 10 ? "0" : "") + (newDate.getMonth() + 1)) + | ||
"-" + | ||
(newDate.getDate() < 10 ? "0" : "") + | ||
newDate.getDate() | ||
); | ||
} | ||
// VALIDATE THE JSON DATA | ||
function atcb_validate(data) { | ||
// validate options | ||
const options = ['Apple', 'Google', 'iCal', 'Microsoft365', 'Outlook.com', 'MicrosoftTeams', 'Yahoo'] | ||
if (!data['options'].every(function(option) { | ||
let cleanOption = option.split('|'); | ||
if (!options.includes(cleanOption[0])) { | ||
console.error("add-to-calendar button generation failed: invalid option [" + cleanOption[0] + "]"); | ||
return false; | ||
} | ||
return true; | ||
})) { | ||
const options = ["Apple", "Google", "iCal", "Microsoft365", "Outlook.com", "MicrosoftTeams", "Yahoo"]; | ||
if ( | ||
!data.options.every(function (option) { | ||
const cleanOption = option.split("|"); | ||
if (!options.includes(cleanOption[0])) { | ||
console.error("add-to-calendar button generation failed: invalid option [" + cleanOption[0] + "]"); | ||
return false; | ||
} | ||
return true; | ||
}) | ||
) { | ||
return false; | ||
} | ||
// validate date | ||
const dates = ['startDate', 'endDate']; | ||
let newDate = dates; | ||
if (!dates.every(function(date) { | ||
if (data[date].length != 10) { | ||
console.error("add-to-calendar button generation failed: date misspelled [-> YYYY-MM-DD]"); | ||
return false; | ||
} | ||
const dateParts = data[date].split('-'); | ||
if (dateParts.length < 3 || dateParts.length > 3) { | ||
console.error("add-to-calendar button generation failed: date misspelled [" + date + ": " + data[date] + "]"); | ||
return false; | ||
} | ||
newDate[date] = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]); | ||
return true; | ||
})) { | ||
return false; | ||
} | ||
// validate time | ||
const times = ['startTime', 'endTime']; | ||
if (!times.every(function(time) { | ||
if (data[time] != null) { | ||
if (data[time].length != 5) { | ||
console.error("add-to-calendar button generation failed: time misspelled [-> HH:MM]"); | ||
const dates = ["startDate", "endDate"]; | ||
const newDate = dates; | ||
if ( | ||
!dates.every(function (date) { | ||
if (data[date].length !== 10) { | ||
console.error("add-to-calendar button generation failed: date misspelled [-> YYYY-MM-DD]"); | ||
return false; | ||
} | ||
const timeParts = data[time].split(':'); | ||
// validate the time parts | ||
if (timeParts.length < 2 || timeParts.length > 2) { | ||
console.error("add-to-calendar button generation failed: time misspelled [" + time + ": " + data[time] + "]"); | ||
const dateParts = data[date].split("-"); | ||
if (dateParts.length < 3 || dateParts.length > 3) { | ||
console.error( | ||
"add-to-calendar button generation failed: date misspelled [" + date + ": " + data[date] + "]" | ||
); | ||
return false; | ||
} | ||
if (timeParts[0] > 23) { | ||
console.error("add-to-calendar button generation failed: time misspelled - hours number too high [" + time + ": " + timeParts[0] + "]"); | ||
return false; | ||
newDate[date] = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]); | ||
return true; | ||
}) | ||
) { | ||
return false; | ||
} | ||
// validate time | ||
const times = ["startTime", "endTime"]; | ||
if ( | ||
!times.every(function (time) { | ||
if (data[time] != null) { | ||
if (data[time].length !== 5) { | ||
console.error("add-to-calendar button generation failed: time misspelled [-> HH:MM]"); | ||
return false; | ||
} | ||
const timeParts = data[time].split(":"); | ||
// validate the time parts | ||
if (timeParts.length < 2 || timeParts.length > 2) { | ||
console.error( | ||
"add-to-calendar button generation failed: time misspelled [" + time + ": " + data[time] + "]" | ||
); | ||
return false; | ||
} | ||
if (timeParts[0] > 23) { | ||
console.error( | ||
"add-to-calendar button generation failed: time misspelled - hours number too high [" + | ||
time + | ||
": " + | ||
timeParts[0] + | ||
"]" | ||
); | ||
return false; | ||
} | ||
if (timeParts[1] > 59) { | ||
console.error( | ||
"add-to-calendar button generation failed: time misspelled - minutes number too high [" + | ||
time + | ||
": " + | ||
timeParts[1] + | ||
"]" | ||
); | ||
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 | ||
); | ||
} | ||
} | ||
if (timeParts[1] > 59) { | ||
console.error("add-to-calendar button generation failed: time misspelled - minutes number too high [" + time + ": " + timeParts[1] + "]"); | ||
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)) | ||
} | ||
} | ||
return true; | ||
})) { | ||
return true; | ||
}) | ||
) { | ||
return false; | ||
} | ||
if ((data['startTime'] != null && data['endTime'] == null) || (data['startTime'] == null && data['endTime'] != null)) { | ||
console.error("add-to-calendar button generation failed: if you set a starting time, you also need to define an end time"); | ||
if ((data.startTime != null && data.endTime == null) || (data.startTime == null && data.endTime != null)) { | ||
console.error( | ||
"add-to-calendar button generation failed: if you set a starting time, you also need to define an end time" | ||
); | ||
return false; | ||
} | ||
// validate whether end is not before start | ||
if (newDate['endDate'] < newDate['startDate']) { | ||
if (newDate.endDate < newDate.startDate) { | ||
console.error("add-to-calendar button generation failed: end date before start date"); | ||
@@ -285,42 +320,60 @@ return false; | ||
// GENERATE THE ACTUAL BUTTON | ||
function atcb_generate_label(parent, icon = false, text) { | ||
// helper function to generate the labels for the button and list options | ||
if (icon) { | ||
const iconEl = document.createElement("span"); | ||
iconEl.classList.add("atcb_icon"); | ||
parent.appendChild(iconEl); | ||
} | ||
if (text != "") { | ||
const textEl = document.createElement("span"); | ||
textEl.classList.add("atcb_text"); | ||
textEl.textContent = text; | ||
parent.appendChild(textEl); | ||
} | ||
} | ||
// GENERATE THE ACTUAL BUTTON | ||
function atcb_generate(button, buttonId, data) { | ||
// clean the placeholder, if flagged that way | ||
if (data['deleteJSON']) { | ||
button.innerHTML = ''; | ||
if (data.deleteJSON) { | ||
button.innerHTML = ""; | ||
} | ||
// generate the wrapper div | ||
let buttonTriggerWrapper = document.createElement('div'); | ||
buttonTriggerWrapper.classList.add('atcb_button_wrapper'); | ||
const buttonTriggerWrapper = document.createElement("div"); | ||
buttonTriggerWrapper.classList.add("atcb_button_wrapper"); | ||
button.appendChild(buttonTriggerWrapper); | ||
// generate the button trigger div | ||
let buttonTrigger = document.createElement('button'); | ||
buttonTrigger.id = 'atcb_button_' + buttonId; | ||
buttonTrigger.classList.add('atcb_button'); | ||
buttonTrigger.setAttribute('type', 'button'); | ||
const buttonTrigger = document.createElement("button"); | ||
buttonTrigger.id = "atcb_button_" + buttonId; | ||
buttonTrigger.classList.add("atcb_button"); | ||
buttonTrigger.setAttribute("type", "button"); | ||
buttonTriggerWrapper.appendChild(buttonTrigger); | ||
buttonTrigger.innerHTML = '<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><path d="M81.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zm-3.65 76.03c1.83 0 3.32 1.49 3.32 3.32s-1.49 3.32-3.32 3.32l-12.95-.04-.04 12.93c0 1.83-1.49 3.32-3.32 3.32s-3.32-1.49-3.32-3.32l.04-12.94-12.93-.05c-1.83 0-3.32-1.49-3.32-3.32s1.49-3.32 3.32-3.32l12.94.04.04-12.93c0-1.83 1.49-3.32 3.32-3.32s3.32 1.49 3.32 3.32l-.04 12.95 12.94.04h0zM29.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zM6.4 45.32h110.08V21.47c0-.8-.33-1.53-.86-2.07-.53-.53-1.26-.86-2.07-.86H103c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h10.55c2.57 0 4.9 1.05 6.59 2.74s2.74 4.02 2.74 6.59v27.06 65.03c0 2.57-1.05 4.9-2.74 6.59s-4.02 2.74-6.59 2.74H9.33c-2.57 0-4.9-1.05-6.59-2.74-1.69-1.7-2.74-4.03-2.74-6.6V48.53 21.47c0-2.57 1.05-4.9 2.74-6.59s4.02-2.74 6.59-2.74H20.6c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H9.33c-.8 0-1.53.33-2.07.86-.53.53-.86 1.26-.86 2.07v23.85h0zm110.08 6.41H6.4v61.82c0 .8.33 1.53.86 2.07.53.53 1.26.86 2.07.86h104.22c.8 0 1.53-.33 2.07-.86.53-.53.86-1.26.86-2.07V51.73h0zM50.43 18.54c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h21.49c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H50.43h0z"/></svg></span>'; | ||
buttonTrigger.innerHTML += '<span class="atcb_text">' + (data['label'] || 'Add to Calendar') + '</span>'; | ||
// generate the icon and text within the button | ||
atcb_generate_label(buttonTrigger, true, data.label || "Add to Calendar"); | ||
buttonTrigger.firstChild.innerHTML = | ||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><path d="M81.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zm-3.65 76.03c1.83 0 3.32 1.49 3.32 3.32s-1.49 3.32-3.32 3.32l-12.95-.04-.04 12.93c0 1.83-1.49 3.32-3.32 3.32s-3.32-1.49-3.32-3.32l.04-12.94-12.93-.05c-1.83 0-3.32-1.49-3.32-3.32s1.49-3.32 3.32-3.32l12.94.04.04-12.93c0-1.83 1.49-3.32 3.32-3.32s3.32 1.49 3.32 3.32l-.04 12.95 12.94.04h0zM29.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zM6.4 45.32h110.08V21.47c0-.8-.33-1.53-.86-2.07-.53-.53-1.26-.86-2.07-.86H103c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h10.55c2.57 0 4.9 1.05 6.59 2.74s2.74 4.02 2.74 6.59v27.06 65.03c0 2.57-1.05 4.9-2.74 6.59s-4.02 2.74-6.59 2.74H9.33c-2.57 0-4.9-1.05-6.59-2.74-1.69-1.7-2.74-4.03-2.74-6.6V48.53 21.47c0-2.57 1.05-4.9 2.74-6.59s4.02-2.74 6.59-2.74H20.6c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H9.33c-.8 0-1.53.33-2.07.86-.53.53-.86 1.26-.86 2.07v23.85h0zm110.08 6.41H6.4v61.82c0 .8.33 1.53.86 2.07.53.53 1.26.86 2.07.86h104.22c.8 0 1.53-.33 2.07-.86.53-.53.86-1.26.86-2.07V51.73h0zM50.43 18.54c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h21.49c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H50.43h0z"/></svg>'; | ||
// set event listeners for the button trigger | ||
if (data['trigger'] == 'click') { | ||
buttonTrigger.addEventListener('mousedown', () => atcb_toggle(data, buttonTrigger, true, false)); | ||
if (data.trigger == "click") { | ||
buttonTrigger.addEventListener("mousedown", () => atcb_toggle(data, buttonTrigger, false, true)); | ||
} else { | ||
buttonTrigger.addEventListener('touchstart', () => atcb_toggle(data, buttonTrigger, true, false), {passive: true}); | ||
buttonTrigger.addEventListener('mouseenter', () => atcb_open(data, buttonTrigger, true, false)); | ||
buttonTrigger.addEventListener("touchstart", () => atcb_toggle(data, buttonTrigger, false, true), { | ||
passive: true, | ||
}); | ||
buttonTrigger.addEventListener("mouseenter", () => atcb_open(data, buttonTrigger, false, true)); | ||
} | ||
buttonTrigger.addEventListener('keydown', function(event) { // trigger click on enter as well | ||
if (event.key == 'Enter') { | ||
atcb_toggle(data, buttonTrigger, true); | ||
buttonTrigger.addEventListener("keydown", function (event) { | ||
// trigger click on enter as well | ||
if (event.key == "Enter") { | ||
atcb_toggle(data, buttonTrigger, true, true); | ||
} | ||
}); | ||
// update the placeholder class to prevent multiple initializations | ||
button.classList.remove('atcb'); | ||
button.classList.add('atcb_initialized'); | ||
button.classList.remove("atcb"); | ||
button.classList.add("atcb_initialized"); | ||
// show the placeholder div | ||
if (data['inline']) { | ||
button.style.display = 'inline-block'; | ||
if (data.inline) { | ||
button.style.display = "inline-block"; | ||
} else { | ||
button.style.display = 'block'; | ||
button.style.display = "block"; | ||
} | ||
@@ -331,11 +384,10 @@ // console log | ||
function atcb_generate_dropdown_list(data) { | ||
const optionsList = document.createElement('div'); | ||
optionsList.classList.add('atcb_list'); | ||
const optionsList = document.createElement("div"); | ||
optionsList.classList.add("atcb_list"); | ||
// generate the list items | ||
data['options'].forEach(function(option) { | ||
let optionParts = option.split('|'); | ||
let optionItem = document.createElement('div'); | ||
optionItem.classList.add('atcb_list_item'); | ||
data.options.forEach(function (option) { | ||
const optionParts = option.split("|"); | ||
const optionItem = document.createElement("div"); | ||
optionItem.classList.add("atcb_list_item"); | ||
optionItem.tabIndex = 0; | ||
@@ -345,7 +397,6 @@ optionsList.appendChild(optionItem); | ||
case "Apple": | ||
optionItem.innerHTML = '<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" viewBox="0 0 640 640"><path d="M494.782 340.02c-.803-81.025 66.084-119.907 69.072-121.832-37.595-54.993-96.167-62.552-117.037-63.402-49.843-5.032-97.242 29.362-122.565 29.362-25.253 0-64.277-28.607-105.604-27.85-54.32.803-104.4 31.594-132.403 80.245C29.81 334.457 71.81 479.58 126.816 558.976c26.87 38.882 58.914 82.56 100.997 81 40.512-1.594 55.843-26.244 104.848-26.244 48.993 0 62.753 26.245 105.64 25.406 43.606-.803 71.232-39.638 97.925-78.65 30.887-45.12 43.548-88.75 44.316-90.994-.969-.437-85.029-32.634-85.879-129.439l.118-.035zM414.23 102.178C436.553 75.095 451.636 37.5 447.514-.024c-32.162 1.311-71.163 21.437-94.253 48.485-20.729 24.012-38.836 62.28-33.993 99.036 35.918 2.8 72.591-18.248 94.926-45.272l.036-.047z"/></svg></span>'; | ||
optionItem.innerHTML += '<span class="atcb_text">'; | ||
optionItem.innerHTML += optionParts[1] || 'Apple'; | ||
optionItem.innerHTML += '</span>'; | ||
optionItem.addEventListener('click', function() { | ||
atcb_generate_label(optionItem, true, optionParts[1] || "Apple"); | ||
optionItem.firstChild.innerHTML = | ||
'<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" viewBox="0 0 640 640"><path d="M494.782 340.02c-.803-81.025 66.084-119.907 69.072-121.832-37.595-54.993-96.167-62.552-117.037-63.402-49.843-5.032-97.242 29.362-122.565 29.362-25.253 0-64.277-28.607-105.604-27.85-54.32.803-104.4 31.594-132.403 80.245C29.81 334.457 71.81 479.58 126.816 558.976c26.87 38.882 58.914 82.56 100.997 81 40.512-1.594 55.843-26.244 104.848-26.244 48.993 0 62.753 26.245 105.64 25.406 43.606-.803 71.232-39.638 97.925-78.65 30.887-45.12 43.548-88.75 44.316-90.994-.969-.437-85.029-32.634-85.879-129.439l.118-.035zM414.23 102.178C436.553 75.095 451.636 37.5 447.514-.024c-32.162 1.311-71.163 21.437-94.253 48.485-20.729 24.012-38.836 62.28-33.993 99.036 35.918 2.8 72.591-18.248 94.926-45.272l.036-.047z"/></svg>'; | ||
optionItem.addEventListener("click", function () { | ||
atcb_generate_ical(data); | ||
@@ -356,7 +407,6 @@ atcb_close(); | ||
case "Google": | ||
optionItem.innerHTML = '<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><path d="M93.78 29.1H29.1v64.68h64.68V29.1z" fill="#fff"/><path d="M93.78 122.88l29.1-29.1h-29.1v29.1z" fill="#f72a25"/><path d="M122.88 29.1h-29.1v64.68h29.1V29.1z" fill="#fbbc04"/><path d="M93.78 93.78H29.1v29.1h64.68v-29.1z" fill="#34a853"/><path d="M0 93.78v19.4c0 5.36 4.34 9.7 9.7 9.7h19.4v-29.1H0h0z" fill="#188038"/><path d="M122.88 29.1V9.7c0-5.36-4.34-9.7-9.7-9.7h-19.4v29.1h29.1 0z" fill="#1967d2"/><path d="M93.78 0H9.7C4.34 0 0 4.34 0 9.7v84.08h29.1V29.1h64.67V0h.01z" fill="#4285f4"/><path d="M42.37 79.27c-2.42-1.63-4.09-4.02-5-7.17l5.61-2.31c.51 1.94 1.4 3.44 2.67 4.51 1.26 1.07 2.8 1.59 4.59 1.59 1.84 0 3.41-.56 4.73-1.67 1.32-1.12 1.98-2.54 1.98-4.26 0-1.76-.7-3.2-2.09-4.32s-3.14-1.67-5.22-1.67H46.4v-5.55h2.91c1.79 0 3.31-.48 4.54-1.46 1.23-.97 1.84-2.3 1.84-3.99 0-1.5-.55-2.7-1.65-3.6s-2.49-1.35-4.18-1.35c-1.65 0-2.96.44-3.93 1.32s-1.7 2-2.12 3.24l-5.55-2.31c.74-2.09 2.09-3.93 4.07-5.52s4.51-2.39 7.58-2.39c2.27 0 4.32.44 6.13 1.32s3.23 2.1 4.26 3.65c1.03 1.56 1.54 3.31 1.54 5.25 0 1.98-.48 3.65-1.43 5.03-.95 1.37-2.13 2.43-3.52 3.16v.33c1.79.74 3.36 1.96 4.51 3.52 1.17 1.58 1.76 3.46 1.76 5.66s-.56 4.16-1.67 5.88c-1.12 1.72-2.66 3.08-4.62 4.07s-4.17 1.49-6.62 1.49c-2.84 0-5.46-.81-7.88-2.45h0 0zm34.46-27.84l-6.16 4.45-3.08-4.67 11.05-7.97h4.24v37.6h-6.05V51.43h0z" fill="#1a73e8"/></svg></span>'; | ||
optionItem.innerHTML += '<span class="atcb_text">'; | ||
optionItem.innerHTML += optionParts[1] || 'Google'; | ||
optionItem.innerHTML += '</span>'; | ||
optionItem.addEventListener('click', function() { | ||
atcb_generate_label(optionItem, true, optionParts[1] || "Google"); | ||
optionItem.firstChild.innerHTML = | ||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><path d="M93.78 29.1H29.1v64.68h64.68V29.1z" fill="#fff"/><path d="M93.78 122.88l29.1-29.1h-29.1v29.1z" fill="#f72a25"/><path d="M122.88 29.1h-29.1v64.68h29.1V29.1z" fill="#fbbc04"/><path d="M93.78 93.78H29.1v29.1h64.68v-29.1z" fill="#34a853"/><path d="M0 93.78v19.4c0 5.36 4.34 9.7 9.7 9.7h19.4v-29.1H0h0z" fill="#188038"/><path d="M122.88 29.1V9.7c0-5.36-4.34-9.7-9.7-9.7h-19.4v29.1h29.1 0z" fill="#1967d2"/><path d="M93.78 0H9.7C4.34 0 0 4.34 0 9.7v84.08h29.1V29.1h64.67V0h.01z" fill="#4285f4"/><path d="M42.37 79.27c-2.42-1.63-4.09-4.02-5-7.17l5.61-2.31c.51 1.94 1.4 3.44 2.67 4.51 1.26 1.07 2.8 1.59 4.59 1.59 1.84 0 3.41-.56 4.73-1.67 1.32-1.12 1.98-2.54 1.98-4.26 0-1.76-.7-3.2-2.09-4.32s-3.14-1.67-5.22-1.67H46.4v-5.55h2.91c1.79 0 3.31-.48 4.54-1.46 1.23-.97 1.84-2.3 1.84-3.99 0-1.5-.55-2.7-1.65-3.6s-2.49-1.35-4.18-1.35c-1.65 0-2.96.44-3.93 1.32s-1.7 2-2.12 3.24l-5.55-2.31c.74-2.09 2.09-3.93 4.07-5.52s4.51-2.39 7.58-2.39c2.27 0 4.32.44 6.13 1.32s3.23 2.1 4.26 3.65c1.03 1.56 1.54 3.31 1.54 5.25 0 1.98-.48 3.65-1.43 5.03-.95 1.37-2.13 2.43-3.52 3.16v.33c1.79.74 3.36 1.96 4.51 3.52 1.17 1.58 1.76 3.46 1.76 5.66s-.56 4.16-1.67 5.88c-1.12 1.72-2.66 3.08-4.62 4.07s-4.17 1.49-6.62 1.49c-2.84 0-5.46-.81-7.88-2.45h0 0zm34.46-27.84l-6.16 4.45-3.08-4.67 11.05-7.97h4.24v37.6h-6.05V51.43h0z" fill="#1a73e8"/></svg>'; | ||
optionItem.addEventListener("click", function () { | ||
atcb_generate_google(data); | ||
@@ -367,7 +417,6 @@ atcb_close(); | ||
case "iCal": | ||
optionItem.innerHTML = '<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><path d="M81.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zm-15.5 99.08c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2H81.9c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H66.11h0zM15.85 67.09c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H15.85h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H40.98h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2H81.9c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H66.11h0zm25.14 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H91.25h0zm-75.4 18.36c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H15.85h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H40.98h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2H81.9c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H66.11h0zm25.14 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H91.25h0zm-75.4 18.36c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H15.85h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H40.98h0zM29.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zM6.4 45.32h110.07V21.47c0-.8-.33-1.53-.86-2.07-.53-.53-1.26-.86-2.07-.86H103c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h10.55c2.57 0 4.9 1.05 6.59 2.74s2.74 4.02 2.74 6.59v27.06 65.03c0 2.57-1.05 4.9-2.74 6.59s-4.02 2.74-6.59 2.74H9.33c-2.57 0-4.9-1.05-6.59-2.74-1.69-1.7-2.74-4.03-2.74-6.6V48.52 21.47c0-2.57 1.05-4.9 2.74-6.59s4.02-2.74 6.59-2.74H20.6c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H9.33c-.8 0-1.53.33-2.07.86-.53.53-.86 1.26-.86 2.07v23.85h0zm110.08 6.41H6.4v61.82c0 .8.33 1.53.86 2.07.53.53 1.26.86 2.07.86h104.22c.8 0 1.53-.33 2.07-.86.53-.53.86-1.26.86-2.07V51.73h0zM50.43 18.54c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h21.49c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H50.43h0z"/></svg></span>'; | ||
optionItem.innerHTML += '<span class="atcb_text">'; | ||
optionItem.innerHTML += optionParts[1] || 'iCal File'; | ||
optionItem.innerHTML += '</span>'; | ||
optionItem.addEventListener('click', function() { | ||
atcb_generate_label(optionItem, true, optionParts[1] || "iCal File"); | ||
optionItem.firstChild.innerHTML = | ||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><path d="M81.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zm-15.5 99.08c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2H81.9c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H66.11h0zM15.85 67.09c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H15.85h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H40.98h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2H81.9c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H66.11h0zm25.14 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H91.25h0zm-75.4 18.36c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H15.85h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H40.98h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2H81.9c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H66.11h0zm25.14 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H91.25h0zm-75.4 18.36c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H15.85h0zm25.13 0c-.34 0-.61-1.43-.61-3.2s.27-3.2.61-3.2h15.79c.34 0 .61 1.43.61 3.2s-.27 3.2-.61 3.2H40.98h0zM29.61 4.73c0-2.61 2.58-4.73 5.77-4.73s5.77 2.12 5.77 4.73v20.72c0 2.61-2.58 4.73-5.77 4.73s-5.77-2.12-5.77-4.73V4.73h0zM6.4 45.32h110.07V21.47c0-.8-.33-1.53-.86-2.07-.53-.53-1.26-.86-2.07-.86H103c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h10.55c2.57 0 4.9 1.05 6.59 2.74s2.74 4.02 2.74 6.59v27.06 65.03c0 2.57-1.05 4.9-2.74 6.59s-4.02 2.74-6.59 2.74H9.33c-2.57 0-4.9-1.05-6.59-2.74-1.69-1.7-2.74-4.03-2.74-6.6V48.52 21.47c0-2.57 1.05-4.9 2.74-6.59s4.02-2.74 6.59-2.74H20.6c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H9.33c-.8 0-1.53.33-2.07.86-.53.53-.86 1.26-.86 2.07v23.85h0zm110.08 6.41H6.4v61.82c0 .8.33 1.53.86 2.07.53.53 1.26.86 2.07.86h104.22c.8 0 1.53-.33 2.07-.86.53-.53.86-1.26.86-2.07V51.73h0zM50.43 18.54c-1.77 0-3.2-1.43-3.2-3.2s1.43-3.2 3.2-3.2h21.49c1.77 0 3.2 1.43 3.2 3.2s-1.43 3.2-3.2 3.2H50.43h0z"/></svg>'; | ||
optionItem.addEventListener("click", function () { | ||
atcb_generate_ical(data); | ||
@@ -378,7 +427,6 @@ atcb_close(); | ||
case "MicrosoftTeams": | ||
optionItem.innerHTML = '<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 2228.833 2073.333"><g fill="#5059c9"><path d="M1554.637 777.5h575.713c54.391 0 98.483 44.092 98.483 98.483v524.398c0 199.901-162.051 361.952-361.952 361.952h0-1.711c-199.901.028-361.975-162-362.004-361.901v-.052-571.409c.001-28.427 23.045-51.471 51.471-51.471h0z"/><circle cx="1943.75" cy="440.583" r="233.25"/></g><g fill="#7b83eb"><circle cx="1218.083" cy="336.917" r="336.917"/><path d="M1667.323 777.5H717.01c-53.743 1.33-96.257 45.931-95.01 99.676v598.105c-7.505 322.519 247.657 590.16 570.167 598.053 322.51-7.893 577.671-275.534 570.167-598.053V877.176c1.245-53.745-41.268-98.346-95.011-99.676z"/></g><path opacity=".1" d="M1244 777.5v838.145c-.258 38.435-23.549 72.964-59.09 87.598-11.316 4.787-23.478 7.254-35.765 7.257H667.613c-6.738-17.105-12.958-34.21-18.142-51.833-18.144-59.477-27.402-121.307-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52H1244z"/><path opacity=".2" d="M1192.167 777.5v889.978a91.84 91.84 0 0 1-7.257 35.765c-14.634 35.541-49.163 58.833-87.598 59.09H691.975c-8.812-17.105-17.105-34.21-24.362-51.833s-12.958-34.21-18.142-51.833a631.28 631.28 0 0 1-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h475.313z"/><path opacity=".2" d="M1192.167 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855h-447.84A631.28 631.28 0 0 1 622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h475.312z"/><path opacity=".2" d="M1140.333 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855H649.472A631.28 631.28 0 0 1 622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h423.478z"/><path opacity=".1" d="M1244 509.522v163.275c-8.812.518-17.105 1.037-25.917 1.037s-17.105-.518-25.917-1.037c-17.496-1.161-34.848-3.937-51.833-8.293a336.92 336.92 0 0 1-233.25-198.003 288.02 288.02 0 0 1-16.587-51.833h258.648c52.305.198 94.657 42.549 94.856 94.854z"/><use xlink:href="#C" opacity=".2"/><use xlink:href="#C" opacity=".2"/><path opacity=".2" d="M1140.333 561.355v103.148A336.92 336.92 0 0 1 907.083 466.5h138.395c52.305.199 94.656 42.551 94.855 94.855z"/><linearGradient id="A" gradientUnits="userSpaceOnUse" x1="198.099" y1="392.261" x2="942.234" y2="1681.073"><stop offset="0" stop-color="#5a62c3"/><stop offset=".5" stop-color="#4d55bd"/><stop offset="1" stop-color="#3940ab"/></linearGradient><path fill="url(#A)" d="M95.01 466.5h950.312c52.473 0 95.01 42.538 95.01 95.01v950.312c0 52.473-42.538 95.01-95.01 95.01H95.01c-52.473 0-95.01-42.538-95.01-95.01V561.51c0-52.472 42.538-95.01 95.01-95.01z"/><path fill="#fff" d="M820.211,828.193H630.241v517.297H509.211V828.193H320.123V727.844h500.088V828.193z"/><defs ><path id="C" d="M1192.167 561.355v111.442c-17.496-1.161-34.848-3.937-51.833-8.293a336.92 336.92 0 0 1-233.25-198.003h190.228c52.304.198 94.656 42.55 94.855 94.854z"/></defs></svg></span>'; | ||
optionItem.innerHTML += '<span class="atcb_text">'; | ||
optionItem.innerHTML += optionParts[1] || 'Microsoft Teams'; | ||
optionItem.innerHTML += '</span>'; | ||
optionItem.addEventListener('click', function() { | ||
atcb_generate_label(optionItem, true, optionParts[1] || "Microsoft Teams"); | ||
optionItem.firstChild.innerHTML = | ||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 2228.833 2073.333"><g fill="#5059c9"><path d="M1554.637 777.5h575.713c54.391 0 98.483 44.092 98.483 98.483v524.398c0 199.901-162.051 361.952-361.952 361.952h0-1.711c-199.901.028-361.975-162-362.004-361.901v-.052-571.409c.001-28.427 23.045-51.471 51.471-51.471h0z"/><circle cx="1943.75" cy="440.583" r="233.25"/></g><g fill="#7b83eb"><circle cx="1218.083" cy="336.917" r="336.917"/><path d="M1667.323 777.5H717.01c-53.743 1.33-96.257 45.931-95.01 99.676v598.105c-7.505 322.519 247.657 590.16 570.167 598.053 322.51-7.893 577.671-275.534 570.167-598.053V877.176c1.245-53.745-41.268-98.346-95.011-99.676z"/></g><path opacity=".1" d="M1244 777.5v838.145c-.258 38.435-23.549 72.964-59.09 87.598-11.316 4.787-23.478 7.254-35.765 7.257H667.613c-6.738-17.105-12.958-34.21-18.142-51.833-18.144-59.477-27.402-121.307-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52H1244z"/><path opacity=".2" d="M1192.167 777.5v889.978a91.84 91.84 0 0 1-7.257 35.765c-14.634 35.541-49.163 58.833-87.598 59.09H691.975c-8.812-17.105-17.105-34.21-24.362-51.833s-12.958-34.21-18.142-51.833a631.28 631.28 0 0 1-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h475.313z"/><path opacity=".2" d="M1192.167 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855h-447.84A631.28 631.28 0 0 1 622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h475.312z"/><path opacity=".2" d="M1140.333 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855H649.472A631.28 631.28 0 0 1 622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52h423.478z"/><path opacity=".1" d="M1244 509.522v163.275c-8.812.518-17.105 1.037-25.917 1.037s-17.105-.518-25.917-1.037c-17.496-1.161-34.848-3.937-51.833-8.293a336.92 336.92 0 0 1-233.25-198.003 288.02 288.02 0 0 1-16.587-51.833h258.648c52.305.198 94.657 42.549 94.856 94.854z"/><use xlink:href="#C" opacity=".2"/><use xlink:href="#C" opacity=".2"/><path opacity=".2" d="M1140.333 561.355v103.148A336.92 336.92 0 0 1 907.083 466.5h138.395c52.305.199 94.656 42.551 94.855 94.855z"/><linearGradient id="A" gradientUnits="userSpaceOnUse" x1="198.099" y1="392.261" x2="942.234" y2="1681.073"><stop offset="0" stop-color="#5a62c3"/><stop offset=".5" stop-color="#4d55bd"/><stop offset="1" stop-color="#3940ab"/></linearGradient><path fill="url(#A)" d="M95.01 466.5h950.312c52.473 0 95.01 42.538 95.01 95.01v950.312c0 52.473-42.538 95.01-95.01 95.01H95.01c-52.473 0-95.01-42.538-95.01-95.01V561.51c0-52.472 42.538-95.01 95.01-95.01z"/><path fill="#fff" d="M820.211,828.193H630.241v517.297H509.211V828.193H320.123V727.844h500.088V828.193z"/><defs ><path id="C" d="M1192.167 561.355v111.442c-17.496-1.161-34.848-3.937-51.833-8.293a336.92 336.92 0 0 1-233.25-198.003h190.228c52.304.198 94.656 42.55 94.855 94.854z"/></defs></svg>'; | ||
optionItem.addEventListener("click", function () { | ||
atcb_generate_teams(data); | ||
@@ -389,8 +437,7 @@ atcb_close(); | ||
case "Microsoft365": | ||
optionItem.innerHTML = '<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 278050 333334" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd"><path fill="#ea3e23" d="M278050 305556l-29-16V28627L178807 0 448 66971l-448 87 22 200227 60865-23821V80555l117920-28193-17 239519L122 267285l178668 65976v73l99231-27462v-316z"/></svg></span>'; | ||
optionItem.innerHTML += '<span class="atcb_text">'; | ||
optionItem.innerHTML += optionParts[1] || 'Microsoft 365'; | ||
optionItem.innerHTML += '</span>'; | ||
optionItem.addEventListener('click', function() { | ||
atcb_generate_microsoft(data, '365'); | ||
atcb_generate_label(optionItem, true, optionParts[1] || "Microsoft 365"); | ||
optionItem.firstChild.innerHTML = | ||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 278050 333334" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd"><path fill="#ea3e23" d="M278050 305556l-29-16V28627L178807 0 448 66971l-448 87 22 200227 60865-23821V80555l117920-28193-17 239519L122 267285l178668 65976v73l99231-27462v-316z"/></svg>'; | ||
optionItem.addEventListener("click", function () { | ||
atcb_generate_microsoft(data, "365"); | ||
atcb_close(); | ||
@@ -400,8 +447,7 @@ }); | ||
case "Outlook.com": | ||
optionItem.innerHTML = '<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.129793726981 0 33.251996719421 32" width="2500" height="2397"><path d="M28.596 2H11.404A1.404 1.404 0 0 0 10 3.404V5l9.69 3L30 5V3.404A1.404 1.404 0 0 0 28.596 2z" fill="#0364b8"/><path d="M31.65 17.405A11.341 11.341 0 0 0 32 16a.666.666 0 0 0-.333-.576l-.013-.008-.004-.002L20.812 9.24a1.499 1.499 0 0 0-1.479-.083 1.49 1.49 0 0 0-.145.082L8.35 15.415l-.004.002-.012.007A.666.666 0 0 0 8 16a11.344 11.344 0 0 0 .35 1.405l11.492 8.405z" fill="#0a2767"/><path d="M24 5h-7l-2.021 3L17 11l7 6h6v-6z" fill="#28a8ea"/><path d="M10 5h7v6h-7z" fill="#0078d4"/><path d="M24 5h6v6h-6z" fill="#50d9ff"/><path d="M24 17l-7-6h-7v6l7 6 10.832 1.768z" fill="#0364b8"/><path d="M17 11h7v6h-7z" fill="#0078d4"/><path d="M10 17h7v6h-7z" fill="#064a8c"/><path d="M24 17h6v6h-6z" fill="#0078d4"/><path d="M20.19 25.218l-11.793-8.6.495-.87 10.909 6.212a.528.528 0 0 0 .42-.012l10.933-6.23.496.869z" fill="#0a2767" opacity=".5"/><path d="M31.667 16.577l-.014.008-.003.002-10.838 6.174a1.497 1.497 0 0 1-1.46.091l3.774 5.061 8.254 1.797v.004A1.498 1.498 0 0 0 32 28.5V16a.666.666 0 0 1-.333.577z" fill="#1490df"/><path d="M32 28.5v-.738l-9.983-5.688-1.205.687a1.497 1.497 0 0 1-1.46.091l3.774 5.061 8.254 1.797v.004A1.498 1.498 0 0 0 32 28.5z" opacity=".05"/><path d="M31.95 28.883L21.007 22.65l-.195.11a1.497 1.497 0 0 1-1.46.092l3.774 5.061 8.254 1.797v.004a1.501 1.501 0 0 0 .57-.83z" opacity=".1"/><path d="M8.35 16.59v-.01h-.01l-.03-.02A.65.65 0 0 1 8 16v12.5A1.498 1.498 0 0 0 9.5 30h21a1.503 1.503 0 0 0 .37-.05.637.637 0 0 0 .18-.06.142.142 0 0 0 .06-.02 1.048 1.048 0 0 0 .23-.13c.02-.01.03-.01.04-.03z" fill="#28a8ea"/><path d="M18 24.667V8.333A1.337 1.337 0 0 0 16.667 7H10.03v7.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v10h8.667A1.337 1.337 0 0 0 18 24.667z" opacity=".1"/><path d="M17 25.667V9.333A1.337 1.337 0 0 0 15.667 8H10.03v6.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v11h7.667A1.337 1.337 0 0 0 17 25.667z" opacity=".2"/><path d="M17 23.667V9.333A1.337 1.337 0 0 0 15.667 8H10.03v6.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v9h7.667A1.337 1.337 0 0 0 17 23.667z" opacity=".2"/><path d="M16 23.667V9.333A1.337 1.337 0 0 0 14.667 8H10.03v6.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v9h6.667A1.337 1.337 0 0 0 16 23.667z" opacity=".2"/><path d="M1.333 8h13.334A1.333 1.333 0 0 1 16 9.333v13.334A1.333 1.333 0 0 1 14.667 24H1.333A1.333 1.333 0 0 1 0 22.667V9.333A1.333 1.333 0 0 1 1.333 8z" fill="#0078d4"/><path d="M3.867 13.468a4.181 4.181 0 0 1 1.642-1.814A4.965 4.965 0 0 1 8.119 11a4.617 4.617 0 0 1 2.413.62 4.14 4.14 0 0 1 1.598 1.733 5.597 5.597 0 0 1 .56 2.55 5.901 5.901 0 0 1-.577 2.666 4.239 4.239 0 0 1-1.645 1.794A4.8 4.8 0 0 1 7.963 21a4.729 4.729 0 0 1-2.468-.627 4.204 4.204 0 0 1-1.618-1.736 5.459 5.459 0 0 1-.567-2.519 6.055 6.055 0 0 1 .557-2.65zm1.75 4.258a2.716 2.716 0 0 0 .923 1.194 2.411 2.411 0 0 0 1.443.435 2.533 2.533 0 0 0 1.541-.449 2.603 2.603 0 0 0 .897-1.197 4.626 4.626 0 0 0 .286-1.665 5.063 5.063 0 0 0-.27-1.686 2.669 2.669 0 0 0-.866-1.24 2.387 2.387 0 0 0-1.527-.473 2.493 2.493 0 0 0-1.477.439 2.741 2.741 0 0 0-.944 1.203 4.776 4.776 0 0 0-.007 3.44z" fill="#fff"/></svg></span>'; | ||
optionItem.innerHTML += '<span class="atcb_text">'; | ||
optionItem.innerHTML += optionParts[1] || 'Outlook.com'; | ||
optionItem.innerHTML += '</span>'; | ||
optionItem.addEventListener('click', function() { | ||
atcb_generate_microsoft(data, 'outlook'); | ||
atcb_generate_label(optionItem, true, optionParts[1] || "Outlook.com"); | ||
optionItem.firstChild.innerHTML = | ||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.129793726981 0 33.251996719421 32" width="2500" height="2397"><path d="M28.596 2H11.404A1.404 1.404 0 0 0 10 3.404V5l9.69 3L30 5V3.404A1.404 1.404 0 0 0 28.596 2z" fill="#0364b8"/><path d="M31.65 17.405A11.341 11.341 0 0 0 32 16a.666.666 0 0 0-.333-.576l-.013-.008-.004-.002L20.812 9.24a1.499 1.499 0 0 0-1.479-.083 1.49 1.49 0 0 0-.145.082L8.35 15.415l-.004.002-.012.007A.666.666 0 0 0 8 16a11.344 11.344 0 0 0 .35 1.405l11.492 8.405z" fill="#0a2767"/><path d="M24 5h-7l-2.021 3L17 11l7 6h6v-6z" fill="#28a8ea"/><path d="M10 5h7v6h-7z" fill="#0078d4"/><path d="M24 5h6v6h-6z" fill="#50d9ff"/><path d="M24 17l-7-6h-7v6l7 6 10.832 1.768z" fill="#0364b8"/><path d="M17 11h7v6h-7z" fill="#0078d4"/><path d="M10 17h7v6h-7z" fill="#064a8c"/><path d="M24 17h6v6h-6z" fill="#0078d4"/><path d="M20.19 25.218l-11.793-8.6.495-.87 10.909 6.212a.528.528 0 0 0 .42-.012l10.933-6.23.496.869z" fill="#0a2767" opacity=".5"/><path d="M31.667 16.577l-.014.008-.003.002-10.838 6.174a1.497 1.497 0 0 1-1.46.091l3.774 5.061 8.254 1.797v.004A1.498 1.498 0 0 0 32 28.5V16a.666.666 0 0 1-.333.577z" fill="#1490df"/><path d="M32 28.5v-.738l-9.983-5.688-1.205.687a1.497 1.497 0 0 1-1.46.091l3.774 5.061 8.254 1.797v.004A1.498 1.498 0 0 0 32 28.5z" opacity=".05"/><path d="M31.95 28.883L21.007 22.65l-.195.11a1.497 1.497 0 0 1-1.46.092l3.774 5.061 8.254 1.797v.004a1.501 1.501 0 0 0 .57-.83z" opacity=".1"/><path d="M8.35 16.59v-.01h-.01l-.03-.02A.65.65 0 0 1 8 16v12.5A1.498 1.498 0 0 0 9.5 30h21a1.503 1.503 0 0 0 .37-.05.637.637 0 0 0 .18-.06.142.142 0 0 0 .06-.02 1.048 1.048 0 0 0 .23-.13c.02-.01.03-.01.04-.03z" fill="#28a8ea"/><path d="M18 24.667V8.333A1.337 1.337 0 0 0 16.667 7H10.03v7.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v10h8.667A1.337 1.337 0 0 0 18 24.667z" opacity=".1"/><path d="M17 25.667V9.333A1.337 1.337 0 0 0 15.667 8H10.03v6.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v11h7.667A1.337 1.337 0 0 0 17 25.667z" opacity=".2"/><path d="M17 23.667V9.333A1.337 1.337 0 0 0 15.667 8H10.03v6.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v9h7.667A1.337 1.337 0 0 0 17 23.667z" opacity=".2"/><path d="M16 23.667V9.333A1.337 1.337 0 0 0 14.667 8H10.03v6.456l-1.68.958-.005.002-.012.007A.666.666 0 0 0 8 16v.005V16v9h6.667A1.337 1.337 0 0 0 16 23.667z" opacity=".2"/><path d="M1.333 8h13.334A1.333 1.333 0 0 1 16 9.333v13.334A1.333 1.333 0 0 1 14.667 24H1.333A1.333 1.333 0 0 1 0 22.667V9.333A1.333 1.333 0 0 1 1.333 8z" fill="#0078d4"/><path d="M3.867 13.468a4.181 4.181 0 0 1 1.642-1.814A4.965 4.965 0 0 1 8.119 11a4.617 4.617 0 0 1 2.413.62 4.14 4.14 0 0 1 1.598 1.733 5.597 5.597 0 0 1 .56 2.55 5.901 5.901 0 0 1-.577 2.666 4.239 4.239 0 0 1-1.645 1.794A4.8 4.8 0 0 1 7.963 21a4.729 4.729 0 0 1-2.468-.627 4.204 4.204 0 0 1-1.618-1.736 5.459 5.459 0 0 1-.567-2.519 6.055 6.055 0 0 1 .557-2.65zm1.75 4.258a2.716 2.716 0 0 0 .923 1.194 2.411 2.411 0 0 0 1.443.435 2.533 2.533 0 0 0 1.541-.449 2.603 2.603 0 0 0 .897-1.197 4.626 4.626 0 0 0 .286-1.665 5.063 5.063 0 0 0-.27-1.686 2.669 2.669 0 0 0-.866-1.24 2.387 2.387 0 0 0-1.527-.473 2.493 2.493 0 0 0-1.477.439 2.741 2.741 0 0 0-.944 1.203 4.776 4.776 0 0 0-.007 3.44z" fill="#fff"/></svg>'; | ||
optionItem.addEventListener("click", function () { | ||
atcb_generate_microsoft(data, "outlook"); | ||
atcb_close(); | ||
@@ -411,7 +457,6 @@ }); | ||
case "Yahoo": | ||
optionItem.innerHTML = '<span class="atcb_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3386.34 3010.5" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd"><path d="M0 732.88h645.84l376.07 962.1 380.96-962.1h628.76l-946.8 2277.62H451.98l259.19-603.53L.02 732.88zm2763.84 768.75h-704.26L2684.65 0l701.69.03-622.5 1501.6zm-519.78 143.72c216.09 0 391.25 175.17 391.25 391.22 0 216.06-175.16 391.23-391.25 391.23-216.06 0-391.19-175.17-391.19-391.23 0-216.05 175.16-391.22 391.19-391.22z" fill="#5f01d1" fill-rule="nonzero"/></svg></span>'; | ||
optionItem.innerHTML += '<span class="atcb_text">'; | ||
optionItem.innerHTML += optionParts[1] || 'Yahoo'; | ||
optionItem.innerHTML += '</span>'; | ||
optionItem.addEventListener('click', function() { | ||
atcb_generate_label(optionItem, true, optionParts[1] || "Yahoo"); | ||
optionItem.firstChild.innerHTML = | ||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3386.34 3010.5" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd"><path d="M0 732.88h645.84l376.07 962.1 380.96-962.1h628.76l-946.8 2277.62H451.98l259.19-603.53L.02 732.88zm2763.84 768.75h-704.26L2684.65 0l701.69.03-622.5 1501.6zm-519.78 143.72c216.09 0 391.25 175.17 391.25 391.22 0 216.06-175.16 391.23-391.25 391.23-216.06 0-391.19-175.17-391.19-391.23 0-216.05 175.16-391.22 391.19-391.22z" fill="#5f01d1" fill-rule="nonzero"/></svg>'; | ||
optionItem.addEventListener("click", function () { | ||
atcb_generate_yahoo(data); | ||
@@ -422,4 +467,5 @@ atcb_close(); | ||
} | ||
optionItem.addEventListener('keydown', function(event) { // trigger click on enter as well | ||
if (event.key == 'Enter') { | ||
optionItem.addEventListener("keydown", function (event) { | ||
// trigger click on enter as well | ||
if (event.key == "Enter") { | ||
this.click(); | ||
@@ -433,21 +479,31 @@ } | ||
// create the background overlay, which also acts as trigger to close any dropdowns | ||
function atcb_generate_bg_overlay(data) { | ||
const bgOverlay = document.createElement('div'); | ||
bgOverlay.classList.add('atcb_bgoverlay'); | ||
const bgOverlay = document.createElement("div"); | ||
bgOverlay.classList.add("atcb_bgoverlay"); | ||
if (data.listStyle !== "modal") { | ||
bgOverlay.classList.add("atcb_animate_bg"); | ||
} | ||
bgOverlay.tabIndex = 0; | ||
bgOverlay.addEventListener('click', () => atcb_close(true)); | ||
bgOverlay.addEventListener("click", () => atcb_close(true)); | ||
let fingerMoved = false; | ||
bgOverlay.addEventListener('touchstart', () => fingerMoved = false, {passive: true}); | ||
bgOverlay.addEventListener('touchmove', () => fingerMoved = true, {passive: true}); | ||
bgOverlay.addEventListener('touchend', function() { | ||
if (fingerMoved == false) { | ||
atcb_close(true); | ||
} | ||
}, {passive: true}); | ||
bgOverlay.addEventListener('focus', () => atcb_close(false)); | ||
if (data['trigger'] !== 'click') { | ||
bgOverlay.addEventListener('mousemove', () => atcb_close(true)); | ||
} else { | ||
bgOverlay.classList.add('atcb_click'); | ||
bgOverlay.addEventListener("touchstart", () => (fingerMoved = false), { | ||
passive: true, | ||
}); | ||
bgOverlay.addEventListener("touchmove", () => (fingerMoved = true), { | ||
passive: true, | ||
}); | ||
bgOverlay.addEventListener( | ||
"touchend", | ||
function () { | ||
if (fingerMoved === false) { | ||
atcb_close(true); | ||
} | ||
}, | ||
{ passive: true } | ||
); | ||
bgOverlay.addEventListener("focus", () => atcb_close(false)); | ||
if (data.trigger !== "click") { | ||
bgOverlay.addEventListener("mousemove", () => atcb_close(true)); | ||
} else { | ||
bgOverlay.classList.add("atcb_click"); | ||
} | ||
@@ -457,11 +513,9 @@ return bgOverlay; | ||
// FUNCTIONS TO CONTROL THE INTERACTION | ||
function atcb_toggle(data, button, buttonGenerated, keyboardTrigger = true) { | ||
function atcb_toggle(data, button, keyboardTrigger = true, generatedButton = false) { | ||
// check for state and adjust accordingly | ||
if (button.classList.contains('atcb_active')) { | ||
if (button.classList.contains("atcb_active")) { | ||
atcb_close(); | ||
} else { | ||
atcb_open(data, button, buttonGenerated, keyboardTrigger); | ||
atcb_open(data, button, keyboardTrigger, generatedButton); | ||
} | ||
@@ -471,22 +525,30 @@ } | ||
// show the dropdown list + background overlay | ||
function atcb_open(data, button, buttonGenerated = false, keyboardTrigger = true) { | ||
function atcb_open(data, button, keyboardTrigger = true, generatedButton = false) { | ||
// abort early if an add-to-calendar dropdown already opened | ||
if (document.querySelector('.atcb_list')) return | ||
if (document.querySelector(".atcb_list")) return; | ||
// generate list | ||
const list = atcb_generate_dropdown_list(data, buttonGenerated); | ||
const list = atcb_generate_dropdown_list(data); | ||
// set list styles, set possible button to atcb_active and go for modal mode if no button is set | ||
// set list styles, set button to atcb_active and force modal listStyle if no button is set | ||
if (button) { | ||
button.classList.add('atcb_active'); | ||
const rect = button.getBoundingClientRect(); | ||
list.style.width = rect.width + 'px'; | ||
list.style.top = rect.bottom + window.scrollY -3 + 'px'; | ||
list.style.left = rect.left + 'px'; | ||
button.classList.add("atcb_active"); | ||
if (data.listStyle === "modal") { | ||
button.classList.add("atcb_modal_style"); | ||
list.classList.add("atcb_modal"); | ||
} else { | ||
const rect = button.getBoundingClientRect(); | ||
list.style.width = rect.width + "px"; | ||
list.style.top = rect.bottom + window.scrollY - 3 + "px"; | ||
list.style.left = rect.left + "px"; | ||
list.classList.add("atcb_dropdown"); | ||
if (generatedButton) { | ||
list.classList.add("atcb_generated_button"); // if the button has been generated by the script, we add some more specifics | ||
} | ||
} | ||
} else { | ||
list.classList.add('atcb_modal') | ||
// if no button is defined, fallback to listStyle "modal" | ||
data.listStyle = "modal"; | ||
list.classList.add("atcb_modal"); | ||
} | ||
if (buttonGenerated) { | ||
list.classList.add('atcb_generated_button'); | ||
} | ||
@@ -499,3 +561,4 @@ // add list to DOM | ||
// give keyboard focus to first item in list, if not blocked, because there is definitely no keyboard trigger | ||
// give keyboard focus to first item in list, if not blocked, because there is definitely no keyboard trigger. | ||
// Mintd that with custom atcb_action function, this might get triggered as well, if the custom function does not provide a proper value. | ||
if (keyboardTrigger) { | ||
@@ -507,126 +570,115 @@ list.firstChild.focus(); | ||
function atcb_close(blockFocus = false) { | ||
// 1. Focus triggering button (if not existing, try a custom element) | ||
// 1. Focus triggering button - especially relevant for keyboard navigation | ||
if (!blockFocus) { | ||
let newFocusEl = document.querySelector('.atcb_active'); | ||
let newFocusEl = document.querySelector(".atcb_active"); | ||
if (newFocusEl) { | ||
newFocusEl.focus(); | ||
} else { | ||
newFocusEl = document.querySelector('.atcb_customTrigger'); | ||
if (newFocusEl) { | ||
newFocusEl.focus(); | ||
} | ||
} | ||
} | ||
// 2. Inactivate all buttons | ||
Array.from(document.querySelectorAll('.atcb_active')).forEach(button => { | ||
button.classList.remove('atcb_active'); | ||
}) | ||
Array.from(document.querySelectorAll(".atcb_active")).forEach((button) => { | ||
button.classList.remove("atcb_active"); | ||
}); | ||
// 3. Remove dropdowns & bg overlays (should only be one of each) | ||
Array.from(document.querySelectorAll('.atcb_list')).concat( | ||
Array.from(document.querySelectorAll('.atcb_bgoverlay')) | ||
).forEach(el => el.remove()); | ||
Array.from(document.querySelectorAll(".atcb_list")) | ||
.concat(Array.from(document.querySelectorAll(".atcb_bgoverlay"))) | ||
.forEach((el) => el.remove()); | ||
} | ||
// prepare data when not using the init function | ||
function atcb_action(data, button) { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
function atcb_action(data, triggerElement, keyboardTrigger = true) { | ||
// validate & decorate data | ||
if (!atcb_check_required(data)) { | ||
throw new Error("data missing; see logs") | ||
throw new Error("data missing; see logs"); | ||
} | ||
data = atcb_decorate_data(data); | ||
if (!atcb_validate(data)) { | ||
throw new Error("Invalid data; see logs") | ||
throw new Error("Invalid data; see logs"); | ||
} | ||
atcb_open(data, button); | ||
atcb_open(data, triggerElement, keyboardTrigger); | ||
} | ||
// FUNCTION TO GENERATE THE GOOGLE URL | ||
function atcb_generate_google(data) { | ||
// base url | ||
let url = 'https://calendar.google.com/calendar/render?action=TEMPLATE'; | ||
let url = "https://calendar.google.com/calendar/render?action=TEMPLATE"; | ||
// generate and add date | ||
let formattedDate = atcb_generate_time(data, 'clean', 'google'); | ||
url += '&dates=' + formattedDate['start'] + '%2F' + formattedDate['end']; | ||
const formattedDate = atcb_generate_time(data, "clean", "google"); | ||
url += "&dates=" + formattedDate.start + "%2F" + formattedDate.end; | ||
// add details (if set) | ||
if (data['name'] != null && data['name'] != '') { | ||
url += '&text=' + encodeURIComponent(data['name']); | ||
if (data.name != null && data.name != "") { | ||
url += "&text=" + encodeURIComponent(data.name); | ||
} | ||
if (data['location'] != null && data['location'] != '') { | ||
url += '&location=' + encodeURIComponent(data['location']); | ||
if (data.location != null && data.location != "") { | ||
url += "&location=" + encodeURIComponent(data.location); | ||
// TODO: Find a better solution for the next temporary workaround. | ||
if (isiOS()) { // workaround to cover a bug, where, when using Google Calendar on an iPhone, the location is not recognized. So, for the moment, we simply add it to the description. | ||
if (data['description'] == null || data['description'] == '') { | ||
data['description'] = ''; | ||
if (isiOS()) { | ||
// workaround to cover a bug, where, when using Google Calendar on an iPhone, the location is not recognized. So, for the moment, we simply add it to the description. | ||
if (data.description == null || data.description == "") { | ||
data.description = ""; | ||
} else { | ||
data['description'] += '<br><br>'; | ||
data.description += "%0A%0A"; | ||
} | ||
data['description'] += '📍: ' + data['location']; | ||
data.description += "📍: " + data.location; | ||
} | ||
} | ||
if (data['description'] != null && data['description'] != '') { | ||
url += '&details=' + encodeURIComponent(data['description']); | ||
if (data.description != null && data.description != "") { | ||
url += "&details=" + encodeURIComponent(data.description); | ||
} | ||
window.open(url, '_blank').focus(); | ||
window.open(url, "_blank").focus(); | ||
} | ||
// FUNCTION TO GENERATE THE YAHOO URL | ||
function atcb_generate_yahoo(data) { | ||
// base url | ||
let url = 'https://calendar.yahoo.com/?v=60'; | ||
let url = "https://calendar.yahoo.com/?v=60"; | ||
// generate and add date | ||
let formattedDate = atcb_generate_time(data, 'clean'); | ||
url += '&st=' + formattedDate['start'] + '&et=' + formattedDate['end']; | ||
if (formattedDate['allday']) { | ||
url += '&dur=allday'; | ||
const formattedDate = atcb_generate_time(data, "clean"); | ||
url += "&st=" + formattedDate.start + "&et=" + formattedDate.end; | ||
if (formattedDate.allday) { | ||
url += "&dur=allday"; | ||
} | ||
// add details (if set) | ||
if (data['name'] != null && data['name'] != '') { | ||
url += '&title=' + encodeURIComponent(data['name']); | ||
if (data.name != null && data.name != "") { | ||
url += "&title=" + encodeURIComponent(data.name); | ||
} | ||
if (data['location'] != null && data['location'] != '') { | ||
url += '&in_loc=' + encodeURIComponent(data['location']); | ||
if (data.location != null && data.location != "") { | ||
url += "&in_loc=" + encodeURIComponent(data.location); | ||
} | ||
if (data['description'] != null && data['description'] != '') { | ||
url += '&desc=' + encodeURIComponent(data['description']); | ||
if (data.description != null && data.description != "") { | ||
url += "&desc=" + encodeURIComponent(data.description); | ||
} | ||
window.open(url, '_blank').focus(); | ||
window.open(url, "_blank").focus(); | ||
} | ||
// FUNCTION TO GENERATE THE MICROSOFT 365 OR OUTLOOK WEB URL | ||
function atcb_generate_microsoft(data, type = '365') { | ||
function atcb_generate_microsoft(data, type = "365") { | ||
// base url | ||
let url = 'https://'; | ||
if (type == 'outlook') { | ||
url += 'outlook.live.com'; | ||
let url = "https://"; | ||
if (type == "outlook") { | ||
url += "outlook.live.com"; | ||
} else { | ||
url += 'outlook.office.com'; | ||
url += "outlook.office.com"; | ||
} | ||
url += '/calendar/0/deeplink/compose?path=%2Fcalendar%2Faction%2Fcompose&rru=addevent'; | ||
url += "/calendar/0/deeplink/compose?path=%2Fcalendar%2Faction%2Fcompose&rru=addevent"; | ||
// generate and add date | ||
let formattedDate = atcb_generate_time(data, 'delimiters', 'microsoft'); | ||
url += '&startdt=' + formattedDate['start'] + '&enddt=' + formattedDate['end']; | ||
if (formattedDate['allday']) { | ||
url += '&allday=true'; | ||
const formattedDate = atcb_generate_time(data, "delimiters", "microsoft"); | ||
url += "&startdt=" + formattedDate.start + "&enddt=" + formattedDate.end; | ||
if (formattedDate.allday) { | ||
url += "&allday=true"; | ||
} | ||
// add details (if set) | ||
if (data['name'] != null && data['name'] != '') { | ||
url += '&subject=' + encodeURIComponent(data['name']); | ||
if (data.name != null && data.name != "") { | ||
url += "&subject=" + encodeURIComponent(data.name); | ||
} | ||
if (data['location'] != null && data['location'] != '') { | ||
url += '&location=' + encodeURIComponent(data['location']); | ||
if (data.location != null && data.location != "") { | ||
url += "&location=" + encodeURIComponent(data.location); | ||
} | ||
if (data['description'] != null && data['description'] != '') { | ||
url += '&body=' + encodeURIComponent(data['description'].replace(/\n/g, '<br>')); | ||
if (data.description != null && data.description != "") { | ||
url += "&body=" + encodeURIComponent(data.description.replace(/\n/g, "%0A")); | ||
} | ||
window.open(url, '_blank').focus(); | ||
window.open(url, "_blank").focus(); | ||
} | ||
// FUNCTION TO GENERATE THE MICROSOFT TEAMS URL | ||
@@ -637,67 +689,67 @@ // Mind that this is still in development mode by Microsoft! (https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/build-and-test/deep-links#deep-linking-to-the-scheduling-dialog) | ||
// base url | ||
let url = 'https://teams.microsoft.com/l/meeting/new?'; | ||
let url = "https://teams.microsoft.com/l/meeting/new?"; | ||
// generate and add date | ||
let formattedDate = atcb_generate_time(data, 'delimiters', 'microsoft'); | ||
url += '&startTime=' + formattedDate['start'] + '&endTime=' + formattedDate['end']; | ||
const formattedDate = atcb_generate_time(data, "delimiters", "microsoft"); | ||
url += "&startTime=" + formattedDate.start + "&endTime=" + formattedDate.end; | ||
// add details (if set) | ||
let locationString = ''; | ||
if (data['name'] != null && data['name'] != '') { | ||
url += '&subject=' + encodeURIComponent(data['name']); | ||
let locationString = ""; | ||
if (data.name != null && data.name != "") { | ||
url += "&subject=" + encodeURIComponent(data.name); | ||
} | ||
if (data['location'] != null && data['location'] != '') { | ||
locationString = encodeURIComponent(data['location']); | ||
url += '&location=' + locationString; | ||
locationString += ' // '; // preparing the workaround putting the location into the description, since the native field is not supported yet | ||
if (data.location != null && data.location != "") { | ||
locationString = encodeURIComponent(data.location); | ||
url += "&location=" + locationString; | ||
locationString += " // "; // preparing the workaround putting the location into the description, since the native field is not supported yet | ||
} | ||
if (data['description_iCal'] != null && data['description_iCal'] != '') { // using description_iCal instead of description, since Teams does not support html tags | ||
url += '&content=' + locationString + encodeURIComponent(data['description_iCal']); | ||
if (data.description_iCal != null && data.description_iCal != "") { | ||
// using description_iCal instead of description, since Teams does not support html tags | ||
url += "&content=" + locationString + encodeURIComponent(data.description_iCal); | ||
} | ||
window.open(url, '_blank').focus(); | ||
window.open(url, "_blank").focus(); | ||
} | ||
// FUNCTION TO GENERATE THE iCAL FILE (also for the Apple option) | ||
function atcb_generate_ical(data) { | ||
let now = new Date(); | ||
now = now.toISOString().replace(/\..../g, '').replace(/[^a-z0-9]/gi,''); | ||
let formattedDate = atcb_generate_time(data, 'clean', 'ical'); | ||
let timeslot = ''; | ||
if (formattedDate['allday']) { | ||
timeslot = ';VALUE=DATE'; | ||
now = now.toISOString(); | ||
const formattedDate = atcb_generate_time(data, "clean", "ical"); | ||
let timeslot = ""; | ||
if (formattedDate.allday) { | ||
timeslot = ";VALUE=DATE"; | ||
} | ||
let ics_lines = [ | ||
"BEGIN:VCALENDAR", | ||
"VERSION:2.0", | ||
"CALSCALE:GREGORIAN", | ||
"BEGIN:VEVENT", | ||
"DTSTAMP:" + formattedDate['start'], | ||
"DTSTART" + timeslot + ":" + formattedDate['start'], | ||
"DTEND" + timeslot + ":" + formattedDate['end'], | ||
"SUMMARY:" + data['name'] | ||
]; | ||
if (data['description_iCal'] != null && data['description_iCal'] != '') { | ||
ics_lines.push("DESCRIPTION:" + data['description_iCal'].replace(/\n/g, '\\n')); | ||
} | ||
if (data['location'] != null && data['location'] != '') { | ||
ics_lines.push("LOCATION:" + data['location']); | ||
} | ||
ics_lines.push( | ||
"STATUS:CONFIRMED", | ||
"LAST-MODIFIED:" + now, | ||
"SEQUENCE:0", | ||
"END:VEVENT", | ||
"END:VCALENDAR" | ||
); | ||
let dlurl = 'data:text/calendar;charset=utf-8,'+encodeURIComponent(ics_lines.join('\r\n')); | ||
const ics_lines = ["BEGIN:VCALENDAR", "VERSION:2.0"]; | ||
const corp = "github.com/jekuer/add-to-calendar-button"; | ||
ics_lines.push("PRODID:-// " + corp + " // atcb v" + atcbVersion + " //EN"); | ||
ics_lines.push("CALSCALE:GREGORIAN"); | ||
ics_lines.push("BEGIN:VEVENT"); | ||
ics_lines.push("UID:" + now + "@add-to-calendar-button"); | ||
ics_lines.push( | ||
"DTSTAMP:" + formattedDate.start, | ||
"DTSTART" + timeslot + ":" + formattedDate.start, | ||
"DTEND" + timeslot + ":" + formattedDate.end, | ||
"SUMMARY:" + data.name.replace(/.{65}/g, "$&" + "\r\n ") // making sure it does not exceed 75 characters per line | ||
); | ||
if (data.description_iCal != null && data.description_iCal != "") { | ||
ics_lines.push( | ||
"DESCRIPTION:" + data.description_iCal.replace(/\n/g, "\\n").replace(/.{60}/g, "$&" + "\r\n ") // adjusting for intended line breaks + making sure it does not exceed 75 characters per line | ||
); | ||
} | ||
if (data.location != null && data.location != "") { | ||
ics_lines.push("LOCATION:" + data.location); | ||
} | ||
now = now.replace(/\.\d{3}/g, "").replace(/[^a-z\d]/gi, ""); | ||
ics_lines.push("STATUS:CONFIRMED", "LAST-MODIFIED:" + now, "SEQUENCE:0", "END:VEVENT", "END:VCALENDAR"); | ||
const dlurl = "data:text/calendar;charset=utf-8," + encodeURIComponent(ics_lines.join("\r\n")); | ||
const filename = data.iCalFileName || "event-to-save-in-my-calendar"; | ||
try { | ||
if (!window.ActiveXObject) { | ||
let save = document.createElement('a'); | ||
const save = document.createElement("a"); | ||
save.href = dlurl; | ||
save.target = '_blank'; | ||
save.download = data['iCalFileName'] || 'event-to-save-in-my-calendar'; | ||
let evt = new MouseEvent('click', { | ||
'view': window, | ||
'bubbles': true, | ||
'cancelable': false | ||
save.target = "_blank"; | ||
save.download = filename; | ||
const evt = new MouseEvent("click", { | ||
view: window, | ||
button: 0, | ||
bubbles: true, | ||
cancelable: false, | ||
}); | ||
@@ -707,3 +759,10 @@ save.dispatchEvent(evt); | ||
} | ||
} catch(e) { | ||
// for IE < 11 (even no longer officially supported) | ||
else if (!!window.ActiveXObject && document.execCommand) { | ||
const _window = window.open(dlurl, "_blank"); | ||
_window.document.close(); | ||
_window.document.execCommand("SaveAs", true, filename || dlurl); | ||
_window.close(); | ||
} | ||
} catch (e) { | ||
console.error(e); | ||
@@ -713,57 +772,72 @@ } | ||
// SHARED FUNCTION TO GENERATE A TIME STRING | ||
function atcb_generate_time(data, style = 'delimiters', targetCal = 'general') { | ||
let startDate = data['startDate'].split('-'); | ||
let endDate = data['endDate'].split('-'); | ||
let start = ''; | ||
let end = ''; | ||
function atcb_generate_time(data, style = "delimiters", targetCal = "general") { | ||
const startDate = data.startDate.split("-"); | ||
const endDate = data.endDate.split("-"); | ||
let start = ""; | ||
let end = ""; | ||
let allday = false; | ||
if (data['startTime'] != null && data['endTime'] != null) { | ||
if (data.startTime != null && data.endTime != null) { | ||
// Adjust for timezone, if set (see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones for either the TZ name or the offset) | ||
if (data['timeZoneOffset'] != null && data['timeZoneOffset'] != '') { | ||
if (data.timeZoneOffset != null && data.timeZoneOffset != "") { | ||
// if we have a timezone offset given, consider it | ||
start = new Date( startDate[0] + '-' + startDate[1] + '-' + startDate[2] + 'T' + data['startTime'] + ':00.000' + data['timeZoneOffset'] ); | ||
end = new Date( endDate[0] + '-' + endDate[1] + '-' + endDate[2] + 'T' + data['endTime'] + ':00.000' + data['timeZoneOffset'] ); | ||
start = start.toISOString().replace('.000', ''); | ||
end = end.toISOString().replace('.000', ''); | ||
if (style == 'clean') { | ||
start = start.replace(/\-/g, '').replace(/\:/g, ''); | ||
end = end.replace(/\-/g, '').replace(/\:/g, ''); | ||
} | ||
start = new Date( | ||
startDate[0] + | ||
"-" + | ||
startDate[1] + | ||
"-" + | ||
startDate[2] + | ||
"T" + | ||
data.startTime + | ||
":00.000" + | ||
data.timeZoneOffset | ||
); | ||
end = new Date( | ||
endDate[0] + | ||
"-" + | ||
endDate[1] + | ||
"-" + | ||
endDate[2] + | ||
"T" + | ||
data.endTime + | ||
":00.000" + | ||
data.timeZoneOffset | ||
); | ||
} else { | ||
// if there is no offset, we prepare the time, assuming it is UTC formatted | ||
start = new Date( startDate[0] + '-' + startDate[1] + '-' + startDate[2] + 'T' + data['startTime'] + ':00.000+00:00' ); | ||
end = new Date( endDate[0] + '-' + endDate[1] + '-' + endDate[2] + 'T' + data['endTime'] + ':00.000+00:00' ); | ||
if (data['timeZone'] != null && data['timeZone'] != '') { | ||
start = new Date( | ||
startDate[0] + "-" + startDate[1] + "-" + startDate[2] + "T" + data.startTime + ":00.000+00:00" | ||
); | ||
end = new Date(endDate[0] + "-" + endDate[1] + "-" + endDate[2] + "T" + data.endTime + ":00.000+00:00"); | ||
if (data.timeZone != null && data.timeZone != "") { | ||
// if a timezone is given, we adjust dynamically with the modern toLocaleString function | ||
let utcDate = new Date(start.toLocaleString('en-US', { timeZone: "UTC" })); | ||
if (data['timeZone'] == 'currentBrowser') { | ||
data['timeZone'] = Intl.DateTimeFormat().resolvedOptions().timeZone; | ||
const utcDate = new Date(start.toLocaleString("en-US", { timeZone: "UTC" })); | ||
if (data.timeZone == "currentBrowser") { | ||
data.timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; | ||
} | ||
let tzDate = new Date(start.toLocaleString('en-US', { timeZone: data['timeZone'] })); | ||
let offset = utcDate.getTime() - tzDate.getTime(); | ||
start.setTime( start.getTime() + offset ); | ||
end.setTime( end.getTime() + offset ); | ||
const tzDate = new Date(start.toLocaleString("en-US", { timeZone: data.timeZone })); | ||
const offset = utcDate.getTime() - tzDate.getTime(); | ||
start.setTime(start.getTime() + offset); | ||
end.setTime(end.getTime() + offset); | ||
} | ||
start = start.toISOString().replace('.000', ''); | ||
end = end.toISOString().replace('.000', ''); | ||
if (style == 'clean') { | ||
start = start.replace(/\-/g, '').replace(/\:/g, ''); | ||
end = end.replace(/\-/g, '').replace(/\:/g, ''); | ||
} | ||
} | ||
} else { // would be an allday event then | ||
start = start.toISOString().replace(".000", ""); | ||
end = end.toISOString().replace(".000", ""); | ||
if (style == "clean") { | ||
start = start.replace(/-/g, "").replace(/:/g, ""); | ||
end = end.replace(/-/g, "").replace(/:/g, ""); | ||
} | ||
} else { | ||
// would be an allday event then | ||
allday = true; | ||
start = new Date(Date.UTC(startDate[0], startDate[1] - 1, startDate[2])); | ||
let breakStart = start.toISOString().replace(/T(.+)Z/g, ''); | ||
let breakStart = start.toISOString().replace(/T(.+)Z/g, ""); | ||
end = new Date(Date.UTC(endDate[0], endDate[1] - 1, endDate[2])); | ||
if (targetCal == 'google' || targetCal == 'microsoft' || targetCal == 'ical') { | ||
if (targetCal == "google" || targetCal == "microsoft" || targetCal == "ical") { | ||
end.setDate(end.getDate() + 1); // increment the day by 1 for Google Calendar, iCal and Outlook | ||
} | ||
let breakEnd = end.toISOString().replace(/T(.+)Z/g, ''); | ||
if (style == 'clean') { | ||
breakStart = breakStart.replace(/\-/g, ''); | ||
breakEnd = breakEnd.replace(/\-/g, ''); | ||
let breakEnd = end.toISOString().replace(/T(.+)Z/g, ""); | ||
if (style == "clean") { | ||
breakStart = breakStart.replace(/-/g, ""); | ||
breakEnd = breakEnd.replace(/-/g, ""); | ||
} | ||
@@ -773,3 +847,3 @@ start = breakStart; | ||
} | ||
let returnObject = {'start':start, 'end':end, 'allday':allday}; | ||
const returnObject = { start, end, allday }; | ||
return returnObject; | ||
@@ -780,15 +854,19 @@ } | ||
// Global listener to ESC key to close dropdown | ||
document.addEventListener('keydown', evt => { | ||
if (evt.key === 'Escape') { | ||
document.addEventListener("keydown", (evt) => { | ||
if (evt.key === "Escape") { | ||
atcb_close(); | ||
} | ||
}); | ||
// Global listener to any screen changes, where we need to close all buttons in order to prevent any bad renderings (more "expensive" alternative would be to re-render them) | ||
window.addEventListener('resize', () => { | ||
// Global listener to any screen changes | ||
// Closing all buttons in order to prevent any bad renderings (more "expensive" alternative would be to re-render them) | ||
// Checking for width change to prevent resize on mobile scroll, where a disappearing browser bar could trigger it | ||
let windowWidth = window.innerWidth; | ||
window.addEventListener("resize", () => { | ||
if (window.innerWidth != windowWidth) { | ||
windowWidth = window.innerWidth; | ||
atcb_close(true); | ||
} | ||
}); | ||
} | ||
export { atcb_action, atcb_init }; |
{ | ||
"name": "add-to-calendar-button", | ||
"version": "1.8.10", | ||
"version": "1.9.0", | ||
"engines": { | ||
"node": ">=12.22.0" | ||
}, | ||
"description": "A convenient JavaScript snippet, which lets you create beautiful buttons, where people can add events to their calendars.", | ||
@@ -52,7 +55,19 @@ "main": "npm_dist/cjs/index.js", | ||
"scripts": { | ||
"release": "grunt -c %npm_config_set% && npm install && npm run build", | ||
"release": "node set-release.js", | ||
"stylelint": "npx stylelint assets/css/*.css", | ||
"stylelint:fix": "npm run stylelint -- --fix", | ||
"eslint": "npx eslint .", | ||
"eslint:fix": "npm run eslint -- --fix", | ||
"prettier": "npx prettier . --check", | ||
"prettier:fix": "npm run prettier -- --write", | ||
"format": "npm run eslint:fix && npm run prettier:fix && npm run stylelint:fix", | ||
"test": "node test/server-side-rendering.js", | ||
"build": "grunt" | ||
"build": "npx grunt" | ||
}, | ||
"devDependencies": { | ||
"@typescript-eslint/eslint-plugin": "^5.29.0", | ||
"@typescript-eslint/parser": "^5.29.0", | ||
"eslint": "^8.18.0", | ||
"eslint-config-prettier": "^8.5.0", | ||
"eslint-plugin-commonjs": "^1.0.2", | ||
"grunt": ">=1.5.3", | ||
@@ -63,4 +78,8 @@ "grunt-contrib-clean": "^2.0.0", | ||
"grunt-contrib-uglify": "^5.0.1", | ||
"grunt-version": "^3.0.0" | ||
"grunt-version": "^3.0.0", | ||
"prettier": "2.7.1", | ||
"stylelint": "^14.9.1", | ||
"stylelint-config-prettier": "^9.0.3", | ||
"stylelint-config-standard": "^26.0.0" | ||
} | ||
} |
277
README.md
@@ -1,8 +0,14 @@ | ||
# 📅 Your next Add-to-Calendar Button! | ||
# Your next Add-to-Calendar Button | ||
![Add-to-Calendar Button](https://github.com/jekuer/add-to-calendar-button/blob/main/repo_image.png?raw=true) | ||
_A convenient JavaScript snippet, which lets you create beautiful buttons, where people can add events to their calendars._ | ||
A convenient JavaScript snippet (VanillaJS, React, Angular, ...), which lets you create beautiful buttons, where people can add events to their calendars. | ||
![#1 Product of the Day on ProductHunt](https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=319458&theme=dark&period=daily) | ||
![#1 Product of the Day on ProductHunt](https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=319458&theme=dark&period=daily) | ||
![Code Quality](https://img.shields.io/codacy/grade/5c7c0bac087c4dfdb0669e8284f0459a/main?style=for-the-badge) | ||
![Build Status](https://img.shields.io/github/workflow/status/jekuer/add-to-calendar-button/Node.js%20Package?style=for-the-badge) | ||
![npm Downloads](https://img.shields.io/npm/dt/add-to-calendar-button?label=npm%20Downloads&style=for-the-badge) | ||
![jsDelivr npm Hits](https://img.shields.io/jsdelivr/npm/hm/add-to-calendar-button?label=jsDelivr%20npm%20hits&style=for-the-badge) | ||
<br /><br /> | ||
@@ -18,19 +24,2 @@ | ||
## 🔎 Background // Why this repo exists | ||
While building a personal wedding page, I was confronted with the task to include a button, where invited people could save the event to their calendars. | ||
I did not want to build this from scratch (first) and therefore started the usual web research. | ||
Unfortunately, all I found where some extremely outdated code snippets, which did not really fit all the modern systems and calendar tools. | ||
Beside that, there was only the solution by AddEvent.com - all over the place. I was looking at CodePen and all I found where thousands of pens, which basically only included the AddEvent tool. | ||
The problems with AddEvent.com: | ||
* it holds tons of features, which I did not need. I do not want to track my button. I just want it to work. | ||
* it limits the free tier to 50 event adds per month (consider the wedding case - this is way too less). | ||
* it brings some data privacy issues, since you basically send your users to their service. GDPR alert! | ||
* the UX/UI is not ideal (imho). | ||
**Bottom line:** Paying for features, which I did not need - at additional privacy concerns - that made me create this solution (for you). | ||
<br /> | ||
## ▶️ Demo | ||
@@ -44,13 +33,13 @@ | ||
* Simple and convenient integration of multiple buttons - configure them directly within the HTML code. | ||
* Optimized UX (for desktop and mobile) - adjustable. | ||
* Beautiful UI (the best combined from experts around the world). | ||
* Up-to-date integration of all popular calendars: | ||
* Google Calendar. | ||
* Yahoo Calender. | ||
* Microsoft 365, Outlook, and Teams. | ||
* Automatically generated iCal/ics files (for all other calendars, like Apple). | ||
* Timed and all-day events. | ||
* Translatable labels and dynamic dates. | ||
* Well documented code, to easily understand the processes and build on top of it. | ||
- Simple and convenient integration of multiple buttons, configurable directly within the HTML code. | ||
- Optimized and adjustable UX (for desktop and mobile). | ||
- Beautiful UI (the best combined from experts around the world). | ||
- Up-to-date integration of all popular calendars: | ||
- Google Calendar. | ||
- Yahoo Calender. | ||
- Microsoft 365, Outlook, and Teams. | ||
- Automatically generated iCal/ics files (for all other calendars, like Apple). | ||
- Timed and all-day events. | ||
- Translatable labels and dynamic dates. | ||
- Well documented code, to easily understand the processes and build on top of it. | ||
@@ -65,27 +54,35 @@ ![Demo Screenshot](https://github.com/jekuer/add-to-calendar-button/blob/main/demo.gif?raw=true) | ||
1. Simply **download** the code from GitHub **or clone** the git repository. | ||
2. Copy the css (atcb.min.css) and js (atcb.min.js) files from the assets (not the "npm_dist"!) folders into your project (the **.min.** files are required, but it is recommended to also copy the raw and map files). | ||
3. Include those files in your project. As usual, the css goes into the <head> (`<link rel="stylesheet" href="./assets/css/atcb.min.css">`), the js into the <body> footer (`<script src="./assets/js/atcb.min.js" defer></script>`). You can also combine them with other files, if you want to. | ||
4. Create your button as can be seen in the "Configuration" section below. | ||
5. That is it. The script takes care of all the rest. | ||
1. Check whether you are looking at the original and therefore up-to-date code at ![github.com/jekuer/add-to-calendar-button](https://github.com/jekuer/add-to-calendar-button). | ||
2. **Download** the code directly from GitHub **or clone** the git repository. | ||
3. Copy the css (atcb.min.css) and js (atcb.min.js) files from the assets (not the "npm_dist"!) folders into your project (the **.min.** files are required, but it is recommended to also copy the raw and map files). | ||
4. Include those files in your project. As usual, the css goes into the <head> (`<link rel="stylesheet" href="./assets/css/atcb.min.css">`), the js into the <body> footer (`<script src="./assets/js/atcb.min.js" defer></script>`). You can also combine them with other files, if you want to. | ||
5. Create your button as can be seen in the "Configuration" section below. | ||
6. That is it. The script takes care of all the rest. | ||
### Option 2: simple (CDN) | ||
1. Instead of downloading the files, you can use the jsDeliver CDN. | ||
3. Put `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/add-to-calendar-button@1.8/assets/css/atcb.min.css">` into the <head> and `<script src="https://cdn.jsdelivr.net/npm/add-to-calendar-button@1.8" defer></script>` into the <body> footer of your website. | ||
4. Create your button as can be seen in the "Configuration" section below. | ||
5. Done. And this setup also automatically keeps track of any bug fixes and minor updates! | ||
1. Instead of downloading the files, you can use the jsDeliver CDN. | ||
2. Put `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/add-to-calendar-button@1.9.0/assets/css/atcb.min.css">` into the <head> and `<script src="https://cdn.jsdelivr.net/npm/add-to-calendar-button@1.9.0" defer></script>` into the <body> footer of your website. Leave out the patch number at the version ("1.9" instead of "1.9.2") to automatically pull the latest patched version. See ![jsDeliver.com](https://www.jsdelivr.com/features#npm) for more options. | ||
3. Create your button as can be seen in the "Configuration" section below. | ||
4. Done. And this setup also automatically keeps track of any bug fixes and minor updates! | ||
### Option 3: npm | ||
1. Requires Node, npm, and a project, which builds on it (e.g. React or Angular). | ||
2. Run **`npm install add-to-calendar-button`**. | ||
3. Import the module into your project/component. For example with Angular/React: `import { atcb_action, atcb_init } from 'add-to-calendar-button';`. | ||
4. Either use `atcb_action` with your own buttons/forms/etc, or run `atcb_init` after the DOM has been loaded. To determine the right moment and execute, ... | ||
1. with Angular, you would use `ngAfterViewInit()` with `atcb_init();` (mind that, depending on your app, other hooks might be better); | ||
2. with React, you might want to include an event listener like `document.addEventListener('DOMContentLoaded', atcb_init, false);` or using hooks in a functional component like `useEffect(() => atcb_init());` | ||
5. Include the css. For example with Angular or React, add the following to the global style.css: `@import 'add-to-calendar-button/assets/css/atcb.min'`; | ||
6. Create your button as can be seen in the "Configuration" section below. | ||
7. You're all done. | ||
1. Requires Node, npm, and a project, which builds on it (e.g. React or Angular). | ||
2. Run **`npm install add-to-calendar-button`**. | ||
3. Import the module into your project/component. For example with Angular/React: `import { atcb_action, atcb_init } from 'add-to-calendar-button';`. | ||
4. Either use `atcb_action` with your own buttons/forms/etc, or run `atcb_init` after the DOM has been loaded. To determine the right moment and execute, ... | ||
- with Angular, you would use `ngAfterViewInit()` with `atcb_init();` (mind that, depending on your app, other hooks might be better); | ||
- with React, you might want to include an event listener like `document.addEventListener('DOMContentLoaded', atcb_init, false);` or using hooks in a functional component like `useEffect(() => atcb_init());` | ||
5. Include the css. For example with Angular or React, add the following to the global style.css: `@import 'add-to-calendar-button/assets/css/atcb.min'`; | ||
6. Create your button as can be seen in the "Configuration" section below. | ||
7. You're all done. | ||
<br /> | ||
@@ -97,7 +94,7 @@ | ||
(The `style="display:none;"` theoretically is not necessary, but should be used for better compatibility.) | ||
```html | ||
<div class="atcb" style="display:none;"> | ||
(...) | ||
</div> | ||
<div class="atcb" style="display:none;">(...)</div> | ||
``` | ||
Within this placeholder, you can easily configure the button, by placing a respective JSON structure. | ||
@@ -144,3 +141,2 @@ Mind that with Angular, you might need to escape the { with `{{ '{' }}` and } with `{{ '}' }}`; with React it would be `{ '{' }` and `{ '}' }`. | ||
"timeZone":"Europe/Berlin", | ||
"timeZoneOffset":"+01:00", | ||
"trigger":"click", | ||
@@ -153,2 +149,3 @@ "iCalFileName":"Reminder-Event" | ||
### Full structure (with schema.org markup) | ||
You can save on the `style="display:none;"`, but mind that you should not use dynamic dates (e.g. "today" or "+1") here! | ||
@@ -162,24 +159,15 @@ You can use startTime and endTime in the event block, but it is recommended to rather add it to startDate and endDate with "T" as delimiter here. | ||
"event": { | ||
"@context":"https://schema.org", | ||
"@type":"Event", | ||
"name":"Add the title of your event", | ||
"description":"A nice description does not hurt", | ||
"startDate":"2022-02-21T10:13", | ||
"endDate":"2022-03-24T17:57", | ||
"location":"Somewhere over the rainbow" | ||
"@context": "https://schema.org", | ||
"@type": "Event", | ||
"name": "Add the title of your event", | ||
"description": "A nice description does not hurt", | ||
"startDate": "2022-02-21T10:13", | ||
"endDate": "2022-03-24T17:57", | ||
"location": "Somewhere over the rainbow" | ||
}, | ||
"label":"Add to Calendar", | ||
"options":[ | ||
"Apple", | ||
"Google", | ||
"iCal", | ||
"Microsoft365", | ||
"MicrosoftTeams", | ||
"Outlook.com", | ||
"Yahoo" | ||
], | ||
"timeZone":"Europe/Berlin", | ||
"timeZoneOffset":"+01:00", | ||
"trigger":"click", | ||
"iCalFileName":"Reminder-Event" | ||
"label": "Add to Calendar", | ||
"options": ["Apple", "Google", "iCal", "Microsoft365", "MicrosoftTeams", "Outlook.com", "Yahoo"], | ||
"timeZone": "Europe/Berlin", | ||
"trigger": "click", | ||
"iCalFileName": "Reminder-Event" | ||
} | ||
@@ -190,19 +178,19 @@ </script> | ||
### React examples | ||
### React examples | ||
#### atcb_action | ||
If you can't or don't want to use `atcb_init`, you can use the `atcb_action` import with your own buttons or other elements/components. If you omit the second argument, the dropdown list will display as a modal in the middle of the viewport - in this case, add the "atcb_customTrigger" class to the submitting element for better keyboard support. | ||
If you can't or don't want to use `atcb_init`, you can use the `atcb_action` import with your own buttons or other elements/components. | ||
This may work better with React and other frontend frameworks, but it misses the Schema.org and button specific functionalities. | ||
This may work better with React and other frontend frameworks, but it misses the Schema.org and button specific functionalities. | ||
```js | ||
import React from 'react' | ||
import { atcb_action } from 'add-to-calendar-button' | ||
import React from 'react'; | ||
import { atcb_action } from 'add-to-calendar-button'; | ||
const MyComponent = () => { | ||
const [name, setName] = React.useState('Some event') | ||
const [name, setName] = React.useState('Some event'); | ||
return ( | ||
<form onSubmit={e => { | ||
e.preventDefault() | ||
e.preventDefault(); | ||
atcb_action({ | ||
@@ -213,8 +201,9 @@ name, | ||
options: ['Apple', 'Google', 'iCal', 'Microsoft365', 'Outlook.com', 'MicrosoftTeams', 'Yahoo'], | ||
timeZone: "Europe/Berlin", | ||
trigger: "click", | ||
iCalFileName: "Reminder-Event", | ||
}) | ||
}, triggerEl); | ||
}}> | ||
<input value={name} onChange={setName} /> | ||
<input class="atcb_customTrigger" type="submit" value="save"> | ||
<input type="submit" value="save"> | ||
</form> | ||
@@ -225,25 +214,26 @@ ); | ||
#### atcb_init | ||
#### atcb_init | ||
Alternatively, you could use `atcb_init` with a `useEffect` hook: | ||
```js | ||
import React from 'react' | ||
import { atcb_init } from 'add-to-calendar-button' | ||
import React from "react"; | ||
import { atcb_init } from "add-to-calendar-button"; | ||
const atcb_action = () => { | ||
React.useEffect(atcb_init, []) | ||
React.useEffect(atcb_init, []); | ||
return ( | ||
<div className='atcb' style={{display: 'none'}}> | ||
<div className="atcb" style={{ display: "none" }}> | ||
{JSON.stringify({ | ||
name: "Some event", | ||
startDate: "2022-01-14", | ||
endDate: "2022-01-18", | ||
options: ['Apple', 'Google', 'iCal', 'Microsoft365', 'Outlook.com', 'MicrosoftTeams', 'Yahoo'], | ||
trigger: "click", | ||
iCalFileName: "Reminder-Event", | ||
})} | ||
name: "Some event", | ||
startDate: "2022-01-14", | ||
endDate: "2022-01-18", | ||
options: ["Apple", "Google", "iCal", "Microsoft365", "Outlook.com", "MicrosoftTeams", "Yahoo"], | ||
timeZone: "Europe/Berlin", | ||
trigger: "click", | ||
iCalFileName: "Reminder-Event", | ||
})} | ||
</div> | ||
); | ||
} | ||
}; | ||
``` | ||
@@ -253,21 +243,20 @@ | ||
* The "label" is optional, but enables you to customize the button text. Default: "Add to Calendar". | ||
* Dates need to be formatted as YYYY-MM-DD ([ISO-8601](https://en.wikipedia.org/wiki/ISO_8601)). | ||
* You can also use the word "today" as date. It will then dynamically use the current day at click (not supported with schema.org style). | ||
* Add "+5" at the end of the date to dynamically add 5 days (or any other number). "2022-01-30+12" would generate the 11th of February 2022. This can be interesting, when combined with "today". | ||
* Times need to be formatted as HH:MM. | ||
* Times are optional. If not set, the button generates all-day events. | ||
* 1 option is required. You can add as many as you want. The supported formats are listed above. | ||
* If you want to rename (or translate) a label, use the following schema at the options: optionName + Pipe + yourLabel. "Google|Google Kalender" would generate a Google Calendar option, but label it as "Google Kalender". | ||
* If no timeZone and no timeZoneOffset is provided, the date refers to UTC time. | ||
* You can add a timeZoneOffset or timeZone (TZ name). You can find a list of them at [Wikipedia](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). | ||
* If the timeZoneOffset is set, it will always override the timeZone. It is recommended to only use 1 of them at a time. | ||
* The timeZone might not work in very old browsers, but also considers dynamic changes like summer/winter time. | ||
* timeZoneOffset works with older browsers, but is quite static. | ||
* Use "currentBrowser" as value for timeZone to dynamically use the time of the user's browser. Use this with caution, since it would mean that the date and time will differ per user, which should not be the usual case! (Requires all times to be set.) | ||
* You can set the trigger to "click". This makes the button open on click at desktop. Otherwise, the default would be to open on hover. On touch devices, this makes no difference. | ||
* If you want to define a specific name for any generated ics file (iCal), you can specify it via the "iCalFileName" option. The default would be "event-to-save-in-my-calendar". | ||
* You can use the option "inline":true in order to make the button appear with inline-block instead of block style. | ||
* Formatting a URL in the description like `[url]https://....[/url]` makes it clickable. | ||
* If you require line breaks within the description, use `\n` or `<br>`. | ||
- The `label` is optional, but enables you to customize the button text. Default: "Add to Calendar". | ||
- Dates need to be formatted as YYYY-MM-DD ([ISO-8601](https://en.wikipedia.org/wiki/ISO_8601)). | ||
- You can also use the word `today` as date. It will then dynamically use the current day at click (not supported with schema.org style). | ||
- Add `+5` at the end of the date to dynamically add 5 days (or any other number). `2022-01-30+12` would generate the 11th of February 2022. This can be interesting, when combined with `today`. | ||
- Times need to be formatted as HH:MM. | ||
- Times are optional. If not set, the button generates all-day events. | ||
- 1 option is required. You can add as many as you want. The supported formats are listed above. | ||
- If you want to rename (or translate) a label, use the following schema at the options: optionName + Pipe + yourLabel. "Google|Google Kalender" would generate a Google Calendar option, but label it as "Google Kalender". | ||
- You can add a `timeZone` (TZ name) or explicit `timeZoneOffset`. You can find a list of them at [Wikipedia](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). | ||
- If the `timeZoneOffset` is set, it will always override the `timeZone`. If none is set, the date refers to UTC time. | ||
- The `timeZone` option is recommended since it considers things like summer/winter time, but might not work in very old browsers. `timeZoneOffset` works with older browsers, but is quite static. | ||
- Use "currentBrowser" as value for `timeZone` to dynamically use the time of the user's browser. Use this with caution, since it would mean that the date and time will differ per user, which should not be the usual case! (Requires all times to be set.) | ||
- You can set the `trigger` to `click`. This makes the button open on click at desktop. Otherwise, the default would be to open on hover. On touch devices, this makes no difference. | ||
- If you want to define a specific name for any generated ics file (iCal), you can specify it via the `iCalFileName` option. The default would be "event-to-save-in-my-calendar". | ||
- You can use the option `"inline":true` in order to make the button appear with inline-block instead of block style. | ||
- The default style for the options list, using the regular button, would be a dropdown. You can set the option `listStyle` to "modal" in order to force the modal version. | ||
- Formatting a URL in the description like `[url]https://....[/url]` makes it clickable. | ||
- If you require line breaks within the description, use `\n` or `<br>`. | ||
@@ -280,5 +269,5 @@ <br /> | ||
* [Bug reports](.github/CONTRIBUTING.md#bugs) | ||
* [Feature requests](.github/CONTRIBUTING.md#features) | ||
* [Pull requests](.github/CONTRIBUTING.md#pull-requests) | ||
- [Bug reports](.github/CONTRIBUTING.md#bugs) | ||
- [Feature requests](.github/CONTRIBUTING.md#features) | ||
- [Pull requests](.github/CONTRIBUTING.md#pull-requests) | ||
@@ -291,3 +280,3 @@ **IMPORTANT NOTE:** Run `npm install` and `npm run build` to create the minified js and css file, its sourcemap files as well as the npm_dist/ folder and content! | ||
The code is available under the [MIT license (with “Commons Clause” License Condition v1.0)](LICENSE.txt). | ||
Copyright (c) [Jens Kuerschner](https://jenskuerschner.de). Licensed under [MIT license (with “Commons Clause” License Condition v1.0)](LICENSE.txt). | ||
@@ -298,11 +287,11 @@ <br /> | ||
* v1.8 : new button style | ||
* v1.7 : new code structure and options + tons of optimizations | ||
* v1.6 : supporting Microsoft Teams | ||
* v1.5 : update to date format and better accesibility | ||
* v1.4 : schema.org support (also changed some keys in the JSON!) | ||
* v1.3 : new license (MIT with “Commons Clause”) | ||
* v1.2 : inline and line break support | ||
* v1.1 : npm functionality | ||
* v1.0 : initial release | ||
- v1.8 : new button style | ||
- v1.7 : new code structure and options + tons of optimizations | ||
- v1.6 : supporting Microsoft Teams | ||
- v1.5 : update to date format and better accesibility | ||
- v1.4 : schema.org support (also changed some keys in the JSON!) | ||
- v1.3 : new license (MIT with “Commons Clause”) | ||
- v1.2 : inline and line break support | ||
- v1.1 : npm functionality | ||
- v1.0 : initial release | ||
@@ -312,5 +301,25 @@ <br /> | ||
## 💜 Kudos go to | ||
* [uxwing.com](https://uxwing.com) | ||
* [Brian R (dudewheresmycode)](https://github.com/dudewheresmycode) | ||
* [Chad Ostrowski (chadoh)](https://github.com/chadoh) | ||
* ... and all other contributors! | ||
- [uxwing.com](https://uxwing.com) | ||
- [Chad Ostrowski (chadoh)](https://github.com/chadoh) | ||
- ... and all other contributors! | ||
<br /> | ||
## 🔎 Background // Why this repo exists | ||
While building a personal wedding page, I was confronted with the task to include a button, where invited people could save the event to their calendars. | ||
I did not want to build this from scratch (first) and therefore started the usual web research. | ||
Unfortunately, all I found where some extremely outdated code snippets, which did not really fit all the modern systems and calendar tools. | ||
Beside that, there was only the solution by AddEvent.com - all over the place. I was looking at CodePen and all I found where thousands of pens, which basically only included the AddEvent tool. | ||
The problems with AddEvent.com: | ||
- it holds tons of features, which I did not need. I do not want to track my button. I just want it to work. | ||
- it limits the free tier to 50 event adds per month (consider the wedding case - this is way too less). | ||
- it brings some data privacy issues, since you basically send your users to their service. GDPR alert! | ||
- the UX/UI is not ideal (imho). | ||
**Bottom line:** Paying for features, which I did not need - at additional privacy concerns - that made me create this solution (for you). | ||
<br /> |
// simple import of the atcb JS, to ensure it can load in NodeJS | ||
require('../assets/js/atcb.js') | ||
require("../assets/js/atcb.js"); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
209639
22
3036
311
15
2