Skip to content

Commit 95ad38a

Browse files
authored
Merge pull request #14 from jekuer/origin/feature-schema-org-support
add schema.org support
2 parents 5f1ba64 + 1918c8d commit 95ad38a

File tree

8 files changed

+383
-134
lines changed

8 files changed

+383
-134
lines changed

README.md

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -87,29 +87,29 @@ Mind that with Angular, you might need to escape the { with `{{ '{' }}` and } wi
8787
```html
8888
<div class="atcb" style="display:none;">
8989
{
90-
"title":"Add the title of your event",
91-
"dateStart":"02-21-2022",
92-
"dateEnd":"03-24-2022",
90+
"name":"Add the title of your event",
91+
"startDate":"02-21-2022",
92+
"endDate":"03-24-2022",
9393
"options":[
9494
"Google"
9595
]
9696
}
9797
</div>
9898
```
9999

100-
### Full structure
100+
### Full structure (without schema.org markup)
101101

102102
```html
103103
<div class="atcb" style="display:none;">
104104
{
105-
"label":"Add to Calendar",
106-
"title":"Add the title of your event",
105+
"name":"Add the title of your event",
107106
"description":"A nice description does not hurt",
108-
"dateStart":"02-21-2022",
109-
"dateEnd":"03-24-2022",
110-
"timeStart":"10:13",
111-
"timeEnd":"17:57",
107+
"startDate":"02-21-2022",
108+
"endDate":"03-24-2022",
109+
"startTime":"10:13",
110+
"endTime":"17:57",
112111
"location":"Somewhere over the rainbow",
112+
"label":"Add to Calendar",
113113
"options":[
114114
"Apple",
115115
"Google",
@@ -125,6 +125,41 @@ Mind that with Angular, you might need to escape the { with `{{ '{' }}` and } wi
125125
}
126126
</div>
127127
```
128+
129+
### Full structure (with schema.org markup)
130+
You can save on the `style="display:none;"`, but mind that you should not use dynamic dates (e.g. "today" or "+1") here!
131+
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.
132+
133+
```html
134+
<div class="atcb">
135+
<script type="application/ld+json">
136+
{
137+
"event": {
138+
"@context":"https://schema.org",
139+
"@type":"Event",
140+
"name":"Add the title of your event",
141+
"description":"A nice description does not hurt",
142+
"startDate":"02-21-2022T10:13",
143+
"endDate":"03-24-2022T17:57",
144+
"location":"Somewhere over the rainbow"
145+
},
146+
"label":"Add to Calendar",
147+
"options":[
148+
"Apple",
149+
"Google",
150+
"iCal",
151+
"Microsoft365",
152+
"Outlook.com",
153+
"Yahoo"
154+
],
155+
"timeZone":"Europe/Berlin",
156+
"timeZoneOffset":"+01:00",
157+
"trigger":"click",
158+
"iCalFileName":"Reminder-Event"
159+
}
160+
</script>
161+
</div>
162+
```
128163

129164

130165
### Important information and hidden features
@@ -164,10 +199,11 @@ The code is available under the [GPU 3.0 license](LICENSE.txt).
164199

165200
## Changelog (without bug fixes)
166201

167-
* v1.3.0 : new license (MIT with “Commons Clause”)
168-
* v1.2.0 : inline and line break support
169-
* v1.1.0 : npm functionality
170-
* v1.0.0 : initial release
202+
* v1.4 : schema.org support (also changed some keys in the JSON!)
203+
* v1.3 : new license (MIT with “Commons Clause”)
204+
* v1.2 : inline and line break support
205+
* v1.1 : npm functionality
206+
* v1.0 : initial release
171207

172208

173209
## Kudos go to

