diff --git a/customize.dist/fonts/cptools/fonts/cptools.svg b/customize.dist/fonts/cptools/fonts/cptools.svg index f7fe0879f..93eef8d38 100644 --- a/customize.dist/fonts/cptools/fonts/cptools.svg +++ b/customize.dist/fonts/cptools/fonts/cptools.svg @@ -25,4 +25,5 @@ + \ No newline at end of file diff --git a/customize.dist/fonts/cptools/fonts/cptools.ttf b/customize.dist/fonts/cptools/fonts/cptools.ttf index 1dac2ff87..18338a9ee 100644 Binary files a/customize.dist/fonts/cptools/fonts/cptools.ttf and b/customize.dist/fonts/cptools/fonts/cptools.ttf differ diff --git a/customize.dist/fonts/cptools/fonts/cptools.woff b/customize.dist/fonts/cptools/fonts/cptools.woff index 4f01d5d15..d8f56ba86 100644 Binary files a/customize.dist/fonts/cptools/fonts/cptools.woff and b/customize.dist/fonts/cptools/fonts/cptools.woff differ diff --git a/customize.dist/fonts/cptools/style.css b/customize.dist/fonts/cptools/style.css index 952207f15..349b62f2b 100644 --- a/customize.dist/fonts/cptools/style.css +++ b/customize.dist/fonts/cptools/style.css @@ -1,9 +1,9 @@ @font-face { font-family: 'cptools'; src: - url('fonts/cptools.ttf?yr9e7c') format('truetype'), - url('fonts/cptools.woff?yr9e7c') format('woff'), - url('fonts/cptools.svg?yr9e7c#cptools') format('svg'); + url('fonts/cptools.ttf?cljhos') format('truetype'), + url('fonts/cptools.woff?cljhos') format('woff'), + url('fonts/cptools.svg?cljhos#cptools') format('svg'); font-weight: normal; font-style: normal; } @@ -24,6 +24,9 @@ -moz-osx-font-smoothing: grayscale; } +.cptools-folder-upload:before { + content: "\e912"; +} .cptools-folder-no-color:before { content: "\e900"; } diff --git a/customize.dist/src/less2/include/notifications.less b/customize.dist/src/less2/include/notifications.less index b19a9c86b..f27d6ed60 100644 --- a/customize.dist/src/less2/include/notifications.less +++ b/customize.dist/src/less2/include/notifications.less @@ -1,4 +1,5 @@ @import (reference) "./colortheme-all.less"; +@import (reference) "./avatar.less"; .notifications_main() { --LessLoader_require: LessLoader_currentFile(); @@ -53,6 +54,19 @@ } } } + .cp-notifications-requestedit-verified { + display: flex; + align-items: center; + &> span.cp-avatar { + .avatar_main(30px); + } + &> span { + margin-right: 10px; + } + &> p { + margin: 0; + } + } } diff --git a/customize.dist/src/less2/include/support.less b/customize.dist/src/less2/include/support.less index 56dcccd8f..0a352ec32 100644 --- a/customize.dist/src/less2/include/support.less +++ b/customize.dist/src/less2/include/support.less @@ -44,6 +44,7 @@ } pre { margin-bottom: 0; + white-space: pre-wrap; &.cp-support-message-content { margin-top: 10px; margin-bottom: 10px; diff --git a/package-lock.json b/package-lock.json index 30b43f03e..7a91644fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cryptpad", - "version": "2.23.0", + "version": "2.25.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -573,16 +573,16 @@ "dev": true }, "jshint": { - "version": "2.9.7", - "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.9.7.tgz", - "integrity": "sha512-Q8XN38hGsVQhdlM+4gd1Xl7OB1VieSuCJf+fEJjpo59JH99bVJhXRXAh26qQ15wfdd1VPMuDWNeSWoNl53T4YA==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.10.2.tgz", + "integrity": "sha512-e7KZgCSXMJxznE/4WULzybCMNXNAd/bf5TSrvVEq78Q/K8ZwFpmBqQeDtNiHc3l49nV4E/+YeHU/JZjSUIrLAA==", "dev": true, "requires": { "cli": "~1.0.0", "console-browserify": "1.1.x", "exit": "0.1.x", "htmlparser2": "3.8.x", - "lodash": "~4.17.10", + "lodash": "~4.17.11", "minimatch": "~3.0.2", "shelljs": "0.3.x", "strip-json-comments": "1.0.x" @@ -697,9 +697,9 @@ } }, "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "version": "4.17.14", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", + "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==", "dev": true }, "lodash.clonedeep": { @@ -709,10 +709,9 @@ "dev": true }, "lodash.merge": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz", - "integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==", - "dev": true + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, "lodash.sortby": { "version": "4.7.0", diff --git a/package.json b/package.json index 1a6462d84..585e8b8fc 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ }, "devDependencies": { "flow-bin": "^0.59.0", - "jshint": "~2.9.1", + "jshint": "^2.10.2", "less": "2.7.1", "lesshint": "^4.5.0", "selenium-webdriver": "^3.6.0" diff --git a/www/admin/inner.js b/www/admin/inner.js index e2edb9ad5..4c335dd12 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -199,13 +199,13 @@ define([ // A ticket has been closed by the admins... if (!$ticket.length) { return; } $ticket.addClass('cp-support-list-closed'); - $ticket.append(Support.makeCloseMessage(common, content, hash)); + $ticket.append(APP.support.makeCloseMessage(content, hash)); return; } if (msg.type !== 'TICKET') { return; } if (!$ticket.length) { - $ticket = Support.makeTicket($div, common, content, function () { + $ticket = APP.support.makeTicket($div, content, function () { var error = false; hashesById[id].forEach(function (d) { common.mailbox.dismiss(d, function (err) { @@ -218,7 +218,7 @@ define([ if (!error) { $ticket.remove(); } }); } - $ticket.append(Support.makeMessage(common, content, hash, true)); + $ticket.append(APP.support.makeMessage(content, hash)); } }); return $div; @@ -349,6 +349,7 @@ define([ APP.privateKey = privateData.supportPrivateKey; APP.origin = privateData.origin; APP.readOnly = privateData.readOnly; + APP.support = Support.create(common, true); // Content var $rightside = APP.$rightside; diff --git a/www/code/inner.js b/www/code/inner.js index 9e4d0a207..b061e36fa 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -363,7 +363,15 @@ define([ }); framework.setFileExporter(CodeMirror.getContentExtension, CodeMirror.fileExporter); - framework.setFileImporter({}, CodeMirror.fileImporter); + framework.setFileImporter({}, function () { + /* setFileImporter currently takes a function with the following signature: + (content, file) => {} + I used 'apply' with 'arguments' to avoid breaking things if this API ever changes. + */ + var ret = CodeMirror.fileImporter.apply(null, Array.prototype.slice.call(arguments)); + previewPane.modeChange(ret.mode); + return ret; + }); framework.setNormalizer(function (c) { return { diff --git a/www/common/curve-put.js b/www/common/curve-put.js deleted file mode 100644 index 450b62564..000000000 --- a/www/common/curve-put.js +++ /dev/null @@ -1,51 +0,0 @@ -define([ - '/common/curve.js', - '/bower_components/chainpad-listmap/chainpad-listmap.js', -], function (Curve, Listmap) { - var Edit = {}; - - Edit.create = function (config, cb) { //network, channel, theirs, mine, cb) { - var network = config.network; - var channel = config.channel; - var keys = config.keys; - - try { - var encryptor = Curve.createEncryptor(keys); - var lm = Listmap.create({ - network: network, - data: {}, - channel: channel, - readOnly: false, - validateKey: keys.validateKey || undefined, - crypto: encryptor, - userName: 'lol', - logLevel: 1, - }); - - var done = function () { - // TODO make this abort and disconnect the session after the - // user has finished making changes to the object, and they - // have propagated. - }; - - lm.proxy - .on('create', function () { - console.log('created'); - }) - .on('ready', function () { - console.log('ready'); - cb(lm, done); - }) - .on('disconnect', function () { - console.log('disconnected'); - }) - .on('change', [], function (o, n, p) { - console.log(o, n, p); - }); - } catch (e) { - console.error(e); - } - }; - - return Edit; -}); diff --git a/www/common/notifications.js b/www/common/notifications.js index e57228265..7ab504fb6 100644 --- a/www/common/notifications.js +++ b/www/common/notifications.js @@ -94,6 +94,18 @@ define([ } }; + // New support message from the admins + handlers['SUPPORT_MESSAGE'] = function (common, data) { + var content = data.content; + content.getFormatText = function () { + return Messages.support_notification; + }; + content.handler = function () { + common.openURL('/support/'); + defaultDismiss(common, data)(); + }; + }; + handlers['REQUEST_PAD_ACCESS'] = function (common, data) { var content = data.content; var msg = content.msg; @@ -103,18 +115,50 @@ define([ // Display the notification content.getFormatText = function () { - return 'Edit access request: ' + msg.content.title + ' - ' + msg.content.user.displayName; - }; // XXX + return Messages._getKey('requestEdit_request', [msg.content.title, msg.content.user.displayName]); + }; // if not archived, add handlers content.handler = function () { - UI.confirm("Give edit rights?", function (yes) { + var metadataMgr = common.getMetadataMgr(); + var priv = metadataMgr.getPrivateData(); + + var link = h('a', { + href: '#' + }, Messages.requestEdit_viewPad); + var verified = h('p.cp-notifications-requestedit-verified'); + var $verified = $(verified); + + if (priv.friends && priv.friends[msg.author]) { + var f = priv.friends[msg.author]; + $verified.append(h('span.fa.fa-certificate')); + var $avatar = $(h('span.cp-avatar')).appendTo($verified); + $verified.append(h('p', Messages._getKey('requestEdit_fromFriend', [f.displayName]))); + common.displayAvatar($avatar, f.avatar, f.displayName); + } else { + $verified.append(Messages.requestEdit_fromStranger); + } + + var div = h('div', [ + UI.setHTML(h('p'), Messages._getKey('requestEdit_confirm', [msg.content.title, msg.content.user.displayName])), + verified, + link + ]); + $(link).click(function (e) { + e.preventDefault(); + e.stopPropagation(); + common.openURL(msg.content.href); + }); + UI.confirm(div, function (yes) { if (!yes) { return; } common.getSframeChannel().event('EV_GIVE_ACCESS', { channel: msg.content.channel, user: msg.content.user }); defaultDismiss(common, data)(); + }, { + ok: Messages.friendRequest_accept, + cancel: Messages.later }); }; @@ -134,8 +178,8 @@ define([ // Display the notification content.getFormatText = function () { - return 'Edit access received: ' + msg.content.title + ' from ' + msg.content.user.displayName; - }; // XXX + return Messages._getKey('requestEdit_accepted', [msg.content.title, msg.content.user.displayName]); + }; // if not archived, add handlers content.handler = function () { diff --git a/www/common/onlyoffice/app-oo.less b/www/common/onlyoffice/app-oo.less index 60510914d..c7bd64e01 100644 --- a/www/common/onlyoffice/app-oo.less +++ b/www/common/onlyoffice/app-oo.less @@ -44,6 +44,7 @@ body.cp-app-sheet, body.cp-app-oodoc, body.cp-app-ooslide { height: 100%; background-color: lightgrey; display: flex; + min-height: 0; } #cp-app-oo-editor { flex: 1; diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js index 42997b3e0..9d383caa1 100644 --- a/www/common/outer/mailbox-handlers.js +++ b/www/common/outer/mailbox-handlers.js @@ -201,6 +201,14 @@ define([ } }; + // Hide duplicates when receiving a SUPPORT_MESSAGE notification + var supportMessage = false; + handlers['SUPPORT_MESSAGE'] = function (ctx, box, data, cb) { + if (supportMessage) { return void cb(true); } + supportMessage = true; + cb(); + }; + // Incoming edit rights request: add data before sending it to inner handlers['REQUEST_PAD_ACCESS'] = function (ctx, box, data, cb) { var msg = data.msg; @@ -214,17 +222,19 @@ define([ if (!res.length) { return void cb(true); } var edPublic = ctx.store.proxy.edPublic; - var title; + var title, href; if (!res.some(function (obj) { if (obj.data && Array.isArray(obj.data.owners) && obj.data.owners.indexOf(edPublic) !== -1 && obj.data.href) { + href = obj.data.href; title = obj.data.filename || obj.data.title; return true; } })) { return void cb(true); } content.title = title; + content.href = href; cb(false); }; diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js index 24d14345f..081f64cc3 100644 --- a/www/common/outer/mailbox.js +++ b/www/common/outer/mailbox.js @@ -245,8 +245,11 @@ proxy.mailboxes = { }); box.queue = []; }; + var lastReceivedHash; // Don't send a duplicate of the last known hash on reconnect box.onMessage = cfg.onMessage = function (msg, user, vKey, isCp, hash, author) { if (hash === m.lastKnownHash) { return; } + if (hash === lastReceivedHash) { return; } + lastReceivedHash = hash; try { msg = JSON.parse(msg); } catch (e) { @@ -364,6 +367,7 @@ proxy.mailboxes = { txid: txid, complete: true }, [req.cId]); + delete ctx.req[txid]; } }); }; diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index e12883eb5..4f235366a 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -506,8 +506,14 @@ define([ var fixRoot = function (elem) { if (typeof(files[ROOT]) !== "object") { debug("ROOT was not an object"); files[ROOT] = {}; } var element = elem || files[ROOT]; + if (!element) { return console.error("Invalid element in root"); } var nbMetadataFolders = 0; for (var el in element) { + if (element[el] === null) { + console.error('element[%s] is null', el); + delete element[el]; + continue; + } if (exp.isFolderData(element[el])) { if (nbMetadataFolders !== 0) { debug("Multiple metadata files in folder"); diff --git a/www/common/sframe-common-codemirror.js b/www/common/sframe-common-codemirror.js index ebf7d752e..8a0e2de63 100644 --- a/www/common/sframe-common-codemirror.js +++ b/www/common/sframe-common-codemirror.js @@ -323,7 +323,7 @@ define([ var mode; if (!mime) { var ext = /.+\.([^.]+)$/.exec(file.name); - if (ext[1]) { + if (ext && ext[1]) { mode = CMeditor.findModeByExtension(ext[1]); mode = mode && mode.mode || null; } @@ -339,7 +339,8 @@ define([ exp.setMode('text'); $toolbarContainer.find('#language-mode').val('text'); } - return { content: content }; + // return the mode so that the code editor can decide how to display the new content + return { content: content, mode: mode }; }; exp.setValueAndCursor = function (oldDoc, remoteDoc) { diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index 53aa93f4f..6c262e2d1 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -585,7 +585,7 @@ MessengerUI, Messages) { var $requestBlock = $('