From 63581e2cf35414c5f2b7791769e5e80ea82f3ad4 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 7 Apr 2021 16:41:39 +0200 Subject: [PATCH] Import calendars from URL and upgrade if needed --- www/calendar/inner.js | 125 +++++++++++++++++++++++------------ www/common/outer/calendar.js | 83 +++++++++++++++++++++-- 2 files changed, 161 insertions(+), 47 deletions(-) diff --git a/www/calendar/inner.js b/www/calendar/inner.js index e5ade2fad..88f7a44fc 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -65,6 +65,7 @@ Messages.calendar_deleteOwned = " It will still be visible for the users it has Messages.calendar_errorNoCalendar = "No editable calendar selected!"; Messages.calendar_myCalendars = "My calendars"; Messages.calendar_tempCalendar = "Temp calendar"; +Messages.calendar_import = "Import to my calendars"; var onCalendarsUpdate = Util.mkEvent(); @@ -86,6 +87,12 @@ Messages.calendar_tempCalendar = "Temp calendar"; cb(null, obj); }); }; + var importCalendar = function (data, cb) { + APP.module.execCommand('IMPORT', data, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(null, obj); + }); + }; var newEvent = function (data, cb) { var start = data.start; var end = data.end; @@ -307,50 +314,83 @@ Messages.calendar_tempCalendar = "Temp calendar"; UI.openCustomModal(m); }; + var isReadOnly = function (id, teamId) { + var data = APP.calendars[id]; + return data.readOnly || (data.roTeams && data.roTeams.indexOf(teamId) !== -1); + }; var makeEditDropdown = function (id, teamId) { - var options = [{ - tag: 'a', - attributes: { - 'class': 'fa fa-pencil', - }, - content: h('span', Messages.tag_edit), - action: function (e) { - e.stopPropagation(); - editCalendar(id); - return true; - } - }, { - tag: 'a', - attributes: { - 'class': 'fa fa-shhare-alt', - }, - content: h('span', Messages.shareButton), - action: function (e) { - e.stopPropagation(); - var friends = common.getFriends(); - var cal = APP.calendars[id]; - var title = Util.find(cal, ['content', 'metadata', 'title']); - var color = Util.find(cal, ['content', 'metadata', 'color']); - Share.getShareModal(common, { - teamId: teamId === 1 ? undefined : teamId, - origin: APP.origin, - pathname: "/calendar/", - friends: friends, - title: title, - password: cal.password, // XXX support passwords - calendar: { - title: title, - color: color, - channel: id, - }, - common: common, - hashes: cal.hashes - }); - return true; - } - }]; + var options = []; var privateData = metadataMgr.getPrivateData(); var cantRemove = teamId === 0 || (teamId !== 1 && privateData.teams[teamId].viewer); + var data = APP.calendars[id]; + if (!data.readOnly) { + options.push({ + tag: 'a', + attributes: { + 'class': 'fa fa-pencil', + }, + content: h('span', Messages.tag_edit), + action: function (e) { + e.stopPropagation(); + editCalendar(id); + return true; + } + }); + } + if (data.teams.indexOf(1) === -1 || teamId === 0) { + options.push({ + tag: 'a', + attributes: { + 'class': 'fa fa-clone', + }, + content: h('span', Messages.calendar_import), + action: function (e) { + e.stopPropagation(); + importCalendar({ + id: id, + teamId: teamId + }, function (obj) { + if (obj && obj.error) { + console.error(obj.error); + return void UI.warn(obj.error); + } + }); + return true; + } + }); + } + if (!data.restricted) { + options.push({ + tag: 'a', + attributes: { + 'class': 'fa fa-shhare-alt', + }, + content: h('span', Messages.shareButton), + action: function (e) { + e.stopPropagation(); + var friends = common.getFriends(); + var cal = APP.calendars[id]; + var title = Util.find(cal, ['content', 'metadata', 'title']); + var color = Util.find(cal, ['content', 'metadata', 'color']); + Share.getShareModal(common, { + teamId: teamId === 1 ? undefined : teamId, + origin: APP.origin, + pathname: "/calendar/", + friends: friends, + title: title, + password: cal.password, // XXX support passwords + calendar: { + title: title, + color: color, + channel: id, + }, + common: common, + hashes: cal.hashes + }); + return true; + } + }); + } if (!cantRemove) { options.push({ tag: 'a', @@ -397,7 +437,7 @@ Messages.calendar_tempCalendar = "Temp calendar"; var edit; if (data.loading) { edit = h('i.fa.fa-spinner.fa-spin'); - } else if (!data.readOnly) { + } else { edit = makeEditDropdown(id, teamId); } var md = Util.find(data, ['content', 'metadata']); @@ -410,6 +450,7 @@ Messages.calendar_tempCalendar = "Temp calendar"; style: 'background-color: '+md.color+';' }), h('span.cp-calendar-title', md.title), + isReadOnly(id, teamId) ? h('i.fa.fa-eye', {title: Messages.readonly}) : undefined, edit ]); $(calendar).click(function () { diff --git a/www/common/outer/calendar.js b/www/common/outer/calendar.js index c504a8191..35969f92d 100644 --- a/www/common/outer/calendar.js +++ b/www/common/outer/calendar.js @@ -96,6 +96,7 @@ ctx.calendars[channel] = { var sendUpdate = function (ctx, c) { ctx.emit('UPDATE', { teams: c.stores, + roTeams: c.roStores, id: c.channel, loading: !c.ready && !c.cacheready, readOnly: c.readOnly || (!c.ready && c.cacheready), @@ -135,12 +136,38 @@ ctx.calendars[channel] = { if (c) { if (c.readOnly && data.href) { - // XXX UPGRADE - // c.hashes.editHash = - // XXX different cases if already ready or not? + // Upgrade readOnly calendar to editable + var upgradeParsed = Hash.parsePadUrl(data.href); + var upgradeSecret = Hash.getSecrets('calendar', upgradeParsed.hash, data.password); + var upgradeCrypto = Crypto.createEncryptor(upgradeSecret.keys); + c.hashes.editHash = Hash.getEditHashFromKeys(upgradeSecret); + c.lm.setReadOnly(false, upgradeCrypto); + c.readOnly = false; + } else if (teamId === 0) { + // Existing calendars can't be "temp calendars" (unless they are an upgrade) + return void cb(); } + + // Remove from roStores when upgrading this store + if (c.roStores.indexOf(teamId) !== -1 && data.href) { + c.roStores.splice(c.roStores.indexOf(teamId), 1); + // If we've upgraded a stored calendar, remove the temp calendar + if (c.stores.indexOf(0) !== -1) { + c.stores.splice(c.stores.indexOf(0), 1); + } + update(); + } + + // Don't store duplicates if (c.stores && c.stores.indexOf(teamId) !== -1) { return void cb(); } + + // If we store a temp calendar to our account or team, remove this "temp calendar" + if (c.stores.indexOf(0) !== -1) { c.stores.splice(c.stores.indexOf(0), 1); } + c.stores.push(teamId); + if (!data.href) { + c.roStores.push(teamId); + } update(); return void cb(); } @@ -152,6 +179,7 @@ ctx.calendars[channel] = { channel: channel, readOnly: !data.href, stores: [teamId], + roStores: data.href ? [] : [teamId], hashes: {} }; @@ -343,6 +371,38 @@ ctx.calendars[channel] = { isNew: true }, cb); }; + var importCalendar = function (ctx, data, cId, cb) { + var id = data.id; + var c = ctx.calendars[id]; + if (!c) { return void cb({error: "ENOENT"}); } + if (!Array.isArray(c.stores) || c.stores.indexOf(data.teamId) === -1) { + return void cb({error: 'EINVAL'}); + } + + // Add to my calendars + var store = ctx.store; + var calendars = store.proxy.calendars = store.proxy.calendars || {}; + var hash = c.hashes.editHash; + var roHash = c.hashes.viewHash; + calendars[id] = { + href: hash && Hash.hashToHref(hash, 'calendar'), + roHref: roHash && Hash.hashToHref(roHash, 'calendar'), + channel: id, + color: Util.find(c,['proxy', 'metadata', 'color']) || Util.getRandomColor(), + title: Util.find(c,['proxy', 'metadata', 'title']) || '...' + }; + ctx.Store.onSync(null, cb); + + // Make the change in memory + openChannel(ctx, { + storeId: 1, + data: { + href: calendars[id].href, + toHref: calendars[id].roHref, + channel: id + } + }); + }; var addCalendar = function (ctx, data, cId, cb) { var store = getStore(ctx, data.teamId); if (!store) { return void cb({error: "NO_STORE"}); } @@ -353,13 +413,21 @@ ctx.calendars[channel] = { var parsed = Hash.parsePadUrl(data.href); var secret = Hash.getSecrets(parsed.type, parsed.hash, data.password); + if (secret.channel !== data.channel) { return void cb({error: 'EINVAL'}); } + + var hash = Hash.getEditHashFromKeys(secret); + var roHash = Hash.getViewHashFromKeys(secret); var cal = { - href: Hash.getEditHashFromKeys(secret), - roHref: Hash.getViewHashFromKeys(secret), + href: hash && Hash.hashToHref(hash, 'calendar'), + roHref: roHash && Hash.hashToHref(roHash, 'calendar'), color: data.color, title: data.title, channel: data.channel }; + + // If it already existed and it's not an upgrade, nothing to do + if (c[data.channel] && (c[data.channel].href || !cal.href)) { return void cb(); } + cal.color = data.color; cal.title = data.title; openChannel(ctx, { @@ -373,6 +441,7 @@ ctx.calendars[channel] = { return void cb({error: err.error}) } // Add the calendar and call back + // If it already existed it means this is an upgrade c[cal.channel] = cal; var pin = store.pin || ctx.pinPads; pin([cal.channel], function (res) { @@ -539,6 +608,10 @@ ctx.calendars[channel] = { }); return; } + if (cmd === 'IMPORT') { + if (ctx.store.offline) { return void cb({error: 'OFFLINE'}); } + return void importCalendar(ctx, data, clientId, cb); + } if (cmd === 'ADD') { if (ctx.store.offline) { return void cb({error: 'OFFLINE'}); } return void addCalendar(ctx, data, clientId, cb);