815 lines
36 KiB
JavaScript
Executable File
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)?' - '+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, '&');
|
|
text = text.replace(/</g, '<');
|
|
text = text.replace(/"/g, '"');
|
|
text = text.replace(/>/g, '>');
|
|
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(' ');
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
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]);
|
|
})();
|