mirror of
https://github.com/cryptpad/cryptpad.git
synced 2026-09-14 11:05:41 +05:00
Support recurrence rules when importing or exporting ICS calendars
This commit is contained in:
parent
af923170a4
commit
65b00736bc
@ -2,7 +2,9 @@
|
||||
// Calendars will be exported using this format instead of plain text.
|
||||
define([
|
||||
'/customize/pages.js',
|
||||
], function (Pages) {
|
||||
'/common/common-util.js',
|
||||
'/calendar/recurrence.js'
|
||||
], function (Pages, Util, Rec) {
|
||||
var module = {};
|
||||
|
||||
var getICSDate = function (str) {
|
||||
@ -57,60 +59,197 @@ define([
|
||||
var data = content[uid];
|
||||
// DTSTAMP: now...
|
||||
// UID: uid
|
||||
var start, end;
|
||||
if (data.isAllDay && data.startDay && data.endDay) {
|
||||
start = "DTSTART;VALUE=DATE:" + getDate(data.startDay);
|
||||
end = "DTEND;VALUE=DATE:" + getDate(data.endDay, true);
|
||||
} else {
|
||||
start = "DTSTART:"+getICSDate(data.start);
|
||||
end = "DTEND:"+getICSDate(data.end);
|
||||
}
|
||||
var getDT = function (data) {
|
||||
var start, end;
|
||||
if (data.isAllDay) {
|
||||
var startDate = new Date(data.start);
|
||||
var endDate = new Date(data.end);
|
||||
data.startDay = data.startDay || (startDate.getFullYear() + '-' + (startDate.getMonth()+1) + '-' + startDate.getDate());
|
||||
data.endDay = data.endDay || (endDate.getFullYear() + '-' + (endDate.getMonth()+1) + '-' + endDate.getDate());
|
||||
start = "DTSTART;VALUE=DATE:" + getDate(data.startDay);
|
||||
end = "DTEND;VALUE=DATE:" + getDate(data.endDay, true);
|
||||
} else {
|
||||
start = "DTSTART:"+getICSDate(data.start);
|
||||
end = "DTEND:"+getICSDate(data.end);
|
||||
}
|
||||
return {
|
||||
start: start,
|
||||
end: end
|
||||
};
|
||||
};
|
||||
|
||||
Array.prototype.push.apply(ICS, [
|
||||
'BEGIN:VEVENT',
|
||||
'DTSTAMP:'+getICSDate(+new Date()),
|
||||
'UID:'+uid,
|
||||
start,
|
||||
end,
|
||||
'SUMMARY:'+ data.title,
|
||||
'LOCATION:'+ data.location,
|
||||
]);
|
||||
|
||||
if (Array.isArray(data.reminders)) {
|
||||
data.reminders.forEach(function (valueMin) {
|
||||
var time = valueMin * 60;
|
||||
var days = Math.floor(time / DAY);
|
||||
time -= days * DAY;
|
||||
var hours = Math.floor(time / HOUR);
|
||||
time -= hours * HOUR;
|
||||
var minutes = Math.floor(time / MINUTE);
|
||||
time -= minutes * MINUTE;
|
||||
var seconds = time;
|
||||
|
||||
var str = "-P" + days + "D";
|
||||
if (hours || minutes || seconds) {
|
||||
str += "T" + hours + "H" + minutes + "M" + seconds + "S";
|
||||
var getRRule = function (data) {
|
||||
if (!data.recurrenceRule || !data.recurrenceRule.freq) { return; }
|
||||
var r = data.recurrenceRule;
|
||||
var rrule = "RRULE:";
|
||||
rrule += "FREQ="+r.freq.toUpperCase();
|
||||
Object.keys(r).forEach(function (k) {
|
||||
if (k === "freq") { return; }
|
||||
if (k === "by") {
|
||||
Object.keys(r.by).forEach(function (_k) {
|
||||
rrule += ";BY"+_k.toUpperCase()+"="+r.by[_k];
|
||||
});
|
||||
return;
|
||||
}
|
||||
rrule += ";"+k.toUpperCase()+"="+r[k];
|
||||
});
|
||||
return rrule;
|
||||
};
|
||||
|
||||
|
||||
|
||||
var addEvent = function (arr, data, recId) {
|
||||
var uid = data.id;
|
||||
var dt = getDT(data);
|
||||
var start = dt.start;
|
||||
var end = dt.end;
|
||||
var rrule = getRRule(data);
|
||||
|
||||
Array.prototype.push.apply(arr, [
|
||||
'BEGIN:VEVENT',
|
||||
'DTSTAMP:'+getICSDate(+new Date()),
|
||||
'UID:'+uid,
|
||||
start,
|
||||
end,
|
||||
recId,
|
||||
rrule,
|
||||
'SUMMARY:'+ data.title,
|
||||
'LOCATION:'+ data.location,
|
||||
].filter(Boolean));
|
||||
|
||||
if (Array.isArray(data.reminders)) {
|
||||
data.reminders.forEach(function (valueMin) {
|
||||
var time = valueMin * 60;
|
||||
var days = Math.floor(time / DAY);
|
||||
time -= days * DAY;
|
||||
var hours = Math.floor(time / HOUR);
|
||||
time -= hours * HOUR;
|
||||
var minutes = Math.floor(time / MINUTE);
|
||||
time -= minutes * MINUTE;
|
||||
var seconds = time;
|
||||
|
||||
var str = "-P" + days + "D";
|
||||
if (hours || minutes || seconds) {
|
||||
str += "T" + hours + "H" + minutes + "M" + seconds + "S";
|
||||
}
|
||||
Array.prototype.push.apply(arr, [
|
||||
'BEGIN:VALARM',
|
||||
'ACTION:DISPLAY',
|
||||
'DESCRIPTION:This is an event reminder',
|
||||
'TRIGGER:'+str,
|
||||
'END:VALARM'
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
if (Array.isArray(data.cp_hidden)) {
|
||||
Array.prototype.push.apply(arr, data.cp_hidden);
|
||||
}
|
||||
|
||||
arr.push('END:VEVENT');
|
||||
};
|
||||
|
||||
|
||||
var applyChanges = function (base, changes) {
|
||||
var applyDiff = function (obj, k) {
|
||||
var diff = obj[k]; // Diff is always compared to origin start/end
|
||||
var d = new Date(base[k]);
|
||||
d.setDate(d.getDate() + diff.d);
|
||||
d.setHours(d.getHours() + diff.h);
|
||||
d.setMinutes(d.getMinutes() + diff.m);
|
||||
base[k] = +d;
|
||||
};
|
||||
Object.keys(changes || {}).forEach(function (k) {
|
||||
if (k === "start" || k === "end") {
|
||||
return applyDiff(changes, k);
|
||||
}
|
||||
base[k] = changes[k];
|
||||
});
|
||||
};
|
||||
|
||||
var prev = data;
|
||||
|
||||
// Check if we have "one-time" or "from date" updates.
|
||||
// "One-time" updates will be added accordingly to the ICS specs
|
||||
// "From date" updates will be added as new events and will add
|
||||
// an "until" value to the initial event's RRULE
|
||||
var toAdd = [];
|
||||
if (data.recurrenceRule && data.recurrenceRule.freq && data.recUpdate) {
|
||||
var ru = data.recUpdate;
|
||||
var _all = {};
|
||||
var duration = data.end - data.start;
|
||||
|
||||
var all = Rec.getAllOccurrences(data); // "false" if infinite
|
||||
|
||||
Object.keys(ru.from || {}).forEach(function (d) {
|
||||
if (!Object.keys(ru.from[d] || {}).length) { return; }
|
||||
_all[d] = _all[d] || {};
|
||||
_all[d].from = ru.from[d];
|
||||
});
|
||||
Object.keys(ru.one || {}).forEach(function (d) {
|
||||
if (!Object.keys(ru.one[d] || {}).length) { return; }
|
||||
_all[d] = _all[d] || {};
|
||||
_all[d].one = ru.one[d];
|
||||
});
|
||||
Object.keys(_all).sort(function (a, b) {
|
||||
return Number(a) - Number(b);
|
||||
}).forEach(function (d) {
|
||||
d = Number(d);
|
||||
var r = prev.recurrenceRule;
|
||||
|
||||
// This rule won't apply if we've reached "until" or "count"
|
||||
var idx = all && all.indexOf(d);
|
||||
if (all && idx === -1) {
|
||||
// Make sure we don't have both count and until
|
||||
if (all.length === r.count) { delete r.until; }
|
||||
else { delete r.count; }
|
||||
return;
|
||||
}
|
||||
|
||||
var ud = _all[d];
|
||||
|
||||
if (ud.from) { // "From" updates are not supported by ICS: make a new event
|
||||
var _new = Util.clone(prev);
|
||||
r.until = getICSDate(d - 1); // Stop previous recursion
|
||||
delete r.count;
|
||||
addEvent(ICS, prev, null); // Add previous event
|
||||
Array.prototype.push.apply(ICS, toAdd); // Add individual updates
|
||||
toAdd = [];
|
||||
prev = _new;
|
||||
if (all) { all = all.slice(idx); }
|
||||
|
||||
// if we updated the recurrence rule, count is reset, nothing to do
|
||||
// if we didn't update the recurrence, we need to fix the count
|
||||
var _r = _new.recurrenceRule;
|
||||
if (all && !ud.from.recurrenceRule && _r && _r.count) {
|
||||
_r.count -= idx;
|
||||
}
|
||||
|
||||
prev.start = d;
|
||||
prev.end = d + duration;
|
||||
prev.id = Util.uid();
|
||||
applyChanges(prev, ud.from);
|
||||
duration = prev.end - prev.start;
|
||||
}
|
||||
if (ud.one) { // Add update
|
||||
var _one = Util.clone(prev);
|
||||
_one.start = d;
|
||||
_one.end = d + duration;
|
||||
applyChanges(_one, ud.one);
|
||||
var recId = "RECURRENCE-ID:"+getICSDate(+d);
|
||||
delete _one.recurrenceRule;
|
||||
addEvent(toAdd, _one, recId); // Add updated event
|
||||
}
|
||||
Array.prototype.push.apply(ICS, [
|
||||
'BEGIN:VALARM',
|
||||
'ACTION:DISPLAY',
|
||||
'DESCRIPTION:This is an event reminder',
|
||||
'TRIGGER:'+str,
|
||||
'END:VALARM'
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
if (Array.isArray(data.cp_hidden)) {
|
||||
Array.prototype.push.apply(ICS, data.cp_hidden);
|
||||
}
|
||||
|
||||
ICS.push('END:VEVENT');
|
||||
addEvent(ICS, prev);
|
||||
Array.prototype.push.apply(ICS, toAdd); // Add individual updates
|
||||
});
|
||||
|
||||
ICS.push('END:VCALENDAR');
|
||||
|
||||
return new Blob([ ICS.join('\n') ], { type: 'text/calendar;charset=utf-8' });
|
||||
return new Blob([ ICS.join('\r\n') ], { type: 'text/calendar;charset=utf-8' });
|
||||
};
|
||||
|
||||
module.import = function (content, id, cb) {
|
||||
@ -171,7 +310,7 @@ define([
|
||||
}
|
||||
|
||||
// Store other properties
|
||||
var used = ['dtstart', 'dtend', 'uid', 'summary', 'location', 'dtstamp'];
|
||||
var used = ['dtstart', 'dtend', 'uid', 'summary', 'location', 'dtstamp', 'rrule', 'recurrence-id'];
|
||||
var hidden = [];
|
||||
ev.getAllProperties().forEach(function (p) {
|
||||
if (used.indexOf(p.name) !== -1) { return; }
|
||||
@ -192,8 +331,25 @@ define([
|
||||
if (reminders.indexOf(minutes) === -1) { reminders.push(minutes); }
|
||||
});
|
||||
|
||||
// Get recurrence rule
|
||||
var rrule = ev.getFirstPropertyValue('rrule');
|
||||
var rec;
|
||||
if (rrule && rrule.freq) {
|
||||
rec = {};
|
||||
rec.freq = rrule.freq.toLowerCase();
|
||||
if (rrule.interval) { rec.interval = rrule.interval; }
|
||||
if (rrule.count) { rec.count = rrule.count; }
|
||||
if (Object.keys(rrule).includes('wkst')) { rec.wkst = (rrule.wkst + 6) % 7; }
|
||||
if (rrule.until) { rec.until = +new Date(rrule.until); }
|
||||
Object.keys(rrule.parts || {}).forEach(function (k) {
|
||||
rec.by = rec.by || {};
|
||||
var _k = k.toLowerCase().slice(2); // "BYDAY" ==> "day"
|
||||
rec.by[_k] = rrule.parts[k];
|
||||
});
|
||||
}
|
||||
|
||||
// Create event
|
||||
res[uid] = {
|
||||
var obj = {
|
||||
calendarId: id,
|
||||
id: uid,
|
||||
category: 'time',
|
||||
@ -203,12 +359,41 @@ define([
|
||||
start: start,
|
||||
end: end,
|
||||
reminders: reminders,
|
||||
cp_hidden: hidden
|
||||
cp_hidden: hidden,
|
||||
};
|
||||
if (rec) { obj.recurrenceRule = rec; }
|
||||
|
||||
if (!hidden.length) { delete res[uid].cp_hidden; }
|
||||
if (!reminders.length) { delete res[uid].reminders; }
|
||||
if (!hidden.length) { delete obj.cp_hidden; }
|
||||
if (!reminders.length) { delete obj.reminders; }
|
||||
|
||||
var recId = ev.getFirstPropertyValue('recurrence-id');
|
||||
if (recId) {
|
||||
setTimeout(function () {
|
||||
if (!res[uid]) { return; }
|
||||
var old = res[uid];
|
||||
var time = +new Date(recId);
|
||||
var diff = {};
|
||||
var from = {};
|
||||
Object.keys(obj).forEach(function (k) {
|
||||
if (JSON.stringify(old[k]) === JSON.stringify(obj[k])) { return; }
|
||||
if (['start','end'].includes(k)) {
|
||||
diff[k] = Rec.diffDate(old[k], obj[k]);
|
||||
return;
|
||||
}
|
||||
if (k === "recurrenceRule") {
|
||||
from[k] = obj[k];
|
||||
return;
|
||||
}
|
||||
diff[k] = obj[k];
|
||||
});
|
||||
old.recUpdate = old.recUpdate || {one:{},from:{}};
|
||||
if (Object.keys(from).length) { old.recUpdate.from[time] = from; }
|
||||
if (Object.keys(diff).length) { old.recUpdate.one[time] = diff; }
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res[uid] = obj;
|
||||
});
|
||||
|
||||
cb(null, res);
|
||||
|
||||
@ -892,32 +892,7 @@ ICS ==> create a new event with the same UID and a RECURRENCE-ID field (with a v
|
||||
*/
|
||||
|
||||
|
||||
var diffDate = function (oldTime, newTime) {
|
||||
var n = new Date(newTime);
|
||||
var o = new Date(oldTime);
|
||||
|
||||
// Diff Days
|
||||
var d = 0;
|
||||
var mult = n < o ? -1 : 1;
|
||||
while (n.toLocaleDateString() !== o.toLocaleDateString() || mult >= 10000) {
|
||||
n.setDate(n.getDate() - mult);
|
||||
d++;
|
||||
}
|
||||
d = mult * d;
|
||||
|
||||
// Diff hours
|
||||
n = new Date(newTime);
|
||||
var h = n.getHours() - o.getHours();
|
||||
|
||||
// Diff minutes
|
||||
var m = n.getMinutes() - o.getMinutes();
|
||||
|
||||
return {
|
||||
d: d,
|
||||
h: h,
|
||||
m: m
|
||||
};
|
||||
};
|
||||
var diffDate = Rec.diffDate;
|
||||
|
||||
var makeCalendar = function (view) {
|
||||
var store = window.cryptpadStore;
|
||||
@ -1018,7 +993,7 @@ ICS ==> create a new event with the same UID and a RECURRENCE-ID field (with a v
|
||||
changes.reminders = reminders;
|
||||
}
|
||||
|
||||
var oldRec = originalEvent.recurrenceRule;
|
||||
var oldRec = ev.recurrenceRule;
|
||||
var rec = APP.recurrenceRule;
|
||||
if (JSONSortify(oldRec || '') !== JSONSortify(rec)) {
|
||||
changes.recurrenceRule = rec;
|
||||
@ -1068,7 +1043,7 @@ ICS ==> create a new event with the same UID and a RECURRENCE-ID field (with a v
|
||||
Messages.calendar_rec_warn_delall = "The recurrence rule was deleted. Only the original event on {0} will be kept.";
|
||||
Messages.calendar_rec_warn_del = "The recurrence rule was deleted. All occurences after the selected one will be removed.";
|
||||
Messages.calendar_rec_warn_updateall = "The recurrence rule was modified. Only the original event on {0} will be kept and new occurences will be created.";
|
||||
Messages.calendar_rec_warn_update = "The recurrence rule was modified. All occurences after the selected one will be removed and recreated with the new rule.";
|
||||
Messages.calendar_rec_warn_update = "The recurrence rule was modified. All occurences after the selected one will be removed and recreated with the new rule."; // XXX NOTE: the "count" value will be reset
|
||||
|
||||
// Confirm modal: select which recurring events to update
|
||||
if (!Object.keys(changes).length) { return void afterConfirm(); }
|
||||
@ -1567,7 +1542,7 @@ APP.recurrenceRule = {
|
||||
};
|
||||
var addUpdate = function () {
|
||||
if (!updatedOn) { return; }
|
||||
var d = new Date(APP.recurrenceRule._next).toLocaleDateString();
|
||||
var d = new Date(updatedOn).toLocaleDateString();
|
||||
$translated.append(h('div', Messages._getKey('calendar_rec_updated', [d])));
|
||||
};
|
||||
var addTranslation = function () {
|
||||
|
||||
@ -219,14 +219,27 @@ define([
|
||||
var all = [];
|
||||
if (![0,1,2,3,4,5,6].includes(day)) { return false; }
|
||||
|
||||
var filterPos = function () {
|
||||
var filterPos = function (m) {
|
||||
if (!pos) { return; }
|
||||
if (pos < 0) {
|
||||
pos = all.length + pos;
|
||||
} else {
|
||||
pos--; // An array starts at 0 but the recurrence rule starts at 1
|
||||
}
|
||||
all = [all[pos]];
|
||||
|
||||
var _all = [];
|
||||
'aaaaaaaaaaaa'.split('').some(function (a, i) {
|
||||
if (typeof(m) !== "undefined" && i !== m) { return; }
|
||||
|
||||
var _pos;
|
||||
var tmp = all.filter(function (d) {
|
||||
return d.getMonth() === i;
|
||||
});
|
||||
if (pos < 0) {
|
||||
_pos = tmp.length + pos;
|
||||
} else {
|
||||
_pos = pos - 1; // An array starts at 0 but the recurrence rule starts at 1
|
||||
}
|
||||
_all.push(tmp[_pos]);
|
||||
|
||||
return typeof(m) !== "undefined" && i === m;
|
||||
});
|
||||
all = _all.filter(Boolean); // The "5th" {day} won't always exist
|
||||
};
|
||||
|
||||
var tmp;
|
||||
@ -250,7 +263,7 @@ define([
|
||||
all.push(new Date(+tmp));
|
||||
tmp.setDate(tmp.getDate()+7);
|
||||
}
|
||||
filterPos();
|
||||
filterPos(m);
|
||||
return all;
|
||||
}
|
||||
|
||||
@ -374,7 +387,7 @@ define([
|
||||
var origin = Util.clone(_origin);
|
||||
var oS = new Date(origin.start);
|
||||
|
||||
var id = origin.id;
|
||||
var id = origin.id.split('|')[0]; // Use same cache when updating recurrence rule
|
||||
|
||||
// "uid" is used for the cache
|
||||
var uid = s.toLocaleDateString();
|
||||
@ -686,7 +699,7 @@ define([
|
||||
evS = +_evS;
|
||||
obj = _ev;
|
||||
rule = nextRule;
|
||||
// XXX
|
||||
nextRule = nextRules.shift();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@ -700,6 +713,55 @@ define([
|
||||
});
|
||||
return toAdd;
|
||||
};
|
||||
Rec.getAllOccurrences = function (ev) {
|
||||
if (!ev.recurrenceRule) { return [ev.start]; }
|
||||
var r = ev.recurrenceRule;
|
||||
// In case of infinite recursion, we can't get all
|
||||
if (!r.until && !r.count) { return false; }
|
||||
var all = [ev.start];
|
||||
var d = new Date(ev.start);
|
||||
d.setDate(15); // Make sure we won't skip a month if the event starts on day > 28
|
||||
var toAdd = [];
|
||||
|
||||
var i = 0;
|
||||
var check = function () {
|
||||
return r.count ? (all.length < r.count) : (+d <= r.until);
|
||||
};
|
||||
while ((toAdd = Rec.getRecurring([Rec.getMonthId(d)], [ev])) && check() && i < (r.count*12)) {
|
||||
Array.prototype.push.apply(all, toAdd.map(function (_ev) { return _ev.start; }));
|
||||
d.setMonth(d.getMonth() + 1);
|
||||
i++;
|
||||
}
|
||||
|
||||
return all;
|
||||
};
|
||||
|
||||
Rec.diffDate = function (oldTime, newTime) {
|
||||
var n = new Date(newTime);
|
||||
var o = new Date(oldTime);
|
||||
|
||||
// Diff Days
|
||||
var d = 0;
|
||||
var mult = n < o ? -1 : 1;
|
||||
while (n.toLocaleDateString() !== o.toLocaleDateString() || mult >= 10000) {
|
||||
n.setDate(n.getDate() - mult);
|
||||
d++;
|
||||
}
|
||||
d = mult * d;
|
||||
|
||||
// Diff hours
|
||||
n = new Date(newTime);
|
||||
var h = n.getHours() - o.getHours();
|
||||
|
||||
// Diff minutes
|
||||
var m = n.getMinutes() - o.getMinutes();
|
||||
|
||||
return {
|
||||
d: d,
|
||||
h: h,
|
||||
m: m
|
||||
};
|
||||
};
|
||||
|
||||
var sortUpdate = function (obj) {
|
||||
return Object.keys(obj).sort(function (d1, d2) {
|
||||
|
||||
@ -408,10 +408,20 @@ define([
|
||||
c.lm = lm;
|
||||
var proxy = c.proxy = lm.proxy;
|
||||
|
||||
var _updateCalled = false;
|
||||
var _update = function () {
|
||||
if (_updateCalled) { return; }
|
||||
_updateCalled = true;
|
||||
setTimeout(function () {
|
||||
_updateCalled = false;
|
||||
update();
|
||||
});
|
||||
};
|
||||
|
||||
lm.proxy.on('cacheready', function () {
|
||||
if (!proxy.metadata) { return; }
|
||||
c.cacheready = true;
|
||||
setTimeout(update);
|
||||
_update();
|
||||
if (cb) { cb(null, lm.proxy); }
|
||||
addInitialReminders(ctx, channel, cfg.lastVisitNotif);
|
||||
}).on('ready', function (info) {
|
||||
@ -428,12 +438,12 @@ define([
|
||||
title: data.title
|
||||
};
|
||||
}
|
||||
setTimeout(update);
|
||||
_update();
|
||||
if (cb) { cb(null, lm.proxy); }
|
||||
addInitialReminders(ctx, channel, cfg.lastVisitNotif);
|
||||
}).on('change', [], function () {
|
||||
if (!c.ready) { return; }
|
||||
setTimeout(update);
|
||||
_update();
|
||||
}).on('change', ['content'], function (o, n, p) {
|
||||
if (p.length === 2 && n && !o) { // New event
|
||||
return void addReminders(ctx, channel, n);
|
||||
@ -457,7 +467,7 @@ define([
|
||||
});
|
||||
}
|
||||
}).on('remove', ['content'], function (x, p) {
|
||||
setTimeout(update);
|
||||
_update();
|
||||
if ((p.length >= 3 && p[2] === 'reminders') ||
|
||||
(p.length >= 6 && p[5] === 'reminders')) {
|
||||
return void setTimeout(function () {
|
||||
@ -471,10 +481,10 @@ define([
|
||||
updateLocalCalendars(ctx, c, md);
|
||||
}).on('disconnect', function () {
|
||||
c.offline = true;
|
||||
setTimeout(update);
|
||||
_update();
|
||||
}).on('reconnect', function () {
|
||||
c.offline = false;
|
||||
setTimeout(update);
|
||||
_update();
|
||||
}).on('error', function (info) {
|
||||
if (!info || !info.error) { return; }
|
||||
if (info.error === "EDELETED" ) {
|
||||
@ -482,7 +492,7 @@ define([
|
||||
}
|
||||
if (info.error === "ERESTRICTED" ) {
|
||||
c.restricted = true;
|
||||
setTimeout(update);
|
||||
_update();
|
||||
}
|
||||
cb(info);
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user