diff --git a/customize.dist/ckeditor-contents.css b/customize.dist/ckeditor-contents.css index cca5d1d20..d37f17f5c 100644 --- a/customize.dist/ckeditor-contents.css +++ b/customize.dist/ckeditor-contents.css @@ -52,6 +52,12 @@ body > .non-realtime:first-child + * { margin-top: 0; } +@media (max-width: 600px) { + body { + padding-bottom: 100px; + } +} + .cke_editable { font-size: 16px; diff --git a/customize.dist/pages/install.js b/customize.dist/pages/install.js index 7bbf9e4cd..dbe68b308 100644 --- a/customize.dist/pages/install.js +++ b/customize.dist/pages/install.js @@ -14,11 +14,6 @@ define([ return; } -/* -Msg.install_header = "CryptPad Install"; // XXX -Msg.install_notes = ""; // XXX -*/ Msg.install_token = "Install token"; document.title = Msg.install_header; diff --git a/lib/decrees.js b/lib/decrees.js index 7afac7578..c7fd79f8a 100644 --- a/lib/decrees.js +++ b/lib/decrees.js @@ -329,8 +329,6 @@ commands.ADD_INSTALL_TOKEN = function (Env, args) { var token = args[0]; - // XXX check length, etc. ? - Env.installToken = token; return true; diff --git a/scripts/build.js b/scripts/build.js index 1f63f50aa..b0c0b5597 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -229,7 +229,7 @@ appIndexesToBuild.forEach(function (app) { write(built, `./www/${app}/index.html`); - // XXX preloading version for inner.html + // TODO preloading version for inner.html }); var instance; diff --git a/scripts/install.js b/scripts/install.js index 0587ce789..c0e57a5eb 100644 --- a/scripts/install.js +++ b/scripts/install.js @@ -22,7 +22,6 @@ nThen(function (w) { console.log('Existing token'); token = Env.installToken; } - // XXX IF ADMINS ABORT? })); }).nThen(function (w) { if (Env.installToken) { return; } diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 14a6c2333..bee31eab9 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -1397,8 +1397,8 @@ ICS ==> create a new event with the same UID and a RECURRENCE-ID field (with a v if (updatedOn) { delete APP.recurrenceRule._next; } APP.wasRecurrent = Boolean(APP.recurrenceRule); -// XXX TEST /* +// Test data: APP.recurrenceRule = { freq: 'yearly', interval: 2, diff --git a/www/code/app-code.less b/www/code/app-code.less index 406f02cd0..ab9f0129a 100644 --- a/www/code/app-code.less +++ b/www/code/app-code.less @@ -146,6 +146,9 @@ flex: 1; max-width: 100%; resize: none; + .CodeMirror-sizer > div { + padding-bottom: 100px; + } } #cp-app-code-preview { display: none !important; diff --git a/www/common/inner/access.js b/www/common/inner/access.js index 58d388271..70de82b99 100644 --- a/www/common/inner/access.js +++ b/www/common/inner/access.js @@ -332,6 +332,12 @@ define([ : Messages.error; return void UI.warn(text); } + sframeChan.query('Q_ACCEPT_OWNERSHIP', data, function (err, res) { + if (err || (res && res.error)) { + return void console.error(err || res.error); + } + UI.log(Messages.saved); + }); })); } }).nThen(function (waitFor) { @@ -867,7 +873,7 @@ define([ // In the properties, we should have the edit href if we know it. // We should know it because the pad is stored, but it's better to check... //if (!data.noEditPassword && !opts.noEditPassword && owned && data.href) { - if (!data.noEditPassword && !opts.noEditPassword && owned && data.href && parsed.type !== "form") { // XXX password change in forms block responses (validation & decryption) + if (!data.noEditPassword && !opts.noEditPassword && owned && data.href && parsed.type !== "form") { // TODO password change in forms block responses (validation & decryption) var isOO = parsed.type === 'sheet'; var isFile = parsed.hashData.type === 'file'; var isSharedFolder = parsed.type === 'drive'; diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 3e06c4818..8dca48b31 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -2134,9 +2134,7 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null var exportXLSXFile = function() { var text = getContent(); var suggestion = Title.suggestTitle(Title.defaultTitle); - var ext = ['.xlsx', '.ods', '.bin', - //'.csv', // XXX 4.11.0 - '.pdf']; + var ext = ['.xlsx', '.ods', '.bin', '.pdf']; var type = common.getMetadataMgr().getPrivateData().ooType; var warning = ''; if (type==="presentation") { diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 327309f20..901487dbc 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1694,7 +1694,7 @@ define([ var ed = Util.find(store, ['proxy', 'teams', teamId, 'keys', 'drive', 'edPublic']); var edPrivate = Util.find(store, ['proxy', 'teams', teamId, 'keys', 'drive', 'edPrivate']); if (allowed.indexOf(ed) === -1) { return false; } - if (!edPrivate) { return false; } // XXX: Only editors can authenticate... + if (!edPrivate) { return false; } // FIXME: Only editors can authenticate... // This team is allowed: use its rpc var t = teamModule.getTeam(teamId); _store = t; @@ -1952,11 +1952,15 @@ define([ // contactPadOwner is used to send "REQUEST_ACCESS" messages // and to notify form owners when sending a response Store.contactPadOwner = function (clientId, data, cb) { - var owner = data.owner; + var owners = data.owners; // If send is true, send the request to the owner. - if (owner) { - if (data.send) { + if (!Array.isArray(owners) || !owners.length) { return cb({state: false}); } + + if (!data.send) { return void cb({state: true}); } + + nThen(function (waitFor) { + owners.forEach(function (owner) { var sendTo = function (query, msg, user, _cb) { if (store.mailbox && !data.anon) { return store.mailbox.sendTo(query, msg, user, _cb); @@ -1969,14 +1973,11 @@ define([ }, { channel: owner.notifications, curvePublic: owner.curvePublic - }, function () { - cb({state: true}); - }); - return; - } - return void cb({state: true}); - } - cb({state: false}); + }, waitFor()); + }); + }).nThen(function () { + cb({state: true}); + }); }; Store.givePadAccess = function (clientId, data, cb) { var edPublic = store.proxy.edPublic; diff --git a/www/common/outer/calendar.js b/www/common/outer/calendar.js index c09208110..486e10dd9 100644 --- a/www/common/outer/calendar.js +++ b/www/common/outer/calendar.js @@ -8,9 +8,10 @@ define([ '/customize/messages.js', '/bower_components/nthen/index.js', 'chainpad-listmap', + '/lib/datepicker/flatpickr.js', '/bower_components/chainpad-crypto/crypto.js', '/bower_components/chainpad/chainpad.dist.js', -], function (Util, Hash, Constants, Realtime, Cache, Rec, Messages, nThen, Listmap, Crypto, ChainPad) { +], function (Util, Hash, Constants, Realtime, Cache, Rec, Messages, nThen, Listmap, FP, Crypto, ChainPad) { var Calendar = {}; var getStore = function (ctx, id) { @@ -131,9 +132,9 @@ define([ var last = ctx.store.data.lastVisit; if (ev.isAllDay) { - if (ev.startDay) { ev.start = +new Date(ev.startDay); } + if (ev.startDay) { ev.start = +FP.parseDate(ev.startDay); } if (ev.endDay) { - var endDate = new Date(ev.endDay); + var endDate = FP.parseDate(ev.endDay); endDate.setHours(23); endDate.setMinutes(59); endDate.setSeconds(59); @@ -223,7 +224,7 @@ define([ }; var addReminders = function (ctx, id, ev) { var calendar = ctx.calendars[id]; - if (!ev) { return; } // XXX deleted event remote: delete reminders + if (!ev) { return; } if (!calendar || !calendar.reminders) { return; } if (calendar.stores.length === 1 && calendar.stores[0] === 0) { return; } @@ -1063,7 +1064,6 @@ define([ Calendar.init = function (cfg, waitFor, emit) { var calendar = {}; var store = cfg.store; - //if (!store.loggedIn || !store.proxy.edPublic) { return; } // XXX logged in only? we should al least allow read-only for URL calendars var ctx = { loggedIn: store.loggedIn && store.proxy.edPublic, store: store, diff --git a/www/common/outer/roster.js b/www/common/outer/roster.js index 93f0880f4..0b72dd6a4 100644 --- a/www/common/outer/roster.js +++ b/www/common/outer/roster.js @@ -470,7 +470,6 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto, Feedback) delete clone.previewChannel; members[curve] = clone; - // XXX var remaining = members[author].remaining || 1; if (remaining === -1) { return true; } // Infinite uses, keep the link if (remaining > 1) { // Remove 1 use diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index 0a9a6c407..84d64401e 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -856,7 +856,7 @@ define([ } } if (!Hash.isValidChannel(el.channel)) { - // XXX delete channel? replace with parsed.channel? + // FIXME delete channel? replace with parsed.channel? console.error('Remove invalid channel', el.channel, el); // toClean.push(id); } diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js index b4c985dc9..60147c245 100644 --- a/www/common/proxy-manager.js +++ b/www/common/proxy-manager.js @@ -249,7 +249,7 @@ define([ var obj = Util.clone(proxy.metadata || {}); for (var k in Env.user.proxy[UserObject.SHARED_FOLDERS][id] || {}) { - if (typeof(Env.user.proxy[UserObject.SHARED_FOLDERS][id][k]) === "undefined") { // XXX "deleted folder" for restricted shared folders when viewer in a team + if (typeof(Env.user.proxy[UserObject.SHARED_FOLDERS][id][k]) === "undefined") { // TODO "deleted folder" for restricted shared folders when viewer in a team continue; } var data = Util.clone(Env.user.proxy[UserObject.SHARED_FOLDERS][id][k]); diff --git a/www/common/sframe-common-codemirror.js b/www/common/sframe-common-codemirror.js index 316a49da5..e245d9cff 100644 --- a/www/common/sframe-common-codemirror.js +++ b/www/common/sframe-common-codemirror.js @@ -140,10 +140,15 @@ define([ return text.trim(); }; + var isMobile = /Android|iPhone/i.test(navigator.userAgent); + module.mkIndentSettings = function (editor, metadataMgr) { var setIndentation = function (units, useTabs, fontSize, spellcheck, brackets) { if (typeof(units) !== 'number') { return; } var doc = editor.getDoc(); + if (isMobile && fontSize < 16) { + fontSize = 16; + } editor.setOption('indentUnit', units); editor.setOption('tabSize', units); editor.setOption('indentWithTabs', useTabs); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 219dc915d..038fd4412 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -943,6 +943,50 @@ define([ }, href); }); + sframeChan.on('Q_ACCEPT_OWNERSHIP', function (data, cb) { + var parsed = Utils.Hash.parsePadUrl(data.href); + if (parsed.type === 'drive') { + // Shared folder + var secret = Utils.Hash.getSecrets(parsed.type, parsed.hash, data.password); + Cryptpad.addSharedFolder(null, secret, cb); + } else { + var _data = { + password: data.password, + href: data.href, + channel: data.channel, + title: data.title, + owners: data.metadata ? data.metadata.owners : data.owners, + expire: data.metadata ? data.metadata.expire : data.expire, + forceSave: true + }; + Cryptpad.setPadTitle(_data, function (err) { + cb({error: err}); + }); + } + + // Also add your mailbox to the metadata object + var padParsed = Utils.Hash.parsePadUrl(data.href); + var padSecret = Utils.Hash.getSecrets(padParsed.type, padParsed.hash, data.password); + var padCrypto = Utils.Crypto.createEncryptor(padSecret.keys); + try { + var value = {}; + value[edPublic] = padCrypto.encrypt(JSON.stringify({ + notifications: notifications, + curvePublic: curvePublic + })); + var msg = { + channel: data.channel, + command: 'ADD_MAILBOX', + value: value + }; + Cryptpad.setPadMetadata(msg, function (res) { + if (res.error) { console.error(res.error); } + }); + } catch (err) { + return void console.error(err); + } + }); + // Add or remove our mailbox from the list if we're an owner sframeChan.on('Q_UPDATE_MAILBOX', function (data, cb) { var metadata = data.metadata; @@ -1024,7 +1068,7 @@ define([ } var send = data.send; var metadata = data.metadata; - var owner, owners; + var owners = []; var _secret = secret; if (metadata && metadata.roHref) { var _parsed = Utils.Hash.parsePadUrl(metadata.roHref); @@ -1037,24 +1081,24 @@ define([ nThen(function (waitFor) { // Try to get the owner's mailbox from the pad metadata first. var todo = function (obj) { - owners = obj.owners; - - var mailbox; - // Get the first available mailbox (the field can be an string or an object) - // TODO maybe we should send the request to all the owners? - if (typeof (obj.mailbox) === "string") { - mailbox = obj.mailbox; - } else if (obj.mailbox && obj.owners && obj.owners.length) { - mailbox = obj.mailbox[obj.owners[0]]; - } - if (mailbox) { + var decrypt = function (mailbox) { try { var dataStr = crypto.decrypt(mailbox, true, true); var data = JSON.parse(dataStr); if (!data.notifications || !data.curvePublic) { return; } - owner = data; + return data; } catch (e) { console.error(e); } + }; + if (typeof (obj.mailbox) === "string") { + owners = [decrypt(obj.mailbox)]; + return; } + if (!obj.mailbox || !obj.owners || !obj.owners.length) { return; } + owners = obj.owners.map(function (edPublic) { + var mailbox = obj.mailbox[edPublic]; + if (typeof(mailbox) !== "string") { return; } + return decrypt(mailbox); + }).filter(Boolean); }; // If we already have metadata, use it, otherwise, try to get it @@ -1069,7 +1113,7 @@ define([ })); }).nThen(function () { // If we are just checking (send === false) and there is a mailbox field, cb state true - if (!send) { return void cb({state: Boolean(owner)}); } + if (!send) { return void cb({state: Boolean(owners.length)}); } Cryptpad.padRpc.contactOwner({ send: send, @@ -1077,7 +1121,6 @@ define([ query: data.query, msgData: data.msgData, channel: _secret.channel, - owner: owner, owners: owners }, cb); }); @@ -1238,50 +1281,6 @@ define([ }); }); - sframeChan.on('Q_ACCEPT_OWNERSHIP', function (data, cb) { - var parsed = Utils.Hash.parsePadUrl(data.href); - if (parsed.type === 'drive') { - // Shared folder - var secret = Utils.Hash.getSecrets(parsed.type, parsed.hash, data.password); - Cryptpad.addSharedFolder(null, secret, cb); - } else { - var _data = { - password: data.password, - href: data.href, - channel: data.channel, - title: data.title, - owners: data.metadata.owners, - expire: data.metadata.expire, - forceSave: true - }; - Cryptpad.setPadTitle(_data, function (err) { - cb({error: err}); - }); - } - - // Also add your mailbox to the metadata object - var padParsed = Utils.Hash.parsePadUrl(data.href); - var padSecret = Utils.Hash.getSecrets(padParsed.type, padParsed.hash, data.password); - var padCrypto = Utils.Crypto.createEncryptor(padSecret.keys); - try { - var value = {}; - value[edPublic] = padCrypto.encrypt(JSON.stringify({ - notifications: notifications, - curvePublic: curvePublic - })); - var msg = { - channel: data.channel, - command: 'ADD_MAILBOX', - value: value - }; - Cryptpad.setPadMetadata(msg, function (res) { - if (res.error) { console.error(res.error); } - }); - } catch (err) { - return void console.error(err); - } - }); - sframeChan.on('Q_IMPORT_MEDIATAG', function (obj, cb) { var key = obj.key; var channel = obj.channel; diff --git a/www/common/test.js b/www/common/test.js index 9600df0e1..eb83ff09c 100644 --- a/www/common/test.js +++ b/www/common/test.js @@ -1,7 +1,6 @@ define([], function () { if (window.__CRYPTPAD_TEST_OBJ_) { return window.__CRYPTPAD_TEST_OBJ_; } /* - // XXX localhost secureiframe fix var out = function () {}; out.options = {}; out.testing = false; diff --git a/www/convert/inner.js b/www/convert/inner.js index 4db50209c..e09b1f718 100644 --- a/www/convert/inner.js +++ b/www/convert/inner.js @@ -294,6 +294,7 @@ define([ Messages.convertPage = "Convert"; // XXX 4.11.0 Messages.convert_hint = "Pick the file you want to convert. The list of output format will be visible afterwards."; // XXX 4.11.0 + Messages.convert_unsupported = "UNSUPPORTED FILE TYPE :("; // XXX var createToolbar = function () { var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle', 'notifications']; @@ -328,7 +329,6 @@ define([ type: 'file' }); APP.$rightside.append([hint, picker]); - Messages.convert_unsupported = "UNSUPPORTED FILE TYPE :("; // XXX $(picker).on('change', function () { APP.$rightside.find('button, div.notice').remove(); diff --git a/www/form/inner.js b/www/form/inner.js index 11f29ba20..d99fb4fef 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -4005,7 +4005,7 @@ define([ $container.empty().append(_content); -// XXX Delete key form_updateMsg +// XXX Delete translation key form_updateMsg if (editable) { var responseMsg = h('div.cp-form-response-msg-container'); var $responseMsg = $(responseMsg).appendTo($container); diff --git a/www/form/main.js b/www/form/main.js index 4ef169869..ccb46bbaf 100644 --- a/www/form/main.js +++ b/www/form/main.js @@ -314,7 +314,7 @@ define([ if (obj && obj.error) { err = obj.error; return; } var messages = obj.messages; if (!messages.length) { - // XXX TODO delete from drive.forms + // TODO delete from drive.forms? return; } if (obj.lastKnownHash !== answer.hash) { return; } diff --git a/www/pad/comments.js b/www/pad/comments.js index 6280a72bf..edfb40957 100644 --- a/www/pad/comments.js +++ b/www/pad/comments.js @@ -79,7 +79,7 @@ define([ Env.metadataMgr.updateMetadata(md); }; - var sendReplyNotification = function(Env, uid) { + var sendReplyNotification = function(Env, uid, mentionedCurve) { if (!Env.comments || !Env.comments.data || !Env.comments.authors) { return; } if (!Env.common.isLoggedIn()) { return; } var thread = Env.comments.data[uid]; @@ -88,8 +88,6 @@ define([ var privateData = Env.metadataMgr.getPrivateData(); var others = {}; - - // XXX mentioned users should be excluded from the list of notified recipients to avoid notifying them twice // Get all the other registered users with a mailbox thread.m.forEach(function(obj) { var u = obj.u; @@ -97,6 +95,9 @@ define([ var author = Env.comments.authors[u]; if (!author || others[u] || !author.notifications || !author.curvePublic) { return; } if (author.curvePublic === userData.curvePublic) { return; } // don't send to yourself + if (Object.keys(mentionedCurve || {}).includes(author.curvePublic)) { + return; // Don't send to mentioned users + } others[u] = { curvePublic: author.curvePublic, comment: obj.m, @@ -203,7 +204,7 @@ define([ }); // Push the content - cb(content); + cb(content, notify); }); $(cancel).click(function(e) { e.stopPropagation(); @@ -525,7 +526,7 @@ define([ $(reply).click(function(e) { e.stopPropagation(); $actions.hide(); - var form = getCommentForm(Env, key, function(val) { + var form = getCommentForm(Env, key, function(val, mentioned) { // Show the "reply" and "resolve" buttons again $(form).closest('.cp-comment-container') .find('.cp-comment-actions').css('display', ''); @@ -551,7 +552,7 @@ define([ }); // Notify other users - sendReplyNotification(Env, key); + sendReplyNotification(Env, key, mentioned); // Send to chainpad updateMetadata(Env); diff --git a/www/pad/inner.js b/www/pad/inner.js index 705350554..1958a1ea0 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -903,6 +903,10 @@ define([ $toc.addClass('hidden'); localHide = true; if (store) { store.put(key, '1'); } + + if (APP.tocScroll) { + APP.tocScroll(); + } }); $(showBtn).click(function () { $toc.removeClass('hidden'); @@ -922,6 +926,23 @@ define([ e.stopPropagation(); if (!obj.el || UIElements.isVisible(obj.el, $contentContainer)) { return; } obj.el.scrollIntoView(); + var $iframe = $('iframe').contents(); + var onScroll = function () { + APP.tocScrollOff(); + }; + APP.tocScrollOff = function () { + delete APP.tocScroll; + delete APP.tocScrollOff; + $iframe.off('scroll', onScroll); + }; + APP.tocScroll = function () { + obj.el.scrollIntoView(); + APP.tocScrollOff(); + }; + //$(window).on('scroll', onScroll); + setTimeout(function () { + $iframe.on('scroll', onScroll); + }); }); a.innerHTML = title; content.push(h('p.cp-pad-toc-'+level, a)); @@ -1003,6 +1024,8 @@ define([ if (scrollMax) { $iframe.scrollTop($iframe.innerHeight()); } + + if (APP.tocScrollOff) { APP.tocScrollOff(); } }); framework.setTextContentGetter(function() { @@ -1017,6 +1040,8 @@ define([ return str; }); framework.setContentGetter(function() { + if (APP.tocScrollOff) { APP.tocScrollOff(); } + $inner.find('span[data-cke-display-name="media-tag"]:empty').each(function(i, el) { $(el).remove(); }); diff --git a/www/secureiframe/main.js b/www/secureiframe/main.js index 8c33a2ff1..5ba4b7657 100644 --- a/www/secureiframe/main.js +++ b/www/secureiframe/main.js @@ -28,7 +28,7 @@ define([ }; window.rc = requireConfig; window.apiconf = ApiConfig; - // XXX extra sandboxing features are temporarily disabled as I suspect this is the cause of a regression in Safari + // FIXME extra sandboxing features are temporarily disabled as I suspect this is the cause of a regression in Safari $('#sbox-secure-iframe')/*.attr('sandbox', 'allow-scripts allow-popups allow-modals')*/.attr('src', ApiConfig.httpSafeOrigin + '/secureiframe/inner.html?' + requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req))); diff --git a/www/slide/app-slide.less b/www/slide/app-slide.less index ce12b1de3..1c246f8b3 100644 --- a/www/slide/app-slide.less +++ b/www/slide/app-slide.less @@ -147,6 +147,16 @@ } } + @media (max-width: @browser_media-medium-screen) { + #cp-app-slide-editor { + #cp-app-slide-editor-container { + .CodeMirror-sizer > div { + padding-bottom: 100px; + } + } + } + } + /* Slide position (print mode) */ @ratio:0.9; @media print {