Remove jshint - move everything over to eslint

Also removed all hints of previous linters
This commit is contained in:
Scott Nonnenberg 2018-07-06 17:48:14 -07:00
parent dc11db92f9
commit 43a44793c5
71 changed files with 1837 additions and 2030 deletions

View file

@ -1,10 +1,15 @@
/* global Backbone, Whisper, storage, _, ConversationController, $ */
/* eslint-disable more/no-then */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.AppView = Backbone.View.extend({
initialize: function(options) {
initialize() {
this.inboxView = null;
this.installView = null;
@ -15,38 +20,41 @@
'click .openInstaller': 'openInstaller', // NetworkStatusView has this button
openInbox: 'openInbox',
},
applyTheme: function() {
var theme = storage.get('theme-setting') || 'light';
applyTheme() {
const theme = storage.get('theme-setting') || 'light';
this.$el
.removeClass('light-theme')
.removeClass('dark-theme')
.addClass(`${theme}-theme`);
},
applyHideMenu: function() {
var hideMenuBar = storage.get('hide-menu-bar', false);
applyHideMenu() {
const hideMenuBar = storage.get('hide-menu-bar', false);
window.setAutoHideMenuBar(hideMenuBar);
window.setMenuBarVisibility(!hideMenuBar);
},
openView: function(view) {
openView(view) {
this.el.innerHTML = '';
this.el.append(view.el);
this.delegateEvents();
},
openDebugLog: function() {
openDebugLog() {
this.closeDebugLog();
this.debugLogView = new Whisper.DebugLogView();
this.debugLogView.$el.appendTo(this.el);
},
closeDebugLog: function() {
closeDebugLog() {
if (this.debugLogView) {
this.debugLogView.remove();
this.debugLogView = null;
}
},
openImporter: function() {
openImporter() {
window.addSetupMenuItems();
this.resetViews();
var importView = (this.importView = new Whisper.ImportView());
const importView = new Whisper.ImportView();
this.importView = importView;
this.listenTo(
importView,
'light-import',
@ -54,21 +62,19 @@
);
this.openView(this.importView);
},
finishLightImport: function() {
var options = {
finishLightImport() {
const options = {
hasExistingData: true,
};
this.openInstaller(options);
},
closeImporter: function() {
closeImporter() {
if (this.importView) {
this.importView.remove();
this.importView = null;
}
},
openInstaller: function(options) {
options = options || {};
openInstaller(options = {}) {
// If we're in the middle of import, we don't want to show the menu options
// allowing the user to switch to other ways to set up the app. If they
// switched back and forth in the middle of a light import, they'd lose all
@ -78,16 +84,18 @@
}
this.resetViews();
var installView = (this.installView = new Whisper.InstallView(options));
const installView = new Whisper.InstallView(options);
this.installView = installView;
this.openView(this.installView);
},
closeInstaller: function() {
closeInstaller() {
if (this.installView) {
this.installView.remove();
this.installView = null;
}
},
openStandalone: function() {
openStandalone() {
if (window.getEnvironment() !== 'production') {
window.addSetupMenuItems();
this.resetViews();
@ -95,19 +103,18 @@
this.openView(this.standaloneView);
}
},
closeStandalone: function() {
closeStandalone() {
if (this.standaloneView) {
this.standaloneView.remove();
this.standaloneView = null;
}
},
resetViews: function() {
resetViews() {
this.closeInstaller();
this.closeImporter();
this.closeStandalone();
},
openInbox: function(options) {
options = options || {};
openInbox(options = {}) {
// The inbox can be created before the 'empty' event fires or afterwards. If
// before, it's straightforward: the onEmpty() handler below updates the
// view directly, and we're in good shape. If we create the inbox late, we
@ -130,44 +137,38 @@
// this.initialLoadComplete between the start of this method and the
// creation of inboxView.
this.inboxView = new Whisper.InboxView({
model: self,
window: window,
window,
initialLoadComplete: options.initialLoadComplete,
});
return ConversationController.loadPromise().then(
function() {
this.openView(this.inboxView);
}.bind(this)
);
} else {
if (!$.contains(this.el, this.inboxView.el)) {
return ConversationController.loadPromise().then(() => {
this.openView(this.inboxView);
}
window.focus(); // FIXME
return Promise.resolve();
});
}
if (!$.contains(this.el, this.inboxView.el)) {
this.openView(this.inboxView);
}
window.focus(); // FIXME
return Promise.resolve();
},
onEmpty: function() {
var view = this.inboxView;
onEmpty() {
const view = this.inboxView;
this.initialLoadComplete = true;
if (view) {
view.onEmpty();
}
},
onProgress: function(count) {
var view = this.inboxView;
onProgress(count) {
const view = this.inboxView;
if (view) {
view.onProgress(count);
}
},
openConversation: function(conversation) {
openConversation(conversation) {
if (conversation) {
this.openInbox().then(
function() {
this.inboxView.openConversation(null, conversation);
}.bind(this)
);
this.openInbox().then(() => {
this.inboxView.openConversation(null, conversation);
});
}
},
});

View file

@ -1,11 +1,15 @@
/* global Whisper */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.AttachmentPreviewView = Whisper.View.extend({
className: 'attachment-preview',
templateName: 'attachment-preview',
render_attributes: function() {
render_attributes() {
return { source: this.src };
},
});

View file

@ -1,5 +1,9 @@
/* global Whisper */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.BannerView = Whisper.View.extend({
@ -9,7 +13,7 @@
'click .dismiss': 'onDismiss',
'click .body': 'onClick',
},
initialize: function(options) {
initialize(options) {
this.message = options.message;
this.callbacks = {
onDismiss: options.onDismiss,
@ -17,16 +21,16 @@
};
this.render();
},
render_attributes: function() {
render_attributes() {
return {
message: this.message,
};
},
onDismiss: function(e) {
onDismiss(e) {
this.callbacks.onDismiss();
e.stopPropagation();
},
onClick: function() {
onClick() {
this.callbacks.onClick();
},
});

View file

@ -1,11 +1,15 @@
/* global Whisper, i18n */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.ConfirmationDialogView = Whisper.View.extend({
className: 'confirmation-dialog modal',
templateName: 'confirmation-dialog',
initialize: function(options) {
initialize(options) {
this.message = options.message;
this.hideCancel = options.hideCancel;
@ -22,7 +26,7 @@
'click .ok': 'ok',
'click .cancel': 'cancel',
},
render_attributes: function() {
render_attributes() {
return {
message: this.message,
showCancel: !this.hideCancel,
@ -30,24 +34,24 @@
ok: this.okText,
};
},
ok: function() {
ok() {
this.remove();
if (this.resolve) {
this.resolve();
}
},
cancel: function() {
cancel() {
this.remove();
if (this.reject) {
this.reject();
}
},
onKeyup: function(event) {
onKeyup(event) {
if (event.key === 'Escape' || event.key === 'Esc') {
this.cancel();
}
},
focusCancel: function() {
focusCancel() {
this.$('.cancel').focus();
},
});

View file

@ -1,5 +1,4 @@
/* global Whisper: false */
/* global i18n: false */
/* global textsecure: false */
// eslint-disable-next-line func-names

View file

@ -1,18 +1,22 @@
/* global Whisper, _, extension, Backbone, Mustache */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
// list of conversations, showing user/group and last message sent
Whisper.ConversationListItemView = Whisper.View.extend({
tagName: 'div',
className: function() {
return 'conversation-list-item contact ' + this.model.cid;
className() {
return `conversation-list-item contact ${this.model.cid}`;
},
templateName: 'conversation-preview',
events: {
click: 'select',
},
initialize: function() {
initialize() {
// auto update
this.listenTo(
this.model,
@ -22,7 +26,7 @@
this.listenTo(this.model, 'destroy', this.remove); // auto update
this.listenTo(this.model, 'opened', this.markSelected); // auto update
var updateLastMessage = _.debounce(
const updateLastMessage = _.debounce(
this.model.updateLastMessage.bind(this.model),
1000
);
@ -33,23 +37,21 @@
);
this.listenTo(this.model, 'newmessage', updateLastMessage);
extension.windows.onClosed(
function() {
this.stopListening();
}.bind(this)
);
extension.windows.onClosed(() => {
this.stopListening();
});
this.timeStampView = new Whisper.TimestampView({ brief: true });
this.model.updateLastMessage();
},
markSelected: function() {
markSelected() {
this.$el
.addClass('selected')
.siblings('.selected')
.removeClass('selected');
},
select: function(e) {
select() {
this.markSelected();
this.$el.trigger('select', this.model);
},
@ -66,7 +68,7 @@
Backbone.View.prototype.remove.call(this);
},
render: function() {
render() {
const lastMessage = this.model.get('lastMessage');
this.$el.html(
@ -117,7 +119,7 @@
this.$('.last-message').append(this.bodyView.el);
}
var unread = this.model.get('unreadCount');
const unread = this.model.get('unreadCount');
if (unread > 0) {
this.$el.addClass('unread');
} else {

View file

@ -1,12 +1,16 @@
/* global Whisper, getInboxCollection */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.ConversationListView = Whisper.ListView.extend({
tagName: 'div',
itemView: Whisper.ConversationListItemView,
updateLocation: function(conversation) {
var $el = this.$('.' + conversation.cid);
updateLocation(conversation) {
const $el = this.$(`.${conversation.cid}`);
if (!$el || !$el.length) {
console.log(
@ -23,11 +27,11 @@
return;
}
var $allConversations = this.$('.conversation-list-item');
var inboxCollection = getInboxCollection();
var index = inboxCollection.indexOf(conversation);
const $allConversations = this.$('.conversation-list-item');
const inboxCollection = getInboxCollection();
const index = inboxCollection.indexOf(conversation);
var elIndex = $allConversations.index($el);
const elIndex = $allConversations.index($el);
if (elIndex < 0) {
console.log(
'updateLocation: did not find index for conversation',
@ -43,8 +47,8 @@
} else if (index === this.collection.length - 1) {
this.$el.append($el);
} else {
var targetConversation = inboxCollection.at(index - 1);
var target = this.$('.' + targetConversation.cid);
const targetConversation = inboxCollection.at(index - 1);
const target = this.$(`.${targetConversation.cid}`);
$el.insertAfter(target);
}
@ -54,8 +58,8 @@
});
}
},
removeItem: function(conversation) {
var $el = this.$('.' + conversation.cid);
removeItem(conversation) {
const $el = this.$(`.${conversation.cid}`);
if ($el && $el.length > 0) {
$el.remove();
}

View file

@ -1,13 +0,0 @@
(function() {
'use strict';
window.Whisper = window.Whisper || {};
var ErrorView = Whisper.View.extend({
className: 'error',
templateName: 'generic-error',
render_attributes: function() {
return this.model;
},
});
})();

View file

@ -1,12 +1,16 @@
/* global Whisper, i18n */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
// TODO: take a title string which could replace the 'members' header
Whisper.GroupMemberList = Whisper.View.extend({
className: 'group-member-list panel',
templateName: 'group-member-list',
initialize: function(options) {
initialize(options) {
this.needVerify = options.needVerify;
this.render();
@ -22,15 +26,15 @@
this.$('.container').append(this.member_list_view.el);
},
render_attributes: function() {
var summary;
render_attributes() {
let summary;
if (this.needVerify) {
summary = i18n('membersNeedingVerification');
}
return {
members: i18n('groupMembers'),
summary: summary,
summary,
};
},
});

View file

@ -1,3 +1,6 @@
/* global Backbone, Whisper */
// eslint-disable-next-line func-names
(function() {
'use strict';
@ -6,19 +9,19 @@
Whisper.GroupUpdateView = Backbone.View.extend({
tagName: 'div',
className: 'group-update',
render: function() {
//TODO l10n
render() {
// TODO l10n
if (this.model.left) {
this.$el.text(this.model.left + ' left the group');
this.$el.text(`${this.model.left} left the group`);
return this;
}
var messages = ['Updated the group.'];
const messages = ['Updated the group.'];
if (this.model.name) {
messages.push("Title is now '" + this.model.name + "'.");
messages.push(`Title is now '${this.model.name}'.`);
}
if (this.model.joined) {
messages.push(this.model.joined.join(', ') + ' joined the group');
messages.push(`${this.model.joined.join(', ')} joined the group`);
}
this.$el.text(messages.join(' '));

View file

@ -1,13 +1,17 @@
/* global Whisper */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.HintView = Whisper.View.extend({
templateName: 'hint',
initialize: function(options) {
initialize(options) {
this.content = options.content;
},
render_attributes: function() {
render_attributes() {
return { content: this.content };
},
});

View file

@ -1,5 +1,9 @@
/* global Whisper, loadImage */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
/*
@ -7,26 +11,26 @@
*/
Whisper.IdenticonSVGView = Whisper.View.extend({
templateName: 'identicon-svg',
initialize: function(options) {
initialize(options) {
this.render_attributes = options;
this.render_attributes.color = COLORS[this.render_attributes.color];
},
getSVGUrl: function() {
var html = this.render().$el.html();
var svg = new Blob([html], { type: 'image/svg+xml;charset=utf-8' });
getSVGUrl() {
const html = this.render().$el.html();
const svg = new Blob([html], { type: 'image/svg+xml;charset=utf-8' });
return URL.createObjectURL(svg);
},
getDataUrl: function() {
var svgurl = this.getSVGUrl();
return new Promise(function(resolve) {
var img = document.createElement('img');
img.onload = function() {
var canvas = loadImage.scale(img, {
getDataUrl() {
const svgurl = this.getSVGUrl();
return new Promise(resolve => {
const img = document.createElement('img');
img.onload = () => {
const canvas = loadImage.scale(img, {
canvas: true,
maxWidth: 100,
maxHeight: 100,
});
var ctx = canvas.getContext('2d');
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
URL.revokeObjectURL(svgurl);
resolve(canvas.toDataURL('image/png'));
@ -37,7 +41,7 @@
},
});
var COLORS = {
const COLORS = {
red: '#EF5350',
pink: '#EC407A',
purple: '#AB47BC',

View file

@ -1,11 +1,15 @@
/* global Whisper, i18n */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.IdentityKeySendErrorPanelView = Whisper.View.extend({
className: 'identity-key-send-error panel',
templateName: 'identity-key-send-error',
initialize: function(options) {
initialize(options) {
this.listenBack = options.listenBack;
this.resetPanel = options.resetPanel;
@ -17,31 +21,31 @@
'click .send-anyway': 'sendAnyway',
'click .cancel': 'cancel',
},
showSafetyNumber: function() {
var view = new Whisper.KeyVerificationPanelView({
showSafetyNumber() {
const view = new Whisper.KeyVerificationPanelView({
model: this.model,
});
this.listenBack(view);
},
sendAnyway: function() {
sendAnyway() {
this.resetPanel();
this.trigger('send-anyway');
},
cancel: function() {
cancel() {
this.resetPanel();
},
render_attributes: function() {
var send = i18n('sendAnyway');
render_attributes() {
let send = i18n('sendAnyway');
if (this.wasUnverified && !this.model.isUnverified()) {
send = i18n('resend');
}
var errorExplanation = i18n('identityKeyErrorOnSend', [
const errorExplanation = i18n('identityKeyErrorOnSend', [
this.model.getTitle(),
this.model.getTitle(),
]);
return {
errorExplanation: errorExplanation,
errorExplanation,
showSafetyNumber: i18n('showSafetyNumber'),
sendAnyway: send,
cancel: i18n('cancel'),

View file

@ -1,37 +1,43 @@
/* global Whisper, storage, i18n, ConversationController */
/* eslint-disable more/no-then */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
var State = {
const State = {
IMPORTING: 1,
COMPLETE: 2,
LIGHT_COMPLETE: 3,
};
var IMPORT_STARTED = 'importStarted';
var IMPORT_COMPLETE = 'importComplete';
var IMPORT_LOCATION = 'importLocation';
const IMPORT_STARTED = 'importStarted';
const IMPORT_COMPLETE = 'importComplete';
const IMPORT_LOCATION = 'importLocation';
Whisper.Import = {
isStarted: function() {
isStarted() {
return Boolean(storage.get(IMPORT_STARTED));
},
isComplete: function() {
isComplete() {
return Boolean(storage.get(IMPORT_COMPLETE));
},
isIncomplete: function() {
isIncomplete() {
return this.isStarted() && !this.isComplete();
},
start: function() {
start() {
return storage.put(IMPORT_STARTED, true);
},
complete: function() {
complete() {
return storage.put(IMPORT_COMPLETE, true);
},
saveLocation: function(location) {
saveLocation(location) {
return storage.put(IMPORT_LOCATION, location);
},
reset: function() {
reset() {
return Whisper.Database.clear();
},
};
@ -45,7 +51,7 @@
'click .cancel': 'onCancel',
'click .register': 'onRegister',
},
initialize: function() {
initialize() {
if (Whisper.Import.isIncomplete()) {
this.error = true;
}
@ -53,7 +59,7 @@
this.render();
this.pending = Promise.resolve();
},
render_attributes: function() {
render_attributes() {
if (this.error) {
return {
isError: true,
@ -64,9 +70,9 @@
};
}
var restartButton = i18n('importCompleteStartButton');
var registerButton = i18n('importCompleteLinkButton');
var step = 'step2';
let restartButton = i18n('importCompleteStartButton');
let registerButton = i18n('importCompleteLinkButton');
let step = 'step2';
if (this.state === State.IMPORTING) {
step = 'step3';
@ -89,22 +95,22 @@
isStep4: step === 'step4',
completeHeader: i18n('importCompleteHeader'),
restartButton: restartButton,
registerButton: registerButton,
restartButton,
registerButton,
};
},
onRestart: function() {
onRestart() {
return window.restart();
},
onCancel: function() {
onCancel() {
this.trigger('cancel');
},
onImport: function() {
onImport() {
window.Signal.Backup.getDirectoryForImport().then(
function(directory) {
directory => {
this.doImport(directory);
}.bind(this),
function(error) {
},
error => {
if (error.name !== 'ChooseError') {
console.log(
'Error choosing directory:',
@ -114,13 +120,13 @@
}
);
},
onRegister: function() {
onRegister() {
// AppView listens for this, and opens up InstallView to the QR code step to
// finish setting this device up.
this.trigger('light-import');
},
doImport: function(directory) {
doImport(directory) {
window.removeSetupMenuItems();
this.error = null;
@ -129,70 +135,64 @@
// Wait for prior database interaction to complete
this.pending = this.pending
.then(function() {
.then(() =>
// For resilience to interruption, clear database both before and on failure
return Whisper.Import.reset();
})
.then(function() {
return Promise.all([
Whisper.Import.reset()
)
.then(() =>
Promise.all([
Whisper.Import.start(),
window.Signal.Backup.importFromDirectory(directory),
]);
})
.then(
function(results) {
var importResult = results[1];
// A full import changes so much we need a restart of the app
if (importResult.fullImport) {
return this.finishFullImport(directory);
}
// A light import just brings in contacts, groups, and messages. And we need a
// normal link to finish the process.
return this.finishLightImport(directory);
}.bind(this)
])
)
.catch(
function(error) {
console.log(
'Error importing:',
error && error.stack ? error.stack : error
);
.then(results => {
const importResult = results[1];
this.error = error || new Error('Something went wrong!');
this.state = null;
this.render();
// A full import changes so much we need a restart of the app
if (importResult.fullImport) {
return this.finishFullImport(directory);
}
return Whisper.Import.reset();
}.bind(this)
);
// A light import just brings in contacts, groups, and messages. And we need a
// normal link to finish the process.
return this.finishLightImport(directory);
})
.catch(error => {
console.log(
'Error importing:',
error && error.stack ? error.stack : error
);
this.error = error || new Error('Something went wrong!');
this.state = null;
this.render();
return Whisper.Import.reset();
});
},
finishLightImport: function(directory) {
finishLightImport(directory) {
ConversationController.reset();
return ConversationController.load()
.then(function() {
return Promise.all([
.then(() =>
Promise.all([
Whisper.Import.saveLocation(directory),
Whisper.Import.complete(),
]);
})
.then(
function() {
this.state = State.LIGHT_COMPLETE;
this.render();
}.bind(this)
);
])
)
.then(() => {
this.state = State.LIGHT_COMPLETE;
this.render();
});
},
finishFullImport: function(directory) {
finishFullImport(directory) {
// Catching in-memory cache up with what's in indexeddb now...
// NOTE: this fires storage.onready, listened to across the app. We'll restart
// to complete the install to start up cleanly with everything now in the DB.
return storage
.fetch()
.then(function() {
return Promise.all([
.then(() =>
Promise.all([
// Clearing any migration-related state inherited from the Chrome App
storage.remove('migrationState'),
storage.remove('migrationEnabled'),
@ -201,14 +201,12 @@
Whisper.Import.saveLocation(directory),
Whisper.Import.complete(),
]);
})
.then(
function() {
this.state = State.COMPLETE;
this.render();
}.bind(this)
);
])
)
.then(() => {
this.state = State.COMPLETE;
this.render();
});
},
});
})();

View file

@ -1,8 +1,14 @@
/* global Whisper, i18n, getAccountManager, $, textsecure, QRCode */
/* eslint-disable more/no-then */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
var Steps = {
const Steps = {
INSTALL_SIGNAL: 2,
SCAN_QR_CODE: 3,
ENTER_NAME: 4,
@ -11,9 +17,9 @@
NETWORK_ERROR: 'NetworkError',
};
var DEVICE_NAME_SELECTOR = 'input.device-name';
var CONNECTION_ERROR = -1;
var TOO_MANY_DEVICES = 411;
const DEVICE_NAME_SELECTOR = 'input.device-name';
const CONNECTION_ERROR = -1;
const TOO_MANY_DEVICES = 411;
Whisper.InstallView = Whisper.View.extend({
templateName: 'link-flow-template',
@ -23,9 +29,7 @@
'click .finish': 'finishLinking',
// the actual next step happens in confirmNumber() on submit form #link-phone
},
initialize: function(options) {
options = options || {};
initialize(options = {}) {
this.selectStep(Steps.SCAN_QR_CODE);
this.connect();
this.on('disconnected', this.reconnect);
@ -34,18 +38,18 @@
this.shouldRetainData =
Whisper.Registration.everDone() || options.hasExistingData;
},
render_attributes: function() {
var errorMessage;
render_attributes() {
let errorMessage;
if (this.error) {
if (
this.error.name === 'HTTPError' &&
this.error.code == TOO_MANY_DEVICES
this.error.code === TOO_MANY_DEVICES
) {
errorMessage = i18n('installTooManyDevices');
} else if (
this.error.name === 'HTTPError' &&
this.error.code == CONNECTION_ERROR
this.error.code === CONNECTION_ERROR
) {
errorMessage = i18n('installConnectionFailed');
} else if (this.error.message === 'websocket closed') {
@ -78,11 +82,11 @@
syncing: i18n('initialSync'),
};
},
selectStep: function(step) {
selectStep(step) {
this.step = step;
this.render();
},
connect: function() {
connect() {
this.error = null;
this.selectStep(Steps.SCAN_QR_CODE);
this.clearQR();
@ -91,7 +95,7 @@
this.timeout = null;
}
var accountManager = getAccountManager();
const accountManager = getAccountManager();
accountManager
.registerSecondDevice(
@ -100,7 +104,7 @@
)
.catch(this.handleDisconnect.bind(this));
},
handleDisconnect: function(e) {
handleDisconnect(e) {
console.log('provisioning failed', e.stack);
this.error = e;
@ -115,20 +119,20 @@
throw e;
}
},
reconnect: function() {
reconnect() {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
this.timeout = setTimeout(this.connect.bind(this), 10000);
},
clearQR: function() {
clearQR() {
this.$('#qr img').remove();
this.$('#qr canvas').remove();
this.$('#qr .container').show();
this.$('#qr').removeClass('ready');
},
setProvisioningUrl: function(url) {
setProvisioningUrl(url) {
if ($('#qr').length === 0) {
console.log('Did not find #qr element in the DOM!');
return;
@ -139,63 +143,57 @@
this.$('#qr').removeAttr('title');
this.$('#qr').addClass('ready');
},
setDeviceNameDefault: function() {
var deviceName = textsecure.storage.user.getDeviceName();
setDeviceNameDefault() {
const deviceName = textsecure.storage.user.getDeviceName();
this.$(DEVICE_NAME_SELECTOR).val(deviceName || window.getHostName());
this.$(DEVICE_NAME_SELECTOR).focus();
},
finishLinking: function() {
finishLinking() {
// We use a form so we get submit-on-enter behavior
this.$('#link-phone').submit();
},
confirmNumber: function(number) {
var tsp = textsecure.storage.protocol;
confirmNumber() {
const tsp = textsecure.storage.protocol;
window.removeSetupMenuItems();
this.selectStep(Steps.ENTER_NAME);
this.setDeviceNameDefault();
return new Promise(
function(resolve, reject) {
this.$('#link-phone').submit(
function(e) {
e.stopPropagation();
e.preventDefault();
return new Promise(resolve => {
this.$('#link-phone').submit(e => {
e.stopPropagation();
e.preventDefault();
var name = this.$(DEVICE_NAME_SELECTOR).val();
name = name.replace(/\0/g, ''); // strip unicode null
if (name.trim().length === 0) {
this.$(DEVICE_NAME_SELECTOR).focus();
return;
}
let name = this.$(DEVICE_NAME_SELECTOR).val();
name = name.replace(/\0/g, ''); // strip unicode null
if (name.trim().length === 0) {
this.$(DEVICE_NAME_SELECTOR).focus();
return null;
}
this.selectStep(Steps.PROGRESS_BAR);
this.selectStep(Steps.PROGRESS_BAR);
var finish = function() {
resolve(name);
};
const finish = () => resolve(name);
// Delete all data from database unless we're in the middle
// of a re-link, or we are finishing a light import. Without this,
// app restarts at certain times can cause weird things to happen,
// like data from a previous incomplete light import showing up
// after a new install.
if (this.shouldRetainData) {
return finish();
}
// Delete all data from database unless we're in the middle
// of a re-link, or we are finishing a light import. Without this,
// app restarts at certain times can cause weird things to happen,
// like data from a previous incomplete light import showing up
// after a new install.
if (this.shouldRetainData) {
return finish();
}
tsp.removeAllData().then(finish, function(error) {
console.log(
'confirmNumber: error clearing database',
error && error.stack ? error.stack : error
);
finish();
});
}.bind(this)
);
}.bind(this)
);
return tsp.removeAllData().then(finish, error => {
console.log(
'confirmNumber: error clearing database',
error && error.stack ? error.stack : error
);
finish();
});
});
});
},
});
})();

View file

@ -1,5 +1,11 @@
/* global Whisper, textsecure, QRCode, dcodeIO, libsignal, i18n, _ */
/* eslint-disable more/no-then */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.KeyVerificationPanelView = Whisper.View.extend({
@ -8,58 +14,54 @@
events: {
'click button.verify': 'toggleVerified',
},
initialize: function(options) {
initialize(options) {
this.ourNumber = textsecure.storage.user.getNumber();
if (options.newKey) {
this.theirKey = options.newKey;
}
this.loadKeys().then(
function() {
this.listenTo(this.model, 'change', this.render);
}.bind(this)
);
this.loadKeys().then(() => {
this.listenTo(this.model, 'change', this.render);
});
},
loadKeys: function() {
loadKeys() {
return Promise.all([this.loadTheirKey(), this.loadOurKey()])
.then(this.generateSecurityNumber.bind(this))
.then(this.render.bind(this));
//.then(this.makeQRCode.bind(this));
// .then(this.makeQRCode.bind(this));
},
makeQRCode: function() {
makeQRCode() {
// Per Lilia: We can't turn this on until it generates a Latin1 string, as is
// required by the mobile clients.
new QRCode(this.$('.qr')[0]).makeCode(
dcodeIO.ByteBuffer.wrap(this.ourKey).toString('base64')
);
},
loadTheirKey: function() {
return textsecure.storage.protocol.loadIdentityKey(this.model.id).then(
function(theirKey) {
loadTheirKey() {
return textsecure.storage.protocol
.loadIdentityKey(this.model.id)
.then(theirKey => {
this.theirKey = theirKey;
}.bind(this)
);
});
},
loadOurKey: function() {
return textsecure.storage.protocol.loadIdentityKey(this.ourNumber).then(
function(ourKey) {
loadOurKey() {
return textsecure.storage.protocol
.loadIdentityKey(this.ourNumber)
.then(ourKey => {
this.ourKey = ourKey;
}.bind(this)
);
});
},
generateSecurityNumber: function() {
generateSecurityNumber() {
return new libsignal.FingerprintGenerator(5200)
.createFor(this.ourNumber, this.ourKey, this.model.id, this.theirKey)
.then(
function(securityNumber) {
this.securityNumber = securityNumber;
}.bind(this)
);
.then(securityNumber => {
this.securityNumber = securityNumber;
});
},
onSafetyNumberChanged: function() {
onSafetyNumberChanged() {
this.model.getProfiles().then(this.loadKeys.bind(this));
var dialog = new Whisper.ConfirmationDialogView({
const dialog = new Whisper.ConfirmationDialogView({
message: i18n('changedRightAfterVerify', [
this.model.getTitle(),
this.model.getTitle(),
@ -70,65 +72,62 @@
dialog.$el.insertBefore(this.el);
dialog.focusCancel();
},
toggleVerified: function() {
toggleVerified() {
this.$('button.verify').attr('disabled', true);
this.model
.toggleVerified()
.catch(
function(result) {
if (result instanceof Error) {
if (result.name === 'OutgoingIdentityKeyError') {
this.onSafetyNumberChanged();
} else {
console.log('failed to toggle verified:', result.stack);
}
.catch(result => {
if (result instanceof Error) {
if (result.name === 'OutgoingIdentityKeyError') {
this.onSafetyNumberChanged();
} else {
var keyError = _.some(result.errors, function(error) {
return error.name === 'OutgoingIdentityKeyError';
});
if (keyError) {
this.onSafetyNumberChanged();
} else {
_.forEach(result.errors, function(error) {
console.log('failed to toggle verified:', error.stack);
});
}
console.log('failed to toggle verified:', result.stack);
}
}.bind(this)
)
.then(
function() {
this.$('button.verify').removeAttr('disabled');
}.bind(this)
);
} else {
const keyError = _.some(
result.errors,
error => error.name === 'OutgoingIdentityKeyError'
);
if (keyError) {
this.onSafetyNumberChanged();
} else {
_.forEach(result.errors, error => {
console.log('failed to toggle verified:', error.stack);
});
}
}
})
.then(() => {
this.$('button.verify').removeAttr('disabled');
});
},
render_attributes: function() {
var s = this.securityNumber;
var chunks = [];
for (var i = 0; i < s.length; i += 5) {
render_attributes() {
const s = this.securityNumber;
const chunks = [];
for (let i = 0; i < s.length; i += 5) {
chunks.push(s.substring(i, i + 5));
}
var name = this.model.getTitle();
var yourSafetyNumberWith = i18n('yourSafetyNumberWith', name);
var isVerified = this.model.isVerified();
var verifyButton = isVerified ? i18n('unverify') : i18n('verify');
var verifiedStatus = isVerified
const name = this.model.getTitle();
const yourSafetyNumberWith = i18n(
'yourSafetyNumberWith',
this.model.getTitle()
);
const isVerified = this.model.isVerified();
const verifyButton = isVerified ? i18n('unverify') : i18n('verify');
const verifiedStatus = isVerified
? i18n('isVerified', name)
: i18n('isNotVerified', name);
return {
learnMore: i18n('learnMore'),
theirKeyUnknown: i18n('theirIdentityUnknown'),
yourSafetyNumberWith: i18n(
'yourSafetyNumberWith',
this.model.getTitle()
),
yourSafetyNumberWith,
verifyHelp: i18n('verifyHelp', this.model.getTitle()),
verifyButton: verifyButton,
verifyButton,
hasTheirKey: this.theirKey !== undefined,
chunks: chunks,
isVerified: isVerified,
verifiedStatus: verifiedStatus,
chunks,
isVerified,
verifiedStatus,
};
},
});

View file

@ -1,34 +1,35 @@
/* global Whisper, i18n */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
var FIVE_SECONDS = 5 * 1000;
window.Whisper = window.Whisper || {};
Whisper.LastSeenIndicatorView = Whisper.View.extend({
className: 'last-seen-indicator-view',
templateName: 'last-seen-indicator-view',
initialize: function(options) {
options = options || {};
initialize(options = {}) {
this.count = options.count || 0;
},
increment: function(count) {
increment(count) {
this.count += count;
this.render();
},
getCount: function() {
getCount() {
return this.count;
},
render_attributes: function() {
var unreadMessages =
render_attributes() {
const unreadMessages =
this.count === 1
? i18n('unreadMessage')
: i18n('unreadMessages', [this.count]);
return {
unreadMessages: unreadMessages,
unreadMessages,
};
},
});

View file

@ -1,5 +1,9 @@
/* global Backbone, Whisper, _ */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
/*
@ -9,27 +13,28 @@
Whisper.ListView = Backbone.View.extend({
tagName: 'ul',
itemView: Backbone.View,
initialize: function(options) {
initialize(options) {
this.options = options || {};
this.listenTo(this.collection, 'add', this.addOne);
this.listenTo(this.collection, 'reset', this.addAll);
},
addOne: function(model) {
addOne(model) {
if (this.itemView) {
var options = _.extend({}, this.options.toInclude, { model: model });
var view = new this.itemView(options);
const options = _.extend({}, this.options.toInclude, { model });
// eslint-disable-next-line new-cap
const view = new this.itemView(options);
this.$el.append(view.render().el);
this.$el.trigger('add');
}
},
addAll: function() {
addAll() {
this.$el.html('');
this.collection.each(this.addOne, this);
},
render: function() {
render() {
this.addAll();
return this;
},

View file

@ -1,32 +1,40 @@
/* global Whisper, i18n, _, ConversationController, Mustache, moment */
/* eslint-disable more/no-then */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
var ContactView = Whisper.View.extend({
const ContactView = Whisper.View.extend({
className: 'contact-detail',
templateName: 'contact-detail',
initialize: function(options) {
initialize(options) {
this.listenBack = options.listenBack;
this.resetPanel = options.resetPanel;
this.message = options.message;
var newIdentity = i18n('newIdentity');
this.errors = _.map(options.errors, function(error) {
const newIdentity = i18n('newIdentity');
this.errors = _.map(options.errors, error => {
if (error.name === 'OutgoingIdentityKeyError') {
// eslint-disable-next-line no-param-reassign
error.message = newIdentity;
}
return error;
});
this.outgoingKeyError = _.find(this.errors, function(error) {
return error.name === 'OutgoingIdentityKeyError';
});
this.outgoingKeyError = _.find(
this.errors,
error => error.name === 'OutgoingIdentityKeyError'
);
},
events: {
click: 'onClick',
},
onClick: function() {
onClick() {
if (this.outgoingKeyError) {
var view = new Whisper.IdentityKeySendErrorPanelView({
const view = new Whisper.IdentityKeySendErrorPanelView({
model: this.model,
listenBack: this.listenBack,
resetPanel: this.resetPanel,
@ -40,41 +48,33 @@
view.$('.cancel').focus();
}
},
forceSend: function() {
forceSend() {
this.model
.updateVerified()
.then(
function() {
if (this.model.isUnverified()) {
return this.model.setVerifiedDefault();
}
}.bind(this)
)
.then(
function() {
return this.model.isUntrusted();
}.bind(this)
)
.then(
function(untrusted) {
if (untrusted) {
return this.model.setApproved();
}
}.bind(this)
)
.then(
function() {
this.message.resend(this.outgoingKeyError.number);
}.bind(this)
);
.then(() => {
if (this.model.isUnverified()) {
return this.model.setVerifiedDefault();
}
return null;
})
.then(() => this.model.isUntrusted())
.then(untrusted => {
if (untrusted) {
return this.model.setApproved();
}
return null;
})
.then(() => {
this.message.resend(this.outgoingKeyError.number);
});
},
onSendAnyway: function() {
onSendAnyway() {
if (this.outgoingKeyError) {
this.forceSend();
}
},
render_attributes: function() {
var showButton = Boolean(this.outgoingKeyError);
render_attributes() {
const showButton = Boolean(this.outgoingKeyError);
return {
status: this.message.getStatus(this.model.id),
@ -90,7 +90,7 @@
Whisper.MessageDetailView = Whisper.View.extend({
className: 'message-detail panel',
templateName: 'message-detail',
initialize: function(options) {
initialize(options) {
this.listenBack = options.listenBack;
this.resetPanel = options.resetPanel;
@ -103,22 +103,22 @@
events: {
'click button.delete': 'onDelete',
},
onDelete: function() {
var dialog = new Whisper.ConfirmationDialogView({
onDelete() {
const dialog = new Whisper.ConfirmationDialogView({
message: i18n('deleteWarning'),
okText: i18n('delete'),
resolve: function() {
resolve: () => {
this.model.destroy();
this.resetPanel();
}.bind(this),
},
});
this.$el.prepend(dialog.el);
dialog.focusCancel();
},
getContacts: function() {
getContacts() {
// Return the set of models to be rendered in this view
var ids;
let ids;
if (this.model.isIncoming()) {
ids = [this.model.get('source')];
} else if (this.model.isOutgoing()) {
@ -130,13 +130,13 @@
}
}
return Promise.all(
ids.map(function(number) {
return ConversationController.getOrCreateAndWait(number, 'private');
})
ids.map(number =>
ConversationController.getOrCreateAndWait(number, 'private')
)
);
},
renderContact: function(contact) {
var view = new ContactView({
renderContact(contact) {
const view = new ContactView({
model: contact,
errors: this.grouped[contact.id],
listenBack: this.listenBack,
@ -145,12 +145,10 @@
}).render();
this.$('.contacts').append(view.el);
},
render: function() {
var errorsWithoutNumber = _.reject(this.model.get('errors'), function(
error
) {
return Boolean(error.number);
});
render() {
const errorsWithoutNumber = _.reject(this.model.get('errors'), error =>
Boolean(error.number)
);
this.$el.html(
Mustache.render(_.result(this, 'template', ''), {
@ -171,19 +169,14 @@
this.grouped = _.groupBy(this.model.get('errors'), 'number');
this.getContacts().then(
function(contacts) {
_.sortBy(
contacts,
function(c) {
var prefix = this.grouped[c.id] ? '0' : '1';
// this prefix ensures that contacts with errors are listed first;
// otherwise it's alphabetical
return prefix + c.getTitle();
}.bind(this)
).forEach(this.renderContact.bind(this));
}.bind(this)
);
this.getContacts().then(contacts => {
_.sortBy(contacts, c => {
const prefix = this.grouped[c.id] ? '0' : '1';
// this prefix ensures that contacts with errors are listed first;
// otherwise it's alphabetical
return prefix + c.getTitle();
}).forEach(this.renderContact.bind(this));
});
},
});
})();

View file

@ -1,5 +1,9 @@
/* global Whisper, _ */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.MessageListView = Whisper.ListView.extend({
@ -9,17 +13,14 @@
events: {
scroll: 'onScroll',
},
initialize: function() {
initialize() {
Whisper.ListView.prototype.initialize.call(this);
this.triggerLazyScroll = _.debounce(
function() {
this.$el.trigger('lazyScroll');
}.bind(this),
500
);
this.triggerLazyScroll = _.debounce(() => {
this.$el.trigger('lazyScroll');
}, 500);
},
onScroll: function() {
onScroll() {
this.measureScrollPosition();
if (this.$el.scrollTop() === 0) {
this.$el.trigger('loadMore');
@ -32,10 +33,10 @@
this.triggerLazyScroll();
},
atBottom: function() {
atBottom() {
return this.bottomOffset < 30;
},
measureScrollPosition: function() {
measureScrollPosition() {
if (this.el.scrollHeight === 0) {
// hidden
return;
@ -45,10 +46,10 @@
this.scrollHeight = this.el.scrollHeight;
this.bottomOffset = this.scrollHeight - this.scrollPosition;
},
resetScrollPosition: function() {
resetScrollPosition() {
this.$el.scrollTop(this.scrollPosition - this.$el.outerHeight());
},
scrollToBottomIfNeeded: function() {
scrollToBottomIfNeeded() {
// This is counter-intuitive. Our current bottomOffset is reflective of what
// we last measured, not necessarily the current state. And this is called
// after we just made a change to the DOM: inserting a message, or an image
@ -58,25 +59,26 @@
this.scrollToBottom();
}
},
scrollToBottom: function() {
scrollToBottom() {
this.$el.scrollTop(this.el.scrollHeight);
this.measureScrollPosition();
},
addOne: function(model) {
var view;
addOne(model) {
let view;
if (model.isExpirationTimerUpdate()) {
view = new Whisper.ExpirationTimerUpdateView({ model: model }).render();
view = new Whisper.ExpirationTimerUpdateView({ model }).render();
} else if (model.get('type') === 'keychange') {
view = new Whisper.KeyChangeView({ model: model }).render();
view = new Whisper.KeyChangeView({ model }).render();
} else if (model.get('type') === 'verified-change') {
view = new Whisper.VerifiedChangeView({ model: model }).render();
view = new Whisper.VerifiedChangeView({ model }).render();
} else {
view = new this.itemView({ model: model }).render();
// eslint-disable-next-line new-cap
view = new this.itemView({ model }).render();
this.listenTo(view, 'beforeChangeHeight', this.measureScrollPosition);
this.listenTo(view, 'afterChangeHeight', this.scrollToBottomIfNeeded);
}
var index = this.collection.indexOf(model);
const index = this.collection.indexOf(model);
this.measureScrollPosition();
if (model.get('unread') && !this.atBottom()) {
@ -91,20 +93,20 @@
this.$el.prepend(view.el);
} else {
// insert
var next = this.$('#' + this.collection.at(index + 1).id);
var prev = this.$('#' + this.collection.at(index - 1).id);
const next = this.$(`#${this.collection.at(index + 1).id}`);
const prev = this.$(`#${this.collection.at(index - 1).id}`);
if (next.length > 0) {
view.$el.insertBefore(next);
} else if (prev.length > 0) {
view.$el.insertAfter(prev);
} else {
// scan for the right spot
var elements = this.$el.children();
const elements = this.$el.children();
if (elements.length > 0) {
for (var i = 0; i < elements.length; ++i) {
var m = this.collection.get(elements[i].id);
var m_index = this.collection.indexOf(m);
if (m_index > index) {
for (let i = 0; i < elements.length; i += 1) {
const m = this.collection.get(elements[i].id);
const mIndex = this.collection.indexOf(m);
if (mIndex > index) {
view.$el.insertBefore(elements[i]);
break;
}

View file

@ -1,3 +1,6 @@
/* global Whisper, extension, Backbone, moment, i18n */
// eslint-disable-next-line func-names
(function() {
'use strict';
@ -6,15 +9,13 @@
Whisper.NetworkStatusView = Whisper.View.extend({
className: 'network-status',
templateName: 'networkStatus',
initialize: function() {
initialize() {
this.$el.hide();
this.renderIntervalHandle = setInterval(this.update.bind(this), 5000);
extension.windows.onClosed(
function() {
clearInterval(this.renderIntervalHandle);
}.bind(this)
);
extension.windows.onClosed(() => {
clearInterval(this.renderIntervalHandle);
});
setTimeout(this.finishConnectingGracePeriod.bind(this), 5000);
@ -27,29 +28,29 @@
this.model = new Backbone.Model();
this.listenTo(this.model, 'change', this.onChange);
},
onReconnectTimer: function() {
onReconnectTimer() {
this.setSocketReconnectInterval(60000);
},
finishConnectingGracePeriod: function() {
finishConnectingGracePeriod() {
this.withinConnectingGracePeriod = false;
},
setSocketReconnectInterval: function(millis) {
setSocketReconnectInterval(millis) {
this.socketReconnectWaitDuration = moment.duration(millis);
},
navigatorOnLine: function() {
navigatorOnLine() {
return navigator.onLine;
},
getSocketStatus: function() {
getSocketStatus() {
return window.getSocketStatus();
},
getNetworkStatus: function() {
var message = '';
var instructions = '';
var hasInterruption = false;
var action = null;
var buttonClass = null;
getNetworkStatus() {
let message = '';
let instructions = '';
let hasInterruption = false;
let action = null;
let buttonClass = null;
var socketStatus = this.getSocketStatus();
const socketStatus = this.getSocketStatus();
switch (socketStatus) {
case WebSocket.CONNECTING:
message = i18n('connecting');
@ -58,12 +59,13 @@
case WebSocket.OPEN:
this.setSocketReconnectInterval(null);
break;
case WebSocket.CLOSING:
case WebSocket.CLOSED:
message = i18n('disconnected');
instructions = i18n('checkNetworkConnection');
hasInterruption = true;
break;
case WebSocket.CLOSED:
case WebSocket.CLOSING:
default:
message = i18n('disconnected');
instructions = i18n('checkNetworkConnection');
hasInterruption = true;
@ -71,7 +73,7 @@
}
if (
socketStatus == WebSocket.CONNECTING &&
socketStatus === WebSocket.CONNECTING &&
!this.withinConnectingGracePeriod
) {
hasInterruption = true;
@ -94,21 +96,21 @@
}
return {
message: message,
instructions: instructions,
hasInterruption: hasInterruption,
action: action,
buttonClass: buttonClass,
message,
instructions,
hasInterruption,
action,
buttonClass,
};
},
update: function() {
var status = this.getNetworkStatus();
update() {
const status = this.getNetworkStatus();
this.model.set(status);
},
render_attributes: function() {
render_attributes() {
return this.model.attributes;
},
onChange: function() {
onChange() {
this.render();
if (this.model.attributes.hasInterruption) {
this.$el.slideDown();

View file

@ -1,12 +1,18 @@
/* global Whisper, _ */
/* eslint-disable more/no-then */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.NewGroupUpdateView = Whisper.View.extend({
tagName: 'div',
className: 'new-group-update',
templateName: 'new-group-update',
initialize: function(options) {
initialize(options) {
this.render();
this.avatarInput = new Whisper.FileInputView({
el: this.$('.group-avatar'),
@ -14,15 +20,13 @@
});
this.recipients_view = new Whisper.RecipientsInputView();
this.listenTo(this.recipients_view.typeahead, 'sync', function() {
this.model.contactCollection.models.forEach(
function(model) {
if (this.recipients_view.typeahead.get(model)) {
this.recipients_view.typeahead.remove(model);
}
}.bind(this)
);
});
this.listenTo(this.recipients_view.typeahead, 'sync', () =>
this.model.contactCollection.models.forEach(model => {
if (this.recipients_view.typeahead.get(model)) {
this.recipients_view.typeahead.remove(model);
}
})
);
this.recipients_view.$el.insertBefore(this.$('.container'));
this.member_list_view = new Whisper.ContactListView({
@ -38,49 +42,47 @@
'focusin input.search': 'showResults',
'focusout input.search': 'hideResults',
},
hideResults: function() {
hideResults() {
this.$('.results').hide();
},
showResults: function() {
showResults() {
this.$('.results').show();
},
goBack: function() {
goBack() {
this.trigger('back');
},
render_attributes: function() {
render_attributes() {
return {
name: this.model.getTitle(),
avatar: this.model.getAvatar(),
};
},
send: function() {
return this.avatarInput.getThumbnail().then(
function(avatarFile) {
var now = Date.now();
var attrs = {
timestamp: now,
active_at: now,
name: this.$('.name').val(),
members: _.union(
this.model.get('members'),
this.recipients_view.recipients.pluck('id')
),
};
if (avatarFile) {
attrs.avatar = avatarFile;
}
this.model.set(attrs);
var group_update = this.model.changed;
this.model.save();
send() {
return this.avatarInput.getThumbnail().then(avatarFile => {
const now = Date.now();
const attrs = {
timestamp: now,
active_at: now,
name: this.$('.name').val(),
members: _.union(
this.model.get('members'),
this.recipients_view.recipients.pluck('id')
),
};
if (avatarFile) {
attrs.avatar = avatarFile;
}
this.model.set(attrs);
const groupUpdate = this.model.changed;
this.model.save();
if (group_update.avatar) {
this.model.trigger('change:avatar');
}
if (groupUpdate.avatar) {
this.model.trigger('change:avatar');
}
this.model.updateGroup(group_update);
this.goBack();
}.bind(this)
);
this.model.updateGroup(groupUpdate);
this.goBack();
});
},
});
})();

View file

@ -1,26 +1,30 @@
/* global libphonenumber, Whisper */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.PhoneInputView = Whisper.View.extend({
tagName: 'div',
className: 'phone-input',
templateName: 'phone-number',
initialize: function() {
initialize() {
this.$('input.number').intlTelInput();
},
events: {
change: 'validateNumber',
keyup: 'validateNumber',
},
validateNumber: function() {
var input = this.$('input.number');
var regionCode = this.$('li.active')
validateNumber() {
const input = this.$('input.number');
const regionCode = this.$('li.active')
.attr('data-country-code')
.toUpperCase();
var number = input.val();
const number = input.val();
var parsedNumber = libphonenumber.util.parseNumber(number, regionCode);
const parsedNumber = libphonenumber.util.parseNumber(number, regionCode);
if (parsedNumber.isValidNumber) {
this.$('.number-container').removeClass('invalid');
this.$('.number-container').addClass('valid');

View file

@ -1,8 +1,12 @@
/* global Whisper, Backbone, ConversationController */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
var ContactsTypeahead = Backbone.TypeaheadCollection.extend({
const ContactsTypeahead = Backbone.TypeaheadCollection.extend({
typeaheadAttributes: [
'name',
'e164_number',
@ -12,7 +16,7 @@
database: Whisper.Database,
storeName: 'conversations',
model: Whisper.Conversation,
fetchContacts: function() {
fetchContacts() {
return this.fetch({ reset: true, conditions: { type: 'private' } });
},
});
@ -24,17 +28,17 @@
'click .remove': 'removeModel',
},
templateName: 'contact_pill',
initialize: function() {
var error = this.model.validate(this.model.attributes);
initialize() {
const error = this.model.validate(this.model.attributes);
if (error) {
this.$el.addClass('error');
}
},
removeModel: function() {
removeModel() {
this.$el.trigger('remove', { modelId: this.model.id });
this.remove();
},
render_attributes: function() {
render_attributes() {
return { name: this.model.getTitle() };
},
});
@ -55,7 +59,7 @@
Whisper.RecipientsInputView = Whisper.View.extend({
className: 'recipients-input',
templateName: 'recipients-input',
initialize: function(options) {
initialize(options) {
if (options) {
this.placeholder = options.placeholder;
}
@ -81,7 +85,7 @@
// View to display the matched contacts from typeahead
this.typeahead_view = new Whisper.SuggestionListView({
collection: new Whisper.ConversationCollection([], {
comparator: function(m) {
comparator(m) {
return m.getTitle().toLowerCase();
},
}),
@ -91,7 +95,7 @@
this.listenTo(this.typeahead, 'reset', this.filterContacts);
},
render_attributes: function() {
render_attributes() {
return { placeholder: this.placeholder || 'name or phone number' };
},
@ -102,8 +106,8 @@
'remove .recipient': 'removeRecipient',
},
filterContacts: function(e) {
var query = this.$input.val();
filterContacts() {
const query = this.$input.val();
if (query.length) {
if (this.maybeNumber(query)) {
this.new_contact_view.model.set('id', query);
@ -117,7 +121,7 @@
}
},
initNewContact: function() {
initNewContact() {
if (this.new_contact_view) {
this.new_contact_view.undelegateEvents();
this.new_contact_view.$el.hide();
@ -132,47 +136,45 @@
}).render();
},
addNewRecipient: function() {
addNewRecipient() {
this.recipients.add(this.new_contact_view.model);
this.initNewContact();
this.resetTypeahead();
},
addRecipient: function(e, conversation) {
addRecipient(e, conversation) {
this.recipients.add(this.typeahead.remove(conversation.id));
this.resetTypeahead();
},
removeRecipient: function(e, data) {
var model = this.recipients.remove(data.modelId);
removeRecipient(e, data) {
const model = this.recipients.remove(data.modelId);
if (!model.get('newContact')) {
this.typeahead.add(model);
}
this.filterContacts();
},
reset: function() {
reset() {
this.delegateEvents();
this.typeahead_view.delegateEvents();
this.recipients_view.delegateEvents();
this.new_contact_view.delegateEvents();
this.typeahead.add(
this.recipients.filter(function(model) {
return !model.get('newContact');
})
this.recipients.filter(model => !model.get('newContact'))
);
this.recipients.reset([]);
this.resetTypeahead();
this.typeahead.fetchContacts();
},
resetTypeahead: function() {
resetTypeahead() {
this.new_contact_view.$el.hide();
this.$input.val('').focus();
this.typeahead_view.collection.reset([]);
},
maybeNumber: function(number) {
maybeNumber(number) {
return number.match(/^\+?[0-9]*$/);
},
});

View file

@ -1,11 +1,17 @@
/* global Whisper, moment, WebAudioRecorder */
/* eslint-disable more/no-then */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.RecorderView = Whisper.View.extend({
className: 'recorder clearfix',
templateName: 'recorder',
initialize: function() {
initialize() {
this.startTime = Date.now();
this.interval = setInterval(this.updateTime.bind(this), 1000);
this.start();
@ -15,16 +21,16 @@
'click .finish': 'finish',
close: 'close',
},
updateTime: function() {
var duration = moment.duration(Date.now() - this.startTime, 'ms');
var minutes = '' + Math.trunc(duration.asMinutes());
var seconds = '' + duration.seconds();
updateTime() {
const duration = moment.duration(Date.now() - this.startTime, 'ms');
const minutes = `${Math.trunc(duration.asMinutes())}`;
let seconds = `${duration.seconds()}`;
if (seconds.length < 2) {
seconds = '0' + seconds;
seconds = `0${seconds}`;
}
this.$('.time').text(minutes + ':' + seconds);
this.$('.time').text(`${minutes}:${seconds}`);
},
close: function() {
close() {
// Note: the 'close' event can be triggered by InboxView, when the user clicks
// anywhere outside the recording pane.
@ -44,7 +50,7 @@
this.source = null;
if (this.context) {
this.context.close().then(function() {
this.context.close().then(() => {
console.log('audio context closed');
});
}
@ -53,16 +59,16 @@
this.remove();
this.trigger('closed');
},
finish: function() {
finish() {
this.recorder.finishRecording();
this.close();
},
handleBlob: function(recorder, blob) {
handleBlob(recorder, blob) {
if (blob) {
this.trigger('send', blob);
}
},
start: function() {
start() {
this.context = new AudioContext();
this.input = this.context.createGain();
this.recorder = new WebAudioRecorder(this.input, {
@ -73,15 +79,15 @@
this.recorder.onError = this.onError;
navigator.webkitGetUserMedia(
{ audio: true },
function(stream) {
stream => {
this.source = this.context.createMediaStreamSource(stream);
this.source.connect(this.input);
}.bind(this),
},
this.onError.bind(this)
);
this.recorder.startRecording();
},
onError: function(error) {
onError(error) {
// Protect against out-of-band errors, which can happen if the user revokes media
// permissions after successfully accessing the microphone.
if (!this.recorder) {

View file

@ -1,26 +1,28 @@
/* global Whisper, i18n */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.ScrollDownButtonView = Whisper.View.extend({
className: 'scroll-down-button-view',
templateName: 'scroll-down-button-view',
initialize: function(options) {
options = options || {};
initialize(options = {}) {
this.count = options.count || 0;
},
increment: function(count) {
count = count || 0;
increment(count = 0) {
this.count += count;
this.render();
},
render_attributes: function() {
var cssClass = this.count > 0 ? 'new-messages' : '';
render_attributes() {
const cssClass = this.count > 0 ? 'new-messages' : '';
var moreBelow = i18n('scrollDown');
let moreBelow = i18n('scrollDown');
if (this.count > 1) {
moreBelow = i18n('messagesBelow');
} else if (this.count === 1) {
@ -28,8 +30,8 @@
}
return {
cssClass: cssClass,
moreBelow: moreBelow,
cssClass,
moreBelow,
};
},
});

View file

@ -1,16 +1,22 @@
/* global Whisper, $, getAccountManager, textsecure */
/* eslint-disable more/no-then */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.StandaloneRegistrationView = Whisper.View.extend({
templateName: 'standalone',
className: 'full-screen-flow',
initialize: function() {
initialize() {
this.accountManager = getAccountManager();
this.render();
var number = textsecure.storage.user.getNumber();
const number = textsecure.storage.user.getNumber();
if (number) {
this.$('input.number').val(number);
}
@ -26,58 +32,59 @@
'change #code': 'onChangeCode',
'click #verifyCode': 'verifyCode',
},
verifyCode: function(e) {
var number = this.phoneView.validateNumber();
var verificationCode = $('#code')
verifyCode() {
const number = this.phoneView.validateNumber();
const verificationCode = $('#code')
.val()
.replace(/\D+/g, '');
this.accountManager
.registerSingleDevice(number, verificationCode)
.then(
function() {
this.$el.trigger('openInbox');
}.bind(this)
)
.then(() => {
this.$el.trigger('openInbox');
})
.catch(this.log.bind(this));
},
log: function(s) {
log(s) {
console.log(s);
this.$('#status').text(s);
},
validateCode: function() {
var verificationCode = $('#code')
validateCode() {
const verificationCode = $('#code')
.val()
.replace(/\D/g, '');
if (verificationCode.length == 6) {
if (verificationCode.length === 6) {
return verificationCode;
}
return null;
},
displayError: function(error) {
displayError(error) {
this.$('#error')
.hide()
.text(error)
.addClass('in')
.fadeIn();
},
onValidation: function() {
onValidation() {
if (this.$('#number-container').hasClass('valid')) {
this.$('#request-sms, #request-voice').removeAttr('disabled');
} else {
this.$('#request-sms, #request-voice').prop('disabled', 'disabled');
}
},
onChangeCode: function() {
onChangeCode() {
if (!this.validateCode()) {
this.$('#code').addClass('invalid');
} else {
this.$('#code').removeClass('invalid');
}
},
requestVoice: function() {
requestVoice() {
window.removeSetupMenuItems();
this.$('#error').hide();
var number = this.phoneView.validateNumber();
const number = this.phoneView.validateNumber();
if (number) {
this.accountManager
.requestVoiceVerification(number)
@ -89,10 +96,10 @@
this.$('#number-container').addClass('invalid');
}
},
requestSMSVerification: function() {
requestSMSVerification() {
window.removeSetupMenuItems();
$('#error').hide();
var number = this.phoneView.validateNumber();
const number = this.phoneView.validateNumber();
if (number) {
this.accountManager
.requestSMSVerification(number)

View file

@ -1,19 +1,23 @@
/* global Whisper, Mustache, _ */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.ToastView = Whisper.View.extend({
className: 'toast',
templateName: 'toast',
initialize: function() {
initialize() {
this.$el.hide();
},
close: function() {
close() {
this.$el.fadeOut(this.remove.bind(this));
},
render: function() {
render() {
this.$el.html(
Mustache.render(
_.result(this, 'template', ''),

View file

@ -1,3 +1,5 @@
/* global Whisper, Backbone, Mustache, _, $ */
/*
* Whisper.View
*
@ -17,64 +19,57 @@
* 4. Provides some common functionality, e.g. confirmation dialog
*
*/
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.View = Backbone.View.extend(
{
constructor: function() {
Backbone.View.apply(this, arguments);
constructor(...params) {
Backbone.View.call(this, ...params);
Mustache.parse(_.result(this, 'template'));
},
render_attributes: function() {
render_attributes() {
return _.result(this.model, 'attributes', {});
},
render_partials: function() {
render_partials() {
return Whisper.View.Templates;
},
template: function() {
template() {
if (this.templateName) {
return Whisper.View.Templates[this.templateName];
}
return '';
},
render: function() {
var attrs = _.result(this, 'render_attributes', {});
var template = _.result(this, 'template', '');
var partials = _.result(this, 'render_partials', '');
render() {
const attrs = _.result(this, 'render_attributes', {});
const template = _.result(this, 'template', '');
const partials = _.result(this, 'render_partials', '');
this.$el.html(Mustache.render(template, attrs, partials));
return this;
},
confirm: function(message, okText) {
return new Promise(
function(resolve, reject) {
var dialog = new Whisper.ConfirmationDialogView({
message: message,
okText: okText,
resolve: resolve,
reject: reject,
});
this.$el.append(dialog.el);
}.bind(this)
);
},
i18n_with_links: function() {
var args = Array.prototype.slice.call(arguments);
for (var i = 1; i < args.length; ++i) {
args[i] =
'class="link" href="' + encodeURI(args[i]) + '" target="_blank"';
}
return i18n(args[0], args.slice(1));
confirm(message, okText) {
return new Promise((resolve, reject) => {
const dialog = new Whisper.ConfirmationDialogView({
message,
okText,
resolve,
reject,
});
this.$el.append(dialog.el);
});
},
},
{
// Class attributes
Templates: (function() {
var templates = {};
$('script[type="text/x-tmpl-mustache"]').each(function(i, el) {
var $el = $(el);
var id = $el.attr('id');
Templates: (() => {
const templates = {};
$('script[type="text/x-tmpl-mustache"]').each((i, el) => {
const $el = $(el);
const id = $el.attr('id');
templates[id] = $el.html();
});
return templates;