diff --git a/css/_variables.scss b/css/_variables.scss index 117fb2a14..a09e0ec1f 100644 --- a/css/_variables.scss +++ b/css/_variables.scss @@ -126,4 +126,5 @@ $selectActiveItemBg: $defaultDarkColor; $inputControlEmColor: #f29424; //buttons $linkFontColor: #489afe; -$linkHoverFontColor: #287ade; \ No newline at end of file +$linkHoverFontColor: #287ade; + diff --git a/css/input-control/_input-control.scss b/css/input-control/_input-control.scss index 4e1b5c570..05ea6c28f 100644 --- a/css/input-control/_input-control.scss +++ b/css/input-control/_input-control.scss @@ -20,6 +20,8 @@ } &__input { + margin-bottom: 8px; + @include transition(all .2s ease-in); &:last-child { margin-bottom: inherit; @@ -28,6 +30,11 @@ &::selection { background-color: $defaultDarkSelectionColor; } + + &.error { + color: $errorColor; + border-color: $errorColor; + } } &__em { @@ -41,6 +48,10 @@ span { vertical-align: middle; } + + &_error { + color: $errorColor; + } } &__container { diff --git a/css/themes/_light.scss b/css/themes/_light.scss index ae8224eff..6062e8bb5 100644 --- a/css/themes/_light.scss +++ b/css/themes/_light.scss @@ -55,6 +55,7 @@ $hintFontSize: em(13, 14); $linkFontColor: #3572b0; $linkHoverFontColor: darken(#3572b0, 10%); $dropdownColor: #333; +$errorColor: #c61600; // Popover colors $popoverBg: #000; diff --git a/lang/main.json b/lang/main.json index acdba53d7..ed791890c 100644 --- a/lang/main.json +++ b/lang/main.json @@ -199,6 +199,7 @@ "passwordError2": "This conversation isn't currently protected by a password. Only the owner of the conference can set a password.", "connectError": "Oops! Something went wrong and we couldn't connect to the conference.", "connectErrorWithMsg": "Oops! Something went wrong and we couldn't connect to the conference: __msg__", + "incorrectPassword": "Password is incorrect", "connecting": "Connecting", "copy": "Copy", "error": "Error", diff --git a/modules/UI/invite/AskForPassword.js b/modules/UI/invite/AskForPassword.js deleted file mode 100644 index 77a27ddf4..000000000 --- a/modules/UI/invite/AskForPassword.js +++ /dev/null @@ -1,31 +0,0 @@ -/* global APP, $ */ - -import UIUtil from '../util/UIUtil'; - -/** - * Show dialog which asks for required conference password. - * @returns {Promise} password or nothing if user canceled - */ -export default function askForPassword () { - let titleKey = "dialog.passwordRequired"; - let msgString = ` - `; - return new Promise(function (resolve, reject) { - APP.UI.messageHandler.openTwoButtonDialog({ - titleKey, - msgString, - leftButtonKey: "dialog.Ok", - submitFunction: $.noop, - closeFunction: function (e, v, m, f) { - if (v && f.lockKey) { - resolve(UIUtil.escapeHtml(f.lockKey)); - } else { - reject(APP.UI.messageHandler.CANCEL); - } - }, - focus: ':input:first' - }); - }); -} \ No newline at end of file diff --git a/modules/UI/invite/Invite.js b/modules/UI/invite/Invite.js index 289df0251..7a786b9fa 100644 --- a/modules/UI/invite/Invite.js +++ b/modules/UI/invite/Invite.js @@ -32,7 +32,7 @@ class Invite { error); if (!locked) { - this.roomLocker.resetPassword(); + this.getRoomLocker().resetPassword(); } this.setLockedFromElsewhere(locked); @@ -46,14 +46,20 @@ class Invite { } }); + this.conference.on(ConferenceEvents.CONFERENCE_JOINED, () => { + let roomLocker = this.getRoomLocker(); + roomLocker.hideRequirePasswordDialog(); + }); + APP.UI.addListener( UIEvents.INVITE_CLICKED, () => { this.openLinkDialog(); }); APP.UI.addListener( UIEvents.PASSWORD_REQUIRED, () => { + let roomLocker = this.getRoomLocker(); this.setLockedFromElsewhere(true); - this.roomLocker.requirePassword().then(() => { - let pass = this.roomLocker.password; + roomLocker.requirePassword().then(() => { + let pass = roomLocker.password; // we received that password is required, but user is trying // anyway to login without a password, mark room as not // locked in case he succeeds (maybe someone removed the @@ -62,7 +68,7 @@ class Invite { // will be marked as locked. if (!pass) this.setLockedFromElsewhere(false); - this.conference.join(this.roomLocker.password); + this.conference.join(pass); }); }); } @@ -124,7 +130,7 @@ class Invite { * @returns {String} password */ getPassword() { - return this.roomLocker.password; + return this.getRoomLocker().password; } /** @@ -144,7 +150,7 @@ class Invite { */ setRoomUnlocked() { if (this.isModerator) { - this.roomLocker.lock().then(() => { + this.getRoomLocker().lock().then(() => { APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK); this.updateView(); }); @@ -159,8 +165,8 @@ class Invite { */ setRoomLocked(newPass) { let isModerator = this.isModerator; - if (isModerator && (newPass || !this.roomLocker.isLocked)) { - this.roomLocker.lock(newPass).then(() => { + if (isModerator && (newPass || !this.getRoomLocker().isLocked)) { + this.getRoomLocker().lock(newPass).then(() => { APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK); this.updateView(); }); @@ -182,7 +188,7 @@ class Invite { * @returns {Boolean} isLocked */ isLocked() { - return this.roomLocker.isLocked; + return this.getRoomLocker().isLocked; } /** @@ -190,9 +196,10 @@ class Invite { * @param isLocked */ setLockedFromElsewhere(isLocked) { - let oldLockState = this.roomLocker.isLocked; + let roomLocker = this.getRoomLocker(); + let oldLockState = roomLocker.isLocked; if (oldLockState !== isLocked) { - this.roomLocker.lockedElsewhere = isLocked; + roomLocker.lockedElsewhere = isLocked; APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK); this.updateView(); } diff --git a/modules/UI/invite/RequirePasswordDialog.js b/modules/UI/invite/RequirePasswordDialog.js new file mode 100644 index 000000000..be612d3e0 --- /dev/null +++ b/modules/UI/invite/RequirePasswordDialog.js @@ -0,0 +1,161 @@ +/* global APP */ + +import UIUtil from '../util/UIUtil'; + +/** + * Show dialog which asks for required conference password. + * @returns {Promise} password or nothing if user canceled + */ +export default class RequirePasswordDialog { + constructor() { + this.titleKey = 'dialog.passwordRequired'; + this.labelKey = 'dialog.passwordLabel'; + this.errorKey = 'dialog.incorrectPassword'; + this.errorId = 'passwordRequiredError'; + this.inputId = 'passwordRequiredInput'; + this.inputErrorClass = 'error'; + this.isOpened = false; + } + + /** + * Registering dialog listeners + * @private + */ + _registerListeners() { + let el = document.getElementById(this.inputId); + el.addEventListener('keypress', this._hideError.bind(this)); + } + + /** + * Helper method returning dialog body + * @returns {string} + * @private + */ + _getBodyMessage() { + return ( + `
+ + +

