diff --git a/CHANGELOG.md b/CHANGELOG.md index a056e5d56..ea5909dab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,51 @@ -# HimalayanQuail (3.7.0) +# IsolobodonPortoricensis release (3.8.0) + +We had some trouble finding an extinct animal whose name started with "I", and we had to resort to using a scientific name. +Despite this long name, this was a very short release cycle. +It's the last release of 2019, so we hope you like it! + +## Goals + +During this release cycle we prioritized the mitigation of some social abuse vectors and the ability to invite users to a team via a link. +We have more improvements planned for both features, but we wanted to release what we had before the end of the year as our team is taking a little time off to recharge for 2020. + +## Update notes + +This is a small and simple release. We made a very minor improvement to the server which will require a restart, but everything will still work if you choose not to. + +Update from 3.7.0 to 3.8.0 with the following procedure: + +1. Take your server down +2. Get the latest code with `git pull origin master` +3. Bring your server back up + +Or if you've set up your admin interface: + +1. Pull the latest code +2. Click the admin panel's "Flush cache" button + +## Features + +* We updated a bunch of styles to improve the platform's visual consistency: + * prettier buttons + * elimination of rounded corners on buttons, text inputs, and password inputs +* We've fixed the default styles on embedded media while their content is loading +* The button to add a user as a contact on their profile page now has a more prominent position at the top of the page +* Users also have the option of muting other people via their profile page. + * these users will not know that you've muted them. + * you can review the complete list of all the people you've muted on your contacts page + * you can mute or unmute from the contacts page as well as their profile + * changes to a user's mute status propagate across pages in real-time +* Some of our Finnish-speaking users have become contributors via our weblate instance (https://weblate.cryptpad.fr/) + * we're always looking for more translators to help more people protect their data, so don't hesitate to contact us if you want to help +* Finally, it's now possible to invite users to a team by creating and sharing a personalized one-time-use link. + * team owners and admins can try it out via their teams' "Members" tab + +## Bug fixes + +* We've fixed a few subtle bugs where various contact status and our one-to-one chat functionality could get into a bad state. + +# HimalayanQuail release (3.7.0) ## Goals diff --git a/customize.dist/pages.js b/customize.dist/pages.js index 0bb596ec8..281f213f6 100644 --- a/customize.dist/pages.js +++ b/customize.dist/pages.js @@ -103,7 +103,7 @@ define([ ])*/ ]) ]), - h('div.cp-version-footer', "CryptPad v3.7.0 (HimalayanQuail)") + h('div.cp-version-footer', "CryptPad v3.8.0 (IsolobodonPortoricensis)") ]); }; diff --git a/customize.dist/src/less2/include/alertify.less b/customize.dist/src/less2/include/alertify.less index 4bd5e298b..a2b787e1c 100644 --- a/customize.dist/src/less2/include/alertify.less +++ b/customize.dist/src/less2/include/alertify.less @@ -3,6 +3,7 @@ @import (reference) "./variables.less"; @import (reference) "./avatar.less"; @import (reference) "./tools.less"; +@import (reference) "./buttons.less"; .alertify_main() { --LessLoader_require: LessLoader_currentFile(); @@ -71,6 +72,10 @@ z-index: 100000; // alertify container font: @colortheme_app-font; + .cp-inline-alert-text { + flex: 1; + } + &.forefront { z-index: @max-z-index; // alertify max forefront } @@ -221,28 +226,6 @@ ::-ms-input-placeholder { /* Microsoft Edge */ color: @cryptpad_color_grey; } - input:not(.form-control), textarea { - background-color: @alertify-input-fg; - color: @cryptpad_text_col; - border: 1px solid @alertify-input-bg; - margin-bottom: @alertify_padding-base; - width: 100%; - font-size: 100%; - padding: @alertify_padding-base; - &[readonly] { - background-color: @alertify-light-bg; - color: @cryptpad_text_col; - border-color: @alertify-light-bg; - } - } - - textarea { - overflow: hidden; - padding: 8px; - &[readonly] { - resize: none; - } - } span.cp-password-container { display: flex; @@ -257,6 +240,12 @@ } } + .fa-question-circle { // help links to FAQ + color: @colortheme_logo-2; + &:hover { + text-decoration: none; + } + } input[type="checkbox"], input[type="radio"] { width: auto; @@ -271,99 +260,17 @@ } } - button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button) { + .buttons_main(); + input:not(.form-control), textarea { + margin-bottom: 15px; + } - background-color: @colortheme_alertify-cancel; - box-sizing: border-box; - position: relative; - outline: 0; + button { display: inline-block; - align-items: center; - padding: 0 6px; + position: relative; margin: 6px 8px; - line-height: 36px; min-height: 36px; - white-space: nowrap; min-width: 88px; - text-align: center; - text-transform: uppercase; - font-size: 14px; - text-decoration: none; - cursor: pointer; - border-radius: 0; - - color: @alertify-btn-fg; - border: 1px solid @alertify-btn-fg; - - &.no-margin { - margin: 0; - } - - &:hover, &:active { - background-color: @alertify-light-bg; - } - - &.safe, &.danger { - color: @colortheme_old-base; - white-space: normal; - font-weight: bold; - } - &.danger { - background-color: @colortheme_alertify-red; - border-color: @colortheme_alertify-red-border; - color: @colortheme_alertify-red-color; - &:hover, &:active { - background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-red, 10%), lighten(@colortheme_alertify-red, 10%)); - } - } - - &.safe { - background-color: @colortheme_alertify-green; - border-color: @colortheme_alertify-green-border; - color: @colortheme_alertify-green-color; - &:hover, &:active { - background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-green, 10%), lighten(@colortheme_alertify-green, 10%)); - } - } - - &.primary { - background-color: @colortheme_alertify-primary; - color: @colortheme_alertify-primary-text; - border-color: @colortheme_alertify-primary-border; - font-weight: bold; - &:hover, &:active { - background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-primary, 10%), lighten(@colortheme_alertify-primary, 10%)); - } - } - - &.cancel { - border-color: @colortheme_alertify-cancel-border; - color: @colortheme_alertify-cancel-border; - &:hover, &:hover { - background-color: fade(@colortheme_alertify-cancel-border, 25%); - } - } - - - - &:focus { - //border: 1px dotted @alertify-base; - box-shadow: 0px 0px 5px @colortheme_alertify-primary; - outline: none; - } - &::-moz-focus-inner { - border: 0; - } - - &:disabled { - cursor: not-allowed !important; - background-color: @colortheme_alertify-disabled; - color: @colortheme_alertify-disabled-text; - border-color: @colortheme_alertify-disabled-border; - &:hover, &:active { - background-color: @colortheme_alertify-disabled; - } - } } nav { @@ -371,8 +278,8 @@ text-align: right; button { margin: 0px !important; - &:not(:last-child) { - margin-right: @alertify_padding-base !important; + &:not(:first-child) { + margin-left: @alertify_padding-base !important; } } } diff --git a/customize.dist/src/less2/include/buttons.less b/customize.dist/src/less2/include/buttons.less new file mode 100644 index 000000000..f3cb50e17 --- /dev/null +++ b/customize.dist/src/less2/include/buttons.less @@ -0,0 +1,129 @@ +@import (reference) "./colortheme-all.less"; +@import (reference) "./variables.less"; + +.buttons_main() { + @alertify-fore: @colortheme_modal-fg; + @alertify-btn-fg: @alertify-fore; + @alertify-light-bg: fade(@alertify-fore, 25%); + @alertify_padding-base: @variables_padding; + @alertify-input-bg: @colortheme_modal-input; + @alertify-input-fg: @colortheme_modal-input-fg; + + input:not(.form-control), textarea { + background-color: @alertify-input-fg; + color: @cryptpad_text_col; + border: 1px solid @alertify-input-bg; + width: 100%; + font-size: 100%; + padding: @alertify_padding-base; + &[readonly] { + background-color: @alertify-light-bg; + color: @cryptpad_text_col; + border-color: @alertify-input-fg; + } + } + + textarea { + overflow: hidden; + padding: 8px; + &[readonly] { + resize: none; + } + } + + + button:not(.pure-button):not(.md-button):not(.mdl-button) { + + background-color: @colortheme_alertify-cancel; + box-sizing: border-box; + outline: 0; + align-items: center; + padding: 0 6px; + line-height: 36px; + white-space: nowrap; + text-align: center; + text-transform: uppercase; + font-size: 14px; + text-decoration: none; + cursor: pointer; + border-radius: 0; + + .fa { + margin-right: 0.2em; + } + + color: @alertify-btn-fg; + border: 1px solid @alertify-btn-fg; + + &.no-margin { + margin: 0; + } + + &:hover, &:active { + background-color: lighten(@alertify-fore, 35%); + } + + &.safe, &.danger, &.btn-safe, &.btn-danger { + color: @colortheme_old-base; + white-space: normal; + font-weight: bold; + } + &.danger, &.btn-danger { + background-color: @colortheme_alertify-red; + border-color: @colortheme_alertify-red-border; + color: @colortheme_alertify-red-color; + &:hover, &:active { + background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-red, 10%), lighten(@colortheme_alertify-red, 10%)); + } + } + + &.safe, &.btn-safe { + background-color: @colortheme_alertify-green; + border-color: @colortheme_alertify-green-border; + color: @colortheme_alertify-green-color; + &:hover, &:active { + background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-green, 10%), lighten(@colortheme_alertify-green, 10%)); + } + } + + &.primary, &.btn-primary { + background-color: @colortheme_alertify-primary; + color: @colortheme_alertify-primary-text; + border-color: @colortheme_alertify-primary-border; + font-weight: bold; + &:hover, &:active { + background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-primary, 10%), lighten(@colortheme_alertify-primary, 10%)); + } + } + + &.cancel, &.btn-cancel { + border-color: @colortheme_alertify-cancel-border; + color: @colortheme_alertify-cancel-border; + &:hover, &:hover { + background-color: fade(@colortheme_alertify-cancel-border, 25%); + } + } + + + + &:focus { + //border: 1px dotted @alertify-base; + box-shadow: 0px 0px 5px @colortheme_alertify-primary; + outline: none; + } + &::-moz-focus-inner { + border: 0; + } + + &:disabled { + cursor: not-allowed !important; + background-color: @colortheme_alertify-disabled; + color: @colortheme_alertify-disabled-text; + border-color: @colortheme_alertify-disabled-border; + &:hover, &:active { + background-color: @colortheme_alertify-disabled; + } + } + } + +} diff --git a/customize.dist/src/less2/include/mediatag.less b/customize.dist/src/less2/include/mediatag.less index 704fd71bd..f8255aab3 100644 --- a/customize.dist/src/less2/include/mediatag.less +++ b/customize.dist/src/less2/include/mediatag.less @@ -7,6 +7,13 @@ text-align: center; } + media-tag:empty { + width: 100px; + height: 100px; + display: inline-block; + border: 1px solid #BBB; + } + media-tag img { flex: 1; max-height: 100% !important; diff --git a/customize.dist/src/less2/include/messenger.less b/customize.dist/src/less2/include/messenger.less index 7f4f7c342..ddf1540c9 100644 --- a/customize.dist/src/less2/include/messenger.less +++ b/customize.dist/src/less2/include/messenger.less @@ -78,6 +78,9 @@ .cp-app-contacts-name { white-space: nowrap; } + .cp-app-contacts-icons { + text-align: right; + } } &:hover { background-color: rgba(0,0,0,0.3); @@ -89,6 +92,7 @@ .cp-app-contacts-remove { cursor: pointer; width: 20px; + text-align: center; &:hover { color: darken(@color, 20%); } @@ -121,8 +125,30 @@ } } } + .cp-app-contacts-muted-button { + margin: 10px; + border: 0; + display: none; + order: 3; + .fa-bell-slash { + margin-right: 10px; + } + } } } + + .cp-contacts-muted-table { + .cp-contacts-muted-user { + margin-bottom: 5px; + .cp-avatar { + margin-right: 10px; + } + button { + margin-right: 0px; + } + } + } + #cp-app-contacts-container.cp-app-contacts-inapp { #cp-app-contacts-friendlist { display: none; diff --git a/customize.dist/src/less2/include/modals-ui-elements.less b/customize.dist/src/less2/include/modals-ui-elements.less index 21707eab8..289cc4e69 100644 --- a/customize.dist/src/less2/include/modals-ui-elements.less +++ b/customize.dist/src/less2/include/modals-ui-elements.less @@ -28,4 +28,26 @@ .cp-app-prop-content { color: @cryptpad_text_col; } + + // teams invite modal + .cp-teams-invite-block { + display: flex; + align-items: center; + margin-bottom: 5px; + } + .cp-teams-invite-message { + resize: none; + } + .cp-teams-invite-alert { + margin-top: 10px; + } + .cp-teams-invite-spinner { + font-size: 1.2em; + .fa { + margin-right: 10px;; + } + } + .cp-teams-help { + margin-left: 10px; + } } diff --git a/customize.dist/src/less2/include/sidebar-layout.less b/customize.dist/src/less2/include/sidebar-layout.less index 24228cfaf..1f9c92457 100644 --- a/customize.dist/src/less2/include/sidebar-layout.less +++ b/customize.dist/src/less2/include/sidebar-layout.less @@ -1,5 +1,6 @@ @import (reference) "/customize/src/less2/include/colortheme-all.less"; @import (reference) "/customize/src/less2/include/leftside-menu.less"; +@import (reference) "/customize/src/less2/include/buttons.less"; @sidebar_button-width: 400px; @@ -95,9 +96,11 @@ } } margin-bottom: 20px; + .buttons_main(); } [type="text"], [type="password"], button { vertical-align: middle; + min-width: 40px; height: 40px; box-sizing: border-box; } @@ -106,12 +109,12 @@ width: @sidebar_button-width; input { flex: 1; - border-radius: 0.25em 0 0 0.25em; + //border-radius: 0.25em 0 0 0.25em; border: 1px solid #adadad; border-right: 0px; } button { - border-radius: 0 0.25em 0.25em 0; + //border-radius: 0 0.25em 0.25em 0; //border: 1px solid #adadad; border-left: 0px; } @@ -119,6 +122,13 @@ &>div { margin: 10px 0; } + button.btn { + margin: 0 5px 0 0; + } + span.cp-password-container { + margin-bottom: 1px; + } +/* button.btn { @button-bg: @colortheme_sidebar-button-bg; @button-red-bg: @colortheme_sidebar-button-red-bg; @@ -126,6 +136,9 @@ background-color: @button-bg; border-color: darken(@button-bg, 10%); color: white; + .fa { + margin-right: 0.2em; + } &:hover { background-color: darken(@button-bg, 10%); } @@ -146,6 +159,7 @@ } } } +*/ } } } diff --git a/package-lock.json b/package-lock.json index 6643e22c7..08328fcb1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cryptpad", - "version": "3.7.0", + "version": "3.8.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ea1431153..835641d6b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cryptpad", "description": "realtime collaborative visual editor with zero knowlege server", - "version": "3.7.0", + "version": "3.8.0", "license": "AGPL-3.0+", "repository": { "type": "git", diff --git a/server.js b/server.js index 26238384b..c47c4e450 100644 --- a/server.js +++ b/server.js @@ -214,7 +214,7 @@ app.get('/api/config', function(req, res){ // FIXME don't send websocketURL if websocketPath is provided. deprecated. websocketURL:'ws' + ((useSecureWebsockets) ? 's' : '') + '://' + host + ':' + websocketPort + '/cryptpad_websocket', - httpUnsafeOrigin: config.httpUnsafeOrigin, + httpUnsafeOrigin: config.httpUnsafeOrigin.replace(/^\s*/, ''), adminEmail: config.adminEmail, adminKeys: admins, inactiveTime: config.inactiveTime, diff --git a/www/code/app-code.less b/www/code/app-code.less index f07ef534d..219143a34 100644 --- a/www/code/app-code.less +++ b/www/code/app-code.less @@ -98,6 +98,13 @@ max-height: 90vh; } } + media-tag:empty { + width: 100px; + height: 100px; + display: inline-block; + border: 1px solid #BBB; + } + .markdown_main(); .cp-app-code-preview-empty { display: none; diff --git a/www/common/application_config_internal.js b/www/common/application_config_internal.js index 72897680c..d4c1954e4 100644 --- a/www/common/application_config_internal.js +++ b/www/common/application_config_internal.js @@ -20,7 +20,7 @@ define(function() { * users and these users will be redirected to the login page if they still try to access * the app */ - config.registeredOnlyTypes = ['teams', 'file', 'contacts', 'oodoc', 'ooslide', 'sheet', 'notifications']; + config.registeredOnlyTypes = ['file', 'contacts', 'oodoc', 'ooslide', 'sheet', 'notifications']; /* CryptPad is available is multiple languages, but only English and French are maintained * by the developers. The other languages may be outdated, and any missing string for a langauge diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 98330367f..4ed1193e3 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -231,10 +231,14 @@ Version 1 } if (['invite'].indexOf(type) !== -1) { parsed.type = 'invite'; - if (hashArr[1] && hashArr[1] === '1') { - parsed.version = 1; - parsed.channel = hashArr[2]; - parsed.pubkey = hashArr[3].replace(/-/g, '/'); + if (hashArr[1] && hashArr[1] === '2') { + parsed.version = 2; + parsed.app = hashArr[2]; + parsed.mode = hashArr[3]; + parsed.key = hashArr[4]; + + options = hashArr.slice(5); + parsed.password = options.indexOf('p') !== -1; return parsed; } return parsed; diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 8dc14cc8c..4528b7a1b 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -475,7 +475,7 @@ define([ opt = opt || {}; var inputBlock = opt.password ? UI.passwordInput() : dialog.textInput(); - var input = opt.password ? $(inputBlock).find('input')[0] : inputBlock; + var input = $(inputBlock).is('input') ? inputBlock : $(inputBlock).find('input')[0]; input.value = typeof(def) === 'string'? def: ''; var message; @@ -592,6 +592,7 @@ define([ }]; var modal = dialog.customModal(content, {buttons: buttons}); UI.openCustomModal(modal); + return modal; }; UI.log = function (msg) { diff --git a/www/common/common-messaging.js b/www/common/common-messaging.js index 91322f526..feb3d79d3 100644 --- a/www/common/common-messaging.js +++ b/www/common/common-messaging.js @@ -84,6 +84,7 @@ define([ var myData = createData(store.proxy, false); if (store.proxy.friends) { store.proxy.friends.me = myData; + delete store.proxy.friends.me.channel; } if (store.modules['team']) { store.modules['team'].updateMyData(myData); diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index d9610c822..92f47648b 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -14,11 +14,13 @@ define([ '/customize/application_config.js', '/customize/pages.js', '/bower_components/nthen/index.js', + '/common/invitation.js', + 'css!/customize/fonts/cptools/style.css', '/bower_components/croppie/croppie.min.js', 'css!/bower_components/croppie/croppie.css', ], function ($, Config, Util, Hash, Language, UI, Constants, Feedback, h, MediaTag, Clipboard, - Messages, AppConfig, Pages, NThen) { + Messages, AppConfig, Pages, NThen, InviteInner) { var UIElements = {}; // Configure MediaTags to use our local viewer @@ -1557,8 +1559,11 @@ define([ var team = privateData.teams[config.teamId]; if (!team) { return void UI.warn(Messages.error); } + var origin = privateData.origin; + var module = config.module || common.makeUniversal('team'); + // Invite contacts var $div; var refreshButton = function () { if (!$div) { return; } @@ -1572,48 +1577,226 @@ define([ $btn.prop('disabled', 'disabled'); } }; - var list = UIElements.getUserGrid(Messages.team_pickFriends, { - common: common, - data: config.friends, - large: true - }, refreshButton); - $div = $(list.div); - refreshButton(); + var getContacts = function () { + var list = UIElements.getUserGrid(Messages.team_pickFriends, { + common: common, + data: config.friends, + large: true + }, refreshButton); + var div = h('div.contains-nav'); + var $div = $(div); + $div.append(list.div); + var contactsButtons = [{ + className: 'primary', + name: Messages.team_inviteModalButton, + onClick: function () { + var $sel = $div.find('.cp-usergrid-user.cp-selected'); + var sel = $sel.toArray(); + if (!sel.length) { return; } - var buttons = [{ + sel.forEach(function (el) { + var curve = $(el).attr('data-curve'); + module.execCommand('INVITE_TO_TEAM', { + teamId: config.teamId, + user: config.friends[curve] + }, function (obj) { + if (obj && obj.error) { + console.error(obj.error); + return UI.warn(Messages.error); + } + }); + }); + }, + keys: [13] + }]; + + return { + content: div, + buttons: contactsButtons + }; + }; + var friendsObject = hasFriends ? getContacts() : noContactsMessage(common); + var friendsList = friendsObject.content; + var contactsButtons = friendsObject.buttons; + contactsButtons.unshift({ + className: 'cancel', + name: Messages.cancel, + onClick: function () {}, + keys: [27] + }); + + var contactsContent = h('div.cp-share-modal', [ + friendsList + ]); + + var frameContacts = UI.dialog.customModal(contactsContent, { + buttons: contactsButtons, + }); + + var linkName, linkPassword, linkMessage, linkError, linkSpinText; + var linkForm, linkSpin, linkResult; + var linkWarning; + // Invite from link + var dismissButton = h('span.fa.fa-times'); + var linkContent = h('div.cp-share-modal', [ + h('p', Messages.team_inviteLinkTitle ), + linkError = h('div.alert.alert-danger.cp-teams-invite-alert', {style : 'display: none;'}), + linkForm = h('div.cp-teams-invite-form', [ + linkName = h('input', { + placeholder: Messages.team_inviteLinkTempName + }), + h('br'), + h('div.cp-teams-invite-block', [ + h('span', Messages.team_inviteLinkSetPassword), + h('a.cp-teams-help.fa.fa-question-circle', { + href: origin + '/faq.html#security-pad_password', + target: "_blank", + 'data-tippy-placement': "right" + }) + ]), + linkPassword = UI.passwordInput({ + id: 'cp-teams-invite-password', + placeholder: Messages.login_password + }), + h('div.cp-teams-invite-block', + h('span', Messages.team_inviteLinkNote) + ), + linkMessage = h('textarea.cp-teams-invite-message', { + placeholder: Messages.team_inviteLinkNoteMsg, + rows: 3 + }) + ]), + linkSpin = h('div.cp-teams-invite-spinner', { + style: 'display: none;' + }, [ + h('i.fa.fa-spinner.fa-spin'), + linkSpinText = h('span', Messages.team_inviteLinkLoading) + ]), + linkResult = h('div', { + style: 'display: none;' + }, h('textarea', { + readonly: 'readonly' + })), + linkWarning = h('div.cp-teams-invite-alert.alert.alert-warning.dismissable', { + style: "display: none;" + }, [ + h('span.cp-inline-alert-text', Messages.team_inviteLinkWarning), + dismissButton + ]) + ]); + $(linkMessage).keydown(function (e) { + if (e.which === 13) { + e.stopPropagation(); + } + }); + var localStore = window.cryptpadStore; + localStore.get('hide-alert-teamInvite', function (val) { + if (val === '1') { return; } + $(linkWarning).show(); + + $(dismissButton).on('click', function () { + localStore.put('hide-alert-teamInvite', '1'); + $(linkWarning).remove(); + }); + }); + var $linkContent = $(linkContent); + var href; + var process = function () { + var $nav = $linkContent.closest('.alertify').find('nav'); + $(linkError).text('').hide(); + var name = $(linkName).val(); + var pw = $(linkPassword).find('input').val(); + var msg = $(linkMessage).val(); + var hash = Hash.createRandomHash('invite', pw); + var hashData = Hash.parseTypeHash('invite', hash); + href = origin + '/teams/#' + hash; + if (!name || !name.trim()) { + $(linkError).text(Messages.team_inviteLinkErrorName).show(); + return true; + } + + var seeds = InviteInner.deriveSeeds(hashData.key); + var salt = InviteInner.deriveSalt(pw, AppConfig.loginSalt); + + var bytes64; + NThen(function (waitFor) { + $(linkForm).hide(); + $(linkSpin).show(); + $nav.find('button.cp-teams-invite-create').hide(); + $nav.find('button.cp-teams-invite-copy').show(); + setTimeout(waitFor(), 150); + }).nThen(function (waitFor) { + InviteInner.deriveBytes(seeds.scrypt, salt, waitFor(function (_bytes) { + bytes64 = _bytes; + })); + }).nThen(function (waitFor) { + module.execCommand('CREATE_INVITE_LINK', { + name: name, + password: pw, + message: msg, + bytes64: bytes64, + hash: hash, + teamId: config.teamId, + seeds: seeds, + }, waitFor(function (obj) { + if (obj && obj.error) { + waitFor.abort(); + $(linkSpin).hide(); + $(linkForm).show(); + $nav.find('button.cp-teams-invite-create').show(); + $nav.find('button.cp-teams-invite-copy').hide(); + return void $(linkError).text(Messages.team_inviteLinkError).show(); + } + // Display result here + $(linkSpin).hide(); + $(linkResult).show().find('textarea').text(href); + $nav.find('button.cp-teams-invite-copy').prop('disabled', ''); + })); + }); + return true; + }; + var linkButtons = [{ className: 'cancel', name: Messages.cancel, onClick: function () {}, keys: [27] }, { - className: 'primary', - name: Messages.team_inviteModalButton, + className: 'primary cp-teams-invite-create', + name: Messages.team_inviteLinkCreate, onClick: function () { - var $sel = $div.find('.cp-usergrid-user.cp-selected'); - var sel = $sel.toArray(); - if (!sel.length) { return; } - - sel.forEach(function (el) { - var curve = $(el).attr('data-curve'); - module.execCommand('INVITE_TO_TEAM', { - teamId: config.teamId, - user: config.friends[curve] - }, function (obj) { - if (obj && obj.error) { - console.error(obj.error); - return UI.warn(Messages.error); - } - }); - }); + return process(); }, - keys: [13] + keys: [] + }, { + className: 'primary cp-teams-invite-copy', + name: Messages.team_inviteLinkCopy, + onClick: function () { + if (!href) { return; } + var success = Clipboard.copy(href); + if (success) { UI.log(Messages.shareSuccess); } + }, + keys: [] }]; - var content = h('div', [ - list.div - ]); + var frameLink = UI.dialog.customModal(linkContent, { + buttons: linkButtons, + }); + $(frameLink).find('.cp-teams-invite-copy').prop('disabled', 'disabled').hide(); - var modal = UI.dialog.customModal(content, {buttons: buttons}); + // Create modal + var tabs = [{ + title: Messages.share_contactCategory, + icon: "fa fa-address-book", + content: frameContacts, + active: hasFriends + }, { + title: Messages.share_linkCategory, + icon: "fa fa-link", + content: frameLink, + active: !hasFriends + }]; + + var modal = UI.dialog.tabs(tabs); UI.openCustomModal(modal); }; @@ -2073,12 +2256,12 @@ define([ for (var k in actions) { $('