assets/js/icsCalendar.js

815 lines
36 KiB
JavaScript
Executable File

function CalendarDate (existingLi) {
if (existingLi) {
this.li = existingLi;
this.fromHTML();
} else this.li = document.createElement('li');
}
CalendarDate.prototype = {
lang : {
de : { monthNames:['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'] },
en : { monthNames:['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] },
get : function (key) {
if (!this.l) {
this.l = navigator.language;
if (!this[this.l]) this.l = 'en';
}
return (this[this.l][key])?this[this.l][key]:key;
}
},
// public readonly
li : null,
start : null,
end : null,
sourceId : -1,
completeDay : false,
title : '',
location : '',
description : '',
url : '',
status : '',
repeatMode : 0,
repeatNr : 0,
repeatEnd : null,
parseRrule : /* local */ function (rrule) {
if (rrule == '') return;
rrule = rrule.split(';');
for (var k = 0; k < rrule.length; k++) {
rrule[k] = rrule[k].toUpperCase().split('=');
if (rrule[k].length == 1) continue;
switch (rrule[k][0]) {
case 'FREQ':
if (rrule[k][1] == 'MONTHLY' && this.repeatMode == 0) this.repeatMode = 1;
if (rrule[k][1] == 'MONTHLY' && this.repeatMode == -3) this.repeatMode = 3;
if (rrule[k][1] == 'DAILY') this.repeatMode = 2;
break;
case 'UNTIL':
if (rrule[k][1].length != 8) break;
var y = parseInt(rrule[k][1].substring(0, rrule[k][1].length-4));
var m = parseInt(rrule[k][1].substring(rrule[k][1].length-4, rrule[k][1].length-2));
var d = parseInt(rrule[k][1].substring(rrule[k][1].length-2, rrule[k][1].length));
this.repeatEnd = new Date(y, m-1, d, 0, 0, 0, 0);
break;
case 'INTERVAL':
if (this.repeatNr == 0) this.repeatNr = parseInt(rrule[k][1]);
else this.repeatNr = -parseInt(rrule[k][1]);
break;
case 'BYDAY':
if (this.repeatMode == 0) this.repeatMode = -3; // will be set to +3 when FREQ is monthly.
else if (this.repeatMode == 1) this.repeatMode = 3;
if (parseInt(rrule[k][1]) < 0) {
if (this.repeatNr == 0) this.repeatNr = -Number.MAX_VALUE; // just remember to make value negative for INTERVAL parsing
else this.repeatNr = -this.repeatNr;
}
break;
}
}
if (this.repeatMode < 0) this.repeatMode = 0; // invalid combination of rules
},
getMonthRepetitionI : /* private */ function (currentDateTime) {
if (this.completeDay) var spl = new Array(this.start.getUTCFullYear(), this.start.getUTCMonth()+1, this.start.getUTCDate());
else var spl = new Array(this.start.getFullYear(), this.start.getMonth()+1, this.start.getDate());
var repI = spl[0]*32*12 + (spl[1]-1)*32 + spl[2];
var currI = currentDateTime.getFullYear()*32*12 + (currentDateTime.getMonth()-1)*32 + currentDateTime.getDate();
if (currI > repI)
repI += Math.ceil((currI - repI + 0.0) / (this.repeatNr*32)) * this.repeatNr*32;
return repI;
},
lastDayOfMonth : /* private */ function (Y, m) {
if (m == 4 || m == 6 || m == 9 || m == 11) return 30;
else if (m == 2) {
if ((Y % 4) == 0 && ((Y % 100) != 0 || (Y % 400) == 0)) return 29;
else return 28;
} else return 31;
},
getNearestRepetition : /* local */ function (currentDateTime) {
switch (this.repeatMode) {
case 1: // every n month repeating event
if (this.repeatNr <= 0) break;
var repI = this.getMonthRepetitionI(currentDateTime);
for (var i = 0; i < 100; i += this.repeatNr) { // if after adding 8 years, no valid date is found, it may never exist!
var Y = Math.floor((repI >> 5) / 12);
var m = (repI >> 5) % 12 + 1;
var d = repI & 31;
var checkdate = null;
try {
checkdate = new Date(Y, m-1, d, this.start.getHours(), this.start.getMinutes(), 0, 0);
} catch (e) { checkdate = null; }
if (checkdate && checkdate.getFullYear() == Y && checkdate.getMonth() == m-1 && checkdate.getDate() == d) {
if (this.repeatEnd && checkdate > this.repeatEnd) break;
if (checkdate >= currentDateTime) return checkdate;
}
repI += this.repeatNr*32;
}
break;
case 2: // every n days repeating event
if (this.repeatNr <= 0) break;
var result = new Date(this.start);
var th = result.getHours();
var tm = result.getMinutes();
result.setHours(12); // set to midday, to prevent day missmatch due to summer/winter time gap
result.setMinutes(0);
if (currentDateTime > this.start) {
var dayDiff = Math.floor((currentDateTime - this.start) / (1000 * 3600 * 24));
result.setDate(result.getDate() + (Math.ceil((dayDiff+0.1) / this.repeatNr)*this.repeatNr));
}
if (this.repeatEnd && result > this.repeatEnd) break;
result.setHours(th);
result.setMinutes(tm);
return result;
case 3:
if (this.repeatNr == 0) break;
var weekday = this.start.getDay();
if (this.repeatNr > 0) {
var ld = 1;
var weekNr = Math.floor((this.start.getDate()-1) / 7);
} else {
var ld = this.lastDayOfMonth(this.start.getFullYear(), this.start.getMonth()+1);
var weekNr = Math.floor((ld - this.start.getDate()) / 7);
}
var repI = this.getMonthRepetitionI(currentDateTime);
for (var i = 0; i < 100; i++) {
var Y = Math.floor((repI >> 5) / 12);
var m = (repI >> 5) % 12 + 1;
if (this.repeatNr < 0) ld = this.lastDayOfMonth(Y, m);
var result = new Date(Y, m-1, ld, this.start.getHours(), this.start.getMinutes(), 0, 0);
if (this.repeatNr > 0) result.setDate(result.getDate() + (((weekday + 7 - result.getDay()) % 7) + weekNr * 7));
else result.setDate(result.getDate() - (weekNr * 7 + ((result.getDay() - weekday + 7) % 7)));
if (this.repeatEnd && result > this.repeatEnd) break;
if ((result >= currentDateTime) && (result.getMonth()+1 == m))
return result;
repI += 32 * Math.abs(this.repeatNr);
}
break;
}
result = new Date(this.start);
return result;
},
fromHTML : /* private */ function () {
if (this.start) throw 'Date already created!';
this.completeDay = true;
var span = this.li.getElementsByTagName('span');
for (var j = 0; j < span.length; j++) {
var className = ' '+span[j].className+' ';
if (className.indexOf(' date ') != -1) { // parse date
d = span[j].textContent.split('.');
if (d.length != 3) d.unshift('-1');
d[0] = Number.parseInt(d[0]);
d[1] = Number.parseInt(d[1]);
d[2] = Number.parseInt(d[2]);
if (d[0] > 31) d.reverse();
this.start = new Date(d[2], d[1]-1, d[0], 0, 0, 0, 0);
} else if (className.indexOf(' title ') != -1) this.title = span[j].textContent;
else if (className.indexOf(' location ') != -1) this.location = span[j].textContent;
else if (className.indexOf(' description ') != -1) this.description = span[j].textContent;
}
if (this.li.getElementsByTagName('a').length > 0) this.url = this.li.getElementsByTagName('a')[0].href;
},
updateHTML : /* public */ function () {
var m = this.start.getMinutes().toString();
if (m.length == 1) m = '0'+m;
var a_begin = '';
var a_end = '';
var hostAndProtocol = window.location.href.substring(0, window.location.href.indexOf(window.location.hostname))+window.location.hostname;
if (this.url) {
var target = '_blank';
if (this.url.indexOf(hostAndProtocol) == 0) target = '_self';
a_begin = '<A href="'+this.url+'" target="'+target+'">'+"\n";
a_end = '<\/A>'+"\n";
}
this.li.innerHTML =
a_begin
+'<DIV class="split">'+"\n"
+' <DIV class="left">'+"\n"
+' <SPAN class="date" title="'+this.start.toLocaleString()+((this.end)?'&nbsp;&nbsp;-&nbsp;&nbsp;'+this.end.toLocaleString():'')+'">'+this.start.getDate()+'. '+this.lang.get('monthNames')[this.start.getMonth()]+' '+this.start.getFullYear()+'<\/SPAN>'+"\n"
+((this.completeDay)?' <SPAN class="time">'+this.start.getHours()+':'+m+'<\/SPAN>'+"\n":'')
+' <\/DIV>'+"\n"
+' <DIV class="right">'+"\n"
+' <SPAN class="title">'+this.makeupText(this.title)+'<\/SPAN>'+"\n"
+' <SPAN class="location">'+this.makeupText(this.location)+'<\/SPAN>'+"\n"
+' <SPAN class="description">'+this.makeupText(this.description)+'<\/SPAN>'+"\n"
+' <\/DIV>'+"\n"
+'<\/DIV>'+"\n"
+a_end;
},
makeupText : /* private */ function (text) {
if (!text) text = '';
text = text.replace(/&/g, '&amp;');
text = text.replace(/</g, '&lt;');
text = text.replace(/"/g, '&quot;');
text = text.replace(/>/g, '&gt;');
text = text.replace(/\n/g, ' <BR> ');
text = text.split(' ');
for (var i = 0; i < text.length; i++) {
if (text[i].substring(0, 8) == 'https:\/\/' || text[i].substring(0, 7) == 'http:\/\/') {
if (i > 0 && text[i-1].substring(text[i-1].length-1, text[i-1].length) == ':') {
text[i-1] = '<A href="'+text[i]+'" class="protokoll" target="_blank">'+text[i-1].substring(0, text[i-1].length-1)+'<\/A>';
text[i] = '';
} else text[i] = '<A href="'+text[i]+'" target="_blank">'+text[i]+'<\/A>';
}
}
return text.join('&#32;');
}
};
function ICSCalendar (calendarBox) {
var _this = this;
this.calendarBox = calendarBox;
this.dates = new Array();
this.sources = new Array();
// find or create ul
if (!this.calendarBox) throw 'No calendar element given!';
if (calendarBox.calendarObject) throw 'Calendar for given element already created!';
calendarBox.calendarObject = this;
if (this.calendarBox.getElementsByTagName('ul').length > 0) this.ul = this.calendarBox.getElementsByTagName('ul')[0];
if (!this.ul) {
this.ul = document.createElement('ul');
this.calendarBox.appendChild(this.ul);
} else {
for (var i = 0; i < this.ul.getElementsByTagName('li').length; i++) {
var ndate = new CalendarDate(this.ul.getElementsByTagName('li')[i]);
ndate.updateHTML();
this.dates.push(ndate);
}
this.ul.innerHTML = '';
}
this.ul.className = 'dates';
this.filterBox = document.createElement('div');
this.filterBox.style.display = 'none';
this.filterLink = document.createElement('a');
this.filterLink.className = 'button';
this.filterLink.href = 'javascript: /* show filter */';
this.filterLink.textContent = this.lang.get('filterlink');
this.filterLink.cal = this;
this.filterLink.addEventListener('click', function () {
this.cal.filterBox.style.display = 'block';
this.cal.filterLink.style.display = 'none';
}, false);
this.calendarBox.insertBefore(this.filterBox, this.ul);
this.calendarBox.insertBefore(this.filterLink, this.ul);
// interpret calendar settings
var srcs = this.calendarBox.getElementsByTagName('source');
function getAttributeOrEmpty(srcEl, attr) {
var r = srcEl.getAttribute(attr);
if (!r) r = '';
return r;
}
for (var i = 0; i < srcs.length; i++) {
var nsrc = {
state : 0, // 0=never fetched 1=currently loading 2=successfully fetched, 3=error
src : getAttributeOrEmpty(srcs[i], 'src'),
generator : getAttributeOrEmpty(srcs[i], 'generator'),
user : getAttributeOrEmpty(srcs[i], 'user'),
password : getAttributeOrEmpty(srcs[i], 'password'),
selected : {
promoters : getAttributeOrEmpty(srcs[i], 'filterPromoters').split(','),
categories : getAttributeOrEmpty(srcs[i], 'filterCategories').split(',')
},
filterTypes : { // 0=no filter 1=whitelist 2=blacklist
promoters : 1,
categories : 1
}
};
if (nsrc.src.indexOf('./') == 0) {
var spl = window.location.href.split('?')[0].split('#')[0].split('/');
if (spl[spl.length-1].lastIndexOf('.htm') >= spl[spl.length-1].length-5 || spl[spl.length-1] == '')
spl.pop();
nsrc.src = spl.join('/')+'/'+nsrc.src.substring(2);
}
for (var f in nsrc.selected) {
if (nsrc.selected[f][0].indexOf('~') == 0) {
nsrc.selected[f][0] = nsrc.selected[f][0].substring(1);
nsrc.filterTypes[f] = 2;
}
while (nsrc.selected[f].length > 0 && nsrc.selected[f][0] == '') nsrc.selected[f].shift();
for (var j = 0; j < nsrc.selected[f].length; j++) nsrc.selected[f][j] = parseInt(nsrc.selected[f][j]);
if (nsrc.selected[f].length == 0) nsrc.filterTypes[f] = 0;
}
this.sources.push(nsrc);
if (nsrc.generator == 'phpBB-calendar') { // autodetect nsrc.src.lastIndexOf('\/app.php\/calendar\/') == nsrc.src.length - 18
if (!this.earlierDatesEl) {
this.earlierDatesEl = document.createElement('a');
this.earlierDatesEl.href = 'javascript:';
this.earlierDatesEl.addEventListener('click', function () { _this.loadEarlierDates(); });
this.earlierDatesEl.className = 'earlierDatesLink button';
this.earlierDatesEl.textContent = this.lang.get('earlierDates');
this.calendarBox.appendChild(this.earlierDatesEl);
}
nsrc.shortSrc = nsrc.src;
this.getFilterInfo(this.sources.length-1);
this.updateUrl(this.sources.length-1, false);
} else { // create sync link for fixed url ics
this.getSimpleInfo(this.sources.length-1);
}
}
this.update();
}
ICSCalendar.prototype = {
lang : {
de : {
filterError:'Fehler beim ermitteln der Filter-Möglichkeiten.', promoters:'Veranstalterinnen', categories:'Kategorien', update:'aktualisieren', all:'alle', whitelist:'nur die Folgenden...', blacklist:'alle außer...',
synchint:'Synchronisieren Sie die folgende URL als <a href="https://de.wikipedia.org/wiki/ICalendar">iCalendar</a>, um die Termine in andere Anwendungen zu übernehmen.', filterlink:'Filtern und Synchronisieren',
timeRange:'Zeitfenster', timeRangeSince:'Alle ab # Tagen vor heute', timeRangeDates:'Von # bis #', earlierDates:String.fromCharCode(8595)+' frühere Termine '+String.fromCharCode(8595)
},
en : {
filterError:'Error getting filter information.', promoters:'Promoters', categories:'Categories', update:'update', all:'all', whitelist:'just the following...', blacklist:'all except of...',
synchint:'Synchronize the following URL as a <a href="https://en.wikipedia.org/wiki/ICalendar">iCalendar</a>, to get the dates into other applications.', filterlink:'filter and synchronize',
timeRange:'time range', timeRangeSince:'All since # days before now', timeRangeDates:'From # til #', earlierDates:String.fromCharCode(8595)+' earlier dates '+String.fromCharCode(8595)
},
get : function (key) {
if (!this.l) {
this.l = navigator.language;
if (!this[this.l]) this.l = 'en';
}
return (this[this.l][key])?this[this.l][key]:key;
}
},
// private
calendarBox : null,
ul : null,
filterBox : null,
filterLink : null,
dates : null,
sources : null,
earlierDatesEl : null,
loadEarlierDates : /* private */ function () {
var _this = this;
for (var sid = 0; sid < this.sources.length; sid++) {
if (!this.sources[sid].earliestDate) continue;
var to = this.sources[sid].earliestDate;
var from = new Date(to);
from.setDate(to.getDate() - parseInt(this.sources[sid].filterInfo.maxDays));
this.sources[sid].earliestDate = from;
var src = this.sources[sid].src.split('?');
if (src.length == 1) src.push();
src[1] = src[1].split('&');
for (var i = src[1].length-1; i >= 0; i--)
if (src[1][i].indexOf('from=') == 0 || src[1][i].indexOf('to=') == 0)
src[1].splice(i, 1);
src[1].push('from='+from.getFullYear()+'-'+(from.getMonth()+1)+'-'+from.getDate());
src[1].push('to='+to.getFullYear()+'-'+(to.getMonth()+1)+'-'+to.getDate());
src[1] = src[1].join('&');
this.sources[sid].src = src.join('?');
this.loadSource(sid, function (_sid) {
_this.addDatesFromSrc(_sid);
_this.sortAndPrintDates();
});
}
},
filterHtmlVisualization : /* private */ function (sid, key) {
function radioChange () {
if (this.elementsDeactivate)
for (var i = 0; i < this.elementsDeactivate.length; i++)
for (var j = 0; j < this.elementsDeactivate[i].getElementsByTagName('input').length; j++)
this.elementsDeactivate[i].getElementsByTagName('input')[j].disabled = this.checked;
if (this.elementsActivate)
for (var i = 0; i < this.elementsActivate.length; i++)
for (var j = 0; j < this.elementsActivate[i].getElementsByTagName('input').length; j++)
this.elementsActivate[i].getElementsByTagName('input')[j].disabled = !this.checked;
}
var filter = this.sources[sid].filterInfo[key];
var col = document.createElement('form');
col.setAttribute('filterKey', key);
var b = document.createElement('b');
b.textContent = this.lang.get(key)+':';
col.appendChild(b);
var radioRow = document.createElement('div');
radioRow.className = 'radioRow';
col.appendChild(radioRow);
if (key == 'timeRange') {
var l = document.createElement('label');
var radio = document.createElement('input');
radio.checked = true;
radio.setAttribute('name', 'timeRangeType');
radio.setAttribute('type', 'radio');
l.appendChild(radio);
var spl = this.lang.get('timeRangeSince').split('#');
spl.push('');
l.appendChild(document.createTextNode(spl[0]));
l.style.marginRight = '0px';
radioRow.appendChild(l);
var inp = document.createElement('input');
inp.setAttribute('type', 'number');
inp.setAttribute('name', 'days');
var maxDays = parseInt(this.sources[sid].filterInfo.maxDays);
inp.setAttribute('max', maxDays);
inp.value = Math.ceil(maxDays/2);
inp.defaultValue = inp.value;
this.sources[sid].earliestDate = new Date();
this.sources[sid].earliestDate.setDate(this.sources[sid].earliestDate.getDate() - parseInt(inp.value));
inp.style.width = '30px';
radioRow.appendChild(inp);
l = document.createElement('label');
l.textContent = spl[1];
radioRow.appendChild(l);
radioRow = document.createElement('div');
radioRow.className = 'radioRow';
l = document.createElement('label');
radio = document.createElement('input');
radio.checked = false;
radio.setAttribute('name', 'timeRangeType');
radio.setAttribute('type', 'radio');
l.appendChild(radio);
spl = this.lang.get('timeRangeDates').split('#');
spl.push('');
spl.push('');
l.appendChild(document.createTextNode(spl[0]));
l.style.marginRight = '0px';
radioRow.appendChild(l);
inp = document.createElement('input');
inp.setAttribute('type', 'date');
inp.setAttribute('name', 'dateFrom');
radioRow.appendChild(inp);
l = document.createElement('label');
l.textContent = spl[1];
l.style.marginRight = '0px';
radioRow.appendChild(l);
inp = document.createElement('input');
inp.setAttribute('type', 'date');
inp.setAttribute('name', 'dateTo');
radioRow.appendChild(inp);
l = document.createElement('label');
l.textContent = spl[2];
radioRow.appendChild(l);
col.appendChild(radioRow);
} else {
var list = document.createElement('div');
var l = document.createElement('label');
var radio = document.createElement('input');
radio.setAttribute('name', 'filtertype');
radio.checked = this.sources[sid].filterTypes[key] == 0;
radio.setAttribute('type', 'radio');
radio.addEventListener('change', radioChange);
radio.elementsDeactivate = [list];
l.appendChild(radio);
l.appendChild(document.createTextNode(this.lang.get('all')));
radioRow.appendChild(l);
l = document.createElement('label');
radio = document.createElement('input');
radio.checked = this.sources[sid].filterTypes[key] == 1;
radio.setAttribute('name', 'filtertype');
radio.setAttribute('type', 'radio');
radio.addEventListener('change', radioChange);
radio.elementsActivate = [list];
l.appendChild(radio);
l.appendChild(document.createTextNode(this.lang.get('whitelist')));
radioRow.appendChild(l);
l = document.createElement('label');
radio = document.createElement('input');
radio.checked = this.sources[sid].filterTypes[key] == 2;
radio.setAttribute('name', 'filtertype');
radio.setAttribute('type', 'radio');
radio.addEventListener('change', radioChange);
radio.elementsActivate = [list];
l.appendChild(radio);
l.appendChild(document.createTextNode(this.lang.get('blacklist')));
radioRow.appendChild(l);
col.appendChild(list);
list.className = 'filterList';
for (var i = 0; i < filter.length; i++) {
var label = document.createElement('label');
var cb = document.createElement('input');
cb.checked = this.sources[sid].selected[key].indexOf(filter[i].id) >= 0;
cb.disabled = true;
cb.type = 'checkbox';
cb.setAttribute('filterId', filter[i].id);
label.appendChild(cb);
label.appendChild(document.createTextNode(filter[i].title));
list.appendChild(label);
}
}
return col;
},
rmHTTP : /* private */ function (url) {
if (url.toLowerCase().indexOf('http:\/\/') == 0) return url.substr(7);
if (url.toLowerCase().indexOf('https:\/\/') == 0) return url.substr(8);
return url;
},
updateUrl : /* private */ function (sid, updateCalendar) {
var url = this.sources[sid].shortSrc+'?action=ics';
if (this.sources[sid].user != '') url += '&user='+encodeURIComponent(this.sources[sid].user)+'&password='+encodeURIComponent(this.sources[sid].password);
if (this.sources[sid].settingsEl) {
var cols = this.sources[sid].settingsEl.getElementsByTagName('form');
for (var i = 0; i < cols.length; i++) {
var filterKey = cols[i].getAttribute('filterKey');
if (filterKey == '') continue;
if (filterKey == 'timeRange') {
if (cols[i].getElementsByTagName('input')[0].checked) {
this.sources[sid].earliestDate = new Date();
this.sources[sid].earliestDate.setDate(this.sources[sid].earliestDate.getDate() - parseInt(cols[i].days.value));
if (cols[i].days.defaultValue != cols[i].days.value)
url += '&from=D'+(-parseInt(cols[i].days.value));
} else {
this.sources[sid].earliestDate = null;
if (cols[i].dateFrom.value != '') {
url += '&from='+cols[i].dateFrom.value;
this.sources[sid].earliestDate = cols[i].dateFrom.valueAsDate;
}
if (!this.sources[sid].earliestDate) {
this.sources[sid].earliestDate = new Date();
this.sources[sid].earliestDate.setDate(this.sources[sid].earliestDate.getDate() - parseInt(cols[i].days.defaultValue));
}
var dTo = null;
if (cols[i].dateTo.value != '') {
dTo = cols[i].dateTo.valueAsDate;
url += '&to='+cols[i].dateTo.value;
}
if (!dTo) dTo = new Date();
var diff = (dTo - this.sources[sid].earliestDate) / (1000 * 60 * 60 * 24);
if (diff > parseInt(cols[i].days.max)) {
this.sources[sid].earliestDate = dTo;
this.sources[sid].earliestDate.setDate(this.sources[sid].earliestDate.getDate() - parseInt(cols[i].days.max));
}
}
} else {
if (cols[i].getElementsByTagName('input')[0].checked) this.sources[sid].filterTypes[filterKey] = 0;
if (cols[i].getElementsByTagName('input')[1].checked) this.sources[sid].filterTypes[filterKey] = 1;
if (cols[i].getElementsByTagName('input')[2].checked) this.sources[sid].filterTypes[filterKey] = 2;
var filterList = null;
for (var j = 0; j < cols[i].getElementsByTagName('div').length; j++)
if ((' '+cols[i].getElementsByTagName('div')[j].className+' ').indexOf(' filterList ') != -1) {
filterList = cols[i].getElementsByTagName('div')[j].getElementsByTagName('input');
break;
}
this.sources[sid].selected[filterKey] = new Array();
if (filterList)
for (var j = 0; j < filterList.length; j++)
if (filterList[j].checked)
this.sources[sid].selected[filterKey].push(filterList[j].getAttribute('filterId'));
}
}
}
for (var f in this.sources[sid].selected) {
if (this.sources[sid].filterTypes[f] == 0 || this.sources[sid].selected[f].length == 0) continue;
url += '&'+f+'=';
if (this.sources[sid].filterTypes[f] == 2) url += '~,';
url += this.sources[sid].selected[f].join(',');
}
this.sources[sid].syncUrl.textContent = url;
this.sources[sid].syncUrl.href = 'webcal://'+this.rmHTTP(url);
this.sources[sid].src = url;
if (updateCalendar) this.update();
},
getSimpleInfo : /* private */ function (sid) { // creates a settings-block for simple ics-file sources
this.sources[sid].settingsEl = document.createElement('div');
this.filterBox.appendChild(this.sources[sid].settingsEl);
var syncUrl = document.createElement('a');
syncUrl.href = this.sources[sid].src;
this.sources[sid].settingsEl.appendChild(syncUrl);
var absUrl = syncUrl.href; // this way, an absoute path is guaranteed
syncUrl.textContent = absUrl;
absUrl = absUrl.split('\/\/');
if (absUrl.length > 1) absUrl[0] = 'webcal:';
syncUrl.href = absUrl.join('\/\/');
},
getFilterInfo : /* private */ function (sid) { // creates a settings-block for phpBB-calendar based sources
var _this = this;
this.sources[sid].settingsEl = document.createElement('div');
this.filterBox.appendChild(this.sources[sid].settingsEl);
var syncUrl = document.createElement('a');
_this.sources[sid].syncUrl = syncUrl;
var xmlHttp = new XMLHttpRequest();
xmlHttp.open('GET', this.sources[sid].shortSrc+'?action=filterinfo', true);
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState != 4) return;
try {
_this.sources[sid].filterInfo = JSON.parse(xmlHttp.responseText);
} catch (e) {
_this.sources[sid].settingsEl.innerHTML = _this.lang.get('filterError');
return;
}
if (_this.sources[sid].filterInfo.promoters) _this.sources[sid].settingsEl.appendChild(_this.filterHtmlVisualization(sid, 'promoters'));
if (_this.sources[sid].filterInfo.categories) _this.sources[sid].settingsEl.appendChild(_this.filterHtmlVisualization(sid, 'categories'));
_this.sources[sid].settingsEl.appendChild(_this.filterHtmlVisualization(sid, 'timeRange'));
var syncBox = document.createElement('div');
var updateBut = document.createElement('a');
updateBut.href = 'javascript: /* update */';
updateBut.className = 'button';
updateBut.textContent = _this.lang.get('update')+' -> ';
updateBut.cal = _this;
updateBut.sid = sid;
updateBut.addEventListener('click', function () { this.cal.updateUrl(this.sid, true); }, false);
var syncHint = document.createElement('p');
syncHint.innerHTML = _this.lang.get('synchint');
syncBox.appendChild(syncHint);
syncBox.appendChild(updateBut);
syncBox.appendChild(syncUrl);
_this.sources[sid].settingsEl.appendChild(syncBox);
};
xmlHttp.onerror = function () {
_this.sources[sid].settingsEl.innerHTML = _this.lang.get('filterError');
};
try {
xmlHttp.send(null);
} catch (e) {
this.sources[sid].settingsEl.innerHTML = this.lang.get('filterError');
}
},
sortAndPrintDates : /* private */ function () {
this.ul.innerHTML = '';
this.dates.sort(function (d1, d2) { return d2.start - d1.start; });
var now = new Date();
var reachedNow = false;
for (var i = 0; i < this.dates.length; i++) {
// gray out passed dates
this.dates[i].li.className = '';
if (this.dates[i].start < now) {
if (this.dates[i].end && this.dates[i].end > now) this.dates[i].li.className += 'current';
else {
this.dates[i].li.className += 'passed';
if (!reachedNow && i > 0) this.dates[i].li.className += ' nowLine';
reachedNow = true;
}
}
if (this.dates[i].status == 'cancelled') this.dates[i].li.className += ' cancelled';
this.ul.appendChild(this.dates[i].li);
}
},
icsToDate : /* private */ function (cal, eventID, endDate) {
if (!endDate || !cal.VEVENT[eventID].DTEND) var d = cal.VEVENT[eventID].DTSTART.split('T');
else var d = cal.VEVENT[eventID].DTEND.split('T');
var a = new Array (0, 0, 0, 0, 0, 0, 0);
a[0] = d[0].substring(0, d[0].length-4);
a[1] = parseInt(d[0].substring(d[0].length-4, d[0].length-2))-1;
a[2] = parseInt(d[0].substring(d[0].length-2, d[0].length));
if (d.length > 1) {
a[3] = parseInt(d[1].substring(0, 2));
a[4] = parseInt(d[1].substring(2, 4));
a[5] = parseInt(d[1].substring(4, 6));
var date = new Date(Date.UTC(a[0], a[1], a[2], a[3], a[4], a[5], a[6]));
} else var date = new Date(a[0], a[1], a[2], a[3], a[4], a[5], a[6]); // Events with complete day begin in the local timezone
if (cal.VEVENT[eventID].DTSTART_TZID && cal.VTIMEZONE)
for (var k = 0; k < cal.VTIMEZONE.length; k++)
if (cal.VTIMEZONE[k].TZID == cal.VEVENT[eventID].DTSTART_TZID) {
var sign = parseInt(cal.VTIMEZONE[k].STANDARD[0].TZOFFSETFROM.substring(0, 1)+'1');
var dh = parseInt(cal.VTIMEZONE[k].STANDARD[0].TZOFFSETFROM.substring(1, 3));
var dm = parseInt(cal.VTIMEZONE[k].STANDARD[0].TZOFFSETFROM.substring(3, 5));
date.setUTCHours(date.getUTCHours() - dh * sign);
date.setUTCMinutes(date.getUTCMinutes() - dm * sign);
break;
}
return date;
},
interpretICS : /* private */ function (ics) {
ics = ics.split("\r").join('').split("\n");
var i = 0;
function readBlock () {
var o = new Object();
var l = ''; // last variable name in o
while (i < ics.length) {
if (ics[i].charAt(0) == ' ' && l != '') {
o[l] += ics[i].substring(1, ics[i].length).split("\\n").join("\n").split("\\,").join(",").split("\\;").join(";");
i++;
continue;
}
var s = ics[i].split(':');
i++;
if (s[0] == 'END') return o;
else if (s[0] == 'BEGIN') {
if (!o[s[1]]) o[s[1]] = new Array();
o[s[1]].push(readBlock());
} else {
l = s[0].split(';');
for (var j = 1; j < l.length; j++) {
l[j] = l[j].split('=');
if (l[j].length == 2) o[l[0]+'_'+l[j][0]] = l[j][1];
}
l = l[0];
s.shift();
o[l] = s.join(':').split("\\n").join("\n").split("\\,").join(",").split("\\;").join(";");
}
}
return o;
}
return readBlock();
},
loadSource : /* private */ function (sid, callback) {
var _this = this;
this.sources[sid].ics = null;
// compose src
var currentSrc = this.sources[sid].src;
currentSrc += ((this.sources[sid].src.indexOf('?') >= 0)?'&':'?')+'nocache='+Math.trunc(Math.random()*10000);
// get
var xmlHttp = new XMLHttpRequest();
xmlHttp.open('GET', currentSrc, true);
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState != 4) return;
_this.sources[sid].ics = _this.interpretICS(xmlHttp.responseText);
_this.sources[sid].state = 2;
if (callback) callback(sid);
};
xmlHttp.onerror = function () {
_this.sources[sid].state = 3;
if (callback) callback(sid);
};
try {
xmlHttp.send(null);
} catch (e) {
_this.sources[sid].state = 3;
if (callback) callback(sid);
}
},
addDatesFromSrc : /* private */ function (sid) {
if (!this.sources[sid].ics || !this.sources[sid].ics.VCALENDAR) return;
for (var i = 0; i < this.sources[sid].ics.VCALENDAR.length; i++)
if (this.sources[sid].ics.VCALENDAR[i].VEVENT)
for (var j = 0; j < this.sources[sid].ics.VCALENDAR[i].VEVENT.length; j++) {
if (!this.sources[sid].ics.VCALENDAR[i].VEVENT[j].DTSTART) continue;
var date = new CalendarDate();
if (this.sources[sid].ics.VCALENDAR[i].VEVENT[j].RRULE) date.parseRrule(this.sources[sid].ics.VCALENDAR[i].VEVENT[j].RRULE);
date.sourceId = sid;
date.start = this.icsToDate(this.sources[sid].ics.VCALENDAR[i], j, false);
date.end = this.icsToDate(this.sources[sid].ics.VCALENDAR[i], j, true);
date.completeDay = this.sources[sid].ics.VCALENDAR[i].VEVENT[j].DTSTART.indexOf('T') >= 0;
date.title = this.sources[sid].ics.VCALENDAR[i].VEVENT[j].SUMMARY;
date.location = this.sources[sid].ics.VCALENDAR[i].VEVENT[j].LOCATION;
date.description = this.sources[sid].ics.VCALENDAR[i].VEVENT[j].DESCRIPTION;
date.url = this.sources[sid].ics.VCALENDAR[i].VEVENT[j].URL;
date.status = this.sources[sid].ics.VCALENDAR[i].VEVENT[j].STATUS;
if (!date.status) date.status = '';
else date.status = date.status.toLowerCase();
date.updateHTML();
this.dates.push(date);
if (date.repeatMode > 0) {
var duration = date.end.getTime() - date.start.getTime();
var prevRep = date.start;
var nextRep = null;
var nDate = null;
do {
nextRep = new Date(prevRep.getFullYear(), prevRep.getMonth(), prevRep.getDate(), 23, 59, 1, 0);
nextRep = date.getNearestRepetition(nextRep);
if (nextRep <= prevRep) break;
prevRep = nextRep;
nDate = new CalendarDate();
nDate.sourceId = date.sourceId;
nDate.start = nextRep;
nDate.end = new Date(nextRep);
nDate.end.setMilliseconds(nDate.end.getMilliseconds() + duration);
nDate.completeDay = date.completeDay;
nDate.title = date.title;
nDate.location = date.location;
nDate.description = date.description;
nDate.url = date.url;
nDate.status = date.status;
nDate.updateHTML();
this.dates.push(nDate);
} while (nextRep <= new Date());
}
}
},
update : /* public */ function () {
var _this = this;
for (var i = 0; i < this.sources.length; i++)
if (this.sources[i].state == 1) return; // a load request is already in work
else this.sources[i].state = 1; // mark all sources as loading
function sourcesLoad () {
// wait until all sources are loaded
for (var i = 0; i < _this.sources.length; i++)
if (_this.sources[i].state == 1) return;
// remove all existing dates, that belong to a successfully (re)loaded source
for (var i = _this.dates.length-1; i >= 0; i--)
if (_this.dates[i].sourceId >= 0 && _this.dates[i].sourceId < _this.sources.length && _this.sources[_this.dates[i].sourceId].state == 2)
_this.dates.splice(i, 1);
// import dates from sources
for (var i = 0; i < _this.sources.length; i++)
if (_this.sources[i].state == 2) _this.addDatesFromSrc(i);
else {
// TODO: Generate error message for load error
}
// update HTML
_this.sortAndPrintDates();
}
for (var i = 0; i < this.sources.length; i++) this.loadSource(i, sourcesLoad);
sourcesLoad(); // if only dates from HTML exist
}
};
// auto-detect calendar in HTML
(function () {
var div = document.getElementsByTagName('div');
if (div.length > 0 && (' '+div[div.length-1].className+' ').indexOf(' calendar ') != -1) new ICSCalendar(div[div.length-1]);
})();