+
` + ); + } + + /** + * Asking for a password + * @returns {Promise} + */ + askForPassword() { + if (!this.isOpened) { + return this.open(); + } + + return new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + this._showError(); + }); + } + + /** + * Opens the dialog + * @returns {Promise} + */ + open() { + let { titleKey } = this; + let msgString = this._getBodyMessage(); + + return new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + let submitFunction = this._submitFunction.bind(this); + let closeFunction = this._closeFunction.bind(this); + + this._dialog = APP.UI.messageHandler.openTwoButtonDialog({ + titleKey, + msgString, + leftButtonKey: "dialog.Ok", + submitFunction, + closeFunction, + focus: ':input:first' + }); + + this._registerListeners(); + this.isOpened = true; + }); + } + + /** + * Submit dialog callback + * @param e - event + * @param v - value + * @param m - message + * @param f - form + * @private + */ + _submitFunction(e, v, m, f) { + e.preventDefault(); + this._processInput(v, f); + } + + /** + * Processing input in dialog + * @param v - value + * @param f - form + * @private + */ + _processInput(v, f) { + if (v && f.lockKey) { + this.resolve(UIUtil.escapeHtml(f.lockKey)); + } else { + this.reject(APP.UI.messageHandler.CANCEL); + } + } + + /** + * Close dialog callback + * @private + */ + _closeFunction(e, v, m, f) { + this._processInput(v, f); + this._hideError(); + this.close(); + } + + /** + * Method showing error hint + * @private + */ + _showError() { + let className = this.inputErrorClass; + let input = document.getElementById(this.inputId); + document.getElementById(this.errorId).classList.remove('hide'); + input.classList.add(className); + input.select(); + } + + /** + * Method hiding error hint + * @private + */ + _hideError() { + let className = this.inputErrorClass; + document.getElementById(this.errorId).classList.add('hide'); + document.getElementById(this.inputId).classList.remove(className); + } + + /** + * Close the dialog + */ + close() { + if (this._dialog) { + this._dialog.close(); + } + this.isOpened = false; + } +} \ No newline at end of file diff --git a/modules/UI/invite/RoomLocker.js b/modules/UI/invite/RoomLocker.js index 83057e871..22dc39a27 100644 --- a/modules/UI/invite/RoomLocker.js +++ b/modules/UI/invite/RoomLocker.js @@ -1,5 +1,5 @@ /* global APP, JitsiMeetJS */ -import askForPassword from './AskForPassword'; +import RequirePasswordDialog from './RequirePasswordDialog'; /** * Show notification that user cannot set password for the conference @@ -31,7 +31,7 @@ const ConferenceErrors = JitsiMeetJS.errors.conference; */ export default function createRoomLocker (room) { let password; - + let requirePasswordDialog = new RequirePasswordDialog(); /** * If the room was locked from someone other than us, we indicate it with * this property in order to have correct roomLocker state of isLocked. @@ -104,7 +104,7 @@ export default function createRoomLocker (room) { * Asks user for required conference password. */ requirePassword () { - return askForPassword().then( + return requirePasswordDialog.askForPassword().then( newPass => { password = newPass; } ).catch( reason => { @@ -116,6 +116,15 @@ export default function createRoomLocker (room) { console.error(reason); } ); + }, + + /** + * Hides require password dialog + */ + hideRequirePasswordDialog() { + if (requirePasswordDialog.isOpened) { + requirePasswordDialog.close(); + } } }; } diff --git a/modules/UI/util/MessageHandler.js b/modules/UI/util/MessageHandler.js index 2f63abbf4..8509c6c48 100644 --- a/modules/UI/util/MessageHandler.js +++ b/modules/UI/util/MessageHandler.js @@ -1,4 +1,4 @@ -/* global $, APP, toastr, Impromptu */ +/* global $, APP, toastr */ import UIUtil from './UIUtil'; import jitsiLocalStorage from '../../util/JitsiLocalStorage'; @@ -138,7 +138,7 @@ var messageHandler = { } }); APP.translation.translateElement(dialog, i18nOptions); - return dialog; + return $.prompt.getApi(); }, /** * Shows a message to the user with two buttons: first is given as a @@ -242,7 +242,7 @@ var messageHandler = { } }); APP.translation.translateElement(twoButtonDialog); - return twoButtonDialog; + return $.prompt.getApi(); }, /** @@ -300,10 +300,10 @@ var messageHandler = { args.closeText = ''; } - let dialog = new Impromptu( + let dialog = $.prompt( msgString + generateDontShowCheckbox(dontShowAgain), args); - APP.translation.translateElement(dialog.getPrompt()); - return dialog; + APP.translation.translateElement(dialog); + return $.prompt.getApi(); }, /** @@ -338,13 +338,6 @@ var messageHandler = { }; }, - /** - * Closes currently opened dialog. - */ - closeDialog: function () { - $.prompt.close(); - }, - /** * Shows a dialog with different states to the user. * @@ -367,9 +360,9 @@ var messageHandler = { = this._getFormattedTitleString(currentState.titleKey); } } - let dialog = new Impromptu(statesObject, options); - APP.translation.translateElement(dialog.getPrompt(), translateOptions); - return dialog; + let dialog = $.prompt(statesObject, options); + APP.translation.translateElement(dialog, translateOptions); + return $.prompt.getApi(); }, /**