assets/css/atcb.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Add-to-Calendar Button
44
* ++++++++++++++++++++++
55
*
6-
* Version: 1.3.1
6+
* Version: 1.4.0
77
* Creator: Jens Kuerschner (https://jenskuerschner.de)
88
* Project: https://github.com/jekuer/add-to-calendar-button
99
* License: MIT with “Commons Clause” License Condition v1.0

assets/js/atcb.js

Lines changed: 94 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Add-to-Calendar Button
44
* ++++++++++++++++++++++
55
*/
6-
const atcbVersion = '1.3.1';
6+
const atcbVersion = '1.4.0';
77
/* Creator: Jens Kuerschner (https://jenskuerschner.de)
88
* Project: https://github.com/jekuer/add-to-calendar-button
99
* License: MIT with “Commons Clause” License Condition v1.0
@@ -29,13 +29,29 @@ function atcb_init() {
2929
if (atcButtons[i].classList.contains('atcb_initialized')) {
3030
continue;
3131
}
32+
let atcbConfig;
33+
// check if schema.org markup is present
34+
let schema = atcButtons[i].querySelector('script');
3235
// get their JSON content first
33-
const atcbConfig = JSON.parse(atcButtons[i].innerHTML);
36+
if (schema && schema.innerHTML) {
37+
// get schema.org event markup and flatten the event block
38+
atcbConfig = JSON.parse(schema.innerHTML);
39+
atcbConfig = atcb_clean_schema_json(atcbConfig);
40+
// set flag to not delete HTML content later
41+
atcbConfig['deleteJSON'] = false;
42+
} else {
43+
// get JSON from HTML block
44+
atcbConfig = JSON.parse(atcButtons[i].innerHTML);
45+
// set flag to delete HTML content later
46+
atcbConfig['deleteJSON'] = true;
47+
}
48+
// rewrite config for backwards compatibility - you can remove this, if you did not use this script before v1.4.0.
49+
atcbConfig = atcb_rewrite_config(atcbConfig);
3450
// check, if all required data is available
3551
if (atcb_check_required(atcbConfig)) {
3652
// calculate the real date values in case that there are some special rules included (e.g. adding days dynamically)
37-
atcbConfig['dateStart'] = atcb_date_calculation(atcbConfig['dateStart']);
38-
atcbConfig['dateEnd'] = atcb_date_calculation(atcbConfig['dateEnd']);
53+
atcbConfig['startDate'] = atcb_date_calculation(atcbConfig['startDate']);
54+
atcbConfig['endDate'] = atcb_date_calculation(atcbConfig['endDate']);
3955
// validate the JSON ...
4056
if (atcb_validate(atcbConfig)) {
4157
// ... and generate the button on success
@@ -48,6 +64,51 @@ function atcb_init() {
4864

4965

5066

67+
// CLEAN/NORMALIZE JSON FROM SCHEMA.ORG MARKUP
68+
function atcb_clean_schema_json(atcbConfig) {
69+
Object.keys(atcbConfig['event']).forEach(key => {
70+
// move entries one level up, but skip schema types
71+
if (key.charAt(0) !== '@') {
72+
atcbConfig[key] = atcbConfig['event'][key];
73+
}
74+
});
75+
// clean schema date+time format
76+
const endpoints = ['start', 'end'];
77+
endpoints.forEach(function(point) {
78+
if (atcbConfig[point + 'Date'] != null) {
79+
let tmpSplitStartDate = atcbConfig[point + 'Date'].split('T');
80+
if (tmpSplitStartDate[1] != null) {
81+
atcbConfig[point + 'Date'] = tmpSplitStartDate[0];
82+
atcbConfig[point + 'Time'] = tmpSplitStartDate[1];
83+
}
84+
}
85+
});
86+
// drop the event block and return
87+
delete atcbConfig.event;
88+
return atcbConfig;
89+
}
90+
91+
92+
93+
// BACKWARDS COMPATIBILITY REWRITE - you can remove this, if you did not use this script before v1.4.0.
94+
function atcb_rewrite_config(atcbConfig) {
95+
const keyChanges = {
96+
'title': 'name',
97+
'dateStart': 'startDate',
98+
'dateEnd': 'endDate',
99+
'timeStart': 'startTime',
100+
'timeEnd': 'endTime',
101+
};
102+
Object.keys(keyChanges).forEach(key => {
103+
if (atcbConfig[keyChanges[key]] == null && atcbConfig[key] != null) {
104+
atcbConfig[keyChanges[key]] = atcbConfig[key];
105+
}
106+
});
107+
return atcbConfig;
108+
}
109+
110+
111+
51112
// CHECK FOR REQUIRED FIELDS
52113
function atcb_check_required(data) {
53114
// check for at least 1 option
@@ -56,7 +117,7 @@ function atcb_check_required(data) {
56117
return false;
57118
}
58119
// check for min required data (without "options")
59-
const requiredField = ['title', 'dateStart', 'dateEnd']
120+
const requiredField = ['name', 'startDate', 'endDate']
60121
return requiredField.every(function(field) {
61122
if (data[field] == null || data[field] == "") {
62123
console.log("add-to-calendar button generation failed: required setting missing [" + field + "]");
@@ -101,7 +162,7 @@ function atcb_validate(data) {
101162
return false;
102163
}
103164
// validate date
104-
const dates = ['dateStart', 'dateEnd'];
165+
const dates = ['startDate', 'endDate'];
105166
let newDate = dates;
106167
if (!dates.every(function(date) {
107168
const dateParts = data[date].split('-');
@@ -115,7 +176,7 @@ function atcb_validate(data) {
115176
return false;
116177
}
117178
// validate time
118-
const times = ['timeStart', 'timeEnd'];
179+
const times = ['startTime', 'endTime'];
119180
if (!times.every(function(time) {
120181
if (data[time] != null) {
121182
const timeParts = data[time].split(':');
@@ -133,23 +194,23 @@ function atcb_validate(data) {
133194
return false;
134195
}
135196
// update the date with the time for further validation steps
136-
if (time == 'timeStart') {
137-
newDate['dateStart'] = new Date(newDate['dateStart'].getTime() + (timeParts[0] * 3600000) + (timeParts[1] * 60000))
197+
if (time == 'startTime') {
198+
newDate['startDate'] = new Date(newDate['startDate'].getTime() + (timeParts[0] * 3600000) + (timeParts[1] * 60000))
138199
}
139-
if (time == 'timeEnd') {
140-
newDate['dateEnd'] = new Date(newDate['dateEnd'].getTime() + (timeParts[0] * 3600000) + (timeParts[1] * 60000))
200+
if (time == 'endTime') {
201+
newDate['endDate'] = new Date(newDate['endDate'].getTime() + (timeParts[0] * 3600000) + (timeParts[1] * 60000))
141202
}
142203
}
143204
return true;
144205
})) {
145206
return false;
146207
}
147-
if ((data['timeStart'] != null && data['timeEnd'] == null) || (data['timeStart'] == null && data['timeEnd'] != null)) {
208+
if ((data['startTime'] != null && data['endTime'] == null) || (data['startTime'] == null && data['endTime'] != null)) {
148209
console.log("add-to-calendar button generation failed: if you set a starting time, you also need to define an end time");
149210
return false;
150211
}
151212
// validate whether end is not before start
152-
if (newDate['dateEnd'] < newDate['dateStart']) {
213+
if (newDate['endDate'] < newDate['startDate']) {
153214
console.log("add-to-calendar button generation failed: end date before start date");
154215
return false;
155216
}
@@ -161,8 +222,10 @@ function atcb_validate(data) {
161222

162223
// GENERATE THE ACTUAL BUTTON
163224
function atcb_generate(button, buttonId, data) {
164-
// clean the placeholder
165-
button.innerHTML = '';
225+
// clean the placeholder, if flagged that way
226+
if (data['deleteJSON']) {
227+
button.innerHTML = '';
228+
}
166229
// generate the wrapper div
167230
let buttonTriggerWrapper = document.createElement('div');
168231
buttonTriggerWrapper.classList.add('atcb_button_wrapper');
@@ -335,8 +398,8 @@ function atcb_generate_google(data) {
335398
if (data['location'] != null && data['location'] != '') {
336399
url += '&location=' + encodeURIComponent(data['location']);
337400
}
338-
if (data['title'] != null && data['title'] != '') {
339-
url += '&text=' + encodeURIComponent(data['title']);
401+
if (data['name'] != null && data['name'] != '') {
402+
url += '&text=' + encodeURIComponent(data['name']);
340403
}
341404
window.open(url, '_blank').focus();
342405
}
@@ -360,8 +423,8 @@ function atcb_generate_yahoo(data) {
360423
if (data['location'] != null && data['location'] != '') {
361424
url += '&in_loc=' + encodeURIComponent(data['location']);
362425
}
363-
if (data['title'] != null && data['title'] != '') {
364-
url += '&title=' + encodeURIComponent(data['title']);
426+
if (data['name'] != null && data['name'] != '') {
427+
url += '&title=' + encodeURIComponent(data['name']);
365428
}
366429
window.open(url, '_blank').focus();
367430
}
@@ -391,8 +454,8 @@ function atcb_generate_microsoft(data, type = '365') {
391454
if (data['location'] != null && data['location'] != '') {
392455
url += '&location=' + encodeURIComponent(data['location']);
393456
}
394-
if (data['title'] != null && data['title'] != '') {
395-
url += '&subject=' + encodeURIComponent(data['title']);
457+
if (data['name'] != null && data['name'] != '') {
458+
url += '&subject=' + encodeURIComponent(data['name']);
396459
}
397460
window.open(url, '_blank').focus();
398461
}
@@ -417,7 +480,7 @@ function atcb_generate_ical(data) {
417480
"DTSTART" + timeslot + ":" + formattedDate['start'],
418481
"DTEND" + timeslot + ":" + formattedDate['end'],
419482
"DESCRIPTION:" + data['description'].replace(/\n/g, '\\n'),
420-
"SUMMARY:" + data['title'],
483+
"SUMMARY:" + data['name'],
421484
"LOCATION:" + data['location'],
422485
"STATUS:CONFIRMED",
423486
"LAST-MODIFIED:" + now,
@@ -449,17 +512,17 @@ function atcb_generate_ical(data) {
449512

450513
// SHARED FUNCTION TO GENERATE A TIME STRING
451514
function atcb_generate_time(data, style = 'delimiters', targetCal = 'general') {
452-
let dateStart = data['dateStart'].split('-');
453-
let dateEnd = data['dateEnd'].split('-');
515+
let startDate = data['startDate'].split('-');
516+
let endDate = data['endDate'].split('-');
454517
let start = '';
455518
let end = '';
456519
let allday = false;
457-
if (data['timeStart'] != null && data['timeEnd'] != null) {
520+
if (data['startTime'] != null && data['endTime'] != null) {
458521
// 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)
459522
if (data['timeZoneOffset'] != null && data['timeZoneOffset'] != '') {
460523
// if we have a timezone offset given, consider it
461-
start = new Date( dateStart[2] + '-' + dateStart[0] + '-' + dateStart[1] + 'T' + data['timeStart'] + ':00.000' + data['timeZoneOffset'] );
462-
end = new Date( dateEnd[2] + '-' + dateEnd[0] + '-' + dateEnd[1] + 'T' + data['timeEnd'] + ':00.000' + data['timeZoneOffset'] );
524+
start = new Date( startDate[2] + '-' + startDate[0] + '-' + startDate[1] + 'T' + data['startTime'] + ':00.000' + data['timeZoneOffset'] );
525+
end = new Date( endDate[2] + '-' + endDate[0] + '-' + endDate[1] + 'T' + data['endTime'] + ':00.000' + data['timeZoneOffset'] );
463526
start = start.toISOString().replace('.000', '');
464527
end = end.toISOString().replace('.000', '');
465528
if (style == 'clean') {
@@ -468,8 +531,8 @@ function atcb_generate_time(data, style = 'delimiters', targetCal = 'general') {
468531
}
469532
} else {
470533
// if there is no offset, we prepare the time, assuming it is UTC formatted
471-
start = new Date( dateStart[2] + '-' + dateStart[0] + '-' + dateStart[1] + 'T' + data['timeStart'] + ':00.000+00:00' );
472-
end = new Date( dateEnd[2] + '-' + dateEnd[0] + '-' + dateEnd[1] + 'T' + data['timeEnd'] + ':00.000+00:00' );
534+
start = new Date( startDate[2] + '-' + startDate[0] + '-' + startDate[1] + 'T' + data['startTime'] + ':00.000+00:00' );
535+
end = new Date( endDate[2] + '-' + endDate[0] + '-' + endDate[1] + 'T' + data['endTime'] + ':00.000+00:00' );
473536
if (data['timeZone'] != null && data['timeZone'] != '') {
474537
// if a timezone is given, we adjust dynamically with the modern toLocaleString function
475538
let utcDate = new Date(start.toLocaleString('en-US', { timeZone: "UTC" }));
@@ -487,10 +550,10 @@ function atcb_generate_time(data, style = 'delimiters', targetCal = 'general') {
487550
}
488551
} else { // would be an allday event then
489552
allday = true;
490-
start = new Date( dateStart[2], dateStart[0] - 1, dateStart[1]);
553+
start = new Date( startDate[2], startDate[0] - 1, startDate[1]);
491554
start.setDate(start.getDate() + 1); // increment the day by 1
492555
let breakStart = start.toISOString().split('T');
493-
end = new Date( dateEnd[2], dateEnd[0] - 1, dateEnd[1]);
556+
end = new Date( endDate[2], endDate[0] - 1, endDate[1]);
494557
if (targetCal == 'google' || targetCal == 'microsoft' || targetCal == 'ical') {
495558
end.setDate(end.getDate() + 2); // increment the day by 2 for Google Calendar, iCal and Outlook
496559
} else {

assets/js/atcb.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)