Remove jshint - move everything over to eslint
Also removed all hints of previous linters
This commit is contained in:
		
					parent
					
						
							
								dc11db92f9
							
						
					
				
			
			
				commit
				
					
						43a44793c5
					
				
			
		
					 71 changed files with 1837 additions and 2030 deletions
				
			
		
							
								
								
									
										4
									
								
								.bowerrc
									
										
									
									
									
								
							
							
						
						
									
										4
									
								
								.bowerrc
									
										
									
									
									
								
							| 
						 | 
					@ -1,4 +0,0 @@
 | 
				
			||||||
{
 | 
					 | 
				
			||||||
  "directory": "components/",
 | 
					 | 
				
			||||||
  "analytics": false
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -5,13 +5,9 @@ dist/**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# these aren't ready yet, pulling files in one-by-one
 | 
					# these aren't ready yet, pulling files in one-by-one
 | 
				
			||||||
libtextsecure/**
 | 
					libtextsecure/**
 | 
				
			||||||
js/*.js
 | 
					 | 
				
			||||||
js/models/**/*.js
 | 
					 | 
				
			||||||
js/views/**/*.js
 | 
					 | 
				
			||||||
test/*.js
 | 
					test/*.js
 | 
				
			||||||
test/models/*.js
 | 
					test/models/*.js
 | 
				
			||||||
test/views/*.js
 | 
					test/views/*.js
 | 
				
			||||||
/*.js
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Generated files
 | 
					# Generated files
 | 
				
			||||||
js/components.js
 | 
					js/components.js
 | 
				
			||||||
| 
						 | 
					@ -24,35 +20,12 @@ test/test.js
 | 
				
			||||||
# Third-party files
 | 
					# Third-party files
 | 
				
			||||||
js/Mp3LameEncoder.min.js
 | 
					js/Mp3LameEncoder.min.js
 | 
				
			||||||
js/WebAudioRecorderMp3.js
 | 
					js/WebAudioRecorderMp3.js
 | 
				
			||||||
 | 
					js/libphonenumber-util.js
 | 
				
			||||||
 | 
					js/libsignal-protocol-worker.js
 | 
				
			||||||
 | 
					libtextsecure/libsignal-protocol.js
 | 
				
			||||||
 | 
					libtextsecure/test/blanket_mocha.js
 | 
				
			||||||
 | 
					test/blanket_mocha.js
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# TypeScript generated files
 | 
					# TypeScript generated files
 | 
				
			||||||
ts/**/*.js
 | 
					ts/**/*.js
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# ES2015+ files
 | 
					 | 
				
			||||||
!libtextsecure/api.js
 | 
					 | 
				
			||||||
!js/background.js
 | 
					 | 
				
			||||||
!js/backup.js
 | 
					 | 
				
			||||||
!js/database.js
 | 
					 | 
				
			||||||
!js/logging.js
 | 
					 | 
				
			||||||
!js/models/conversations.js
 | 
					 | 
				
			||||||
!js/models/messages.js
 | 
					 | 
				
			||||||
!js/notifications.js
 | 
					 | 
				
			||||||
!js/expiring_messages.js
 | 
					 | 
				
			||||||
!js/views/attachment_view.js
 | 
					 | 
				
			||||||
!js/views/backbone_wrapper_view.js
 | 
					 | 
				
			||||||
!js/views/clear_data_view.js
 | 
					 | 
				
			||||||
!js/views/contact_list_view.js
 | 
					 | 
				
			||||||
!js/views/conversation_search_view.js
 | 
					 | 
				
			||||||
!js/views/conversation_view.js
 | 
					 | 
				
			||||||
!js/views/debug_log_view.js
 | 
					 | 
				
			||||||
!js/views/file_input_view.js
 | 
					 | 
				
			||||||
!js/views/inbox_view.js
 | 
					 | 
				
			||||||
!js/views/message_view.js
 | 
					 | 
				
			||||||
!js/views/settings_view.js
 | 
					 | 
				
			||||||
!js/views/timestamp_view.js
 | 
					 | 
				
			||||||
!test/backup_test.js
 | 
					 | 
				
			||||||
!test/views/attachment_view_test.js
 | 
					 | 
				
			||||||
!libtextsecure/message_receiver.js
 | 
					 | 
				
			||||||
!main.js
 | 
					 | 
				
			||||||
!preload.js
 | 
					 | 
				
			||||||
!prepare_build.js
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										33
									
								
								.jscsrc
									
										
									
									
									
								
							
							
						
						
									
										33
									
								
								.jscsrc
									
										
									
									
									
								
							| 
						 | 
					@ -1,33 +0,0 @@
 | 
				
			||||||
{
 | 
					 | 
				
			||||||
  "disallowMixedSpacesAndTabs": true,
 | 
					 | 
				
			||||||
  "disallowTrailingWhitespace": true,
 | 
					 | 
				
			||||||
  "disallowNewlineBeforeBlockStatements": true,
 | 
					 | 
				
			||||||
  "requireCommaBeforeLineBreak": true,
 | 
					 | 
				
			||||||
  "requireSemicolons": true,
 | 
					 | 
				
			||||||
  "requireSpaceBeforeBlockStatements": true,
 | 
					 | 
				
			||||||
  "disallowSpacesInNamedFunctionExpression": {
 | 
					 | 
				
			||||||
    "beforeOpeningRoundBrace": true
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "requireSpacesInNamedFunctionExpression": {
 | 
					 | 
				
			||||||
    "beforeOpeningCurlyBrace": true
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "requireCurlyBraces": [
 | 
					 | 
				
			||||||
    "if",
 | 
					 | 
				
			||||||
    "else",
 | 
					 | 
				
			||||||
    "for",
 | 
					 | 
				
			||||||
    "while",
 | 
					 | 
				
			||||||
    "do",
 | 
					 | 
				
			||||||
    "try",
 | 
					 | 
				
			||||||
    "catch"
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  "requireSpaceAfterKeywords": [
 | 
					 | 
				
			||||||
    "if",
 | 
					 | 
				
			||||||
    "else",
 | 
					 | 
				
			||||||
    "for",
 | 
					 | 
				
			||||||
    "while",
 | 
					 | 
				
			||||||
    "case",
 | 
					 | 
				
			||||||
    "try",
 | 
					 | 
				
			||||||
    "typeof",
 | 
					 | 
				
			||||||
    "return"
 | 
					 | 
				
			||||||
  ]
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										73
									
								
								.jshintrc
									
										
									
									
									
								
							
							
						
						
									
										73
									
								
								.jshintrc
									
										
									
									
									
								
							| 
						 | 
					@ -1,73 +0,0 @@
 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    "maxerr"        : 50,
 | 
					 | 
				
			||||||
    "bitwise"       : false,
 | 
					 | 
				
			||||||
    "camelcase"     : false,
 | 
					 | 
				
			||||||
    "curly"         : false,
 | 
					 | 
				
			||||||
    "eqeqeq"        : false,
 | 
					 | 
				
			||||||
    "forin"         : false,
 | 
					 | 
				
			||||||
    "freeze"        : false,
 | 
					 | 
				
			||||||
    "immed"         : false,
 | 
					 | 
				
			||||||
    "indent"        : 4,
 | 
					 | 
				
			||||||
    "latedef"       : false,
 | 
					 | 
				
			||||||
    "newcap"        : false,
 | 
					 | 
				
			||||||
    "noarg"         : false,
 | 
					 | 
				
			||||||
    "noempty"       : false,
 | 
					 | 
				
			||||||
    "nonbsp"        : false,
 | 
					 | 
				
			||||||
    "nonew"         : false,
 | 
					 | 
				
			||||||
    "plusplus"      : false,
 | 
					 | 
				
			||||||
    "quotmark"      : false,
 | 
					 | 
				
			||||||
    "undef"         : false,
 | 
					 | 
				
			||||||
    "unused"        : false,
 | 
					 | 
				
			||||||
    "strict"        : false,
 | 
					 | 
				
			||||||
    "maxparams"     : false,
 | 
					 | 
				
			||||||
    "maxdepth"      : false,
 | 
					 | 
				
			||||||
    "maxstatements" : false,
 | 
					 | 
				
			||||||
    "maxcomplexity" : false,
 | 
					 | 
				
			||||||
    "maxlen"        : false,
 | 
					 | 
				
			||||||
    "asi"           : false,
 | 
					 | 
				
			||||||
    "boss"          : false,
 | 
					 | 
				
			||||||
    "debug"         : false,
 | 
					 | 
				
			||||||
    "eqnull"        : false,
 | 
					 | 
				
			||||||
    "es5"           : false,
 | 
					 | 
				
			||||||
    "esversion"     : 6,
 | 
					 | 
				
			||||||
    "esnext"        : false,
 | 
					 | 
				
			||||||
    "moz"           : false,
 | 
					 | 
				
			||||||
    "evil"          : false,
 | 
					 | 
				
			||||||
    "expr"          : false,
 | 
					 | 
				
			||||||
    "funcscope"     : false,
 | 
					 | 
				
			||||||
    "globalstrict"  : false,
 | 
					 | 
				
			||||||
    "iterator"      : false,
 | 
					 | 
				
			||||||
    "lastsemic"     : false,
 | 
					 | 
				
			||||||
    "laxbreak"      : true,
 | 
					 | 
				
			||||||
    "laxcomma"      : false,
 | 
					 | 
				
			||||||
    "loopfunc"      : false,
 | 
					 | 
				
			||||||
    "multistr"      : false,
 | 
					 | 
				
			||||||
    "noyield"       : false,
 | 
					 | 
				
			||||||
    "notypeof"      : false,
 | 
					 | 
				
			||||||
    "proto"         : false,
 | 
					 | 
				
			||||||
    "scripturl"     : false,
 | 
					 | 
				
			||||||
    "shadow"        : false,
 | 
					 | 
				
			||||||
    "sub"           : false,
 | 
					 | 
				
			||||||
    "supernew"      : false,
 | 
					 | 
				
			||||||
    "validthis"     : false,
 | 
					 | 
				
			||||||
    "browser"       : false,
 | 
					 | 
				
			||||||
    "browserify"    : false,
 | 
					 | 
				
			||||||
    "couch"         : false,
 | 
					 | 
				
			||||||
    "devel"         : false,
 | 
					 | 
				
			||||||
    "dojo"          : false,
 | 
					 | 
				
			||||||
    "jasmine"       : false,
 | 
					 | 
				
			||||||
    "jquery"        : false,
 | 
					 | 
				
			||||||
    "mocha"         : false,
 | 
					 | 
				
			||||||
    "mootools"      : false,
 | 
					 | 
				
			||||||
    "node"          : false,
 | 
					 | 
				
			||||||
    "nonstandard"   : false,
 | 
					 | 
				
			||||||
    "prototypejs"   : false,
 | 
					 | 
				
			||||||
    "qunit"         : false,
 | 
					 | 
				
			||||||
    "rhino"         : false,
 | 
					 | 
				
			||||||
    "shelljs"       : false,
 | 
					 | 
				
			||||||
    "worker"        : false,
 | 
					 | 
				
			||||||
    "wsh"           : false,
 | 
					 | 
				
			||||||
    "yui"           : false,
 | 
					 | 
				
			||||||
    "globals"       : {},
 | 
					 | 
				
			||||||
    "-W100"         : true
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,6 @@ config/local-*.json
 | 
				
			||||||
config/local.json
 | 
					config/local.json
 | 
				
			||||||
dist/**
 | 
					dist/**
 | 
				
			||||||
js/components.js
 | 
					js/components.js
 | 
				
			||||||
js/libsignal-protocol-worker.js
 | 
					 | 
				
			||||||
js/libtextsecure.js
 | 
					js/libtextsecure.js
 | 
				
			||||||
libtextsecure/components.js
 | 
					libtextsecure/components.js
 | 
				
			||||||
libtextsecure/test/test.js
 | 
					libtextsecure/test/test.js
 | 
				
			||||||
| 
						 | 
					@ -21,6 +20,7 @@ stylesheets/manifest.css
 | 
				
			||||||
components/**
 | 
					components/**
 | 
				
			||||||
js/Mp3LameEncoder.min.js
 | 
					js/Mp3LameEncoder.min.js
 | 
				
			||||||
js/WebAudioRecorderMp3.js
 | 
					js/WebAudioRecorderMp3.js
 | 
				
			||||||
 | 
					js/libsignal-protocol-worker.js
 | 
				
			||||||
libtextsecure/libsignal-protocol.js
 | 
					libtextsecure/libsignal-protocol.js
 | 
				
			||||||
libtextsecure/test/blanket_mocha.js
 | 
					libtextsecure/test/blanket_mocha.js
 | 
				
			||||||
test/blanket_mocha.js
 | 
					test/blanket_mocha.js
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										283
									
								
								Gruntfile.js
									
										
									
									
									
								
							
							
						
						
									
										283
									
								
								Gruntfile.js
									
										
									
									
									
								
							| 
						 | 
					@ -1,21 +1,29 @@
 | 
				
			||||||
var path = require('path');
 | 
					const path = require('path');
 | 
				
			||||||
var packageJson = require('./package.json');
 | 
					const packageJson = require('./package.json');
 | 
				
			||||||
 | 
					const importOnce = require('node-sass-import-once');
 | 
				
			||||||
 | 
					const rimraf = require('rimraf');
 | 
				
			||||||
 | 
					const mkdirp = require('mkdirp');
 | 
				
			||||||
 | 
					const spectron = require('spectron');
 | 
				
			||||||
 | 
					const asar = require('asar');
 | 
				
			||||||
 | 
					const fs = require('fs');
 | 
				
			||||||
 | 
					const assert = require('assert');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = function(grunt) {
 | 
					/* eslint-disable more/no-then */
 | 
				
			||||||
  'use strict';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var bower = grunt.file.readJSON('bower.json');
 | 
					module.exports = grunt => {
 | 
				
			||||||
  var components = [];
 | 
					  const bower = grunt.file.readJSON('bower.json');
 | 
				
			||||||
  for (var i in bower.concat.app) {
 | 
					  const components = [];
 | 
				
			||||||
 | 
					  // eslint-disable-next-line guard-for-in, no-restricted-syntax
 | 
				
			||||||
 | 
					  for (const i in bower.concat.app) {
 | 
				
			||||||
    components.push(bower.concat.app[i]);
 | 
					    components.push(bower.concat.app[i]);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var libtextsecurecomponents = [];
 | 
					  const libtextsecurecomponents = [];
 | 
				
			||||||
  for (i in bower.concat.libtextsecure) {
 | 
					  // eslint-disable-next-line guard-for-in, no-restricted-syntax
 | 
				
			||||||
 | 
					  for (const i in bower.concat.libtextsecure) {
 | 
				
			||||||
    libtextsecurecomponents.push(bower.concat.libtextsecure[i]);
 | 
					    libtextsecurecomponents.push(bower.concat.libtextsecure[i]);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var importOnce = require('node-sass-import-once');
 | 
					 | 
				
			||||||
  grunt.loadNpmTasks('grunt-sass');
 | 
					  grunt.loadNpmTasks('grunt-sass');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  grunt.initConfig({
 | 
					  grunt.initConfig({
 | 
				
			||||||
| 
						 | 
					@ -91,39 +99,6 @@ module.exports = function(grunt) {
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    jshint: {
 | 
					 | 
				
			||||||
      files: [
 | 
					 | 
				
			||||||
        'Gruntfile.js',
 | 
					 | 
				
			||||||
        'js/**/*.js',
 | 
					 | 
				
			||||||
        '!js/background.js',
 | 
					 | 
				
			||||||
        '!js/backup.js',
 | 
					 | 
				
			||||||
        '!js/components.js',
 | 
					 | 
				
			||||||
        '!js/database.js',
 | 
					 | 
				
			||||||
        '!js/libsignal-protocol-worker.js',
 | 
					 | 
				
			||||||
        '!js/libtextsecure.js',
 | 
					 | 
				
			||||||
        '!js/logging.js',
 | 
					 | 
				
			||||||
        '!js/expiring_messages.js',
 | 
					 | 
				
			||||||
        '!js/modules/**/*.js',
 | 
					 | 
				
			||||||
        '!js/Mp3LameEncoder.min.js',
 | 
					 | 
				
			||||||
        '!js/settings_start.js',
 | 
					 | 
				
			||||||
        '!js/signal_protocol_store.js',
 | 
					 | 
				
			||||||
        '!js/views/clear_data_view.js',
 | 
					 | 
				
			||||||
        '!js/views/conversation_search_view.js',
 | 
					 | 
				
			||||||
        '!js/views/conversation_view.js',
 | 
					 | 
				
			||||||
        '!js/views/debug_log_view.js',
 | 
					 | 
				
			||||||
        '!js/views/file_input_view.js',
 | 
					 | 
				
			||||||
        '!js/views/timestamp_view.js',
 | 
					 | 
				
			||||||
        '!js/views/message_view.js',
 | 
					 | 
				
			||||||
        '!js/views/settings_view.js',
 | 
					 | 
				
			||||||
        '!js/views/contact_list_view.js',
 | 
					 | 
				
			||||||
        '!js/models/conversations.js',
 | 
					 | 
				
			||||||
        '!js/models/messages.js',
 | 
					 | 
				
			||||||
        '!js/WebAudioRecorderMp3.js',
 | 
					 | 
				
			||||||
        '!libtextsecure/message_receiver.js',
 | 
					 | 
				
			||||||
        '_locales/**/*',
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
      options: { jshintrc: '.jshintrc' },
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    copy: {
 | 
					    copy: {
 | 
				
			||||||
      deps: {
 | 
					      deps: {
 | 
				
			||||||
        files: [
 | 
					        files: [
 | 
				
			||||||
| 
						 | 
					@ -151,10 +126,6 @@ module.exports = function(grunt) {
 | 
				
			||||||
        files: ['./stylesheets/*.scss'],
 | 
					        files: ['./stylesheets/*.scss'],
 | 
				
			||||||
        tasks: ['sass'],
 | 
					        tasks: ['sass'],
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      scripts: {
 | 
					 | 
				
			||||||
        files: ['<%= jshint.files %>'],
 | 
					 | 
				
			||||||
        tasks: ['jshint'],
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      transpile: {
 | 
					      transpile: {
 | 
				
			||||||
        files: ['./ts/**/*.ts'],
 | 
					        files: ['./ts/**/*.ts'],
 | 
				
			||||||
        tasks: ['exec:transpile'],
 | 
					        tasks: ['exec:transpile'],
 | 
				
			||||||
| 
						 | 
					@ -173,41 +144,37 @@ module.exports = function(grunt) {
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    'test-release': {
 | 
					    'test-release': {
 | 
				
			||||||
      osx: {
 | 
					      osx: {
 | 
				
			||||||
        archive:
 | 
					        archive: `mac/${
 | 
				
			||||||
          'mac/' + packageJson.productName + '.app/Contents/Resources/app.asar',
 | 
					          packageJson.productName
 | 
				
			||||||
        appUpdateYML:
 | 
					        }.app/Contents/Resources/app.asar`,
 | 
				
			||||||
          'mac/' +
 | 
					        appUpdateYML: `mac/${
 | 
				
			||||||
          packageJson.productName +
 | 
					          packageJson.productName
 | 
				
			||||||
          '.app/Contents/Resources/app-update.yml',
 | 
					        }.app/Contents/Resources/app-update.yml`,
 | 
				
			||||||
        exe:
 | 
					        exe: `mac/${packageJson.productName}.app/Contents/MacOS/${
 | 
				
			||||||
          'mac/' +
 | 
					          packageJson.productName
 | 
				
			||||||
          packageJson.productName +
 | 
					        }`,
 | 
				
			||||||
          '.app/Contents/MacOS/' +
 | 
					 | 
				
			||||||
          packageJson.productName,
 | 
					 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      mas: {
 | 
					      mas: {
 | 
				
			||||||
        archive: 'mas/Signal.app/Contents/Resources/app.asar',
 | 
					        archive: 'mas/Signal.app/Contents/Resources/app.asar',
 | 
				
			||||||
        appUpdateYML: 'mac/Signal.app/Contents/Resources/app-update.yml',
 | 
					        appUpdateYML: 'mac/Signal.app/Contents/Resources/app-update.yml',
 | 
				
			||||||
        exe:
 | 
					        exe: `mas/${packageJson.productName}.app/Contents/MacOS/${
 | 
				
			||||||
          'mas/' +
 | 
					          packageJson.productName
 | 
				
			||||||
          packageJson.productName +
 | 
					        }`,
 | 
				
			||||||
          '.app/Contents/MacOS/' +
 | 
					 | 
				
			||||||
          packageJson.productName,
 | 
					 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      linux: {
 | 
					      linux: {
 | 
				
			||||||
        archive: 'linux-unpacked/resources/app.asar',
 | 
					        archive: 'linux-unpacked/resources/app.asar',
 | 
				
			||||||
        exe: 'linux-unpacked/' + packageJson.name,
 | 
					        exe: `linux-unpacked/${packageJson.name}`,
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      win: {
 | 
					      win: {
 | 
				
			||||||
        archive: 'win-unpacked/resources/app.asar',
 | 
					        archive: 'win-unpacked/resources/app.asar',
 | 
				
			||||||
        appUpdateYML: 'win-unpacked/resources/app-update.yml',
 | 
					        appUpdateYML: 'win-unpacked/resources/app-update.yml',
 | 
				
			||||||
        exe: 'win-unpacked/' + packageJson.productName + '.exe',
 | 
					        exe: `win-unpacked/${packageJson.productName}.exe`,
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    gitinfo: {}, // to be populated by grunt gitinfo
 | 
					    gitinfo: {}, // to be populated by grunt gitinfo
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Object.keys(grunt.config.get('pkg').devDependencies).forEach(function(key) {
 | 
					  Object.keys(grunt.config.get('pkg').devDependencies).forEach(key => {
 | 
				
			||||||
    if (/^grunt(?!(-cli)?$)/.test(key)) {
 | 
					    if (/^grunt(?!(-cli)?$)/.test(key)) {
 | 
				
			||||||
      // ignore grunt and grunt-cli
 | 
					      // ignore grunt and grunt-cli
 | 
				
			||||||
      grunt.loadNpmTasks(key);
 | 
					      grunt.loadNpmTasks(key);
 | 
				
			||||||
| 
						 | 
					@ -216,20 +183,16 @@ module.exports = function(grunt) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Transifex does not understand placeholders, so this task patches all non-en
 | 
					  // Transifex does not understand placeholders, so this task patches all non-en
 | 
				
			||||||
  // locales with missing placeholders
 | 
					  // locales with missing placeholders
 | 
				
			||||||
  grunt.registerTask('locale-patch', function() {
 | 
					  grunt.registerTask('locale-patch', () => {
 | 
				
			||||||
    var en = grunt.file.readJSON('_locales/en/messages.json');
 | 
					    const en = grunt.file.readJSON('_locales/en/messages.json');
 | 
				
			||||||
    grunt.file.recurse('_locales', function(
 | 
					    grunt.file.recurse('_locales', (abspath, rootdir, subdir, filename) => {
 | 
				
			||||||
      abspath,
 | 
					 | 
				
			||||||
      rootdir,
 | 
					 | 
				
			||||||
      subdir,
 | 
					 | 
				
			||||||
      filename
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
      if (subdir === 'en' || filename !== 'messages.json') {
 | 
					      if (subdir === 'en' || filename !== 'messages.json') {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      var messages = grunt.file.readJSON(abspath);
 | 
					      const messages = grunt.file.readJSON(abspath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      for (var key in messages) {
 | 
					      // eslint-disable-next-line no-restricted-syntax
 | 
				
			||||||
 | 
					      for (const key in messages) {
 | 
				
			||||||
        if (en[key] !== undefined && messages[key] !== undefined) {
 | 
					        if (en[key] !== undefined && messages[key] !== undefined) {
 | 
				
			||||||
          if (
 | 
					          if (
 | 
				
			||||||
            en[key].placeholders !== undefined &&
 | 
					            en[key].placeholders !== undefined &&
 | 
				
			||||||
| 
						 | 
					@ -240,32 +203,32 @@ module.exports = function(grunt) {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      grunt.file.write(abspath, JSON.stringify(messages, null, 4) + '\n');
 | 
					      grunt.file.write(abspath, `${JSON.stringify(messages, null, 4)}\n`);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  grunt.registerTask('getExpireTime', function() {
 | 
					  grunt.registerTask('getExpireTime', () => {
 | 
				
			||||||
    grunt.task.requires('gitinfo');
 | 
					    grunt.task.requires('gitinfo');
 | 
				
			||||||
    var gitinfo = grunt.config.get('gitinfo');
 | 
					    const gitinfo = grunt.config.get('gitinfo');
 | 
				
			||||||
    var commited = gitinfo.local.branch.current.lastCommitTime;
 | 
					    const commited = gitinfo.local.branch.current.lastCommitTime;
 | 
				
			||||||
    var time = Date.parse(commited) + 1000 * 60 * 60 * 24 * 90;
 | 
					    const time = Date.parse(commited) + 1000 * 60 * 60 * 24 * 90;
 | 
				
			||||||
    grunt.file.write(
 | 
					    grunt.file.write(
 | 
				
			||||||
      'config/local-production.json',
 | 
					      'config/local-production.json',
 | 
				
			||||||
      JSON.stringify({ buildExpiration: time }) + '\n'
 | 
					      `${JSON.stringify({ buildExpiration: time })}\n`
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  grunt.registerTask('clean-release', function() {
 | 
					  grunt.registerTask('clean-release', () => {
 | 
				
			||||||
    require('rimraf').sync('release');
 | 
					    rimraf.sync('release');
 | 
				
			||||||
    require('mkdirp').sync('release');
 | 
					    mkdirp.sync('release');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function runTests(environment, cb) {
 | 
					  function runTests(environment, cb) {
 | 
				
			||||||
    var failure;
 | 
					    let failure;
 | 
				
			||||||
    var Application = require('spectron').Application;
 | 
					    const { Application } = spectron;
 | 
				
			||||||
    var electronBinary =
 | 
					    const electronBinary =
 | 
				
			||||||
      process.platform === 'win32' ? 'electron.cmd' : 'electron';
 | 
					      process.platform === 'win32' ? 'electron.cmd' : 'electron';
 | 
				
			||||||
    var app = new Application({
 | 
					    const app = new Application({
 | 
				
			||||||
      path: path.join(__dirname, 'node_modules', '.bin', electronBinary),
 | 
					      path: path.join(__dirname, 'node_modules', '.bin', electronBinary),
 | 
				
			||||||
      args: [path.join(__dirname, 'main.js')],
 | 
					      args: [path.join(__dirname, 'main.js')],
 | 
				
			||||||
      env: {
 | 
					      env: {
 | 
				
			||||||
| 
						 | 
					@ -275,78 +238,71 @@ module.exports = function(grunt) {
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function getMochaResults() {
 | 
					    function getMochaResults() {
 | 
				
			||||||
 | 
					      // eslint-disable-next-line no-undef
 | 
				
			||||||
      return window.mochaResults;
 | 
					      return window.mochaResults;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    app
 | 
					    app
 | 
				
			||||||
      .start()
 | 
					      .start()
 | 
				
			||||||
      .then(function() {
 | 
					      .then(() =>
 | 
				
			||||||
        return app.client.waitUntil(
 | 
					        app.client.waitUntil(
 | 
				
			||||||
          function() {
 | 
					          () =>
 | 
				
			||||||
            return app.client.execute(getMochaResults).then(function(data) {
 | 
					            app.client
 | 
				
			||||||
              return Boolean(data.value);
 | 
					              .execute(getMochaResults)
 | 
				
			||||||
            });
 | 
					              .then(data => Boolean(data.value)),
 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
          10000,
 | 
					          10000,
 | 
				
			||||||
          'Expected to find window.mochaResults set!'
 | 
					          'Expected to find window.mochaResults set!'
 | 
				
			||||||
        );
 | 
					        )
 | 
				
			||||||
      })
 | 
					      )
 | 
				
			||||||
      .then(function() {
 | 
					      .then(() => app.client.execute(getMochaResults))
 | 
				
			||||||
        return app.client.execute(getMochaResults);
 | 
					      .then(data => {
 | 
				
			||||||
      })
 | 
					        const results = data.value;
 | 
				
			||||||
      .then(function(data) {
 | 
					 | 
				
			||||||
        var results = data.value;
 | 
					 | 
				
			||||||
        if (results.failures > 0) {
 | 
					        if (results.failures > 0) {
 | 
				
			||||||
          console.error(results.reports);
 | 
					          console.error(results.reports);
 | 
				
			||||||
          failure = function() {
 | 
					          failure = () =>
 | 
				
			||||||
            grunt.fail.fatal(
 | 
					            grunt.fail.fatal(`Found ${results.failures} failing unit tests.`);
 | 
				
			||||||
              'Found ' + results.failures + ' failing unit tests.'
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
          };
 | 
					 | 
				
			||||||
          return app.client.log('browser');
 | 
					          return app.client.log('browser');
 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          grunt.log.ok(results.passes + ' tests passed.');
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        grunt.log.ok(`${results.passes} tests passed.`);
 | 
				
			||||||
 | 
					        return null;
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .then(function(logs) {
 | 
					      .then(logs => {
 | 
				
			||||||
        if (logs) {
 | 
					        if (logs) {
 | 
				
			||||||
          console.error();
 | 
					          console.error();
 | 
				
			||||||
          console.error('Because tests failed, printing browser logs:');
 | 
					          console.error('Because tests failed, printing browser logs:');
 | 
				
			||||||
          console.error(logs);
 | 
					          console.error(logs);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch(function(error) {
 | 
					      .catch(error => {
 | 
				
			||||||
        failure = function() {
 | 
					        failure = () =>
 | 
				
			||||||
          grunt.fail.fatal(
 | 
					          grunt.fail.fatal(
 | 
				
			||||||
            'Something went wrong: ' + error.message + ' ' + error.stack
 | 
					            `Something went wrong: ${error.message} ${error.stack}`
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .then(function() {
 | 
					      .then(() => {
 | 
				
			||||||
        // We need to use the failure variable and this early stop to clean up before
 | 
					        // We need to use the failure variable and this early stop to clean up before
 | 
				
			||||||
        // shutting down. Grunt's fail methods are the only way to set the return value,
 | 
					        // shutting down. Grunt's fail methods are the only way to set the return value,
 | 
				
			||||||
        // but they shut the process down immediately!
 | 
					        // but they shut the process down immediately!
 | 
				
			||||||
        if (failure) {
 | 
					        if (failure) {
 | 
				
			||||||
          console.log();
 | 
					          console.log();
 | 
				
			||||||
          console.log('Main process logs:');
 | 
					          console.log('Main process logs:');
 | 
				
			||||||
          return app.client.getMainProcessLogs().then(function(logs) {
 | 
					          return app.client.getMainProcessLogs().then(logs => {
 | 
				
			||||||
            logs.forEach(function(log) {
 | 
					            logs.forEach(log => {
 | 
				
			||||||
              console.log(log);
 | 
					              console.log(log);
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return app.stop();
 | 
					            return app.stop();
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          return app.stop();
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        return app.stop();
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .then(function() {
 | 
					      .then(() => {
 | 
				
			||||||
        if (failure) {
 | 
					        if (failure) {
 | 
				
			||||||
          failure();
 | 
					          failure();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        cb();
 | 
					        cb();
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch(function(error) {
 | 
					      .catch(error => {
 | 
				
			||||||
        console.error('Second-level error:', error.message, error.stack);
 | 
					        console.error('Second-level error:', error.message, error.stack);
 | 
				
			||||||
        if (failure) {
 | 
					        if (failure) {
 | 
				
			||||||
          failure();
 | 
					          failure();
 | 
				
			||||||
| 
						 | 
					@ -355,9 +311,9 @@ module.exports = function(grunt) {
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  grunt.registerTask('unit-tests', 'Run unit tests w/Electron', function() {
 | 
					  grunt.registerTask('unit-tests', 'Run unit tests w/Electron', () => {
 | 
				
			||||||
    var environment = grunt.option('env') || 'test';
 | 
					    const environment = grunt.option('env') || 'test';
 | 
				
			||||||
    var done = this.async();
 | 
					    const done = this.async();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    runTests(environment, done);
 | 
					    runTests(environment, done);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
| 
						 | 
					@ -365,102 +321,93 @@ module.exports = function(grunt) {
 | 
				
			||||||
  grunt.registerTask(
 | 
					  grunt.registerTask(
 | 
				
			||||||
    'lib-unit-tests',
 | 
					    'lib-unit-tests',
 | 
				
			||||||
    'Run libtextsecure unit tests w/Electron',
 | 
					    'Run libtextsecure unit tests w/Electron',
 | 
				
			||||||
    function() {
 | 
					    () => {
 | 
				
			||||||
      var environment = grunt.option('env') || 'test-lib';
 | 
					      const environment = grunt.option('env') || 'test-lib';
 | 
				
			||||||
      var done = this.async();
 | 
					      const done = this.async();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      runTests(environment, done);
 | 
					      runTests(environment, done);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  grunt.registerMultiTask('test-release', 'Test packaged releases', function() {
 | 
					  grunt.registerMultiTask('test-release', 'Test packaged releases', () => {
 | 
				
			||||||
    var dir = grunt.option('dir') || 'dist';
 | 
					    const dir = grunt.option('dir') || 'dist';
 | 
				
			||||||
    var environment = grunt.option('env') || 'production';
 | 
					    const environment = grunt.option('env') || 'production';
 | 
				
			||||||
    var asar = require('asar');
 | 
					    const config = this.data;
 | 
				
			||||||
    var config = this.data;
 | 
					    const archive = [dir, config.archive].join('/');
 | 
				
			||||||
    var archive = [dir, config.archive].join('/');
 | 
					    const files = [
 | 
				
			||||||
    var files = [
 | 
					 | 
				
			||||||
      'config/default.json',
 | 
					      'config/default.json',
 | 
				
			||||||
      'config/' + environment + '.json',
 | 
					      `config/${environment}.json`,
 | 
				
			||||||
      'config/local-' + environment + '.json',
 | 
					      `config/local-${environment}.json`,
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    console.log(this.target, archive);
 | 
					    console.log(this.target, archive);
 | 
				
			||||||
    var releaseFiles = files.concat(config.files || []);
 | 
					    const releaseFiles = files.concat(config.files || []);
 | 
				
			||||||
    releaseFiles.forEach(function(fileName) {
 | 
					    releaseFiles.forEach(fileName => {
 | 
				
			||||||
      console.log(fileName);
 | 
					      console.log(fileName);
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        asar.statFile(archive, fileName);
 | 
					        asar.statFile(archive, fileName);
 | 
				
			||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
      } catch (e) {
 | 
					      } catch (e) {
 | 
				
			||||||
        console.log(e);
 | 
					        console.log(e);
 | 
				
			||||||
        throw new Error('Missing file ' + fileName);
 | 
					        throw new Error(`Missing file ${fileName}`);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (config.appUpdateYML) {
 | 
					    if (config.appUpdateYML) {
 | 
				
			||||||
      var appUpdateYML = [dir, config.appUpdateYML].join('/');
 | 
					      const appUpdateYML = [dir, config.appUpdateYML].join('/');
 | 
				
			||||||
      if (require('fs').existsSync(appUpdateYML)) {
 | 
					      if (fs.existsSync(appUpdateYML)) {
 | 
				
			||||||
        console.log('auto update ok');
 | 
					        console.log('auto update ok');
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        throw new Error('Missing auto update config ' + appUpdateYML);
 | 
					        throw new Error(`Missing auto update config ${appUpdateYML}`);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var done = this.async();
 | 
					    const done = this.async();
 | 
				
			||||||
    // A simple test to verify a visible window is opened with a title
 | 
					    // A simple test to verify a visible window is opened with a title
 | 
				
			||||||
    var Application = require('spectron').Application;
 | 
					    const { Application } = spectron;
 | 
				
			||||||
    var assert = require('assert');
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var app = new Application({
 | 
					    const app = new Application({
 | 
				
			||||||
      path: [dir, config.exe].join('/'),
 | 
					      path: [dir, config.exe].join('/'),
 | 
				
			||||||
      requireName: 'unused',
 | 
					      requireName: 'unused',
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    app
 | 
					    app
 | 
				
			||||||
      .start()
 | 
					      .start()
 | 
				
			||||||
      .then(function() {
 | 
					      .then(() => app.client.getWindowCount())
 | 
				
			||||||
        return app.client.getWindowCount();
 | 
					      .then(count => {
 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .then(function(count) {
 | 
					 | 
				
			||||||
        assert.equal(count, 1);
 | 
					        assert.equal(count, 1);
 | 
				
			||||||
        console.log('window opened');
 | 
					        console.log('window opened');
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .then(function() {
 | 
					      .then(() =>
 | 
				
			||||||
        // Get the window's title
 | 
					        // Get the window's title
 | 
				
			||||||
        return app.client.getTitle();
 | 
					        app.client.getTitle()
 | 
				
			||||||
      })
 | 
					      )
 | 
				
			||||||
      .then(function(title) {
 | 
					      .then(title => {
 | 
				
			||||||
        // Verify the window's title
 | 
					        // Verify the window's title
 | 
				
			||||||
        assert.equal(title, packageJson.productName);
 | 
					        assert.equal(title, packageJson.productName);
 | 
				
			||||||
        console.log('title ok');
 | 
					        console.log('title ok');
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .then(function() {
 | 
					      .then(() => {
 | 
				
			||||||
        assert(
 | 
					        assert(
 | 
				
			||||||
          app.chromeDriver.logLines.indexOf('NODE_ENV ' + environment) > -1
 | 
					          app.chromeDriver.logLines.indexOf(`NODE_ENV ${environment}`) > -1
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        console.log('environment ok');
 | 
					        console.log('environment ok');
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .then(
 | 
					      .then(
 | 
				
			||||||
        function() {
 | 
					        () =>
 | 
				
			||||||
          // Successfully completed test
 | 
					          // Successfully completed test
 | 
				
			||||||
          return app.stop();
 | 
					          app.stop(),
 | 
				
			||||||
        },
 | 
					        error =>
 | 
				
			||||||
        function(error) {
 | 
					 | 
				
			||||||
          // Test failed!
 | 
					          // Test failed!
 | 
				
			||||||
          return app.stop().then(function() {
 | 
					          app.stop().then(() => {
 | 
				
			||||||
            grunt.fail.fatal(
 | 
					            grunt.fail.fatal(`Test failed: ${error.message} ${error.stack}`);
 | 
				
			||||||
              'Test failed: ' + error.message + ' ' + error.stack
 | 
					          })
 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
      .then(done);
 | 
					      .then(done);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  grunt.registerTask('tx', ['exec:tx-pull', 'locale-patch']);
 | 
					  grunt.registerTask('tx', ['exec:tx-pull', 'locale-patch']);
 | 
				
			||||||
  grunt.registerTask('dev', ['default', 'watch']);
 | 
					  grunt.registerTask('dev', ['default', 'watch']);
 | 
				
			||||||
  grunt.registerTask('lint', ['jshint']);
 | 
					 | 
				
			||||||
  grunt.registerTask('test', ['unit-tests', 'lib-unit-tests']);
 | 
					  grunt.registerTask('test', ['unit-tests', 'lib-unit-tests']);
 | 
				
			||||||
  grunt.registerTask('date', ['gitinfo', 'getExpireTime']);
 | 
					  grunt.registerTask('date', ['gitinfo', 'getExpireTime']);
 | 
				
			||||||
  grunt.registerTask('default', [
 | 
					  grunt.registerTask('default', [
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,5 @@
 | 
				
			||||||
 | 
					/* global window */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { ipcRenderer } = require('electron');
 | 
					const { ipcRenderer } = require('electron');
 | 
				
			||||||
const url = require('url');
 | 
					const url = require('url');
 | 
				
			||||||
const i18n = require('./js/modules/i18n');
 | 
					const i18n = require('./js/modules/i18n');
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -471,9 +471,6 @@
 | 
				
			||||||
        <div class='contacts'></div>
 | 
					        <div class='contacts'></div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
  </script>
 | 
					  </script>
 | 
				
			||||||
  <script type='text/x-tmpl-mustache' id='generic-error'>
 | 
					 | 
				
			||||||
    <p>{{ message }}</p>
 | 
					 | 
				
			||||||
  </script>
 | 
					 | 
				
			||||||
  <script type='text/x-tmpl-mustache' id='error-icon'>
 | 
					  <script type='text/x-tmpl-mustache' id='error-icon'>
 | 
				
			||||||
    <span class='error-icon'>
 | 
					    <span class='error-icon'>
 | 
				
			||||||
    </span>
 | 
					    </span>
 | 
				
			||||||
| 
						 | 
					@ -805,7 +802,6 @@
 | 
				
			||||||
  <script type='text/javascript' src='js/views/contact_list_view.js'></script>
 | 
					  <script type='text/javascript' src='js/views/contact_list_view.js'></script>
 | 
				
			||||||
  <script type='text/javascript' src='js/views/new_group_update_view.js'></script>
 | 
					  <script type='text/javascript' src='js/views/new_group_update_view.js'></script>
 | 
				
			||||||
  <script type='text/javascript' src='js/views/attachment_view.js'></script>
 | 
					  <script type='text/javascript' src='js/views/attachment_view.js'></script>
 | 
				
			||||||
  <script type='text/javascript' src='js/views/error_view.js'></script>
 | 
					 | 
				
			||||||
  <script type='text/javascript' src='js/views/timestamp_view.js'></script>
 | 
					  <script type='text/javascript' src='js/views/timestamp_view.js'></script>
 | 
				
			||||||
  <script type='text/javascript' src='js/views/message_view.js'></script>
 | 
					  <script type='text/javascript' src='js/views/message_view.js'></script>
 | 
				
			||||||
  <script type='text/javascript' src='js/views/key_verification_view.js'></script>
 | 
					  <script type='text/javascript' src='js/views/key_verification_view.js'></script>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,5 @@
 | 
				
			||||||
 | 
					/* global window */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { ipcRenderer } = require('electron');
 | 
					const { ipcRenderer } = require('electron');
 | 
				
			||||||
const url = require('url');
 | 
					const url = require('url');
 | 
				
			||||||
const i18n = require('./js/modules/i18n');
 | 
					const i18n = require('./js/modules/i18n');
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,5 @@
 | 
				
			||||||
 | 
					/* global $: false */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Add version
 | 
					// Add version
 | 
				
			||||||
$('.version').text(`v${window.getVersion()}`);
 | 
					$('.version').text(`v${window.getVersion()}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +16,9 @@ if (window.getAppInstance()) {
 | 
				
			||||||
$('.environment').text(states.join(' - '));
 | 
					$('.environment').text(states.join(' - '));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Install the 'dismiss with escape key' handler
 | 
					// Install the 'dismiss with escape key' handler
 | 
				
			||||||
$(document).on('keyup', function(e) {
 | 
					$(document).on('keyup', e => {
 | 
				
			||||||
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (e.keyCode === 27) {
 | 
					  if (e.keyCode === 27) {
 | 
				
			||||||
    window.closeAbout();
 | 
					    window.closeAbout();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,14 @@
 | 
				
			||||||
 | 
					/* global extension: false */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Browser specific functions for Chrom*
 | 
					  // Browser specific functions for Chrom*
 | 
				
			||||||
  window.extension = window.extension || {};
 | 
					  window.extension = window.extension || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  extension.windows = {
 | 
					  extension.windows = {
 | 
				
			||||||
    onClosed: function(callback) {
 | 
					    onClosed(callback) {
 | 
				
			||||||
      window.addEventListener('beforeunload', callback);
 | 
					      window.addEventListener('beforeunload', callback);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,20 +1,20 @@
 | 
				
			||||||
/*global $, Whisper, Backbone, textsecure, extension*/
 | 
					/* global _, Whisper, Backbone, storage */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// This script should only be included in background.html
 | 
					/* eslint-disable more/no-then */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var conversations = new Whisper.ConversationCollection();
 | 
					  const conversations = new Whisper.ConversationCollection();
 | 
				
			||||||
  var inboxCollection = new (Backbone.Collection.extend({
 | 
					  const inboxCollection = new (Backbone.Collection.extend({
 | 
				
			||||||
    initialize: function() {
 | 
					    initialize() {
 | 
				
			||||||
      this.on('change:timestamp change:name change:number', this.sort);
 | 
					      this.on('change:timestamp change:name change:number', this.sort);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.listenTo(conversations, 'add change:active_at', this.addActive);
 | 
					      this.listenTo(conversations, 'add change:active_at', this.addActive);
 | 
				
			||||||
      this.listenTo(conversations, 'reset', function() {
 | 
					      this.listenTo(conversations, 'reset', () => this.reset([]));
 | 
				
			||||||
        this.reset([]);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.on(
 | 
					      this.on(
 | 
				
			||||||
        'add remove change:unreadCount',
 | 
					        'add remove change:unreadCount',
 | 
				
			||||||
| 
						 | 
					@ -24,9 +24,9 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.collator = new Intl.Collator();
 | 
					      this.collator = new Intl.Collator();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    comparator: function(m1, m2) {
 | 
					    comparator(m1, m2) {
 | 
				
			||||||
      var timestamp1 = m1.get('timestamp');
 | 
					      const timestamp1 = m1.get('timestamp');
 | 
				
			||||||
      var timestamp2 = m2.get('timestamp');
 | 
					      const timestamp2 = m2.get('timestamp');
 | 
				
			||||||
      if (timestamp1 && !timestamp2) {
 | 
					      if (timestamp1 && !timestamp2) {
 | 
				
			||||||
        return -1;
 | 
					        return -1;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
| 
						 | 
					@ -37,57 +37,48 @@
 | 
				
			||||||
        return timestamp2 - timestamp1;
 | 
					        return timestamp2 - timestamp1;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var title1 = m1.getTitle().toLowerCase();
 | 
					      const title1 = m1.getTitle().toLowerCase();
 | 
				
			||||||
      var title2 = m2.getTitle().toLowerCase();
 | 
					      const title2 = m2.getTitle().toLowerCase();
 | 
				
			||||||
      return this.collator.compare(title1, title2);
 | 
					      return this.collator.compare(title1, title2);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    addActive: function(model) {
 | 
					    addActive(model) {
 | 
				
			||||||
      if (model.get('active_at')) {
 | 
					      if (model.get('active_at')) {
 | 
				
			||||||
        this.add(model);
 | 
					        this.add(model);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        this.remove(model);
 | 
					        this.remove(model);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    updateUnreadCount: function() {
 | 
					    updateUnreadCount() {
 | 
				
			||||||
      var newUnreadCount = _.reduce(
 | 
					      const newUnreadCount = _.reduce(
 | 
				
			||||||
        this.map(function(m) {
 | 
					        this.map(m => m.get('unreadCount')),
 | 
				
			||||||
          return m.get('unreadCount');
 | 
					        (item, memo) => item + memo,
 | 
				
			||||||
        }),
 | 
					 | 
				
			||||||
        function(item, memo) {
 | 
					 | 
				
			||||||
          return item + memo;
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        0
 | 
					        0
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      storage.put('unreadCount', newUnreadCount);
 | 
					      storage.put('unreadCount', newUnreadCount);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (newUnreadCount > 0) {
 | 
					      if (newUnreadCount > 0) {
 | 
				
			||||||
        window.setBadgeCount(newUnreadCount);
 | 
					        window.setBadgeCount(newUnreadCount);
 | 
				
			||||||
        window.document.title = window.getTitle() + ' (' + newUnreadCount + ')';
 | 
					        window.document.title = `${window.getTitle()} (${newUnreadCount})`;
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        window.setBadgeCount(0);
 | 
					        window.setBadgeCount(0);
 | 
				
			||||||
        window.document.title = window.getTitle();
 | 
					        window.document.title = window.getTitle();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      window.updateTrayIcon(newUnreadCount);
 | 
					      window.updateTrayIcon(newUnreadCount);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    startPruning: function() {
 | 
					    startPruning() {
 | 
				
			||||||
      var halfHour = 30 * 60 * 1000;
 | 
					      const halfHour = 30 * 60 * 1000;
 | 
				
			||||||
      this.interval = setInterval(
 | 
					      this.interval = setInterval(() => {
 | 
				
			||||||
        function() {
 | 
					        this.forEach(conversation => {
 | 
				
			||||||
          this.forEach(function(conversation) {
 | 
					 | 
				
			||||||
          conversation.trigger('prune');
 | 
					          conversation.trigger('prune');
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        }.bind(this),
 | 
					      }, halfHour);
 | 
				
			||||||
        halfHour
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  }))();
 | 
					  }))();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.getInboxCollection = function() {
 | 
					  window.getInboxCollection = () => inboxCollection;
 | 
				
			||||||
    return inboxCollection;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.ConversationController = {
 | 
					  window.ConversationController = {
 | 
				
			||||||
    get: function(id) {
 | 
					    get(id) {
 | 
				
			||||||
      if (!this._initialFetchComplete) {
 | 
					      if (!this._initialFetchComplete) {
 | 
				
			||||||
        throw new Error(
 | 
					        throw new Error(
 | 
				
			||||||
          'ConversationController.get() needs complete initial fetch'
 | 
					          'ConversationController.get() needs complete initial fetch'
 | 
				
			||||||
| 
						 | 
					@ -97,13 +88,13 @@
 | 
				
			||||||
      return conversations.get(id);
 | 
					      return conversations.get(id);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    // Needed for some model setup which happens during the initial fetch() call below
 | 
					    // Needed for some model setup which happens during the initial fetch() call below
 | 
				
			||||||
    getUnsafe: function(id) {
 | 
					    getUnsafe(id) {
 | 
				
			||||||
      return conversations.get(id);
 | 
					      return conversations.get(id);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    dangerouslyCreateAndAdd: function(attributes) {
 | 
					    dangerouslyCreateAndAdd(attributes) {
 | 
				
			||||||
      return conversations.add(attributes);
 | 
					      return conversations.add(attributes);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    getOrCreate: function(id, type) {
 | 
					    getOrCreate(id, type) {
 | 
				
			||||||
      if (typeof id !== 'string') {
 | 
					      if (typeof id !== 'string') {
 | 
				
			||||||
        throw new TypeError("'id' must be a string");
 | 
					        throw new TypeError("'id' must be a string");
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
| 
						 | 
					@ -120,18 +111,18 @@
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var conversation = conversations.get(id);
 | 
					      let conversation = conversations.get(id);
 | 
				
			||||||
      if (conversation) {
 | 
					      if (conversation) {
 | 
				
			||||||
        return conversation;
 | 
					        return conversation;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      conversation = conversations.add({
 | 
					      conversation = conversations.add({
 | 
				
			||||||
        id: id,
 | 
					        id,
 | 
				
			||||||
        type: type,
 | 
					        type,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      conversation.initialPromise = new Promise(function(resolve, reject) {
 | 
					      conversation.initialPromise = new Promise((resolve, reject) => {
 | 
				
			||||||
        if (!conversation.isValid()) {
 | 
					        if (!conversation.isValid()) {
 | 
				
			||||||
          var validationError = conversation.validationError || {};
 | 
					          const validationError = conversation.validationError || {};
 | 
				
			||||||
          console.log(
 | 
					          console.log(
 | 
				
			||||||
            'Contact is not valid. Not saving, but adding to collection:',
 | 
					            'Contact is not valid. Not saving, but adding to collection:',
 | 
				
			||||||
            conversation.idForLogging(),
 | 
					            conversation.idForLogging(),
 | 
				
			||||||
| 
						 | 
					@ -141,63 +132,56 @@
 | 
				
			||||||
          return resolve(conversation);
 | 
					          return resolve(conversation);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var deferred = conversation.save();
 | 
					        const deferred = conversation.save();
 | 
				
			||||||
        if (!deferred) {
 | 
					        if (!deferred) {
 | 
				
			||||||
          console.log('Conversation save failed! ', id, type);
 | 
					          console.log('Conversation save failed! ', id, type);
 | 
				
			||||||
          return reject(new Error('getOrCreate: Conversation save failed'));
 | 
					          return reject(new Error('getOrCreate: Conversation save failed'));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        deferred.then(function() {
 | 
					        return deferred.then(() => {
 | 
				
			||||||
          resolve(conversation);
 | 
					          resolve(conversation);
 | 
				
			||||||
        }, reject);
 | 
					        }, reject);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return conversation;
 | 
					      return conversation;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    getOrCreateAndWait: function(id, type) {
 | 
					    getOrCreateAndWait(id, type) {
 | 
				
			||||||
      return this._initialPromise.then(
 | 
					      return this._initialPromise.then(() => {
 | 
				
			||||||
        function() {
 | 
					        const conversation = this.getOrCreate(id, type);
 | 
				
			||||||
          var conversation = this.getOrCreate(id, type);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (conversation) {
 | 
					        if (conversation) {
 | 
				
			||||||
            return conversation.initialPromise.then(function() {
 | 
					          return conversation.initialPromise.then(() => conversation);
 | 
				
			||||||
              return conversation;
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return Promise.reject(
 | 
					        return Promise.reject(
 | 
				
			||||||
          new Error('getOrCreateAndWait: did not get conversation')
 | 
					          new Error('getOrCreateAndWait: did not get conversation')
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        }.bind(this)
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    getAllGroupsInvolvingId: function(id) {
 | 
					 | 
				
			||||||
      var groups = new Whisper.GroupCollection();
 | 
					 | 
				
			||||||
      return groups.fetchGroups(id).then(function() {
 | 
					 | 
				
			||||||
        return groups.map(function(group) {
 | 
					 | 
				
			||||||
          return conversations.add(group);
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    loadPromise: function() {
 | 
					    getAllGroupsInvolvingId(id) {
 | 
				
			||||||
 | 
					      const groups = new Whisper.GroupCollection();
 | 
				
			||||||
 | 
					      return groups
 | 
				
			||||||
 | 
					        .fetchGroups(id)
 | 
				
			||||||
 | 
					        .then(() => groups.map(group => conversations.add(group)));
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    loadPromise() {
 | 
				
			||||||
      return this._initialPromise;
 | 
					      return this._initialPromise;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    reset: function() {
 | 
					    reset() {
 | 
				
			||||||
      this._initialPromise = Promise.resolve();
 | 
					      this._initialPromise = Promise.resolve();
 | 
				
			||||||
      conversations.reset([]);
 | 
					      conversations.reset([]);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    load: function() {
 | 
					    load() {
 | 
				
			||||||
      console.log('ConversationController: starting initial fetch');
 | 
					      console.log('ConversationController: starting initial fetch');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this._initialPromise = new Promise(
 | 
					      this._initialPromise = new Promise((resolve, reject) => {
 | 
				
			||||||
        function(resolve, reject) {
 | 
					 | 
				
			||||||
        conversations.fetch().then(
 | 
					        conversations.fetch().then(
 | 
				
			||||||
            function() {
 | 
					          () => {
 | 
				
			||||||
            console.log('ConversationController: done with initial fetch');
 | 
					            console.log('ConversationController: done with initial fetch');
 | 
				
			||||||
            this._initialFetchComplete = true;
 | 
					            this._initialFetchComplete = true;
 | 
				
			||||||
            resolve();
 | 
					            resolve();
 | 
				
			||||||
            }.bind(this),
 | 
					          },
 | 
				
			||||||
            function(error) {
 | 
					          error => {
 | 
				
			||||||
            console.log(
 | 
					            console.log(
 | 
				
			||||||
              'ConversationController: initial fetch failed',
 | 
					              'ConversationController: initial fetch failed',
 | 
				
			||||||
              error && error.stack ? error.stack : error
 | 
					              error && error.stack ? error.stack : error
 | 
				
			||||||
| 
						 | 
					@ -205,8 +189,7 @@
 | 
				
			||||||
            reject(error);
 | 
					            reject(error);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        }.bind(this)
 | 
					      });
 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return this._initialPromise;
 | 
					      return this._initialPromise;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,9 @@
 | 
				
			||||||
$(document).on('keyup', function(e) {
 | 
					/* global $: false */
 | 
				
			||||||
 | 
					/* global Whisper: false */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$(document).on('keyup', e => {
 | 
				
			||||||
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (e.keyCode === 27) {
 | 
					  if (e.keyCode === 27) {
 | 
				
			||||||
    window.closeDebugLog();
 | 
					    window.closeDebugLog();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,72 +1,73 @@
 | 
				
			||||||
 | 
					/* global Backbone: false */
 | 
				
			||||||
 | 
					/* global Whisper: false */
 | 
				
			||||||
 | 
					/* global ConversationController: false */
 | 
				
			||||||
 | 
					/* global _: false */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* eslint-disable more/no-then */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Whisper.DeliveryReceipts = new (Backbone.Collection.extend({
 | 
					  Whisper.DeliveryReceipts = new (Backbone.Collection.extend({
 | 
				
			||||||
    forMessage: function(conversation, message) {
 | 
					    forMessage(conversation, message) {
 | 
				
			||||||
      var recipients;
 | 
					      let recipients;
 | 
				
			||||||
      if (conversation.isPrivate()) {
 | 
					      if (conversation.isPrivate()) {
 | 
				
			||||||
        recipients = [conversation.id];
 | 
					        recipients = [conversation.id];
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        recipients = conversation.get('members') || [];
 | 
					        recipients = conversation.get('members') || [];
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      var receipts = this.filter(function(receipt) {
 | 
					      const receipts = this.filter(
 | 
				
			||||||
        return (
 | 
					        receipt =>
 | 
				
			||||||
          receipt.get('timestamp') === message.get('sent_at') &&
 | 
					          receipt.get('timestamp') === message.get('sent_at') &&
 | 
				
			||||||
          recipients.indexOf(receipt.get('source')) > -1
 | 
					          recipients.indexOf(receipt.get('source')) > -1
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      this.remove(receipts);
 | 
					      this.remove(receipts);
 | 
				
			||||||
      return receipts;
 | 
					      return receipts;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    onReceipt: function(receipt) {
 | 
					    onReceipt(receipt) {
 | 
				
			||||||
      var messages = new Whisper.MessageCollection();
 | 
					      const messages = new Whisper.MessageCollection();
 | 
				
			||||||
      return messages
 | 
					      return messages
 | 
				
			||||||
        .fetchSentAt(receipt.get('timestamp'))
 | 
					        .fetchSentAt(receipt.get('timestamp'))
 | 
				
			||||||
        .then(function() {
 | 
					        .then(() => {
 | 
				
			||||||
          if (messages.length === 0) {
 | 
					          if (messages.length === 0) {
 | 
				
			||||||
            return;
 | 
					            return null;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          var message = messages.find(function(message) {
 | 
					          const message = messages.find(
 | 
				
			||||||
            return (
 | 
					            item =>
 | 
				
			||||||
              !message.isIncoming() &&
 | 
					              !item.isIncoming() &&
 | 
				
			||||||
              receipt.get('source') === message.get('conversationId')
 | 
					              receipt.get('source') === item.get('conversationId')
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
          if (message) {
 | 
					          if (message) {
 | 
				
			||||||
            return message;
 | 
					            return message;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          var groups = new Whisper.GroupCollection();
 | 
					          const groups = new Whisper.GroupCollection();
 | 
				
			||||||
          return groups.fetchGroups(receipt.get('source')).then(function() {
 | 
					          return groups.fetchGroups(receipt.get('source')).then(() => {
 | 
				
			||||||
            var ids = groups.pluck('id');
 | 
					            const ids = groups.pluck('id');
 | 
				
			||||||
            ids.push(receipt.get('source'));
 | 
					            ids.push(receipt.get('source'));
 | 
				
			||||||
            return messages.find(function(message) {
 | 
					            return messages.find(
 | 
				
			||||||
              return (
 | 
					              item =>
 | 
				
			||||||
                !message.isIncoming() &&
 | 
					                !item.isIncoming() &&
 | 
				
			||||||
                _.contains(ids, message.get('conversationId'))
 | 
					                _.contains(ids, item.get('conversationId'))
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .then(
 | 
					        .then(message => {
 | 
				
			||||||
          function(message) {
 | 
					 | 
				
			||||||
          if (message) {
 | 
					          if (message) {
 | 
				
			||||||
              var deliveries = message.get('delivered') || 0;
 | 
					            const deliveries = message.get('delivered') || 0;
 | 
				
			||||||
              var delivered_to = message.get('delivered_to') || [];
 | 
					            const deliveredTo = message.get('delivered_to') || [];
 | 
				
			||||||
              return new Promise(
 | 
					            return new Promise((resolve, reject) => {
 | 
				
			||||||
                function(resolve, reject) {
 | 
					 | 
				
			||||||
              message
 | 
					              message
 | 
				
			||||||
                .save({
 | 
					                .save({
 | 
				
			||||||
                      delivered_to: _.union(delivered_to, [
 | 
					                  delivered_to: _.union(deliveredTo, [receipt.get('source')]),
 | 
				
			||||||
                        receipt.get('source'),
 | 
					 | 
				
			||||||
                      ]),
 | 
					 | 
				
			||||||
                  delivered: deliveries + 1,
 | 
					                  delivered: deliveries + 1,
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
                    .then(
 | 
					                .then(() => {
 | 
				
			||||||
                      function() {
 | 
					 | 
				
			||||||
                  // notify frontend listeners
 | 
					                  // notify frontend listeners
 | 
				
			||||||
                        var conversation = ConversationController.get(
 | 
					                  const conversation = ConversationController.get(
 | 
				
			||||||
                    message.get('conversationId')
 | 
					                    message.get('conversationId')
 | 
				
			||||||
                  );
 | 
					                  );
 | 
				
			||||||
                  if (conversation) {
 | 
					                  if (conversation) {
 | 
				
			||||||
| 
						 | 
					@ -75,23 +76,20 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                  this.remove(receipt);
 | 
					                  this.remove(receipt);
 | 
				
			||||||
                  resolve();
 | 
					                  resolve();
 | 
				
			||||||
                      }.bind(this),
 | 
					                }, reject);
 | 
				
			||||||
                      reject
 | 
					            });
 | 
				
			||||||
                    );
 | 
					 | 
				
			||||||
                }.bind(this)
 | 
					 | 
				
			||||||
              );
 | 
					 | 
				
			||||||
            // TODO: consider keeping a list of numbers we've
 | 
					            // TODO: consider keeping a list of numbers we've
 | 
				
			||||||
            // successfully delivered to?
 | 
					            // successfully delivered to?
 | 
				
			||||||
            } else {
 | 
					          }
 | 
				
			||||||
          console.log(
 | 
					          console.log(
 | 
				
			||||||
            'No message for delivery receipt',
 | 
					            'No message for delivery receipt',
 | 
				
			||||||
            receipt.get('source'),
 | 
					            receipt.get('source'),
 | 
				
			||||||
            receipt.get('timestamp')
 | 
					            receipt.get('timestamp')
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
            }
 | 
					
 | 
				
			||||||
          }.bind(this)
 | 
					          return null;
 | 
				
			||||||
        )
 | 
					        })
 | 
				
			||||||
        .catch(function(error) {
 | 
					        .catch(error => {
 | 
				
			||||||
          console.log(
 | 
					          console.log(
 | 
				
			||||||
            'DeliveryReceipts.onReceipt error:',
 | 
					            'DeliveryReceipts.onReceipt error:',
 | 
				
			||||||
            error && error.stack ? error.stack : error
 | 
					            error && error.stack ? error.stack : error
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										15
									
								
								js/expire.js
									
										
									
									
									
								
							
							
						
						
									
										15
									
								
								js/expire.js
									
										
									
									
									
								
							| 
						 | 
					@ -1,16 +1,19 @@
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
  var BUILD_EXPIRATION = 0;
 | 
					
 | 
				
			||||||
 | 
					  let BUILD_EXPIRATION = 0;
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    BUILD_EXPIRATION = parseInt(window.getExpiration());
 | 
					    BUILD_EXPIRATION = parseInt(window.getExpiration(), 10);
 | 
				
			||||||
    if (BUILD_EXPIRATION) {
 | 
					    if (BUILD_EXPIRATION) {
 | 
				
			||||||
      console.log('Build expires: ', new Date(BUILD_EXPIRATION).toISOString());
 | 
					      console.log('Build expires: ', new Date(BUILD_EXPIRATION).toISOString());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  } catch (e) {}
 | 
					  } catch (e) {
 | 
				
			||||||
 | 
					    // nothing
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.extension = window.extension || {};
 | 
					  window.extension = window.extension || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  extension.expired = function() {
 | 
					  window.extension.expired = () =>
 | 
				
			||||||
    return BUILD_EXPIRATION && Date.now() > BUILD_EXPIRATION;
 | 
					    BUILD_EXPIRATION && Date.now() > BUILD_EXPIRATION;
 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
})();
 | 
					})();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,15 +1,14 @@
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var windowFocused = false;
 | 
					  let windowFocused = false;
 | 
				
			||||||
  window.addEventListener('blur', function() {
 | 
					  window.addEventListener('blur', () => {
 | 
				
			||||||
    windowFocused = false;
 | 
					    windowFocused = false;
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  window.addEventListener('focus', function() {
 | 
					  window.addEventListener('focus', () => {
 | 
				
			||||||
    windowFocused = true;
 | 
					    windowFocused = true;
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.isFocused = function() {
 | 
					  window.isFocused = () => windowFocused;
 | 
				
			||||||
    return windowFocused;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
})();
 | 
					})();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,27 +1,31 @@
 | 
				
			||||||
 | 
					/* global Whisper, SignalProtocolStore, ConversationController, _ */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* eslint-disable more/no-then */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Whisper.KeyChangeListener = {
 | 
					  Whisper.KeyChangeListener = {
 | 
				
			||||||
    init: function(signalProtocolStore) {
 | 
					    init(signalProtocolStore) {
 | 
				
			||||||
      if (!(signalProtocolStore instanceof SignalProtocolStore)) {
 | 
					      if (!(signalProtocolStore instanceof SignalProtocolStore)) {
 | 
				
			||||||
        throw new Error('KeyChangeListener requires a SignalProtocolStore');
 | 
					        throw new Error('KeyChangeListener requires a SignalProtocolStore');
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      signalProtocolStore.on('keychange', function(id) {
 | 
					      signalProtocolStore.on('keychange', id => {
 | 
				
			||||||
        ConversationController.getOrCreateAndWait(id, 'private').then(function(
 | 
					        ConversationController.getOrCreateAndWait(id, 'private').then(
 | 
				
			||||||
          conversation
 | 
					          conversation => {
 | 
				
			||||||
        ) {
 | 
					 | 
				
			||||||
            conversation.addKeyChange(id);
 | 
					            conversation.addKeyChange(id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          ConversationController.getAllGroupsInvolvingId(id).then(function(
 | 
					            ConversationController.getAllGroupsInvolvingId(id).then(groups => {
 | 
				
			||||||
            groups
 | 
					              _.forEach(groups, group => {
 | 
				
			||||||
          ) {
 | 
					 | 
				
			||||||
            _.forEach(groups, function(group) {
 | 
					 | 
				
			||||||
                group.addKeyChange(id);
 | 
					                group.addKeyChange(id);
 | 
				
			||||||
              });
 | 
					              });
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        });
 | 
					          }
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,16 @@
 | 
				
			||||||
 | 
					/* global storage, _ */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
  storage.isBlocked = function(number) {
 | 
					
 | 
				
			||||||
    var numbers = storage.get('blocked', []);
 | 
					  storage.isBlocked = number => {
 | 
				
			||||||
 | 
					    const numbers = storage.get('blocked', []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return _.include(numbers, number);
 | 
					    return _.include(numbers, number);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  storage.addBlockedNumber = function(number) {
 | 
					  storage.addBlockedNumber = number => {
 | 
				
			||||||
    var numbers = storage.get('blocked', []);
 | 
					    const numbers = storage.get('blocked', []);
 | 
				
			||||||
    if (_.include(numbers, number)) {
 | 
					    if (_.include(numbers, number)) {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -14,8 +18,8 @@
 | 
				
			||||||
    console.log('adding', number, 'to blocked list');
 | 
					    console.log('adding', number, 'to blocked list');
 | 
				
			||||||
    storage.put('blocked', numbers.concat(number));
 | 
					    storage.put('blocked', numbers.concat(number));
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  storage.removeBlockedNumber = function(number) {
 | 
					  storage.removeBlockedNumber = number => {
 | 
				
			||||||
    var numbers = storage.get('blocked', []);
 | 
					    const numbers = storage.get('blocked', []);
 | 
				
			||||||
    if (!_.include(numbers, number)) {
 | 
					    if (!_.include(numbers, number)) {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,8 @@
 | 
				
			||||||
$(document).on('keyup', function(e) {
 | 
					/* global $, Whisper, i18n */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$(document).on('keyup', e => {
 | 
				
			||||||
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (e.keyCode === 27) {
 | 
					  if (e.keyCode === 27) {
 | 
				
			||||||
    window.closePermissionsPopup();
 | 
					    window.closePermissionsPopup();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -11,6 +15,8 @@ window.view = new Whisper.ConfirmationDialogView({
 | 
				
			||||||
  message: i18n('audioPermissionNeeded'),
 | 
					  message: i18n('audioPermissionNeeded'),
 | 
				
			||||||
  okText: i18n('allowAccess'),
 | 
					  okText: i18n('allowAccess'),
 | 
				
			||||||
  resolve: () => {
 | 
					  resolve: () => {
 | 
				
			||||||
 | 
					    'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    window.setMediaPermissions(true);
 | 
					    window.setMediaPermissions(true);
 | 
				
			||||||
    window.closePermissionsPopup();
 | 
					    window.closePermissionsPopup();
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,70 +1,69 @@
 | 
				
			||||||
 | 
					/* global Whisper, Backbone, _, ConversationController */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* eslint-disable more/no-then */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
  Whisper.ReadReceipts = new (Backbone.Collection.extend({
 | 
					  Whisper.ReadReceipts = new (Backbone.Collection.extend({
 | 
				
			||||||
    forMessage: function(conversation, message) {
 | 
					    forMessage(conversation, message) {
 | 
				
			||||||
      if (!message.isOutgoing()) {
 | 
					      if (!message.isOutgoing()) {
 | 
				
			||||||
        return [];
 | 
					        return [];
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      var ids = [];
 | 
					      let ids = [];
 | 
				
			||||||
      if (conversation.isPrivate()) {
 | 
					      if (conversation.isPrivate()) {
 | 
				
			||||||
        ids = [conversation.id];
 | 
					        ids = [conversation.id];
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        ids = conversation.get('members');
 | 
					        ids = conversation.get('members');
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      var receipts = this.filter(function(receipt) {
 | 
					      const receipts = this.filter(
 | 
				
			||||||
        return (
 | 
					        receipt =>
 | 
				
			||||||
          receipt.get('timestamp') === message.get('sent_at') &&
 | 
					          receipt.get('timestamp') === message.get('sent_at') &&
 | 
				
			||||||
          _.contains(ids, receipt.get('reader'))
 | 
					          _.contains(ids, receipt.get('reader'))
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      if (receipts.length) {
 | 
					      if (receipts.length) {
 | 
				
			||||||
        console.log('Found early read receipts for message');
 | 
					        console.log('Found early read receipts for message');
 | 
				
			||||||
        this.remove(receipts);
 | 
					        this.remove(receipts);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return receipts;
 | 
					      return receipts;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    onReceipt: function(receipt) {
 | 
					    onReceipt(receipt) {
 | 
				
			||||||
      var messages = new Whisper.MessageCollection();
 | 
					      const messages = new Whisper.MessageCollection();
 | 
				
			||||||
      return messages
 | 
					      return messages
 | 
				
			||||||
        .fetchSentAt(receipt.get('timestamp'))
 | 
					        .fetchSentAt(receipt.get('timestamp'))
 | 
				
			||||||
        .then(function() {
 | 
					        .then(() => {
 | 
				
			||||||
          if (messages.length === 0) {
 | 
					          if (messages.length === 0) {
 | 
				
			||||||
            return;
 | 
					            return null;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          var message = messages.find(function(message) {
 | 
					          const message = messages.find(
 | 
				
			||||||
            return (
 | 
					            item =>
 | 
				
			||||||
              message.isOutgoing() &&
 | 
					              item.isOutgoing() &&
 | 
				
			||||||
              receipt.get('reader') === message.get('conversationId')
 | 
					              receipt.get('reader') === item.get('conversationId')
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
          if (message) {
 | 
					          if (message) {
 | 
				
			||||||
            return message;
 | 
					            return message;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          var groups = new Whisper.GroupCollection();
 | 
					          const groups = new Whisper.GroupCollection();
 | 
				
			||||||
          return groups.fetchGroups(receipt.get('reader')).then(function() {
 | 
					          return groups.fetchGroups(receipt.get('reader')).then(() => {
 | 
				
			||||||
            var ids = groups.pluck('id');
 | 
					            const ids = groups.pluck('id');
 | 
				
			||||||
            ids.push(receipt.get('reader'));
 | 
					            ids.push(receipt.get('reader'));
 | 
				
			||||||
            return messages.find(function(message) {
 | 
					            return messages.find(
 | 
				
			||||||
              return (
 | 
					              item =>
 | 
				
			||||||
                message.isOutgoing() &&
 | 
					                item.isOutgoing() && _.contains(ids, item.get('conversationId'))
 | 
				
			||||||
                _.contains(ids, message.get('conversationId'))
 | 
					 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .then(
 | 
					        .then(message => {
 | 
				
			||||||
          function(message) {
 | 
					 | 
				
			||||||
          if (message) {
 | 
					          if (message) {
 | 
				
			||||||
              var read_by = message.get('read_by') || [];
 | 
					            const readBy = message.get('read_by') || [];
 | 
				
			||||||
              read_by.push(receipt.get('reader'));
 | 
					            readBy.push(receipt.get('reader'));
 | 
				
			||||||
              return new Promise(
 | 
					            return new Promise((resolve, reject) => {
 | 
				
			||||||
                function(resolve, reject) {
 | 
					              message.save({ read_by: readBy }).then(() => {
 | 
				
			||||||
                  message.save({ read_by: read_by }).then(
 | 
					 | 
				
			||||||
                    function() {
 | 
					 | 
				
			||||||
                // notify frontend listeners
 | 
					                // notify frontend listeners
 | 
				
			||||||
                      var conversation = ConversationController.get(
 | 
					                const conversation = ConversationController.get(
 | 
				
			||||||
                  message.get('conversationId')
 | 
					                  message.get('conversationId')
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
                if (conversation) {
 | 
					                if (conversation) {
 | 
				
			||||||
| 
						 | 
					@ -73,21 +72,18 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                this.remove(receipt);
 | 
					                this.remove(receipt);
 | 
				
			||||||
                resolve();
 | 
					                resolve();
 | 
				
			||||||
                    }.bind(this),
 | 
					              }, reject);
 | 
				
			||||||
                    reject
 | 
					            });
 | 
				
			||||||
                  );
 | 
					          }
 | 
				
			||||||
                }.bind(this)
 | 
					 | 
				
			||||||
              );
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
          console.log(
 | 
					          console.log(
 | 
				
			||||||
            'No message for read receipt',
 | 
					            'No message for read receipt',
 | 
				
			||||||
            receipt.get('reader'),
 | 
					            receipt.get('reader'),
 | 
				
			||||||
            receipt.get('timestamp')
 | 
					            receipt.get('timestamp')
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
            }
 | 
					
 | 
				
			||||||
          }.bind(this)
 | 
					          return null;
 | 
				
			||||||
        )
 | 
					        })
 | 
				
			||||||
        .catch(function(error) {
 | 
					        .catch(error => {
 | 
				
			||||||
          console.log(
 | 
					          console.log(
 | 
				
			||||||
            'ReadReceipts.onReceipt error:',
 | 
					            'ReadReceipts.onReceipt error:',
 | 
				
			||||||
            error && error.stack ? error.stack : error
 | 
					            error && error.stack ? error.stack : error
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,9 +1,15 @@
 | 
				
			||||||
 | 
					/* global Backbone, Whisper, ConversationController */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* eslint-disable more/no-then */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
  Whisper.ReadSyncs = new (Backbone.Collection.extend({
 | 
					  Whisper.ReadSyncs = new (Backbone.Collection.extend({
 | 
				
			||||||
    forMessage: function(message) {
 | 
					    forMessage(message) {
 | 
				
			||||||
      var receipt = this.findWhere({
 | 
					      const receipt = this.findWhere({
 | 
				
			||||||
        sender: message.get('source'),
 | 
					        sender: message.get('source'),
 | 
				
			||||||
        timestamp: message.get('sent_at'),
 | 
					        timestamp: message.get('sent_at'),
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
| 
						 | 
					@ -12,18 +18,18 @@
 | 
				
			||||||
        this.remove(receipt);
 | 
					        this.remove(receipt);
 | 
				
			||||||
        return receipt;
 | 
					        return receipt;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    onReceipt: function(receipt) {
 | 
					    onReceipt(receipt) {
 | 
				
			||||||
      var messages = new Whisper.MessageCollection();
 | 
					      const messages = new Whisper.MessageCollection();
 | 
				
			||||||
      return messages.fetchSentAt(receipt.get('timestamp')).then(
 | 
					      return messages.fetchSentAt(receipt.get('timestamp')).then(() => {
 | 
				
			||||||
        function() {
 | 
					        const message = messages.find(
 | 
				
			||||||
          var message = messages.find(function(message) {
 | 
					          item =>
 | 
				
			||||||
            return (
 | 
					            item.isIncoming() &&
 | 
				
			||||||
              message.isIncoming() &&
 | 
					            item.isUnread() &&
 | 
				
			||||||
              message.isUnread() &&
 | 
					            item.get('source') === receipt.get('sender')
 | 
				
			||||||
              message.get('source') === receipt.get('sender')
 | 
					 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
        const notificationForMessage = message
 | 
					        const notificationForMessage = message
 | 
				
			||||||
          ? Whisper.Notifications.findWhere({ messageId: message.id })
 | 
					          ? Whisper.Notifications.findWhere({ messageId: message.id })
 | 
				
			||||||
          : null;
 | 
					          : null;
 | 
				
			||||||
| 
						 | 
					@ -43,21 +49,18 @@
 | 
				
			||||||
          wasNotificationRemoved,
 | 
					          wasNotificationRemoved,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        return message
 | 
					        return message
 | 
				
			||||||
            ? message.markRead(receipt.get('read_at')).then(
 | 
					          ? message.markRead(receipt.get('read_at')).then(() => {
 | 
				
			||||||
                function() {
 | 
					 | 
				
			||||||
              // This notification may result in messages older than this one being
 | 
					              // This notification may result in messages older than this one being
 | 
				
			||||||
              //   marked read. We want those messages to have the same expire timer
 | 
					              //   marked read. We want those messages to have the same expire timer
 | 
				
			||||||
              //   start time as this one, so we pass the read_at value through.
 | 
					              //   start time as this one, so we pass the read_at value through.
 | 
				
			||||||
              this.notifyConversation(message, receipt.get('read_at'));
 | 
					              this.notifyConversation(message, receipt.get('read_at'));
 | 
				
			||||||
              this.remove(receipt);
 | 
					              this.remove(receipt);
 | 
				
			||||||
                }.bind(this)
 | 
					            })
 | 
				
			||||||
              )
 | 
					 | 
				
			||||||
          : Promise.resolve();
 | 
					          : Promise.resolve();
 | 
				
			||||||
        }.bind(this)
 | 
					      });
 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    notifyConversation: function(message, readAt) {
 | 
					    notifyConversation(message, readAt) {
 | 
				
			||||||
      var conversation = ConversationController.get({
 | 
					      const conversation = ConversationController.get({
 | 
				
			||||||
        id: message.get('conversationId'),
 | 
					        id: message.get('conversationId'),
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,23 +1,27 @@
 | 
				
			||||||
 | 
					/* global storage, Whisper */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Whisper.Registration = {
 | 
					  Whisper.Registration = {
 | 
				
			||||||
    markEverDone: function() {
 | 
					    markEverDone() {
 | 
				
			||||||
      storage.put('chromiumRegistrationDoneEver', '');
 | 
					      storage.put('chromiumRegistrationDoneEver', '');
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    markDone: function() {
 | 
					    markDone() {
 | 
				
			||||||
      this.markEverDone();
 | 
					      this.markEverDone();
 | 
				
			||||||
      storage.put('chromiumRegistrationDone', '');
 | 
					      storage.put('chromiumRegistrationDone', '');
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    isDone: function() {
 | 
					    isDone() {
 | 
				
			||||||
      return storage.get('chromiumRegistrationDone') === '';
 | 
					      return storage.get('chromiumRegistrationDone') === '';
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    everDone: function() {
 | 
					    everDone() {
 | 
				
			||||||
      return (
 | 
					      return (
 | 
				
			||||||
        storage.get('chromiumRegistrationDoneEver') === '' ||
 | 
					        storage.get('chromiumRegistrationDoneEver') === '' ||
 | 
				
			||||||
        storage.get('chromiumRegistrationDone') === ''
 | 
					        storage.get('chromiumRegistrationDone') === ''
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    remove: function() {
 | 
					    remove() {
 | 
				
			||||||
      storage.remove('chromiumRegistrationDone');
 | 
					      storage.remove('chromiumRegistrationDone');
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,41 +1,50 @@
 | 
				
			||||||
 | 
					/* eslint-disable */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// This file was taken from Backbone and then modified. It does not conform to this
 | 
				
			||||||
 | 
					//   project's standards.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Note: this is all the code required to customize Backbone's trigger() method to make
 | 
					  // Note: this is all the code required to customize Backbone's trigger() method to make
 | 
				
			||||||
  //   it resilient to exceptions thrown by event handlers. Indentation and code styles
 | 
					  //   it resilient to exceptions thrown by event handlers. Indentation and code styles
 | 
				
			||||||
  //   were kept inline with the Backbone implementation for easier diffs.
 | 
					  //   were kept inline with the Backbone implementation for easier diffs.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // The changes are:
 | 
					  // The changes are:
 | 
				
			||||||
  //   1. added 'name' parameter to triggerEvents to give it access to the current event name
 | 
					  //   1. added 'name' parameter to triggerEvents to give it access to the
 | 
				
			||||||
  //   2. added try/catch handlers to triggerEvents with error logging inside every while loop
 | 
					  //      current event name
 | 
				
			||||||
 | 
					  //   2. added try/catch handlers to triggerEvents with error logging inside
 | 
				
			||||||
 | 
					  //      every while loop
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // And of course, we update the protoypes of Backbone.Model/Backbone.View as well as
 | 
					  // And of course, we update the protoypes of Backbone.Model/Backbone.View as well as
 | 
				
			||||||
  //   Backbone.Events itself
 | 
					  //   Backbone.Events itself
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var arr = [];
 | 
					  const arr = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var slice = arr.slice;
 | 
					  const slice = arr.slice;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Regular expression used to split event strings.
 | 
					  // Regular expression used to split event strings.
 | 
				
			||||||
  var eventSplitter = /\s+/;
 | 
					  const eventSplitter = /\s+/;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Implement fancy features of the Events API such as multiple event
 | 
					  // Implement fancy features of the Events API such as multiple event
 | 
				
			||||||
  // names `"change blur"` and jQuery-style event maps `{change: action}`
 | 
					  // names `"change blur"` and jQuery-style event maps `{change: action}`
 | 
				
			||||||
  // in terms of the existing API.
 | 
					  // in terms of the existing API.
 | 
				
			||||||
  var eventsApi = function(obj, action, name, rest) {
 | 
					  const eventsApi = function(obj, action, name, rest) {
 | 
				
			||||||
    if (!name) return true;
 | 
					    if (!name) return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Handle event maps.
 | 
					    // Handle event maps.
 | 
				
			||||||
    if (typeof name === 'object') {
 | 
					    if (typeof name === 'object') {
 | 
				
			||||||
      for (var key in name) {
 | 
					      for (const key in name) {
 | 
				
			||||||
        obj[action].apply(obj, [key, name[key]].concat(rest));
 | 
					        obj[action](...[key, name[key]].concat(rest));
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return false;
 | 
					      return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Handle space separated event names.
 | 
					    // Handle space separated event names.
 | 
				
			||||||
    if (eventSplitter.test(name)) {
 | 
					    if (eventSplitter.test(name)) {
 | 
				
			||||||
      var names = name.split(eventSplitter);
 | 
					      const names = name.split(eventSplitter);
 | 
				
			||||||
      for (var i = 0, l = names.length; i < l; i++) {
 | 
					      for (let i = 0, l = names.length; i < l; i++) {
 | 
				
			||||||
        obj[action].apply(obj, [names[i]].concat(rest));
 | 
					        obj[action](...[names[i]].concat(rest));
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return false;
 | 
					      return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -46,14 +55,14 @@
 | 
				
			||||||
  // A difficult-to-believe, but optimized internal dispatch function for
 | 
					  // A difficult-to-believe, but optimized internal dispatch function for
 | 
				
			||||||
  // triggering events. Tries to keep the usual cases speedy (most internal
 | 
					  // triggering events. Tries to keep the usual cases speedy (most internal
 | 
				
			||||||
  // Backbone events have 3 arguments).
 | 
					  // Backbone events have 3 arguments).
 | 
				
			||||||
  var triggerEvents = function(events, name, args) {
 | 
					  const triggerEvents = function(events, name, args) {
 | 
				
			||||||
    var ev,
 | 
					    let ev,
 | 
				
			||||||
      i = -1,
 | 
					      i = -1,
 | 
				
			||||||
      l = events.length,
 | 
					      l = events.length,
 | 
				
			||||||
      a1 = args[0],
 | 
					      a1 = args[0],
 | 
				
			||||||
      a2 = args[1],
 | 
					      a2 = args[1],
 | 
				
			||||||
      a3 = args[2];
 | 
					      a3 = args[2];
 | 
				
			||||||
    var logError = function(error) {
 | 
					    const logError = function(error) {
 | 
				
			||||||
      console.log(
 | 
					      console.log(
 | 
				
			||||||
        'Model caught error triggering',
 | 
					        'Model caught error triggering',
 | 
				
			||||||
        name,
 | 
					        name,
 | 
				
			||||||
| 
						 | 
					@ -106,7 +115,6 @@
 | 
				
			||||||
            logError(error);
 | 
					            logError(error);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -116,10 +124,10 @@
 | 
				
			||||||
  // receive the true name of the event as the first argument).
 | 
					  // receive the true name of the event as the first argument).
 | 
				
			||||||
  function trigger(name) {
 | 
					  function trigger(name) {
 | 
				
			||||||
    if (!this._events) return this;
 | 
					    if (!this._events) return this;
 | 
				
			||||||
    var args = slice.call(arguments, 1);
 | 
					    const args = slice.call(arguments, 1);
 | 
				
			||||||
    if (!eventsApi(this, 'trigger', name, args)) return this;
 | 
					    if (!eventsApi(this, 'trigger', name, args)) return this;
 | 
				
			||||||
    var events = this._events[name];
 | 
					    const events = this._events[name];
 | 
				
			||||||
    var allEvents = this._events.all;
 | 
					    const allEvents = this._events.all;
 | 
				
			||||||
    if (events) triggerEvents(events, name, args);
 | 
					    if (events) triggerEvents(events, name, args);
 | 
				
			||||||
    if (allEvents) triggerEvents(allEvents, name, arguments);
 | 
					    if (allEvents) triggerEvents(allEvents, name, arguments);
 | 
				
			||||||
    return this;
 | 
					    return this;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,13 +1,17 @@
 | 
				
			||||||
 | 
					/* global Whisper, storage, getAccountManager */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
  var ROTATION_INTERVAL = 48 * 60 * 60 * 1000;
 | 
					  const ROTATION_INTERVAL = 48 * 60 * 60 * 1000;
 | 
				
			||||||
  var timeout;
 | 
					  let timeout;
 | 
				
			||||||
  var scheduledTime;
 | 
					  let scheduledTime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function scheduleNextRotation() {
 | 
					  function scheduleNextRotation() {
 | 
				
			||||||
    var now = Date.now();
 | 
					    const now = Date.now();
 | 
				
			||||||
    var nextTime = now + ROTATION_INTERVAL;
 | 
					    const nextTime = now + ROTATION_INTERVAL;
 | 
				
			||||||
    storage.put('nextSignedKeyRotationTime', nextTime);
 | 
					    storage.put('nextSignedKeyRotationTime', nextTime);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +19,7 @@
 | 
				
			||||||
    console.log('Rotating signed prekey...');
 | 
					    console.log('Rotating signed prekey...');
 | 
				
			||||||
    getAccountManager()
 | 
					    getAccountManager()
 | 
				
			||||||
      .rotateSignedPreKey()
 | 
					      .rotateSignedPreKey()
 | 
				
			||||||
      .catch(function() {
 | 
					      .catch(() => {
 | 
				
			||||||
        console.log(
 | 
					        console.log(
 | 
				
			||||||
          'rotateSignedPrekey() failed. Trying again in five seconds'
 | 
					          'rotateSignedPrekey() failed. Trying again in five seconds'
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
| 
						 | 
					@ -32,7 +36,7 @@
 | 
				
			||||||
      console.log(
 | 
					      console.log(
 | 
				
			||||||
        'We are offline; keys will be rotated when we are next online'
 | 
					        'We are offline; keys will be rotated when we are next online'
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      var listener = function() {
 | 
					      const listener = () => {
 | 
				
			||||||
        window.removeEventListener('online', listener);
 | 
					        window.removeEventListener('online', listener);
 | 
				
			||||||
        run();
 | 
					        run();
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
| 
						 | 
					@ -41,8 +45,8 @@
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function setTimeoutForNextRun() {
 | 
					  function setTimeoutForNextRun() {
 | 
				
			||||||
    var now = Date.now();
 | 
					    const now = Date.now();
 | 
				
			||||||
    var time = storage.get('nextSignedKeyRotationTime', now);
 | 
					    const time = storage.get('nextSignedKeyRotationTime', now);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (scheduledTime !== time || !timeout) {
 | 
					    if (scheduledTime !== time || !timeout) {
 | 
				
			||||||
      console.log(
 | 
					      console.log(
 | 
				
			||||||
| 
						 | 
					@ -52,7 +56,7 @@
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    scheduledTime = time;
 | 
					    scheduledTime = time;
 | 
				
			||||||
    var waitTime = time - now;
 | 
					    let waitTime = time - now;
 | 
				
			||||||
    if (waitTime < 0) {
 | 
					    if (waitTime < 0) {
 | 
				
			||||||
      waitTime = 0;
 | 
					      waitTime = 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -61,9 +65,9 @@
 | 
				
			||||||
    timeout = setTimeout(runWhenOnline, waitTime);
 | 
					    timeout = setTimeout(runWhenOnline, waitTime);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var initComplete;
 | 
					  let initComplete;
 | 
				
			||||||
  Whisper.RotateSignedPreKeyListener = {
 | 
					  Whisper.RotateSignedPreKeyListener = {
 | 
				
			||||||
    init: function(events, newVersion) {
 | 
					    init(events, newVersion) {
 | 
				
			||||||
      if (initComplete) {
 | 
					      if (initComplete) {
 | 
				
			||||||
        console.log('Rotate signed prekey listener: Already initialized');
 | 
					        console.log('Rotate signed prekey listener: Already initialized');
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
| 
						 | 
					@ -76,7 +80,7 @@
 | 
				
			||||||
        setTimeoutForNextRun();
 | 
					        setTimeoutForNextRun();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      events.on('timetravel', function() {
 | 
					      events.on('timetravel', () => {
 | 
				
			||||||
        if (Whisper.Registration.isDone()) {
 | 
					        if (Whisper.Registration.isDone()) {
 | 
				
			||||||
          setTimeoutForNextRun();
 | 
					          setTimeoutForNextRun();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,8 @@
 | 
				
			||||||
$(document).on('keyup', function(e) {
 | 
					/* global $, Whisper */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$(document).on('keyup', e => {
 | 
				
			||||||
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (e.keyCode === 27) {
 | 
					  if (e.keyCode === 27) {
 | 
				
			||||||
    window.closeSettings();
 | 
					    window.closeSettings();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -7,6 +11,7 @@ $(document).on('keyup', function(e) {
 | 
				
			||||||
const $body = $(document.body);
 | 
					const $body = $(document.body);
 | 
				
			||||||
$body.addClass(`${window.theme}-theme`);
 | 
					$body.addClass(`${window.theme}-theme`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line strict
 | 
				
			||||||
const getInitialData = async () => ({
 | 
					const getInitialData = async () => ({
 | 
				
			||||||
  deviceName: await window.getDeviceName(),
 | 
					  deviceName: await window.getDeviceName(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,7 +28,11 @@ const getInitialData = async () => ({
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
window.initialRequest = getInitialData();
 | 
					window.initialRequest = getInitialData();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line more/no-then
 | 
				
			||||||
window.initialRequest.then(data => {
 | 
					window.initialRequest.then(data => {
 | 
				
			||||||
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.initialData = data;
 | 
					  window.initialData = data;
 | 
				
			||||||
  window.view = new Whisper.SettingsView();
 | 
					  window.view = new Whisper.SettingsView();
 | 
				
			||||||
  window.view.$el.appendTo($body);
 | 
					  window.view.$el.appendTo($body);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							| 
						 | 
					@ -1,22 +1,23 @@
 | 
				
			||||||
(function() {
 | 
					/* global require, process, _ */
 | 
				
			||||||
  var electron = require('electron');
 | 
					 | 
				
			||||||
  var remote = electron.remote;
 | 
					 | 
				
			||||||
  var app = remote.app;
 | 
					 | 
				
			||||||
  var webFrame = electron.webFrame;
 | 
					 | 
				
			||||||
  var path = require('path');
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var osLocale = require('os-locale');
 | 
					/* eslint-disable strict */
 | 
				
			||||||
  var os = require('os');
 | 
					
 | 
				
			||||||
  var semver = require('semver');
 | 
					const electron = require('electron');
 | 
				
			||||||
  var spellchecker = require('spellchecker');
 | 
					
 | 
				
			||||||
 | 
					const osLocale = require('os-locale');
 | 
				
			||||||
 | 
					const os = require('os');
 | 
				
			||||||
 | 
					const semver = require('semver');
 | 
				
			||||||
 | 
					const spellchecker = require('spellchecker');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { remote, webFrame } = electron;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// `remote.require` since `Menu` is a main-process module.
 | 
					// `remote.require` since `Menu` is a main-process module.
 | 
				
			||||||
  var buildEditorContextMenu = remote.require('electron-editor-context-menu');
 | 
					const buildEditorContextMenu = remote.require('electron-editor-context-menu');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var EN_VARIANT = /^en/;
 | 
					const EN_VARIANT = /^en/;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Prevent the spellchecker from showing contractions as errors.
 | 
					// Prevent the spellchecker from showing contractions as errors.
 | 
				
			||||||
  var ENGLISH_SKIP_WORDS = [
 | 
					const ENGLISH_SKIP_WORDS = [
 | 
				
			||||||
  'ain',
 | 
					  'ain',
 | 
				
			||||||
  'couldn',
 | 
					  'couldn',
 | 
				
			||||||
  'didn',
 | 
					  'didn',
 | 
				
			||||||
| 
						 | 
					@ -36,8 +37,9 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function setupLinux(locale) {
 | 
					function setupLinux(locale) {
 | 
				
			||||||
  if (process.env.HUNSPELL_DICTIONARIES || locale !== 'en_US') {
 | 
					  if (process.env.HUNSPELL_DICTIONARIES || locale !== 'en_US') {
 | 
				
			||||||
      // apt-get install hunspell-<locale> can be run for easy access to other dictionaries
 | 
					    // apt-get install hunspell-<locale> can be run for easy access
 | 
				
			||||||
      var location = process.env.HUNSPELL_DICTIONARIES || '/usr/share/hunspell';
 | 
					    //   to other dictionaries
 | 
				
			||||||
 | 
					    const location = process.env.HUNSPELL_DICTIONARIES || '/usr/share/hunspell';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    console.log(
 | 
					    console.log(
 | 
				
			||||||
      'Detected Linux. Setting up spell check with locale',
 | 
					      'Detected Linux. Setting up spell check with locale',
 | 
				
			||||||
| 
						 | 
					@ -53,7 +55,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function setupWin7AndEarlier(locale) {
 | 
					function setupWin7AndEarlier(locale) {
 | 
				
			||||||
  if (process.env.HUNSPELL_DICTIONARIES || locale !== 'en_US') {
 | 
					  if (process.env.HUNSPELL_DICTIONARIES || locale !== 'en_US') {
 | 
				
			||||||
      var location = process.env.HUNSPELL_DICTIONARIES;
 | 
					    const location = process.env.HUNSPELL_DICTIONARIES;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    console.log(
 | 
					    console.log(
 | 
				
			||||||
      'Detected Windows 7 or below. Setting up spell-check with locale',
 | 
					      'Detected Windows 7 or below. Setting up spell-check with locale',
 | 
				
			||||||
| 
						 | 
					@ -71,7 +73,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// We load locale this way and not via app.getLocale() because this call returns
 | 
					// We load locale this way and not via app.getLocale() because this call returns
 | 
				
			||||||
//   'es_ES' and not just 'es.' And hunspell requires the fully-qualified locale.
 | 
					//   'es_ES' and not just 'es.' And hunspell requires the fully-qualified locale.
 | 
				
			||||||
  var locale = osLocale.sync().replace('-', '_');
 | 
					const locale = osLocale.sync().replace('-', '_');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// The LANG environment variable is how node spellchecker finds its default language:
 | 
					// The LANG environment variable is how node spellchecker finds its default language:
 | 
				
			||||||
//   https://github.com/atom/node-spellchecker/blob/59d2d5eee5785c4b34e9669cd5d987181d17c098/lib/spellchecker.js#L29
 | 
					//   https://github.com/atom/node-spellchecker/blob/59d2d5eee5785c4b34e9669cd5d987181d17c098/lib/spellchecker.js#L29
 | 
				
			||||||
| 
						 | 
					@ -81,22 +83,19 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (process.platform === 'linux') {
 | 
					if (process.platform === 'linux') {
 | 
				
			||||||
  setupLinux(locale);
 | 
					  setupLinux(locale);
 | 
				
			||||||
  } else if (
 | 
					} else if (process.platform === 'windows' && semver.lt(os.release(), '8.0.0')) {
 | 
				
			||||||
    process.platform === 'windows' &&
 | 
					 | 
				
			||||||
    semver.lt(os.release(), '8.0.0')
 | 
					 | 
				
			||||||
  ) {
 | 
					 | 
				
			||||||
  setupWin7AndEarlier(locale);
 | 
					  setupWin7AndEarlier(locale);
 | 
				
			||||||
} else {
 | 
					} else {
 | 
				
			||||||
  // OSX and Windows 8+ have OS-level spellcheck APIs
 | 
					  // OSX and Windows 8+ have OS-level spellcheck APIs
 | 
				
			||||||
  console.log('Using OS-level spell check API with locale', process.env.LANG);
 | 
					  console.log('Using OS-level spell check API with locale', process.env.LANG);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var simpleChecker = (window.spellChecker = {
 | 
					const simpleChecker = {
 | 
				
			||||||
    spellCheck: function(text) {
 | 
					  spellCheck(text) {
 | 
				
			||||||
    return !this.isMisspelled(text);
 | 
					    return !this.isMisspelled(text);
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
    isMisspelled: function(text) {
 | 
					  isMisspelled(text) {
 | 
				
			||||||
      var misspelled = spellchecker.isMisspelled(text);
 | 
					    const misspelled = spellchecker.isMisspelled(text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // The idea is to make this as fast as possible. For the many, many calls which
 | 
					    // The idea is to make this as fast as possible. For the many, many calls which
 | 
				
			||||||
    //   don't result in the red squiggly, we minimize the number of checks.
 | 
					    //   don't result in the red squiggly, we minimize the number of checks.
 | 
				
			||||||
| 
						 | 
					@ -111,13 +110,15 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return true;
 | 
					    return true;
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
    getSuggestions: function(text) {
 | 
					  getSuggestions(text) {
 | 
				
			||||||
    return spellchecker.getCorrectionsForMisspelling(text);
 | 
					    return spellchecker.getCorrectionsForMisspelling(text);
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
    add: function(text) {
 | 
					  add(text) {
 | 
				
			||||||
    spellchecker.add(text);
 | 
					    spellchecker.add(text);
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  });
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					window.spellChecker = simpleChecker;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
webFrame.setSpellCheckProvider(
 | 
					webFrame.setSpellCheckProvider(
 | 
				
			||||||
  'en-US',
 | 
					  'en-US',
 | 
				
			||||||
| 
						 | 
					@ -127,26 +128,26 @@
 | 
				
			||||||
  simpleChecker
 | 
					  simpleChecker
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.addEventListener('contextmenu', function(e) {
 | 
					window.addEventListener('contextmenu', e => {
 | 
				
			||||||
  // Only show the context menu in text editors.
 | 
					  // Only show the context menu in text editors.
 | 
				
			||||||
  if (!e.target.closest('textarea, input, [contenteditable="true"]')) {
 | 
					  if (!e.target.closest('textarea, input, [contenteditable="true"]')) {
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var selectedText = window.getSelection().toString();
 | 
					  const selectedText = window.getSelection().toString();
 | 
				
			||||||
    var isMisspelled = selectedText && simpleChecker.isMisspelled(selectedText);
 | 
					  const isMisspelled = selectedText && simpleChecker.isMisspelled(selectedText);
 | 
				
			||||||
    var spellingSuggestions =
 | 
					  const spellingSuggestions =
 | 
				
			||||||
    isMisspelled && simpleChecker.getSuggestions(selectedText).slice(0, 5);
 | 
					    isMisspelled && simpleChecker.getSuggestions(selectedText).slice(0, 5);
 | 
				
			||||||
    var menu = buildEditorContextMenu({
 | 
					  const menu = buildEditorContextMenu({
 | 
				
			||||||
      isMisspelled: isMisspelled,
 | 
					    isMisspelled,
 | 
				
			||||||
      spellingSuggestions: spellingSuggestions,
 | 
					    spellingSuggestions,
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // The 'contextmenu' event is emitted after 'selectionchange' has fired but possibly before the
 | 
					  // The 'contextmenu' event is emitted after 'selectionchange' has fired
 | 
				
			||||||
    // visible selection has changed. Try to wait to show the menu until after that, otherwise the
 | 
					  //   but possibly before the visible selection has changed. Try to wait
 | 
				
			||||||
    // visible selection will update after the menu dismisses and look weird.
 | 
					  //   to show the menu until after that, otherwise the visible selection
 | 
				
			||||||
    setTimeout(function() {
 | 
					  //   will update after the menu dismisses and look weird.
 | 
				
			||||||
 | 
					  setTimeout(() => {
 | 
				
			||||||
    menu.popup(remote.getCurrentWindow());
 | 
					    menu.popup(remote.getCurrentWindow());
 | 
				
			||||||
  }, 30);
 | 
					  }, 30);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
})();
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,58 +1,64 @@
 | 
				
			||||||
 | 
					/* global Backbone, Whisper */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* eslint-disable more/no-then */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
  var Item = Backbone.Model.extend({
 | 
					  const Item = Backbone.Model.extend({
 | 
				
			||||||
    database: Whisper.Database,
 | 
					    database: Whisper.Database,
 | 
				
			||||||
    storeName: 'items',
 | 
					    storeName: 'items',
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  var ItemCollection = Backbone.Collection.extend({
 | 
					  const ItemCollection = Backbone.Collection.extend({
 | 
				
			||||||
    model: Item,
 | 
					    model: Item,
 | 
				
			||||||
    storeName: 'items',
 | 
					    storeName: 'items',
 | 
				
			||||||
    database: Whisper.Database,
 | 
					    database: Whisper.Database,
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var ready = false;
 | 
					  let ready = false;
 | 
				
			||||||
  var items = new ItemCollection();
 | 
					  const items = new ItemCollection();
 | 
				
			||||||
  items.on('reset', function() {
 | 
					  items.on('reset', () => {
 | 
				
			||||||
    ready = true;
 | 
					    ready = true;
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  window.storage = {
 | 
					  window.storage = {
 | 
				
			||||||
    /** ***************************
 | 
					    /** ***************************
 | 
				
			||||||
     *** Base Storage Routines ***
 | 
					     *** Base Storage Routines ***
 | 
				
			||||||
     **************************** */
 | 
					     **************************** */
 | 
				
			||||||
    put: function(key, value) {
 | 
					    put(key, value) {
 | 
				
			||||||
      if (value === undefined) {
 | 
					      if (value === undefined) {
 | 
				
			||||||
        throw new Error('Tried to store undefined');
 | 
					        throw new Error('Tried to store undefined');
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (!ready) {
 | 
					      if (!ready) {
 | 
				
			||||||
        console.log('Called storage.put before storage is ready. key:', key);
 | 
					        console.log('Called storage.put before storage is ready. key:', key);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      var item = items.add({ id: key, value: value }, { merge: true });
 | 
					      const item = items.add({ id: key, value }, { merge: true });
 | 
				
			||||||
      return new Promise(function(resolve, reject) {
 | 
					      return new Promise((resolve, reject) => {
 | 
				
			||||||
        item.save().then(resolve, reject);
 | 
					        item.save().then(resolve, reject);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    get: function(key, defaultValue) {
 | 
					    get(key, defaultValue) {
 | 
				
			||||||
      var item = items.get('' + key);
 | 
					      const item = items.get(`${key}`);
 | 
				
			||||||
      if (!item) {
 | 
					      if (!item) {
 | 
				
			||||||
        return defaultValue;
 | 
					        return defaultValue;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return item.get('value');
 | 
					      return item.get('value');
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    remove: function(key) {
 | 
					    remove(key) {
 | 
				
			||||||
      var item = items.get('' + key);
 | 
					      const item = items.get(`${key}`);
 | 
				
			||||||
      if (item) {
 | 
					      if (item) {
 | 
				
			||||||
        items.remove(item);
 | 
					        items.remove(item);
 | 
				
			||||||
        return new Promise(function(resolve, reject) {
 | 
					        return new Promise((resolve, reject) => {
 | 
				
			||||||
          item.destroy().then(resolve, reject);
 | 
					          item.destroy().then(resolve, reject);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return Promise.resolve();
 | 
					      return Promise.resolve();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    onready: function(callback) {
 | 
					    onready(callback) {
 | 
				
			||||||
      if (ready) {
 | 
					      if (ready) {
 | 
				
			||||||
        callback();
 | 
					        callback();
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
| 
						 | 
					@ -60,7 +66,7 @@
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fetch: function() {
 | 
					    fetch() {
 | 
				
			||||||
      return new Promise((resolve, reject) => {
 | 
					      return new Promise((resolve, reject) => {
 | 
				
			||||||
        items
 | 
					        items
 | 
				
			||||||
          .fetch({ reset: true })
 | 
					          .fetch({ reset: true })
 | 
				
			||||||
| 
						 | 
					@ -76,7 +82,7 @@
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    reset: function() {
 | 
					    reset() {
 | 
				
			||||||
      items.reset();
 | 
					      items.reset();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,15 @@
 | 
				
			||||||
 | 
					/* global Backbone, Whisper, storage, _, ConversationController, $ */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* eslint-disable more/no-then */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Whisper.AppView = Backbone.View.extend({
 | 
					  Whisper.AppView = Backbone.View.extend({
 | 
				
			||||||
    initialize: function(options) {
 | 
					    initialize() {
 | 
				
			||||||
      this.inboxView = null;
 | 
					      this.inboxView = null;
 | 
				
			||||||
      this.installView = null;
 | 
					      this.installView = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,38 +20,41 @@
 | 
				
			||||||
      'click .openInstaller': 'openInstaller', // NetworkStatusView has this button
 | 
					      'click .openInstaller': 'openInstaller', // NetworkStatusView has this button
 | 
				
			||||||
      openInbox: 'openInbox',
 | 
					      openInbox: 'openInbox',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    applyTheme: function() {
 | 
					    applyTheme() {
 | 
				
			||||||
      var theme = storage.get('theme-setting') || 'light';
 | 
					      const theme = storage.get('theme-setting') || 'light';
 | 
				
			||||||
      this.$el
 | 
					      this.$el
 | 
				
			||||||
        .removeClass('light-theme')
 | 
					        .removeClass('light-theme')
 | 
				
			||||||
        .removeClass('dark-theme')
 | 
					        .removeClass('dark-theme')
 | 
				
			||||||
        .addClass(`${theme}-theme`);
 | 
					        .addClass(`${theme}-theme`);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    applyHideMenu: function() {
 | 
					    applyHideMenu() {
 | 
				
			||||||
      var hideMenuBar = storage.get('hide-menu-bar', false);
 | 
					      const hideMenuBar = storage.get('hide-menu-bar', false);
 | 
				
			||||||
      window.setAutoHideMenuBar(hideMenuBar);
 | 
					      window.setAutoHideMenuBar(hideMenuBar);
 | 
				
			||||||
      window.setMenuBarVisibility(!hideMenuBar);
 | 
					      window.setMenuBarVisibility(!hideMenuBar);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    openView: function(view) {
 | 
					    openView(view) {
 | 
				
			||||||
      this.el.innerHTML = '';
 | 
					      this.el.innerHTML = '';
 | 
				
			||||||
      this.el.append(view.el);
 | 
					      this.el.append(view.el);
 | 
				
			||||||
      this.delegateEvents();
 | 
					      this.delegateEvents();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    openDebugLog: function() {
 | 
					    openDebugLog() {
 | 
				
			||||||
      this.closeDebugLog();
 | 
					      this.closeDebugLog();
 | 
				
			||||||
      this.debugLogView = new Whisper.DebugLogView();
 | 
					      this.debugLogView = new Whisper.DebugLogView();
 | 
				
			||||||
      this.debugLogView.$el.appendTo(this.el);
 | 
					      this.debugLogView.$el.appendTo(this.el);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    closeDebugLog: function() {
 | 
					    closeDebugLog() {
 | 
				
			||||||
      if (this.debugLogView) {
 | 
					      if (this.debugLogView) {
 | 
				
			||||||
        this.debugLogView.remove();
 | 
					        this.debugLogView.remove();
 | 
				
			||||||
        this.debugLogView = null;
 | 
					        this.debugLogView = null;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    openImporter: function() {
 | 
					    openImporter() {
 | 
				
			||||||
      window.addSetupMenuItems();
 | 
					      window.addSetupMenuItems();
 | 
				
			||||||
      this.resetViews();
 | 
					      this.resetViews();
 | 
				
			||||||
      var importView = (this.importView = new Whisper.ImportView());
 | 
					
 | 
				
			||||||
 | 
					      const importView = new Whisper.ImportView();
 | 
				
			||||||
 | 
					      this.importView = importView;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.listenTo(
 | 
					      this.listenTo(
 | 
				
			||||||
        importView,
 | 
					        importView,
 | 
				
			||||||
        'light-import',
 | 
					        'light-import',
 | 
				
			||||||
| 
						 | 
					@ -54,21 +62,19 @@
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      this.openView(this.importView);
 | 
					      this.openView(this.importView);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    finishLightImport: function() {
 | 
					    finishLightImport() {
 | 
				
			||||||
      var options = {
 | 
					      const options = {
 | 
				
			||||||
        hasExistingData: true,
 | 
					        hasExistingData: true,
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
      this.openInstaller(options);
 | 
					      this.openInstaller(options);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    closeImporter: function() {
 | 
					    closeImporter() {
 | 
				
			||||||
      if (this.importView) {
 | 
					      if (this.importView) {
 | 
				
			||||||
        this.importView.remove();
 | 
					        this.importView.remove();
 | 
				
			||||||
        this.importView = null;
 | 
					        this.importView = null;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    openInstaller: function(options) {
 | 
					    openInstaller(options = {}) {
 | 
				
			||||||
      options = options || {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // If we're in the middle of import, we don't want to show the menu 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
 | 
					      //   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
 | 
					      //   switched back and forth in the middle of a light import, they'd lose all
 | 
				
			||||||
| 
						 | 
					@ -78,16 +84,18 @@
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.resetViews();
 | 
					      this.resetViews();
 | 
				
			||||||
      var installView = (this.installView = new Whisper.InstallView(options));
 | 
					      const installView = new Whisper.InstallView(options);
 | 
				
			||||||
 | 
					      this.installView = installView;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.openView(this.installView);
 | 
					      this.openView(this.installView);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    closeInstaller: function() {
 | 
					    closeInstaller() {
 | 
				
			||||||
      if (this.installView) {
 | 
					      if (this.installView) {
 | 
				
			||||||
        this.installView.remove();
 | 
					        this.installView.remove();
 | 
				
			||||||
        this.installView = null;
 | 
					        this.installView = null;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    openStandalone: function() {
 | 
					    openStandalone() {
 | 
				
			||||||
      if (window.getEnvironment() !== 'production') {
 | 
					      if (window.getEnvironment() !== 'production') {
 | 
				
			||||||
        window.addSetupMenuItems();
 | 
					        window.addSetupMenuItems();
 | 
				
			||||||
        this.resetViews();
 | 
					        this.resetViews();
 | 
				
			||||||
| 
						 | 
					@ -95,19 +103,18 @@
 | 
				
			||||||
        this.openView(this.standaloneView);
 | 
					        this.openView(this.standaloneView);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    closeStandalone: function() {
 | 
					    closeStandalone() {
 | 
				
			||||||
      if (this.standaloneView) {
 | 
					      if (this.standaloneView) {
 | 
				
			||||||
        this.standaloneView.remove();
 | 
					        this.standaloneView.remove();
 | 
				
			||||||
        this.standaloneView = null;
 | 
					        this.standaloneView = null;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    resetViews: function() {
 | 
					    resetViews() {
 | 
				
			||||||
      this.closeInstaller();
 | 
					      this.closeInstaller();
 | 
				
			||||||
      this.closeImporter();
 | 
					      this.closeImporter();
 | 
				
			||||||
      this.closeStandalone();
 | 
					      this.closeStandalone();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    openInbox: function(options) {
 | 
					    openInbox(options = {}) {
 | 
				
			||||||
      options = options || {};
 | 
					 | 
				
			||||||
      // The inbox can be created before the 'empty' event fires or afterwards. If
 | 
					      // The inbox can be created before the 'empty' event fires or afterwards. If
 | 
				
			||||||
      //   before, it's straightforward: the onEmpty() handler below updates the
 | 
					      //   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
 | 
					      //   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
 | 
					        //   this.initialLoadComplete between the start of this method and the
 | 
				
			||||||
        //   creation of inboxView.
 | 
					        //   creation of inboxView.
 | 
				
			||||||
        this.inboxView = new Whisper.InboxView({
 | 
					        this.inboxView = new Whisper.InboxView({
 | 
				
			||||||
          model: self,
 | 
					          window,
 | 
				
			||||||
          window: window,
 | 
					 | 
				
			||||||
          initialLoadComplete: options.initialLoadComplete,
 | 
					          initialLoadComplete: options.initialLoadComplete,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        return ConversationController.loadPromise().then(
 | 
					        return ConversationController.loadPromise().then(() => {
 | 
				
			||||||
          function() {
 | 
					 | 
				
			||||||
          this.openView(this.inboxView);
 | 
					          this.openView(this.inboxView);
 | 
				
			||||||
          }.bind(this)
 | 
					        });
 | 
				
			||||||
        );
 | 
					      }
 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
      if (!$.contains(this.el, this.inboxView.el)) {
 | 
					      if (!$.contains(this.el, this.inboxView.el)) {
 | 
				
			||||||
        this.openView(this.inboxView);
 | 
					        this.openView(this.inboxView);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      window.focus(); // FIXME
 | 
					      window.focus(); // FIXME
 | 
				
			||||||
      return Promise.resolve();
 | 
					      return Promise.resolve();
 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    onEmpty: function() {
 | 
					    onEmpty() {
 | 
				
			||||||
      var view = this.inboxView;
 | 
					      const view = this.inboxView;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.initialLoadComplete = true;
 | 
					      this.initialLoadComplete = true;
 | 
				
			||||||
      if (view) {
 | 
					      if (view) {
 | 
				
			||||||
        view.onEmpty();
 | 
					        view.onEmpty();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    onProgress: function(count) {
 | 
					    onProgress(count) {
 | 
				
			||||||
      var view = this.inboxView;
 | 
					      const view = this.inboxView;
 | 
				
			||||||
      if (view) {
 | 
					      if (view) {
 | 
				
			||||||
        view.onProgress(count);
 | 
					        view.onProgress(count);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    openConversation: function(conversation) {
 | 
					    openConversation(conversation) {
 | 
				
			||||||
      if (conversation) {
 | 
					      if (conversation) {
 | 
				
			||||||
        this.openInbox().then(
 | 
					        this.openInbox().then(() => {
 | 
				
			||||||
          function() {
 | 
					 | 
				
			||||||
          this.inboxView.openConversation(null, conversation);
 | 
					          this.inboxView.openConversation(null, conversation);
 | 
				
			||||||
          }.bind(this)
 | 
					        });
 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,15 @@
 | 
				
			||||||
 | 
					/* global Whisper */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Whisper.AttachmentPreviewView = Whisper.View.extend({
 | 
					  Whisper.AttachmentPreviewView = Whisper.View.extend({
 | 
				
			||||||
    className: 'attachment-preview',
 | 
					    className: 'attachment-preview',
 | 
				
			||||||
    templateName: 'attachment-preview',
 | 
					    templateName: 'attachment-preview',
 | 
				
			||||||
    render_attributes: function() {
 | 
					    render_attributes() {
 | 
				
			||||||
      return { source: this.src };
 | 
					      return { source: this.src };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,9 @@
 | 
				
			||||||
 | 
					/* global Whisper */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Whisper.BannerView = Whisper.View.extend({
 | 
					  Whisper.BannerView = Whisper.View.extend({
 | 
				
			||||||
| 
						 | 
					@ -9,7 +13,7 @@
 | 
				
			||||||
      'click .dismiss': 'onDismiss',
 | 
					      'click .dismiss': 'onDismiss',
 | 
				
			||||||
      'click .body': 'onClick',
 | 
					      'click .body': 'onClick',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    initialize: function(options) {
 | 
					    initialize(options) {
 | 
				
			||||||
      this.message = options.message;
 | 
					      this.message = options.message;
 | 
				
			||||||
      this.callbacks = {
 | 
					      this.callbacks = {
 | 
				
			||||||
        onDismiss: options.onDismiss,
 | 
					        onDismiss: options.onDismiss,
 | 
				
			||||||
| 
						 | 
					@ -17,16 +21,16 @@
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
      this.render();
 | 
					      this.render();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    render_attributes: function() {
 | 
					    render_attributes() {
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        message: this.message,
 | 
					        message: this.message,
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    onDismiss: function(e) {
 | 
					    onDismiss(e) {
 | 
				
			||||||
      this.callbacks.onDismiss();
 | 
					      this.callbacks.onDismiss();
 | 
				
			||||||
      e.stopPropagation();
 | 
					      e.stopPropagation();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    onClick: function() {
 | 
					    onClick() {
 | 
				
			||||||
      this.callbacks.onClick();
 | 
					      this.callbacks.onClick();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,15 @@
 | 
				
			||||||
 | 
					/* global Whisper, i18n */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Whisper.ConfirmationDialogView = Whisper.View.extend({
 | 
					  Whisper.ConfirmationDialogView = Whisper.View.extend({
 | 
				
			||||||
    className: 'confirmation-dialog modal',
 | 
					    className: 'confirmation-dialog modal',
 | 
				
			||||||
    templateName: 'confirmation-dialog',
 | 
					    templateName: 'confirmation-dialog',
 | 
				
			||||||
    initialize: function(options) {
 | 
					    initialize(options) {
 | 
				
			||||||
      this.message = options.message;
 | 
					      this.message = options.message;
 | 
				
			||||||
      this.hideCancel = options.hideCancel;
 | 
					      this.hideCancel = options.hideCancel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,7 +26,7 @@
 | 
				
			||||||
      'click .ok': 'ok',
 | 
					      'click .ok': 'ok',
 | 
				
			||||||
      'click .cancel': 'cancel',
 | 
					      'click .cancel': 'cancel',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    render_attributes: function() {
 | 
					    render_attributes() {
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        message: this.message,
 | 
					        message: this.message,
 | 
				
			||||||
        showCancel: !this.hideCancel,
 | 
					        showCancel: !this.hideCancel,
 | 
				
			||||||
| 
						 | 
					@ -30,24 +34,24 @@
 | 
				
			||||||
        ok: this.okText,
 | 
					        ok: this.okText,
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    ok: function() {
 | 
					    ok() {
 | 
				
			||||||
      this.remove();
 | 
					      this.remove();
 | 
				
			||||||
      if (this.resolve) {
 | 
					      if (this.resolve) {
 | 
				
			||||||
        this.resolve();
 | 
					        this.resolve();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    cancel: function() {
 | 
					    cancel() {
 | 
				
			||||||
      this.remove();
 | 
					      this.remove();
 | 
				
			||||||
      if (this.reject) {
 | 
					      if (this.reject) {
 | 
				
			||||||
        this.reject();
 | 
					        this.reject();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    onKeyup: function(event) {
 | 
					    onKeyup(event) {
 | 
				
			||||||
      if (event.key === 'Escape' || event.key === 'Esc') {
 | 
					      if (event.key === 'Escape' || event.key === 'Esc') {
 | 
				
			||||||
        this.cancel();
 | 
					        this.cancel();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    focusCancel: function() {
 | 
					    focusCancel() {
 | 
				
			||||||
      this.$('.cancel').focus();
 | 
					      this.$('.cancel').focus();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,4 @@
 | 
				
			||||||
/* global Whisper: false */
 | 
					/* global Whisper: false */
 | 
				
			||||||
/* global i18n: false */
 | 
					 | 
				
			||||||
/* global textsecure: false */
 | 
					/* global textsecure: false */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// eslint-disable-next-line func-names
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,18 +1,22 @@
 | 
				
			||||||
 | 
					/* global Whisper, _, extension, Backbone, Mustache */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // list of conversations, showing user/group and last message sent
 | 
					  // list of conversations, showing user/group and last message sent
 | 
				
			||||||
  Whisper.ConversationListItemView = Whisper.View.extend({
 | 
					  Whisper.ConversationListItemView = Whisper.View.extend({
 | 
				
			||||||
    tagName: 'div',
 | 
					    tagName: 'div',
 | 
				
			||||||
    className: function() {
 | 
					    className() {
 | 
				
			||||||
      return 'conversation-list-item contact ' + this.model.cid;
 | 
					      return `conversation-list-item contact ${this.model.cid}`;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    templateName: 'conversation-preview',
 | 
					    templateName: 'conversation-preview',
 | 
				
			||||||
    events: {
 | 
					    events: {
 | 
				
			||||||
      click: 'select',
 | 
					      click: 'select',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    initialize: function() {
 | 
					    initialize() {
 | 
				
			||||||
      // auto update
 | 
					      // auto update
 | 
				
			||||||
      this.listenTo(
 | 
					      this.listenTo(
 | 
				
			||||||
        this.model,
 | 
					        this.model,
 | 
				
			||||||
| 
						 | 
					@ -22,7 +26,7 @@
 | 
				
			||||||
      this.listenTo(this.model, 'destroy', this.remove); // auto update
 | 
					      this.listenTo(this.model, 'destroy', this.remove); // auto update
 | 
				
			||||||
      this.listenTo(this.model, 'opened', this.markSelected); // auto update
 | 
					      this.listenTo(this.model, 'opened', this.markSelected); // auto update
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var updateLastMessage = _.debounce(
 | 
					      const updateLastMessage = _.debounce(
 | 
				
			||||||
        this.model.updateLastMessage.bind(this.model),
 | 
					        this.model.updateLastMessage.bind(this.model),
 | 
				
			||||||
        1000
 | 
					        1000
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
| 
						 | 
					@ -33,23 +37,21 @@
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      this.listenTo(this.model, 'newmessage', updateLastMessage);
 | 
					      this.listenTo(this.model, 'newmessage', updateLastMessage);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      extension.windows.onClosed(
 | 
					      extension.windows.onClosed(() => {
 | 
				
			||||||
        function() {
 | 
					 | 
				
			||||||
        this.stopListening();
 | 
					        this.stopListening();
 | 
				
			||||||
        }.bind(this)
 | 
					      });
 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      this.timeStampView = new Whisper.TimestampView({ brief: true });
 | 
					      this.timeStampView = new Whisper.TimestampView({ brief: true });
 | 
				
			||||||
      this.model.updateLastMessage();
 | 
					      this.model.updateLastMessage();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    markSelected: function() {
 | 
					    markSelected() {
 | 
				
			||||||
      this.$el
 | 
					      this.$el
 | 
				
			||||||
        .addClass('selected')
 | 
					        .addClass('selected')
 | 
				
			||||||
        .siblings('.selected')
 | 
					        .siblings('.selected')
 | 
				
			||||||
        .removeClass('selected');
 | 
					        .removeClass('selected');
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    select: function(e) {
 | 
					    select() {
 | 
				
			||||||
      this.markSelected();
 | 
					      this.markSelected();
 | 
				
			||||||
      this.$el.trigger('select', this.model);
 | 
					      this.$el.trigger('select', this.model);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
| 
						 | 
					@ -66,7 +68,7 @@
 | 
				
			||||||
      Backbone.View.prototype.remove.call(this);
 | 
					      Backbone.View.prototype.remove.call(this);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    render: function() {
 | 
					    render() {
 | 
				
			||||||
      const lastMessage = this.model.get('lastMessage');
 | 
					      const lastMessage = this.model.get('lastMessage');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.$el.html(
 | 
					      this.$el.html(
 | 
				
			||||||
| 
						 | 
					@ -117,7 +119,7 @@
 | 
				
			||||||
        this.$('.last-message').append(this.bodyView.el);
 | 
					        this.$('.last-message').append(this.bodyView.el);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var unread = this.model.get('unreadCount');
 | 
					      const unread = this.model.get('unreadCount');
 | 
				
			||||||
      if (unread > 0) {
 | 
					      if (unread > 0) {
 | 
				
			||||||
        this.$el.addClass('unread');
 | 
					        this.$el.addClass('unread');
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,16 @@
 | 
				
			||||||
 | 
					/* global Whisper, getInboxCollection */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Whisper.ConversationListView = Whisper.ListView.extend({
 | 
					  Whisper.ConversationListView = Whisper.ListView.extend({
 | 
				
			||||||
    tagName: 'div',
 | 
					    tagName: 'div',
 | 
				
			||||||
    itemView: Whisper.ConversationListItemView,
 | 
					    itemView: Whisper.ConversationListItemView,
 | 
				
			||||||
    updateLocation: function(conversation) {
 | 
					    updateLocation(conversation) {
 | 
				
			||||||
      var $el = this.$('.' + conversation.cid);
 | 
					      const $el = this.$(`.${conversation.cid}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (!$el || !$el.length) {
 | 
					      if (!$el || !$el.length) {
 | 
				
			||||||
        console.log(
 | 
					        console.log(
 | 
				
			||||||
| 
						 | 
					@ -23,11 +27,11 @@
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var $allConversations = this.$('.conversation-list-item');
 | 
					      const $allConversations = this.$('.conversation-list-item');
 | 
				
			||||||
      var inboxCollection = getInboxCollection();
 | 
					      const inboxCollection = getInboxCollection();
 | 
				
			||||||
      var index = inboxCollection.indexOf(conversation);
 | 
					      const index = inboxCollection.indexOf(conversation);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var elIndex = $allConversations.index($el);
 | 
					      const elIndex = $allConversations.index($el);
 | 
				
			||||||
      if (elIndex < 0) {
 | 
					      if (elIndex < 0) {
 | 
				
			||||||
        console.log(
 | 
					        console.log(
 | 
				
			||||||
          'updateLocation: did not find index for conversation',
 | 
					          'updateLocation: did not find index for conversation',
 | 
				
			||||||
| 
						 | 
					@ -43,8 +47,8 @@
 | 
				
			||||||
      } else if (index === this.collection.length - 1) {
 | 
					      } else if (index === this.collection.length - 1) {
 | 
				
			||||||
        this.$el.append($el);
 | 
					        this.$el.append($el);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        var targetConversation = inboxCollection.at(index - 1);
 | 
					        const targetConversation = inboxCollection.at(index - 1);
 | 
				
			||||||
        var target = this.$('.' + targetConversation.cid);
 | 
					        const target = this.$(`.${targetConversation.cid}`);
 | 
				
			||||||
        $el.insertAfter(target);
 | 
					        $el.insertAfter(target);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -54,8 +58,8 @@
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    removeItem: function(conversation) {
 | 
					    removeItem(conversation) {
 | 
				
			||||||
      var $el = this.$('.' + conversation.cid);
 | 
					      const $el = this.$(`.${conversation.cid}`);
 | 
				
			||||||
      if ($el && $el.length > 0) {
 | 
					      if ($el && $el.length > 0) {
 | 
				
			||||||
        $el.remove();
 | 
					        $el.remove();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
})();
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,16 @@
 | 
				
			||||||
 | 
					/* global Whisper, i18n */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // TODO: take a title string which could replace the 'members' header
 | 
					  // TODO: take a title string which could replace the 'members' header
 | 
				
			||||||
  Whisper.GroupMemberList = Whisper.View.extend({
 | 
					  Whisper.GroupMemberList = Whisper.View.extend({
 | 
				
			||||||
    className: 'group-member-list panel',
 | 
					    className: 'group-member-list panel',
 | 
				
			||||||
    templateName: 'group-member-list',
 | 
					    templateName: 'group-member-list',
 | 
				
			||||||
    initialize: function(options) {
 | 
					    initialize(options) {
 | 
				
			||||||
      this.needVerify = options.needVerify;
 | 
					      this.needVerify = options.needVerify;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.render();
 | 
					      this.render();
 | 
				
			||||||
| 
						 | 
					@ -22,15 +26,15 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.$('.container').append(this.member_list_view.el);
 | 
					      this.$('.container').append(this.member_list_view.el);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    render_attributes: function() {
 | 
					    render_attributes() {
 | 
				
			||||||
      var summary;
 | 
					      let summary;
 | 
				
			||||||
      if (this.needVerify) {
 | 
					      if (this.needVerify) {
 | 
				
			||||||
        summary = i18n('membersNeedingVerification');
 | 
					        summary = i18n('membersNeedingVerification');
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        members: i18n('groupMembers'),
 | 
					        members: i18n('groupMembers'),
 | 
				
			||||||
        summary: summary,
 | 
					        summary,
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,6 @@
 | 
				
			||||||
 | 
					/* global Backbone, Whisper */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,19 +9,19 @@
 | 
				
			||||||
  Whisper.GroupUpdateView = Backbone.View.extend({
 | 
					  Whisper.GroupUpdateView = Backbone.View.extend({
 | 
				
			||||||
    tagName: 'div',
 | 
					    tagName: 'div',
 | 
				
			||||||
    className: 'group-update',
 | 
					    className: 'group-update',
 | 
				
			||||||
    render: function() {
 | 
					    render() {
 | 
				
			||||||
      // TODO l10n
 | 
					      // TODO l10n
 | 
				
			||||||
      if (this.model.left) {
 | 
					      if (this.model.left) {
 | 
				
			||||||
        this.$el.text(this.model.left + ' left the group');
 | 
					        this.$el.text(`${this.model.left} left the group`);
 | 
				
			||||||
        return this;
 | 
					        return this;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var messages = ['Updated the group.'];
 | 
					      const messages = ['Updated the group.'];
 | 
				
			||||||
      if (this.model.name) {
 | 
					      if (this.model.name) {
 | 
				
			||||||
        messages.push("Title is now '" + this.model.name + "'.");
 | 
					        messages.push(`Title is now '${this.model.name}'.`);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (this.model.joined) {
 | 
					      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(' '));
 | 
					      this.$el.text(messages.join(' '));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,13 +1,17 @@
 | 
				
			||||||
 | 
					/* global Whisper */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Whisper.HintView = Whisper.View.extend({
 | 
					  Whisper.HintView = Whisper.View.extend({
 | 
				
			||||||
    templateName: 'hint',
 | 
					    templateName: 'hint',
 | 
				
			||||||
    initialize: function(options) {
 | 
					    initialize(options) {
 | 
				
			||||||
      this.content = options.content;
 | 
					      this.content = options.content;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    render_attributes: function() {
 | 
					    render_attributes() {
 | 
				
			||||||
      return { content: this.content };
 | 
					      return { content: this.content };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,9 @@
 | 
				
			||||||
 | 
					/* global Whisper, loadImage */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /*
 | 
					  /*
 | 
				
			||||||
| 
						 | 
					@ -7,26 +11,26 @@
 | 
				
			||||||
    */
 | 
					    */
 | 
				
			||||||
  Whisper.IdenticonSVGView = Whisper.View.extend({
 | 
					  Whisper.IdenticonSVGView = Whisper.View.extend({
 | 
				
			||||||
    templateName: 'identicon-svg',
 | 
					    templateName: 'identicon-svg',
 | 
				
			||||||
    initialize: function(options) {
 | 
					    initialize(options) {
 | 
				
			||||||
      this.render_attributes = options;
 | 
					      this.render_attributes = options;
 | 
				
			||||||
      this.render_attributes.color = COLORS[this.render_attributes.color];
 | 
					      this.render_attributes.color = COLORS[this.render_attributes.color];
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    getSVGUrl: function() {
 | 
					    getSVGUrl() {
 | 
				
			||||||
      var html = this.render().$el.html();
 | 
					      const html = this.render().$el.html();
 | 
				
			||||||
      var svg = new Blob([html], { type: 'image/svg+xml;charset=utf-8' });
 | 
					      const svg = new Blob([html], { type: 'image/svg+xml;charset=utf-8' });
 | 
				
			||||||
      return URL.createObjectURL(svg);
 | 
					      return URL.createObjectURL(svg);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    getDataUrl: function() {
 | 
					    getDataUrl() {
 | 
				
			||||||
      var svgurl = this.getSVGUrl();
 | 
					      const svgurl = this.getSVGUrl();
 | 
				
			||||||
      return new Promise(function(resolve) {
 | 
					      return new Promise(resolve => {
 | 
				
			||||||
        var img = document.createElement('img');
 | 
					        const img = document.createElement('img');
 | 
				
			||||||
        img.onload = function() {
 | 
					        img.onload = () => {
 | 
				
			||||||
          var canvas = loadImage.scale(img, {
 | 
					          const canvas = loadImage.scale(img, {
 | 
				
			||||||
            canvas: true,
 | 
					            canvas: true,
 | 
				
			||||||
            maxWidth: 100,
 | 
					            maxWidth: 100,
 | 
				
			||||||
            maxHeight: 100,
 | 
					            maxHeight: 100,
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
          var ctx = canvas.getContext('2d');
 | 
					          const ctx = canvas.getContext('2d');
 | 
				
			||||||
          ctx.drawImage(img, 0, 0);
 | 
					          ctx.drawImage(img, 0, 0);
 | 
				
			||||||
          URL.revokeObjectURL(svgurl);
 | 
					          URL.revokeObjectURL(svgurl);
 | 
				
			||||||
          resolve(canvas.toDataURL('image/png'));
 | 
					          resolve(canvas.toDataURL('image/png'));
 | 
				
			||||||
| 
						 | 
					@ -37,7 +41,7 @@
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var COLORS = {
 | 
					  const COLORS = {
 | 
				
			||||||
    red: '#EF5350',
 | 
					    red: '#EF5350',
 | 
				
			||||||
    pink: '#EC407A',
 | 
					    pink: '#EC407A',
 | 
				
			||||||
    purple: '#AB47BC',
 | 
					    purple: '#AB47BC',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,15 @@
 | 
				
			||||||
 | 
					/* global Whisper, i18n */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Whisper.IdentityKeySendErrorPanelView = Whisper.View.extend({
 | 
					  Whisper.IdentityKeySendErrorPanelView = Whisper.View.extend({
 | 
				
			||||||
    className: 'identity-key-send-error panel',
 | 
					    className: 'identity-key-send-error panel',
 | 
				
			||||||
    templateName: 'identity-key-send-error',
 | 
					    templateName: 'identity-key-send-error',
 | 
				
			||||||
    initialize: function(options) {
 | 
					    initialize(options) {
 | 
				
			||||||
      this.listenBack = options.listenBack;
 | 
					      this.listenBack = options.listenBack;
 | 
				
			||||||
      this.resetPanel = options.resetPanel;
 | 
					      this.resetPanel = options.resetPanel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,31 +21,31 @@
 | 
				
			||||||
      'click .send-anyway': 'sendAnyway',
 | 
					      'click .send-anyway': 'sendAnyway',
 | 
				
			||||||
      'click .cancel': 'cancel',
 | 
					      'click .cancel': 'cancel',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    showSafetyNumber: function() {
 | 
					    showSafetyNumber() {
 | 
				
			||||||
      var view = new Whisper.KeyVerificationPanelView({
 | 
					      const view = new Whisper.KeyVerificationPanelView({
 | 
				
			||||||
        model: this.model,
 | 
					        model: this.model,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      this.listenBack(view);
 | 
					      this.listenBack(view);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    sendAnyway: function() {
 | 
					    sendAnyway() {
 | 
				
			||||||
      this.resetPanel();
 | 
					      this.resetPanel();
 | 
				
			||||||
      this.trigger('send-anyway');
 | 
					      this.trigger('send-anyway');
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    cancel: function() {
 | 
					    cancel() {
 | 
				
			||||||
      this.resetPanel();
 | 
					      this.resetPanel();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    render_attributes: function() {
 | 
					    render_attributes() {
 | 
				
			||||||
      var send = i18n('sendAnyway');
 | 
					      let send = i18n('sendAnyway');
 | 
				
			||||||
      if (this.wasUnverified && !this.model.isUnverified()) {
 | 
					      if (this.wasUnverified && !this.model.isUnverified()) {
 | 
				
			||||||
        send = i18n('resend');
 | 
					        send = i18n('resend');
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var errorExplanation = i18n('identityKeyErrorOnSend', [
 | 
					      const errorExplanation = i18n('identityKeyErrorOnSend', [
 | 
				
			||||||
        this.model.getTitle(),
 | 
					        this.model.getTitle(),
 | 
				
			||||||
        this.model.getTitle(),
 | 
					        this.model.getTitle(),
 | 
				
			||||||
      ]);
 | 
					      ]);
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        errorExplanation: errorExplanation,
 | 
					        errorExplanation,
 | 
				
			||||||
        showSafetyNumber: i18n('showSafetyNumber'),
 | 
					        showSafetyNumber: i18n('showSafetyNumber'),
 | 
				
			||||||
        sendAnyway: send,
 | 
					        sendAnyway: send,
 | 
				
			||||||
        cancel: i18n('cancel'),
 | 
					        cancel: i18n('cancel'),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,37 +1,43 @@
 | 
				
			||||||
 | 
					/* global Whisper, storage, i18n, ConversationController */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* eslint-disable more/no-then */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var State = {
 | 
					  const State = {
 | 
				
			||||||
    IMPORTING: 1,
 | 
					    IMPORTING: 1,
 | 
				
			||||||
    COMPLETE: 2,
 | 
					    COMPLETE: 2,
 | 
				
			||||||
    LIGHT_COMPLETE: 3,
 | 
					    LIGHT_COMPLETE: 3,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var IMPORT_STARTED = 'importStarted';
 | 
					  const IMPORT_STARTED = 'importStarted';
 | 
				
			||||||
  var IMPORT_COMPLETE = 'importComplete';
 | 
					  const IMPORT_COMPLETE = 'importComplete';
 | 
				
			||||||
  var IMPORT_LOCATION = 'importLocation';
 | 
					  const IMPORT_LOCATION = 'importLocation';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Whisper.Import = {
 | 
					  Whisper.Import = {
 | 
				
			||||||
    isStarted: function() {
 | 
					    isStarted() {
 | 
				
			||||||
      return Boolean(storage.get(IMPORT_STARTED));
 | 
					      return Boolean(storage.get(IMPORT_STARTED));
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    isComplete: function() {
 | 
					    isComplete() {
 | 
				
			||||||
      return Boolean(storage.get(IMPORT_COMPLETE));
 | 
					      return Boolean(storage.get(IMPORT_COMPLETE));
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    isIncomplete: function() {
 | 
					    isIncomplete() {
 | 
				
			||||||
      return this.isStarted() && !this.isComplete();
 | 
					      return this.isStarted() && !this.isComplete();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    start: function() {
 | 
					    start() {
 | 
				
			||||||
      return storage.put(IMPORT_STARTED, true);
 | 
					      return storage.put(IMPORT_STARTED, true);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    complete: function() {
 | 
					    complete() {
 | 
				
			||||||
      return storage.put(IMPORT_COMPLETE, true);
 | 
					      return storage.put(IMPORT_COMPLETE, true);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    saveLocation: function(location) {
 | 
					    saveLocation(location) {
 | 
				
			||||||
      return storage.put(IMPORT_LOCATION, location);
 | 
					      return storage.put(IMPORT_LOCATION, location);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    reset: function() {
 | 
					    reset() {
 | 
				
			||||||
      return Whisper.Database.clear();
 | 
					      return Whisper.Database.clear();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
| 
						 | 
					@ -45,7 +51,7 @@
 | 
				
			||||||
      'click .cancel': 'onCancel',
 | 
					      'click .cancel': 'onCancel',
 | 
				
			||||||
      'click .register': 'onRegister',
 | 
					      'click .register': 'onRegister',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    initialize: function() {
 | 
					    initialize() {
 | 
				
			||||||
      if (Whisper.Import.isIncomplete()) {
 | 
					      if (Whisper.Import.isIncomplete()) {
 | 
				
			||||||
        this.error = true;
 | 
					        this.error = true;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
| 
						 | 
					@ -53,7 +59,7 @@
 | 
				
			||||||
      this.render();
 | 
					      this.render();
 | 
				
			||||||
      this.pending = Promise.resolve();
 | 
					      this.pending = Promise.resolve();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    render_attributes: function() {
 | 
					    render_attributes() {
 | 
				
			||||||
      if (this.error) {
 | 
					      if (this.error) {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
          isError: true,
 | 
					          isError: true,
 | 
				
			||||||
| 
						 | 
					@ -64,9 +70,9 @@
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var restartButton = i18n('importCompleteStartButton');
 | 
					      let restartButton = i18n('importCompleteStartButton');
 | 
				
			||||||
      var registerButton = i18n('importCompleteLinkButton');
 | 
					      let registerButton = i18n('importCompleteLinkButton');
 | 
				
			||||||
      var step = 'step2';
 | 
					      let step = 'step2';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (this.state === State.IMPORTING) {
 | 
					      if (this.state === State.IMPORTING) {
 | 
				
			||||||
        step = 'step3';
 | 
					        step = 'step3';
 | 
				
			||||||
| 
						 | 
					@ -89,22 +95,22 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        isStep4: step === 'step4',
 | 
					        isStep4: step === 'step4',
 | 
				
			||||||
        completeHeader: i18n('importCompleteHeader'),
 | 
					        completeHeader: i18n('importCompleteHeader'),
 | 
				
			||||||
        restartButton: restartButton,
 | 
					        restartButton,
 | 
				
			||||||
        registerButton: registerButton,
 | 
					        registerButton,
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    onRestart: function() {
 | 
					    onRestart() {
 | 
				
			||||||
      return window.restart();
 | 
					      return window.restart();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    onCancel: function() {
 | 
					    onCancel() {
 | 
				
			||||||
      this.trigger('cancel');
 | 
					      this.trigger('cancel');
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    onImport: function() {
 | 
					    onImport() {
 | 
				
			||||||
      window.Signal.Backup.getDirectoryForImport().then(
 | 
					      window.Signal.Backup.getDirectoryForImport().then(
 | 
				
			||||||
        function(directory) {
 | 
					        directory => {
 | 
				
			||||||
          this.doImport(directory);
 | 
					          this.doImport(directory);
 | 
				
			||||||
        }.bind(this),
 | 
					        },
 | 
				
			||||||
        function(error) {
 | 
					        error => {
 | 
				
			||||||
          if (error.name !== 'ChooseError') {
 | 
					          if (error.name !== 'ChooseError') {
 | 
				
			||||||
            console.log(
 | 
					            console.log(
 | 
				
			||||||
              'Error choosing directory:',
 | 
					              'Error choosing directory:',
 | 
				
			||||||
| 
						 | 
					@ -114,13 +120,13 @@
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    onRegister: function() {
 | 
					    onRegister() {
 | 
				
			||||||
      // AppView listens for this, and opens up InstallView to the QR code step to
 | 
					      // AppView listens for this, and opens up InstallView to the QR code step to
 | 
				
			||||||
      //   finish setting this device up.
 | 
					      //   finish setting this device up.
 | 
				
			||||||
      this.trigger('light-import');
 | 
					      this.trigger('light-import');
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    doImport: function(directory) {
 | 
					    doImport(directory) {
 | 
				
			||||||
      window.removeSetupMenuItems();
 | 
					      window.removeSetupMenuItems();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.error = null;
 | 
					      this.error = null;
 | 
				
			||||||
| 
						 | 
					@ -129,19 +135,18 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Wait for prior database interaction to complete
 | 
					      // Wait for prior database interaction to complete
 | 
				
			||||||
      this.pending = this.pending
 | 
					      this.pending = this.pending
 | 
				
			||||||
        .then(function() {
 | 
					        .then(() =>
 | 
				
			||||||
          // For resilience to interruption, clear database both before and on failure
 | 
					          // For resilience to interruption, clear database both before and on failure
 | 
				
			||||||
          return Whisper.Import.reset();
 | 
					          Whisper.Import.reset()
 | 
				
			||||||
        })
 | 
					        )
 | 
				
			||||||
        .then(function() {
 | 
					        .then(() =>
 | 
				
			||||||
          return Promise.all([
 | 
					          Promise.all([
 | 
				
			||||||
            Whisper.Import.start(),
 | 
					            Whisper.Import.start(),
 | 
				
			||||||
            window.Signal.Backup.importFromDirectory(directory),
 | 
					            window.Signal.Backup.importFromDirectory(directory),
 | 
				
			||||||
          ]);
 | 
					          ])
 | 
				
			||||||
        })
 | 
					        )
 | 
				
			||||||
        .then(
 | 
					        .then(results => {
 | 
				
			||||||
          function(results) {
 | 
					          const importResult = results[1];
 | 
				
			||||||
            var importResult = results[1];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
          // A full import changes so much we need a restart of the app
 | 
					          // A full import changes so much we need a restart of the app
 | 
				
			||||||
          if (importResult.fullImport) {
 | 
					          if (importResult.fullImport) {
 | 
				
			||||||
| 
						 | 
					@ -151,10 +156,8 @@
 | 
				
			||||||
          // A light import just brings in contacts, groups, and messages. And we need a
 | 
					          // A light import just brings in contacts, groups, and messages. And we need a
 | 
				
			||||||
          //   normal link to finish the process.
 | 
					          //   normal link to finish the process.
 | 
				
			||||||
          return this.finishLightImport(directory);
 | 
					          return this.finishLightImport(directory);
 | 
				
			||||||
          }.bind(this)
 | 
					        })
 | 
				
			||||||
        )
 | 
					        .catch(error => {
 | 
				
			||||||
        .catch(
 | 
					 | 
				
			||||||
          function(error) {
 | 
					 | 
				
			||||||
          console.log(
 | 
					          console.log(
 | 
				
			||||||
            'Error importing:',
 | 
					            'Error importing:',
 | 
				
			||||||
            error && error.stack ? error.stack : error
 | 
					            error && error.stack ? error.stack : error
 | 
				
			||||||
| 
						 | 
					@ -165,34 +168,31 @@
 | 
				
			||||||
          this.render();
 | 
					          this.render();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          return Whisper.Import.reset();
 | 
					          return Whisper.Import.reset();
 | 
				
			||||||
          }.bind(this)
 | 
					        });
 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    finishLightImport: function(directory) {
 | 
					    finishLightImport(directory) {
 | 
				
			||||||
      ConversationController.reset();
 | 
					      ConversationController.reset();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return ConversationController.load()
 | 
					      return ConversationController.load()
 | 
				
			||||||
        .then(function() {
 | 
					        .then(() =>
 | 
				
			||||||
          return Promise.all([
 | 
					          Promise.all([
 | 
				
			||||||
            Whisper.Import.saveLocation(directory),
 | 
					            Whisper.Import.saveLocation(directory),
 | 
				
			||||||
            Whisper.Import.complete(),
 | 
					            Whisper.Import.complete(),
 | 
				
			||||||
          ]);
 | 
					          ])
 | 
				
			||||||
        })
 | 
					        )
 | 
				
			||||||
        .then(
 | 
					        .then(() => {
 | 
				
			||||||
          function() {
 | 
					 | 
				
			||||||
          this.state = State.LIGHT_COMPLETE;
 | 
					          this.state = State.LIGHT_COMPLETE;
 | 
				
			||||||
          this.render();
 | 
					          this.render();
 | 
				
			||||||
          }.bind(this)
 | 
					        });
 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    finishFullImport: function(directory) {
 | 
					    finishFullImport(directory) {
 | 
				
			||||||
      // Catching in-memory cache up with what's in indexeddb now...
 | 
					      // Catching in-memory cache up with what's in indexeddb now...
 | 
				
			||||||
      // NOTE: this fires storage.onready, listened to across the app. We'll restart
 | 
					      // 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.
 | 
					      //   to complete the install to start up cleanly with everything now in the DB.
 | 
				
			||||||
      return storage
 | 
					      return storage
 | 
				
			||||||
        .fetch()
 | 
					        .fetch()
 | 
				
			||||||
        .then(function() {
 | 
					        .then(() =>
 | 
				
			||||||
          return Promise.all([
 | 
					          Promise.all([
 | 
				
			||||||
            // Clearing any migration-related state inherited from the Chrome App
 | 
					            // Clearing any migration-related state inherited from the Chrome App
 | 
				
			||||||
            storage.remove('migrationState'),
 | 
					            storage.remove('migrationState'),
 | 
				
			||||||
            storage.remove('migrationEnabled'),
 | 
					            storage.remove('migrationEnabled'),
 | 
				
			||||||
| 
						 | 
					@ -201,14 +201,12 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Whisper.Import.saveLocation(directory),
 | 
					            Whisper.Import.saveLocation(directory),
 | 
				
			||||||
            Whisper.Import.complete(),
 | 
					            Whisper.Import.complete(),
 | 
				
			||||||
          ]);
 | 
					          ])
 | 
				
			||||||
        })
 | 
					        )
 | 
				
			||||||
        .then(
 | 
					        .then(() => {
 | 
				
			||||||
          function() {
 | 
					 | 
				
			||||||
          this.state = State.COMPLETE;
 | 
					          this.state = State.COMPLETE;
 | 
				
			||||||
          this.render();
 | 
					          this.render();
 | 
				
			||||||
          }.bind(this)
 | 
					        });
 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
})();
 | 
					})();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,14 @@
 | 
				
			||||||
 | 
					/* global Whisper, i18n, getAccountManager, $, textsecure, QRCode */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* eslint-disable more/no-then */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var Steps = {
 | 
					  const Steps = {
 | 
				
			||||||
    INSTALL_SIGNAL: 2,
 | 
					    INSTALL_SIGNAL: 2,
 | 
				
			||||||
    SCAN_QR_CODE: 3,
 | 
					    SCAN_QR_CODE: 3,
 | 
				
			||||||
    ENTER_NAME: 4,
 | 
					    ENTER_NAME: 4,
 | 
				
			||||||
| 
						 | 
					@ -11,9 +17,9 @@
 | 
				
			||||||
    NETWORK_ERROR: 'NetworkError',
 | 
					    NETWORK_ERROR: 'NetworkError',
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var DEVICE_NAME_SELECTOR = 'input.device-name';
 | 
					  const DEVICE_NAME_SELECTOR = 'input.device-name';
 | 
				
			||||||
  var CONNECTION_ERROR = -1;
 | 
					  const CONNECTION_ERROR = -1;
 | 
				
			||||||
  var TOO_MANY_DEVICES = 411;
 | 
					  const TOO_MANY_DEVICES = 411;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Whisper.InstallView = Whisper.View.extend({
 | 
					  Whisper.InstallView = Whisper.View.extend({
 | 
				
			||||||
    templateName: 'link-flow-template',
 | 
					    templateName: 'link-flow-template',
 | 
				
			||||||
| 
						 | 
					@ -23,9 +29,7 @@
 | 
				
			||||||
      'click .finish': 'finishLinking',
 | 
					      'click .finish': 'finishLinking',
 | 
				
			||||||
      // the actual next step happens in confirmNumber() on submit form #link-phone
 | 
					      // the actual next step happens in confirmNumber() on submit form #link-phone
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    initialize: function(options) {
 | 
					    initialize(options = {}) {
 | 
				
			||||||
      options = options || {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      this.selectStep(Steps.SCAN_QR_CODE);
 | 
					      this.selectStep(Steps.SCAN_QR_CODE);
 | 
				
			||||||
      this.connect();
 | 
					      this.connect();
 | 
				
			||||||
      this.on('disconnected', this.reconnect);
 | 
					      this.on('disconnected', this.reconnect);
 | 
				
			||||||
| 
						 | 
					@ -34,18 +38,18 @@
 | 
				
			||||||
      this.shouldRetainData =
 | 
					      this.shouldRetainData =
 | 
				
			||||||
        Whisper.Registration.everDone() || options.hasExistingData;
 | 
					        Whisper.Registration.everDone() || options.hasExistingData;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    render_attributes: function() {
 | 
					    render_attributes() {
 | 
				
			||||||
      var errorMessage;
 | 
					      let errorMessage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (this.error) {
 | 
					      if (this.error) {
 | 
				
			||||||
        if (
 | 
					        if (
 | 
				
			||||||
          this.error.name === 'HTTPError' &&
 | 
					          this.error.name === 'HTTPError' &&
 | 
				
			||||||
          this.error.code == TOO_MANY_DEVICES
 | 
					          this.error.code === TOO_MANY_DEVICES
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
          errorMessage = i18n('installTooManyDevices');
 | 
					          errorMessage = i18n('installTooManyDevices');
 | 
				
			||||||
        } else if (
 | 
					        } else if (
 | 
				
			||||||
          this.error.name === 'HTTPError' &&
 | 
					          this.error.name === 'HTTPError' &&
 | 
				
			||||||
          this.error.code == CONNECTION_ERROR
 | 
					          this.error.code === CONNECTION_ERROR
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
          errorMessage = i18n('installConnectionFailed');
 | 
					          errorMessage = i18n('installConnectionFailed');
 | 
				
			||||||
        } else if (this.error.message === 'websocket closed') {
 | 
					        } else if (this.error.message === 'websocket closed') {
 | 
				
			||||||
| 
						 | 
					@ -78,11 +82,11 @@
 | 
				
			||||||
        syncing: i18n('initialSync'),
 | 
					        syncing: i18n('initialSync'),
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    selectStep: function(step) {
 | 
					    selectStep(step) {
 | 
				
			||||||
      this.step = step;
 | 
					      this.step = step;
 | 
				
			||||||
      this.render();
 | 
					      this.render();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    connect: function() {
 | 
					    connect() {
 | 
				
			||||||
      this.error = null;
 | 
					      this.error = null;
 | 
				
			||||||
      this.selectStep(Steps.SCAN_QR_CODE);
 | 
					      this.selectStep(Steps.SCAN_QR_CODE);
 | 
				
			||||||
      this.clearQR();
 | 
					      this.clearQR();
 | 
				
			||||||
| 
						 | 
					@ -91,7 +95,7 @@
 | 
				
			||||||
        this.timeout = null;
 | 
					        this.timeout = null;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var accountManager = getAccountManager();
 | 
					      const accountManager = getAccountManager();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      accountManager
 | 
					      accountManager
 | 
				
			||||||
        .registerSecondDevice(
 | 
					        .registerSecondDevice(
 | 
				
			||||||
| 
						 | 
					@ -100,7 +104,7 @@
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .catch(this.handleDisconnect.bind(this));
 | 
					        .catch(this.handleDisconnect.bind(this));
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    handleDisconnect: function(e) {
 | 
					    handleDisconnect(e) {
 | 
				
			||||||
      console.log('provisioning failed', e.stack);
 | 
					      console.log('provisioning failed', e.stack);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.error = e;
 | 
					      this.error = e;
 | 
				
			||||||
| 
						 | 
					@ -115,20 +119,20 @@
 | 
				
			||||||
        throw e;
 | 
					        throw e;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    reconnect: function() {
 | 
					    reconnect() {
 | 
				
			||||||
      if (this.timeout) {
 | 
					      if (this.timeout) {
 | 
				
			||||||
        clearTimeout(this.timeout);
 | 
					        clearTimeout(this.timeout);
 | 
				
			||||||
        this.timeout = null;
 | 
					        this.timeout = null;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this.timeout = setTimeout(this.connect.bind(this), 10000);
 | 
					      this.timeout = setTimeout(this.connect.bind(this), 10000);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    clearQR: function() {
 | 
					    clearQR() {
 | 
				
			||||||
      this.$('#qr img').remove();
 | 
					      this.$('#qr img').remove();
 | 
				
			||||||
      this.$('#qr canvas').remove();
 | 
					      this.$('#qr canvas').remove();
 | 
				
			||||||
      this.$('#qr .container').show();
 | 
					      this.$('#qr .container').show();
 | 
				
			||||||
      this.$('#qr').removeClass('ready');
 | 
					      this.$('#qr').removeClass('ready');
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    setProvisioningUrl: function(url) {
 | 
					    setProvisioningUrl(url) {
 | 
				
			||||||
      if ($('#qr').length === 0) {
 | 
					      if ($('#qr').length === 0) {
 | 
				
			||||||
        console.log('Did not find #qr element in the DOM!');
 | 
					        console.log('Did not find #qr element in the DOM!');
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
| 
						 | 
					@ -139,42 +143,38 @@
 | 
				
			||||||
      this.$('#qr').removeAttr('title');
 | 
					      this.$('#qr').removeAttr('title');
 | 
				
			||||||
      this.$('#qr').addClass('ready');
 | 
					      this.$('#qr').addClass('ready');
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    setDeviceNameDefault: function() {
 | 
					    setDeviceNameDefault() {
 | 
				
			||||||
      var deviceName = textsecure.storage.user.getDeviceName();
 | 
					      const deviceName = textsecure.storage.user.getDeviceName();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.$(DEVICE_NAME_SELECTOR).val(deviceName || window.getHostName());
 | 
					      this.$(DEVICE_NAME_SELECTOR).val(deviceName || window.getHostName());
 | 
				
			||||||
      this.$(DEVICE_NAME_SELECTOR).focus();
 | 
					      this.$(DEVICE_NAME_SELECTOR).focus();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    finishLinking: function() {
 | 
					    finishLinking() {
 | 
				
			||||||
      // We use a form so we get submit-on-enter behavior
 | 
					      // We use a form so we get submit-on-enter behavior
 | 
				
			||||||
      this.$('#link-phone').submit();
 | 
					      this.$('#link-phone').submit();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    confirmNumber: function(number) {
 | 
					    confirmNumber() {
 | 
				
			||||||
      var tsp = textsecure.storage.protocol;
 | 
					      const tsp = textsecure.storage.protocol;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      window.removeSetupMenuItems();
 | 
					      window.removeSetupMenuItems();
 | 
				
			||||||
      this.selectStep(Steps.ENTER_NAME);
 | 
					      this.selectStep(Steps.ENTER_NAME);
 | 
				
			||||||
      this.setDeviceNameDefault();
 | 
					      this.setDeviceNameDefault();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return new Promise(
 | 
					      return new Promise(resolve => {
 | 
				
			||||||
        function(resolve, reject) {
 | 
					        this.$('#link-phone').submit(e => {
 | 
				
			||||||
          this.$('#link-phone').submit(
 | 
					 | 
				
			||||||
            function(e) {
 | 
					 | 
				
			||||||
          e.stopPropagation();
 | 
					          e.stopPropagation();
 | 
				
			||||||
          e.preventDefault();
 | 
					          e.preventDefault();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              var name = this.$(DEVICE_NAME_SELECTOR).val();
 | 
					          let name = this.$(DEVICE_NAME_SELECTOR).val();
 | 
				
			||||||
          name = name.replace(/\0/g, ''); // strip unicode null
 | 
					          name = name.replace(/\0/g, ''); // strip unicode null
 | 
				
			||||||
          if (name.trim().length === 0) {
 | 
					          if (name.trim().length === 0) {
 | 
				
			||||||
            this.$(DEVICE_NAME_SELECTOR).focus();
 | 
					            this.$(DEVICE_NAME_SELECTOR).focus();
 | 
				
			||||||
                return;
 | 
					            return null;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          this.selectStep(Steps.PROGRESS_BAR);
 | 
					          this.selectStep(Steps.PROGRESS_BAR);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              var finish = function() {
 | 
					          const finish = () => resolve(name);
 | 
				
			||||||
                resolve(name);
 | 
					 | 
				
			||||||
              };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
          // Delete all data from database unless we're in the middle
 | 
					          // Delete all data from database unless we're in the middle
 | 
				
			||||||
          //   of a re-link, or we are finishing a light import. Without this,
 | 
					          //   of a re-link, or we are finishing a light import. Without this,
 | 
				
			||||||
| 
						 | 
					@ -185,17 +185,15 @@
 | 
				
			||||||
            return finish();
 | 
					            return finish();
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              tsp.removeAllData().then(finish, function(error) {
 | 
					          return tsp.removeAllData().then(finish, error => {
 | 
				
			||||||
            console.log(
 | 
					            console.log(
 | 
				
			||||||
              'confirmNumber: error clearing database',
 | 
					              'confirmNumber: error clearing database',
 | 
				
			||||||
              error && error.stack ? error.stack : error
 | 
					              error && error.stack ? error.stack : error
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
            finish();
 | 
					            finish();
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
            }.bind(this)
 | 
					        });
 | 
				
			||||||
          );
 | 
					      });
 | 
				
			||||||
        }.bind(this)
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
})();
 | 
					})();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,11 @@
 | 
				
			||||||
 | 
					/* global Whisper, textsecure, QRCode, dcodeIO, libsignal, i18n, _ */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* eslint-disable more/no-then */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Whisper.KeyVerificationPanelView = Whisper.View.extend({
 | 
					  Whisper.KeyVerificationPanelView = Whisper.View.extend({
 | 
				
			||||||
| 
						 | 
					@ -8,58 +14,54 @@
 | 
				
			||||||
    events: {
 | 
					    events: {
 | 
				
			||||||
      'click button.verify': 'toggleVerified',
 | 
					      'click button.verify': 'toggleVerified',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    initialize: function(options) {
 | 
					    initialize(options) {
 | 
				
			||||||
      this.ourNumber = textsecure.storage.user.getNumber();
 | 
					      this.ourNumber = textsecure.storage.user.getNumber();
 | 
				
			||||||
      if (options.newKey) {
 | 
					      if (options.newKey) {
 | 
				
			||||||
        this.theirKey = options.newKey;
 | 
					        this.theirKey = options.newKey;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.loadKeys().then(
 | 
					      this.loadKeys().then(() => {
 | 
				
			||||||
        function() {
 | 
					 | 
				
			||||||
        this.listenTo(this.model, 'change', this.render);
 | 
					        this.listenTo(this.model, 'change', this.render);
 | 
				
			||||||
        }.bind(this)
 | 
					      });
 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    loadKeys: function() {
 | 
					    loadKeys() {
 | 
				
			||||||
      return Promise.all([this.loadTheirKey(), this.loadOurKey()])
 | 
					      return Promise.all([this.loadTheirKey(), this.loadOurKey()])
 | 
				
			||||||
        .then(this.generateSecurityNumber.bind(this))
 | 
					        .then(this.generateSecurityNumber.bind(this))
 | 
				
			||||||
        .then(this.render.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
 | 
					      // Per Lilia: We can't turn this on until it generates a Latin1 string, as is
 | 
				
			||||||
      //   required by the mobile clients.
 | 
					      //   required by the mobile clients.
 | 
				
			||||||
      new QRCode(this.$('.qr')[0]).makeCode(
 | 
					      new QRCode(this.$('.qr')[0]).makeCode(
 | 
				
			||||||
        dcodeIO.ByteBuffer.wrap(this.ourKey).toString('base64')
 | 
					        dcodeIO.ByteBuffer.wrap(this.ourKey).toString('base64')
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    loadTheirKey: function() {
 | 
					    loadTheirKey() {
 | 
				
			||||||
      return textsecure.storage.protocol.loadIdentityKey(this.model.id).then(
 | 
					      return textsecure.storage.protocol
 | 
				
			||||||
        function(theirKey) {
 | 
					        .loadIdentityKey(this.model.id)
 | 
				
			||||||
 | 
					        .then(theirKey => {
 | 
				
			||||||
          this.theirKey = theirKey;
 | 
					          this.theirKey = theirKey;
 | 
				
			||||||
        }.bind(this)
 | 
					        });
 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    loadOurKey: function() {
 | 
					    loadOurKey() {
 | 
				
			||||||
      return textsecure.storage.protocol.loadIdentityKey(this.ourNumber).then(
 | 
					      return textsecure.storage.protocol
 | 
				
			||||||
        function(ourKey) {
 | 
					        .loadIdentityKey(this.ourNumber)
 | 
				
			||||||
 | 
					        .then(ourKey => {
 | 
				
			||||||
          this.ourKey = ourKey;
 | 
					          this.ourKey = ourKey;
 | 
				
			||||||
        }.bind(this)
 | 
					        });
 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    generateSecurityNumber: function() {
 | 
					    generateSecurityNumber() {
 | 
				
			||||||
      return new libsignal.FingerprintGenerator(5200)
 | 
					      return new libsignal.FingerprintGenerator(5200)
 | 
				
			||||||
        .createFor(this.ourNumber, this.ourKey, this.model.id, this.theirKey)
 | 
					        .createFor(this.ourNumber, this.ourKey, this.model.id, this.theirKey)
 | 
				
			||||||
        .then(
 | 
					        .then(securityNumber => {
 | 
				
			||||||
          function(securityNumber) {
 | 
					 | 
				
			||||||
          this.securityNumber = securityNumber;
 | 
					          this.securityNumber = securityNumber;
 | 
				
			||||||
          }.bind(this)
 | 
					        });
 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    onSafetyNumberChanged: function() {
 | 
					    onSafetyNumberChanged() {
 | 
				
			||||||
      this.model.getProfiles().then(this.loadKeys.bind(this));
 | 
					      this.model.getProfiles().then(this.loadKeys.bind(this));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var dialog = new Whisper.ConfirmationDialogView({
 | 
					      const dialog = new Whisper.ConfirmationDialogView({
 | 
				
			||||||
        message: i18n('changedRightAfterVerify', [
 | 
					        message: i18n('changedRightAfterVerify', [
 | 
				
			||||||
          this.model.getTitle(),
 | 
					          this.model.getTitle(),
 | 
				
			||||||
          this.model.getTitle(),
 | 
					          this.model.getTitle(),
 | 
				
			||||||
| 
						 | 
					@ -70,12 +72,11 @@
 | 
				
			||||||
      dialog.$el.insertBefore(this.el);
 | 
					      dialog.$el.insertBefore(this.el);
 | 
				
			||||||
      dialog.focusCancel();
 | 
					      dialog.focusCancel();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    toggleVerified: function() {
 | 
					    toggleVerified() {
 | 
				
			||||||
      this.$('button.verify').attr('disabled', true);
 | 
					      this.$('button.verify').attr('disabled', true);
 | 
				
			||||||
      this.model
 | 
					      this.model
 | 
				
			||||||
        .toggleVerified()
 | 
					        .toggleVerified()
 | 
				
			||||||
        .catch(
 | 
					        .catch(result => {
 | 
				
			||||||
          function(result) {
 | 
					 | 
				
			||||||
          if (result instanceof Error) {
 | 
					          if (result instanceof Error) {
 | 
				
			||||||
            if (result.name === 'OutgoingIdentityKeyError') {
 | 
					            if (result.name === 'OutgoingIdentityKeyError') {
 | 
				
			||||||
              this.onSafetyNumberChanged();
 | 
					              this.onSafetyNumberChanged();
 | 
				
			||||||
| 
						 | 
					@ -83,52 +84,50 @@
 | 
				
			||||||
              console.log('failed to toggle verified:', result.stack);
 | 
					              console.log('failed to toggle verified:', result.stack);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
              var keyError = _.some(result.errors, function(error) {
 | 
					            const keyError = _.some(
 | 
				
			||||||
                return error.name === 'OutgoingIdentityKeyError';
 | 
					              result.errors,
 | 
				
			||||||
              });
 | 
					              error => error.name === 'OutgoingIdentityKeyError'
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
            if (keyError) {
 | 
					            if (keyError) {
 | 
				
			||||||
              this.onSafetyNumberChanged();
 | 
					              this.onSafetyNumberChanged();
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                _.forEach(result.errors, function(error) {
 | 
					              _.forEach(result.errors, error => {
 | 
				
			||||||
                console.log('failed to toggle verified:', error.stack);
 | 
					                console.log('failed to toggle verified:', error.stack);
 | 
				
			||||||
              });
 | 
					              });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          }.bind(this)
 | 
					        })
 | 
				
			||||||
        )
 | 
					        .then(() => {
 | 
				
			||||||
        .then(
 | 
					 | 
				
			||||||
          function() {
 | 
					 | 
				
			||||||
          this.$('button.verify').removeAttr('disabled');
 | 
					          this.$('button.verify').removeAttr('disabled');
 | 
				
			||||||
          }.bind(this)
 | 
					        });
 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    render_attributes: function() {
 | 
					    render_attributes() {
 | 
				
			||||||
      var s = this.securityNumber;
 | 
					      const s = this.securityNumber;
 | 
				
			||||||
      var chunks = [];
 | 
					      const chunks = [];
 | 
				
			||||||
      for (var i = 0; i < s.length; i += 5) {
 | 
					      for (let i = 0; i < s.length; i += 5) {
 | 
				
			||||||
        chunks.push(s.substring(i, i + 5));
 | 
					        chunks.push(s.substring(i, i + 5));
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      var name = this.model.getTitle();
 | 
					      const name = this.model.getTitle();
 | 
				
			||||||
      var yourSafetyNumberWith = i18n('yourSafetyNumberWith', name);
 | 
					      const yourSafetyNumberWith = i18n(
 | 
				
			||||||
      var isVerified = this.model.isVerified();
 | 
					        'yourSafetyNumberWith',
 | 
				
			||||||
      var verifyButton = isVerified ? i18n('unverify') : i18n('verify');
 | 
					        this.model.getTitle()
 | 
				
			||||||
      var verifiedStatus = isVerified
 | 
					      );
 | 
				
			||||||
 | 
					      const isVerified = this.model.isVerified();
 | 
				
			||||||
 | 
					      const verifyButton = isVerified ? i18n('unverify') : i18n('verify');
 | 
				
			||||||
 | 
					      const verifiedStatus = isVerified
 | 
				
			||||||
        ? i18n('isVerified', name)
 | 
					        ? i18n('isVerified', name)
 | 
				
			||||||
        : i18n('isNotVerified', name);
 | 
					        : i18n('isNotVerified', name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        learnMore: i18n('learnMore'),
 | 
					        learnMore: i18n('learnMore'),
 | 
				
			||||||
        theirKeyUnknown: i18n('theirIdentityUnknown'),
 | 
					        theirKeyUnknown: i18n('theirIdentityUnknown'),
 | 
				
			||||||
        yourSafetyNumberWith: i18n(
 | 
					        yourSafetyNumberWith,
 | 
				
			||||||
          'yourSafetyNumberWith',
 | 
					 | 
				
			||||||
          this.model.getTitle()
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        verifyHelp: i18n('verifyHelp', this.model.getTitle()),
 | 
					        verifyHelp: i18n('verifyHelp', this.model.getTitle()),
 | 
				
			||||||
        verifyButton: verifyButton,
 | 
					        verifyButton,
 | 
				
			||||||
        hasTheirKey: this.theirKey !== undefined,
 | 
					        hasTheirKey: this.theirKey !== undefined,
 | 
				
			||||||
        chunks: chunks,
 | 
					        chunks,
 | 
				
			||||||
        isVerified: isVerified,
 | 
					        isVerified,
 | 
				
			||||||
        verifiedStatus: verifiedStatus,
 | 
					        verifiedStatus,
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,34 +1,35 @@
 | 
				
			||||||
 | 
					/* global Whisper, i18n */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var FIVE_SECONDS = 5 * 1000;
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Whisper.LastSeenIndicatorView = Whisper.View.extend({
 | 
					  Whisper.LastSeenIndicatorView = Whisper.View.extend({
 | 
				
			||||||
    className: 'last-seen-indicator-view',
 | 
					    className: 'last-seen-indicator-view',
 | 
				
			||||||
    templateName: 'last-seen-indicator-view',
 | 
					    templateName: 'last-seen-indicator-view',
 | 
				
			||||||
    initialize: function(options) {
 | 
					    initialize(options = {}) {
 | 
				
			||||||
      options = options || {};
 | 
					 | 
				
			||||||
      this.count = options.count || 0;
 | 
					      this.count = options.count || 0;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    increment: function(count) {
 | 
					    increment(count) {
 | 
				
			||||||
      this.count += count;
 | 
					      this.count += count;
 | 
				
			||||||
      this.render();
 | 
					      this.render();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getCount: function() {
 | 
					    getCount() {
 | 
				
			||||||
      return this.count;
 | 
					      return this.count;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    render_attributes: function() {
 | 
					    render_attributes() {
 | 
				
			||||||
      var unreadMessages =
 | 
					      const unreadMessages =
 | 
				
			||||||
        this.count === 1
 | 
					        this.count === 1
 | 
				
			||||||
          ? i18n('unreadMessage')
 | 
					          ? i18n('unreadMessage')
 | 
				
			||||||
          : i18n('unreadMessages', [this.count]);
 | 
					          : i18n('unreadMessages', [this.count]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        unreadMessages: unreadMessages,
 | 
					        unreadMessages,
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,9 @@
 | 
				
			||||||
 | 
					/* global Backbone, Whisper, _ */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /*
 | 
					  /*
 | 
				
			||||||
| 
						 | 
					@ -9,27 +13,28 @@
 | 
				
			||||||
  Whisper.ListView = Backbone.View.extend({
 | 
					  Whisper.ListView = Backbone.View.extend({
 | 
				
			||||||
    tagName: 'ul',
 | 
					    tagName: 'ul',
 | 
				
			||||||
    itemView: Backbone.View,
 | 
					    itemView: Backbone.View,
 | 
				
			||||||
    initialize: function(options) {
 | 
					    initialize(options) {
 | 
				
			||||||
      this.options = options || {};
 | 
					      this.options = options || {};
 | 
				
			||||||
      this.listenTo(this.collection, 'add', this.addOne);
 | 
					      this.listenTo(this.collection, 'add', this.addOne);
 | 
				
			||||||
      this.listenTo(this.collection, 'reset', this.addAll);
 | 
					      this.listenTo(this.collection, 'reset', this.addAll);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    addOne: function(model) {
 | 
					    addOne(model) {
 | 
				
			||||||
      if (this.itemView) {
 | 
					      if (this.itemView) {
 | 
				
			||||||
        var options = _.extend({}, this.options.toInclude, { model: model });
 | 
					        const options = _.extend({}, this.options.toInclude, { model });
 | 
				
			||||||
        var view = new this.itemView(options);
 | 
					        // eslint-disable-next-line new-cap
 | 
				
			||||||
 | 
					        const view = new this.itemView(options);
 | 
				
			||||||
        this.$el.append(view.render().el);
 | 
					        this.$el.append(view.render().el);
 | 
				
			||||||
        this.$el.trigger('add');
 | 
					        this.$el.trigger('add');
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    addAll: function() {
 | 
					    addAll() {
 | 
				
			||||||
      this.$el.html('');
 | 
					      this.$el.html('');
 | 
				
			||||||
      this.collection.each(this.addOne, this);
 | 
					      this.collection.each(this.addOne, this);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    render: function() {
 | 
					    render() {
 | 
				
			||||||
      this.addAll();
 | 
					      this.addAll();
 | 
				
			||||||
      return this;
 | 
					      return this;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,32 +1,40 @@
 | 
				
			||||||
 | 
					/* global Whisper, i18n, _, ConversationController, Mustache, moment */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* eslint-disable more/no-then */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var ContactView = Whisper.View.extend({
 | 
					  const ContactView = Whisper.View.extend({
 | 
				
			||||||
    className: 'contact-detail',
 | 
					    className: 'contact-detail',
 | 
				
			||||||
    templateName: 'contact-detail',
 | 
					    templateName: 'contact-detail',
 | 
				
			||||||
    initialize: function(options) {
 | 
					    initialize(options) {
 | 
				
			||||||
      this.listenBack = options.listenBack;
 | 
					      this.listenBack = options.listenBack;
 | 
				
			||||||
      this.resetPanel = options.resetPanel;
 | 
					      this.resetPanel = options.resetPanel;
 | 
				
			||||||
      this.message = options.message;
 | 
					      this.message = options.message;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var newIdentity = i18n('newIdentity');
 | 
					      const newIdentity = i18n('newIdentity');
 | 
				
			||||||
      this.errors = _.map(options.errors, function(error) {
 | 
					      this.errors = _.map(options.errors, error => {
 | 
				
			||||||
        if (error.name === 'OutgoingIdentityKeyError') {
 | 
					        if (error.name === 'OutgoingIdentityKeyError') {
 | 
				
			||||||
 | 
					          // eslint-disable-next-line no-param-reassign
 | 
				
			||||||
          error.message = newIdentity;
 | 
					          error.message = newIdentity;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return error;
 | 
					        return error;
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      this.outgoingKeyError = _.find(this.errors, function(error) {
 | 
					      this.outgoingKeyError = _.find(
 | 
				
			||||||
        return error.name === 'OutgoingIdentityKeyError';
 | 
					        this.errors,
 | 
				
			||||||
      });
 | 
					        error => error.name === 'OutgoingIdentityKeyError'
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    events: {
 | 
					    events: {
 | 
				
			||||||
      click: 'onClick',
 | 
					      click: 'onClick',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    onClick: function() {
 | 
					    onClick() {
 | 
				
			||||||
      if (this.outgoingKeyError) {
 | 
					      if (this.outgoingKeyError) {
 | 
				
			||||||
        var view = new Whisper.IdentityKeySendErrorPanelView({
 | 
					        const view = new Whisper.IdentityKeySendErrorPanelView({
 | 
				
			||||||
          model: this.model,
 | 
					          model: this.model,
 | 
				
			||||||
          listenBack: this.listenBack,
 | 
					          listenBack: this.listenBack,
 | 
				
			||||||
          resetPanel: this.resetPanel,
 | 
					          resetPanel: this.resetPanel,
 | 
				
			||||||
| 
						 | 
					@ -40,41 +48,33 @@
 | 
				
			||||||
        view.$('.cancel').focus();
 | 
					        view.$('.cancel').focus();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    forceSend: function() {
 | 
					    forceSend() {
 | 
				
			||||||
      this.model
 | 
					      this.model
 | 
				
			||||||
        .updateVerified()
 | 
					        .updateVerified()
 | 
				
			||||||
        .then(
 | 
					        .then(() => {
 | 
				
			||||||
          function() {
 | 
					 | 
				
			||||||
          if (this.model.isUnverified()) {
 | 
					          if (this.model.isUnverified()) {
 | 
				
			||||||
            return this.model.setVerifiedDefault();
 | 
					            return this.model.setVerifiedDefault();
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          }.bind(this)
 | 
					          return null;
 | 
				
			||||||
        )
 | 
					        })
 | 
				
			||||||
        .then(
 | 
					        .then(() => this.model.isUntrusted())
 | 
				
			||||||
          function() {
 | 
					        .then(untrusted => {
 | 
				
			||||||
            return this.model.isUntrusted();
 | 
					 | 
				
			||||||
          }.bind(this)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        .then(
 | 
					 | 
				
			||||||
          function(untrusted) {
 | 
					 | 
				
			||||||
          if (untrusted) {
 | 
					          if (untrusted) {
 | 
				
			||||||
            return this.model.setApproved();
 | 
					            return this.model.setApproved();
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          }.bind(this)
 | 
					          return null;
 | 
				
			||||||
        )
 | 
					        })
 | 
				
			||||||
        .then(
 | 
					        .then(() => {
 | 
				
			||||||
          function() {
 | 
					 | 
				
			||||||
          this.message.resend(this.outgoingKeyError.number);
 | 
					          this.message.resend(this.outgoingKeyError.number);
 | 
				
			||||||
          }.bind(this)
 | 
					        });
 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    onSendAnyway: function() {
 | 
					    onSendAnyway() {
 | 
				
			||||||
      if (this.outgoingKeyError) {
 | 
					      if (this.outgoingKeyError) {
 | 
				
			||||||
        this.forceSend();
 | 
					        this.forceSend();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    render_attributes: function() {
 | 
					    render_attributes() {
 | 
				
			||||||
      var showButton = Boolean(this.outgoingKeyError);
 | 
					      const showButton = Boolean(this.outgoingKeyError);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        status: this.message.getStatus(this.model.id),
 | 
					        status: this.message.getStatus(this.model.id),
 | 
				
			||||||
| 
						 | 
					@ -90,7 +90,7 @@
 | 
				
			||||||
  Whisper.MessageDetailView = Whisper.View.extend({
 | 
					  Whisper.MessageDetailView = Whisper.View.extend({
 | 
				
			||||||
    className: 'message-detail panel',
 | 
					    className: 'message-detail panel',
 | 
				
			||||||
    templateName: 'message-detail',
 | 
					    templateName: 'message-detail',
 | 
				
			||||||
    initialize: function(options) {
 | 
					    initialize(options) {
 | 
				
			||||||
      this.listenBack = options.listenBack;
 | 
					      this.listenBack = options.listenBack;
 | 
				
			||||||
      this.resetPanel = options.resetPanel;
 | 
					      this.resetPanel = options.resetPanel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -103,22 +103,22 @@
 | 
				
			||||||
    events: {
 | 
					    events: {
 | 
				
			||||||
      'click button.delete': 'onDelete',
 | 
					      'click button.delete': 'onDelete',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    onDelete: function() {
 | 
					    onDelete() {
 | 
				
			||||||
      var dialog = new Whisper.ConfirmationDialogView({
 | 
					      const dialog = new Whisper.ConfirmationDialogView({
 | 
				
			||||||
        message: i18n('deleteWarning'),
 | 
					        message: i18n('deleteWarning'),
 | 
				
			||||||
        okText: i18n('delete'),
 | 
					        okText: i18n('delete'),
 | 
				
			||||||
        resolve: function() {
 | 
					        resolve: () => {
 | 
				
			||||||
          this.model.destroy();
 | 
					          this.model.destroy();
 | 
				
			||||||
          this.resetPanel();
 | 
					          this.resetPanel();
 | 
				
			||||||
        }.bind(this),
 | 
					        },
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.$el.prepend(dialog.el);
 | 
					      this.$el.prepend(dialog.el);
 | 
				
			||||||
      dialog.focusCancel();
 | 
					      dialog.focusCancel();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    getContacts: function() {
 | 
					    getContacts() {
 | 
				
			||||||
      // Return the set of models to be rendered in this view
 | 
					      // Return the set of models to be rendered in this view
 | 
				
			||||||
      var ids;
 | 
					      let ids;
 | 
				
			||||||
      if (this.model.isIncoming()) {
 | 
					      if (this.model.isIncoming()) {
 | 
				
			||||||
        ids = [this.model.get('source')];
 | 
					        ids = [this.model.get('source')];
 | 
				
			||||||
      } else if (this.model.isOutgoing()) {
 | 
					      } else if (this.model.isOutgoing()) {
 | 
				
			||||||
| 
						 | 
					@ -130,13 +130,13 @@
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return Promise.all(
 | 
					      return Promise.all(
 | 
				
			||||||
        ids.map(function(number) {
 | 
					        ids.map(number =>
 | 
				
			||||||
          return ConversationController.getOrCreateAndWait(number, 'private');
 | 
					          ConversationController.getOrCreateAndWait(number, 'private')
 | 
				
			||||||
        })
 | 
					        )
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    renderContact: function(contact) {
 | 
					    renderContact(contact) {
 | 
				
			||||||
      var view = new ContactView({
 | 
					      const view = new ContactView({
 | 
				
			||||||
        model: contact,
 | 
					        model: contact,
 | 
				
			||||||
        errors: this.grouped[contact.id],
 | 
					        errors: this.grouped[contact.id],
 | 
				
			||||||
        listenBack: this.listenBack,
 | 
					        listenBack: this.listenBack,
 | 
				
			||||||
| 
						 | 
					@ -145,12 +145,10 @@
 | 
				
			||||||
      }).render();
 | 
					      }).render();
 | 
				
			||||||
      this.$('.contacts').append(view.el);
 | 
					      this.$('.contacts').append(view.el);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    render: function() {
 | 
					    render() {
 | 
				
			||||||
      var errorsWithoutNumber = _.reject(this.model.get('errors'), function(
 | 
					      const errorsWithoutNumber = _.reject(this.model.get('errors'), error =>
 | 
				
			||||||
        error
 | 
					        Boolean(error.number)
 | 
				
			||||||
      ) {
 | 
					      );
 | 
				
			||||||
        return Boolean(error.number);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.$el.html(
 | 
					      this.$el.html(
 | 
				
			||||||
        Mustache.render(_.result(this, 'template', ''), {
 | 
					        Mustache.render(_.result(this, 'template', ''), {
 | 
				
			||||||
| 
						 | 
					@ -171,19 +169,14 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.grouped = _.groupBy(this.model.get('errors'), 'number');
 | 
					      this.grouped = _.groupBy(this.model.get('errors'), 'number');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.getContacts().then(
 | 
					      this.getContacts().then(contacts => {
 | 
				
			||||||
        function(contacts) {
 | 
					        _.sortBy(contacts, c => {
 | 
				
			||||||
          _.sortBy(
 | 
					          const prefix = this.grouped[c.id] ? '0' : '1';
 | 
				
			||||||
            contacts,
 | 
					 | 
				
			||||||
            function(c) {
 | 
					 | 
				
			||||||
              var prefix = this.grouped[c.id] ? '0' : '1';
 | 
					 | 
				
			||||||
          // this prefix ensures that contacts with errors are listed first;
 | 
					          // this prefix ensures that contacts with errors are listed first;
 | 
				
			||||||
          //   otherwise it's alphabetical
 | 
					          //   otherwise it's alphabetical
 | 
				
			||||||
          return prefix + c.getTitle();
 | 
					          return prefix + c.getTitle();
 | 
				
			||||||
            }.bind(this)
 | 
					        }).forEach(this.renderContact.bind(this));
 | 
				
			||||||
          ).forEach(this.renderContact.bind(this));
 | 
					      });
 | 
				
			||||||
        }.bind(this)
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
})();
 | 
					})();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,9 @@
 | 
				
			||||||
 | 
					/* global Whisper, _ */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Whisper.MessageListView = Whisper.ListView.extend({
 | 
					  Whisper.MessageListView = Whisper.ListView.extend({
 | 
				
			||||||
| 
						 | 
					@ -9,17 +13,14 @@
 | 
				
			||||||
    events: {
 | 
					    events: {
 | 
				
			||||||
      scroll: 'onScroll',
 | 
					      scroll: 'onScroll',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    initialize: function() {
 | 
					    initialize() {
 | 
				
			||||||
      Whisper.ListView.prototype.initialize.call(this);
 | 
					      Whisper.ListView.prototype.initialize.call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.triggerLazyScroll = _.debounce(
 | 
					      this.triggerLazyScroll = _.debounce(() => {
 | 
				
			||||||
        function() {
 | 
					 | 
				
			||||||
        this.$el.trigger('lazyScroll');
 | 
					        this.$el.trigger('lazyScroll');
 | 
				
			||||||
        }.bind(this),
 | 
					      }, 500);
 | 
				
			||||||
        500
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    onScroll: function() {
 | 
					    onScroll() {
 | 
				
			||||||
      this.measureScrollPosition();
 | 
					      this.measureScrollPosition();
 | 
				
			||||||
      if (this.$el.scrollTop() === 0) {
 | 
					      if (this.$el.scrollTop() === 0) {
 | 
				
			||||||
        this.$el.trigger('loadMore');
 | 
					        this.$el.trigger('loadMore');
 | 
				
			||||||
| 
						 | 
					@ -32,10 +33,10 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.triggerLazyScroll();
 | 
					      this.triggerLazyScroll();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    atBottom: function() {
 | 
					    atBottom() {
 | 
				
			||||||
      return this.bottomOffset < 30;
 | 
					      return this.bottomOffset < 30;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    measureScrollPosition: function() {
 | 
					    measureScrollPosition() {
 | 
				
			||||||
      if (this.el.scrollHeight === 0) {
 | 
					      if (this.el.scrollHeight === 0) {
 | 
				
			||||||
        // hidden
 | 
					        // hidden
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
| 
						 | 
					@ -45,10 +46,10 @@
 | 
				
			||||||
      this.scrollHeight = this.el.scrollHeight;
 | 
					      this.scrollHeight = this.el.scrollHeight;
 | 
				
			||||||
      this.bottomOffset = this.scrollHeight - this.scrollPosition;
 | 
					      this.bottomOffset = this.scrollHeight - this.scrollPosition;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    resetScrollPosition: function() {
 | 
					    resetScrollPosition() {
 | 
				
			||||||
      this.$el.scrollTop(this.scrollPosition - this.$el.outerHeight());
 | 
					      this.$el.scrollTop(this.scrollPosition - this.$el.outerHeight());
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    scrollToBottomIfNeeded: function() {
 | 
					    scrollToBottomIfNeeded() {
 | 
				
			||||||
      // This is counter-intuitive. Our current bottomOffset is reflective of what
 | 
					      // This is counter-intuitive. Our current bottomOffset is reflective of what
 | 
				
			||||||
      //   we last measured, not necessarily the current state. And this is called
 | 
					      //   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
 | 
					      //   after we just made a change to the DOM: inserting a message, or an image
 | 
				
			||||||
| 
						 | 
					@ -58,25 +59,26 @@
 | 
				
			||||||
        this.scrollToBottom();
 | 
					        this.scrollToBottom();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    scrollToBottom: function() {
 | 
					    scrollToBottom() {
 | 
				
			||||||
      this.$el.scrollTop(this.el.scrollHeight);
 | 
					      this.$el.scrollTop(this.el.scrollHeight);
 | 
				
			||||||
      this.measureScrollPosition();
 | 
					      this.measureScrollPosition();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    addOne: function(model) {
 | 
					    addOne(model) {
 | 
				
			||||||
      var view;
 | 
					      let view;
 | 
				
			||||||
      if (model.isExpirationTimerUpdate()) {
 | 
					      if (model.isExpirationTimerUpdate()) {
 | 
				
			||||||
        view = new Whisper.ExpirationTimerUpdateView({ model: model }).render();
 | 
					        view = new Whisper.ExpirationTimerUpdateView({ model }).render();
 | 
				
			||||||
      } else if (model.get('type') === 'keychange') {
 | 
					      } 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') {
 | 
					      } else if (model.get('type') === 'verified-change') {
 | 
				
			||||||
        view = new Whisper.VerifiedChangeView({ model: model }).render();
 | 
					        view = new Whisper.VerifiedChangeView({ model }).render();
 | 
				
			||||||
      } else {
 | 
					      } 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, 'beforeChangeHeight', this.measureScrollPosition);
 | 
				
			||||||
        this.listenTo(view, 'afterChangeHeight', this.scrollToBottomIfNeeded);
 | 
					        this.listenTo(view, 'afterChangeHeight', this.scrollToBottomIfNeeded);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var index = this.collection.indexOf(model);
 | 
					      const index = this.collection.indexOf(model);
 | 
				
			||||||
      this.measureScrollPosition();
 | 
					      this.measureScrollPosition();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (model.get('unread') && !this.atBottom()) {
 | 
					      if (model.get('unread') && !this.atBottom()) {
 | 
				
			||||||
| 
						 | 
					@ -91,20 +93,20 @@
 | 
				
			||||||
        this.$el.prepend(view.el);
 | 
					        this.$el.prepend(view.el);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        // insert
 | 
					        // insert
 | 
				
			||||||
        var next = this.$('#' + this.collection.at(index + 1).id);
 | 
					        const next = this.$(`#${this.collection.at(index + 1).id}`);
 | 
				
			||||||
        var prev = this.$('#' + this.collection.at(index - 1).id);
 | 
					        const prev = this.$(`#${this.collection.at(index - 1).id}`);
 | 
				
			||||||
        if (next.length > 0) {
 | 
					        if (next.length > 0) {
 | 
				
			||||||
          view.$el.insertBefore(next);
 | 
					          view.$el.insertBefore(next);
 | 
				
			||||||
        } else if (prev.length > 0) {
 | 
					        } else if (prev.length > 0) {
 | 
				
			||||||
          view.$el.insertAfter(prev);
 | 
					          view.$el.insertAfter(prev);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          // scan for the right spot
 | 
					          // scan for the right spot
 | 
				
			||||||
          var elements = this.$el.children();
 | 
					          const elements = this.$el.children();
 | 
				
			||||||
          if (elements.length > 0) {
 | 
					          if (elements.length > 0) {
 | 
				
			||||||
            for (var i = 0; i < elements.length; ++i) {
 | 
					            for (let i = 0; i < elements.length; i += 1) {
 | 
				
			||||||
              var m = this.collection.get(elements[i].id);
 | 
					              const m = this.collection.get(elements[i].id);
 | 
				
			||||||
              var m_index = this.collection.indexOf(m);
 | 
					              const mIndex = this.collection.indexOf(m);
 | 
				
			||||||
              if (m_index > index) {
 | 
					              if (mIndex > index) {
 | 
				
			||||||
                view.$el.insertBefore(elements[i]);
 | 
					                view.$el.insertBefore(elements[i]);
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,6 @@
 | 
				
			||||||
 | 
					/* global Whisper, extension, Backbone, moment, i18n */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,15 +9,13 @@
 | 
				
			||||||
  Whisper.NetworkStatusView = Whisper.View.extend({
 | 
					  Whisper.NetworkStatusView = Whisper.View.extend({
 | 
				
			||||||
    className: 'network-status',
 | 
					    className: 'network-status',
 | 
				
			||||||
    templateName: 'networkStatus',
 | 
					    templateName: 'networkStatus',
 | 
				
			||||||
    initialize: function() {
 | 
					    initialize() {
 | 
				
			||||||
      this.$el.hide();
 | 
					      this.$el.hide();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.renderIntervalHandle = setInterval(this.update.bind(this), 5000);
 | 
					      this.renderIntervalHandle = setInterval(this.update.bind(this), 5000);
 | 
				
			||||||
      extension.windows.onClosed(
 | 
					      extension.windows.onClosed(() => {
 | 
				
			||||||
        function() {
 | 
					 | 
				
			||||||
        clearInterval(this.renderIntervalHandle);
 | 
					        clearInterval(this.renderIntervalHandle);
 | 
				
			||||||
        }.bind(this)
 | 
					      });
 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      setTimeout(this.finishConnectingGracePeriod.bind(this), 5000);
 | 
					      setTimeout(this.finishConnectingGracePeriod.bind(this), 5000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,29 +28,29 @@
 | 
				
			||||||
      this.model = new Backbone.Model();
 | 
					      this.model = new Backbone.Model();
 | 
				
			||||||
      this.listenTo(this.model, 'change', this.onChange);
 | 
					      this.listenTo(this.model, 'change', this.onChange);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    onReconnectTimer: function() {
 | 
					    onReconnectTimer() {
 | 
				
			||||||
      this.setSocketReconnectInterval(60000);
 | 
					      this.setSocketReconnectInterval(60000);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    finishConnectingGracePeriod: function() {
 | 
					    finishConnectingGracePeriod() {
 | 
				
			||||||
      this.withinConnectingGracePeriod = false;
 | 
					      this.withinConnectingGracePeriod = false;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    setSocketReconnectInterval: function(millis) {
 | 
					    setSocketReconnectInterval(millis) {
 | 
				
			||||||
      this.socketReconnectWaitDuration = moment.duration(millis);
 | 
					      this.socketReconnectWaitDuration = moment.duration(millis);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    navigatorOnLine: function() {
 | 
					    navigatorOnLine() {
 | 
				
			||||||
      return navigator.onLine;
 | 
					      return navigator.onLine;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    getSocketStatus: function() {
 | 
					    getSocketStatus() {
 | 
				
			||||||
      return window.getSocketStatus();
 | 
					      return window.getSocketStatus();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    getNetworkStatus: function() {
 | 
					    getNetworkStatus() {
 | 
				
			||||||
      var message = '';
 | 
					      let message = '';
 | 
				
			||||||
      var instructions = '';
 | 
					      let instructions = '';
 | 
				
			||||||
      var hasInterruption = false;
 | 
					      let hasInterruption = false;
 | 
				
			||||||
      var action = null;
 | 
					      let action = null;
 | 
				
			||||||
      var buttonClass = null;
 | 
					      let buttonClass = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var socketStatus = this.getSocketStatus();
 | 
					      const socketStatus = this.getSocketStatus();
 | 
				
			||||||
      switch (socketStatus) {
 | 
					      switch (socketStatus) {
 | 
				
			||||||
        case WebSocket.CONNECTING:
 | 
					        case WebSocket.CONNECTING:
 | 
				
			||||||
          message = i18n('connecting');
 | 
					          message = i18n('connecting');
 | 
				
			||||||
| 
						 | 
					@ -58,12 +59,13 @@
 | 
				
			||||||
        case WebSocket.OPEN:
 | 
					        case WebSocket.OPEN:
 | 
				
			||||||
          this.setSocketReconnectInterval(null);
 | 
					          this.setSocketReconnectInterval(null);
 | 
				
			||||||
          break;
 | 
					          break;
 | 
				
			||||||
        case WebSocket.CLOSING:
 | 
					        case WebSocket.CLOSED:
 | 
				
			||||||
          message = i18n('disconnected');
 | 
					          message = i18n('disconnected');
 | 
				
			||||||
          instructions = i18n('checkNetworkConnection');
 | 
					          instructions = i18n('checkNetworkConnection');
 | 
				
			||||||
          hasInterruption = true;
 | 
					          hasInterruption = true;
 | 
				
			||||||
          break;
 | 
					          break;
 | 
				
			||||||
        case WebSocket.CLOSED:
 | 
					        case WebSocket.CLOSING:
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
          message = i18n('disconnected');
 | 
					          message = i18n('disconnected');
 | 
				
			||||||
          instructions = i18n('checkNetworkConnection');
 | 
					          instructions = i18n('checkNetworkConnection');
 | 
				
			||||||
          hasInterruption = true;
 | 
					          hasInterruption = true;
 | 
				
			||||||
| 
						 | 
					@ -71,7 +73,7 @@
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (
 | 
					      if (
 | 
				
			||||||
        socketStatus == WebSocket.CONNECTING &&
 | 
					        socketStatus === WebSocket.CONNECTING &&
 | 
				
			||||||
        !this.withinConnectingGracePeriod
 | 
					        !this.withinConnectingGracePeriod
 | 
				
			||||||
      ) {
 | 
					      ) {
 | 
				
			||||||
        hasInterruption = true;
 | 
					        hasInterruption = true;
 | 
				
			||||||
| 
						 | 
					@ -94,21 +96,21 @@
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        message: message,
 | 
					        message,
 | 
				
			||||||
        instructions: instructions,
 | 
					        instructions,
 | 
				
			||||||
        hasInterruption: hasInterruption,
 | 
					        hasInterruption,
 | 
				
			||||||
        action: action,
 | 
					        action,
 | 
				
			||||||
        buttonClass: buttonClass,
 | 
					        buttonClass,
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    update: function() {
 | 
					    update() {
 | 
				
			||||||
      var status = this.getNetworkStatus();
 | 
					      const status = this.getNetworkStatus();
 | 
				
			||||||
      this.model.set(status);
 | 
					      this.model.set(status);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    render_attributes: function() {
 | 
					    render_attributes() {
 | 
				
			||||||
      return this.model.attributes;
 | 
					      return this.model.attributes;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    onChange: function() {
 | 
					    onChange() {
 | 
				
			||||||
      this.render();
 | 
					      this.render();
 | 
				
			||||||
      if (this.model.attributes.hasInterruption) {
 | 
					      if (this.model.attributes.hasInterruption) {
 | 
				
			||||||
        this.$el.slideDown();
 | 
					        this.$el.slideDown();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,18 @@
 | 
				
			||||||
 | 
					/* global Whisper, _ */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* eslint-disable more/no-then */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Whisper.NewGroupUpdateView = Whisper.View.extend({
 | 
					  Whisper.NewGroupUpdateView = Whisper.View.extend({
 | 
				
			||||||
    tagName: 'div',
 | 
					    tagName: 'div',
 | 
				
			||||||
    className: 'new-group-update',
 | 
					    className: 'new-group-update',
 | 
				
			||||||
    templateName: 'new-group-update',
 | 
					    templateName: 'new-group-update',
 | 
				
			||||||
    initialize: function(options) {
 | 
					    initialize(options) {
 | 
				
			||||||
      this.render();
 | 
					      this.render();
 | 
				
			||||||
      this.avatarInput = new Whisper.FileInputView({
 | 
					      this.avatarInput = new Whisper.FileInputView({
 | 
				
			||||||
        el: this.$('.group-avatar'),
 | 
					        el: this.$('.group-avatar'),
 | 
				
			||||||
| 
						 | 
					@ -14,15 +20,13 @@
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.recipients_view = new Whisper.RecipientsInputView();
 | 
					      this.recipients_view = new Whisper.RecipientsInputView();
 | 
				
			||||||
      this.listenTo(this.recipients_view.typeahead, 'sync', function() {
 | 
					      this.listenTo(this.recipients_view.typeahead, 'sync', () =>
 | 
				
			||||||
        this.model.contactCollection.models.forEach(
 | 
					        this.model.contactCollection.models.forEach(model => {
 | 
				
			||||||
          function(model) {
 | 
					 | 
				
			||||||
          if (this.recipients_view.typeahead.get(model)) {
 | 
					          if (this.recipients_view.typeahead.get(model)) {
 | 
				
			||||||
            this.recipients_view.typeahead.remove(model);
 | 
					            this.recipients_view.typeahead.remove(model);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          }.bind(this)
 | 
					        })
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      this.recipients_view.$el.insertBefore(this.$('.container'));
 | 
					      this.recipients_view.$el.insertBefore(this.$('.container'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.member_list_view = new Whisper.ContactListView({
 | 
					      this.member_list_view = new Whisper.ContactListView({
 | 
				
			||||||
| 
						 | 
					@ -38,26 +42,25 @@
 | 
				
			||||||
      'focusin input.search': 'showResults',
 | 
					      'focusin input.search': 'showResults',
 | 
				
			||||||
      'focusout input.search': 'hideResults',
 | 
					      'focusout input.search': 'hideResults',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    hideResults: function() {
 | 
					    hideResults() {
 | 
				
			||||||
      this.$('.results').hide();
 | 
					      this.$('.results').hide();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    showResults: function() {
 | 
					    showResults() {
 | 
				
			||||||
      this.$('.results').show();
 | 
					      this.$('.results').show();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    goBack: function() {
 | 
					    goBack() {
 | 
				
			||||||
      this.trigger('back');
 | 
					      this.trigger('back');
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    render_attributes: function() {
 | 
					    render_attributes() {
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        name: this.model.getTitle(),
 | 
					        name: this.model.getTitle(),
 | 
				
			||||||
        avatar: this.model.getAvatar(),
 | 
					        avatar: this.model.getAvatar(),
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    send: function() {
 | 
					    send() {
 | 
				
			||||||
      return this.avatarInput.getThumbnail().then(
 | 
					      return this.avatarInput.getThumbnail().then(avatarFile => {
 | 
				
			||||||
        function(avatarFile) {
 | 
					        const now = Date.now();
 | 
				
			||||||
          var now = Date.now();
 | 
					        const attrs = {
 | 
				
			||||||
          var attrs = {
 | 
					 | 
				
			||||||
          timestamp: now,
 | 
					          timestamp: now,
 | 
				
			||||||
          active_at: now,
 | 
					          active_at: now,
 | 
				
			||||||
          name: this.$('.name').val(),
 | 
					          name: this.$('.name').val(),
 | 
				
			||||||
| 
						 | 
					@ -70,17 +73,16 @@
 | 
				
			||||||
          attrs.avatar = avatarFile;
 | 
					          attrs.avatar = avatarFile;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this.model.set(attrs);
 | 
					        this.model.set(attrs);
 | 
				
			||||||
          var group_update = this.model.changed;
 | 
					        const groupUpdate = this.model.changed;
 | 
				
			||||||
        this.model.save();
 | 
					        this.model.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          if (group_update.avatar) {
 | 
					        if (groupUpdate.avatar) {
 | 
				
			||||||
          this.model.trigger('change:avatar');
 | 
					          this.model.trigger('change:avatar');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          this.model.updateGroup(group_update);
 | 
					        this.model.updateGroup(groupUpdate);
 | 
				
			||||||
        this.goBack();
 | 
					        this.goBack();
 | 
				
			||||||
        }.bind(this)
 | 
					      });
 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
})();
 | 
					})();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,26 +1,30 @@
 | 
				
			||||||
 | 
					/* global libphonenumber, Whisper */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Whisper.PhoneInputView = Whisper.View.extend({
 | 
					  Whisper.PhoneInputView = Whisper.View.extend({
 | 
				
			||||||
    tagName: 'div',
 | 
					    tagName: 'div',
 | 
				
			||||||
    className: 'phone-input',
 | 
					    className: 'phone-input',
 | 
				
			||||||
    templateName: 'phone-number',
 | 
					    templateName: 'phone-number',
 | 
				
			||||||
    initialize: function() {
 | 
					    initialize() {
 | 
				
			||||||
      this.$('input.number').intlTelInput();
 | 
					      this.$('input.number').intlTelInput();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    events: {
 | 
					    events: {
 | 
				
			||||||
      change: 'validateNumber',
 | 
					      change: 'validateNumber',
 | 
				
			||||||
      keyup: 'validateNumber',
 | 
					      keyup: 'validateNumber',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    validateNumber: function() {
 | 
					    validateNumber() {
 | 
				
			||||||
      var input = this.$('input.number');
 | 
					      const input = this.$('input.number');
 | 
				
			||||||
      var regionCode = this.$('li.active')
 | 
					      const regionCode = this.$('li.active')
 | 
				
			||||||
        .attr('data-country-code')
 | 
					        .attr('data-country-code')
 | 
				
			||||||
        .toUpperCase();
 | 
					        .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) {
 | 
					      if (parsedNumber.isValidNumber) {
 | 
				
			||||||
        this.$('.number-container').removeClass('invalid');
 | 
					        this.$('.number-container').removeClass('invalid');
 | 
				
			||||||
        this.$('.number-container').addClass('valid');
 | 
					        this.$('.number-container').addClass('valid');
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,12 @@
 | 
				
			||||||
 | 
					/* global Whisper, Backbone, ConversationController */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var ContactsTypeahead = Backbone.TypeaheadCollection.extend({
 | 
					  const ContactsTypeahead = Backbone.TypeaheadCollection.extend({
 | 
				
			||||||
    typeaheadAttributes: [
 | 
					    typeaheadAttributes: [
 | 
				
			||||||
      'name',
 | 
					      'name',
 | 
				
			||||||
      'e164_number',
 | 
					      'e164_number',
 | 
				
			||||||
| 
						 | 
					@ -12,7 +16,7 @@
 | 
				
			||||||
    database: Whisper.Database,
 | 
					    database: Whisper.Database,
 | 
				
			||||||
    storeName: 'conversations',
 | 
					    storeName: 'conversations',
 | 
				
			||||||
    model: Whisper.Conversation,
 | 
					    model: Whisper.Conversation,
 | 
				
			||||||
    fetchContacts: function() {
 | 
					    fetchContacts() {
 | 
				
			||||||
      return this.fetch({ reset: true, conditions: { type: 'private' } });
 | 
					      return this.fetch({ reset: true, conditions: { type: 'private' } });
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
| 
						 | 
					@ -24,17 +28,17 @@
 | 
				
			||||||
      'click .remove': 'removeModel',
 | 
					      'click .remove': 'removeModel',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    templateName: 'contact_pill',
 | 
					    templateName: 'contact_pill',
 | 
				
			||||||
    initialize: function() {
 | 
					    initialize() {
 | 
				
			||||||
      var error = this.model.validate(this.model.attributes);
 | 
					      const error = this.model.validate(this.model.attributes);
 | 
				
			||||||
      if (error) {
 | 
					      if (error) {
 | 
				
			||||||
        this.$el.addClass('error');
 | 
					        this.$el.addClass('error');
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    removeModel: function() {
 | 
					    removeModel() {
 | 
				
			||||||
      this.$el.trigger('remove', { modelId: this.model.id });
 | 
					      this.$el.trigger('remove', { modelId: this.model.id });
 | 
				
			||||||
      this.remove();
 | 
					      this.remove();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    render_attributes: function() {
 | 
					    render_attributes() {
 | 
				
			||||||
      return { name: this.model.getTitle() };
 | 
					      return { name: this.model.getTitle() };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
| 
						 | 
					@ -55,7 +59,7 @@
 | 
				
			||||||
  Whisper.RecipientsInputView = Whisper.View.extend({
 | 
					  Whisper.RecipientsInputView = Whisper.View.extend({
 | 
				
			||||||
    className: 'recipients-input',
 | 
					    className: 'recipients-input',
 | 
				
			||||||
    templateName: 'recipients-input',
 | 
					    templateName: 'recipients-input',
 | 
				
			||||||
    initialize: function(options) {
 | 
					    initialize(options) {
 | 
				
			||||||
      if (options) {
 | 
					      if (options) {
 | 
				
			||||||
        this.placeholder = options.placeholder;
 | 
					        this.placeholder = options.placeholder;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
| 
						 | 
					@ -81,7 +85,7 @@
 | 
				
			||||||
      // View to display the matched contacts from typeahead
 | 
					      // View to display the matched contacts from typeahead
 | 
				
			||||||
      this.typeahead_view = new Whisper.SuggestionListView({
 | 
					      this.typeahead_view = new Whisper.SuggestionListView({
 | 
				
			||||||
        collection: new Whisper.ConversationCollection([], {
 | 
					        collection: new Whisper.ConversationCollection([], {
 | 
				
			||||||
          comparator: function(m) {
 | 
					          comparator(m) {
 | 
				
			||||||
            return m.getTitle().toLowerCase();
 | 
					            return m.getTitle().toLowerCase();
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
        }),
 | 
					        }),
 | 
				
			||||||
| 
						 | 
					@ -91,7 +95,7 @@
 | 
				
			||||||
      this.listenTo(this.typeahead, 'reset', this.filterContacts);
 | 
					      this.listenTo(this.typeahead, 'reset', this.filterContacts);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    render_attributes: function() {
 | 
					    render_attributes() {
 | 
				
			||||||
      return { placeholder: this.placeholder || 'name or phone number' };
 | 
					      return { placeholder: this.placeholder || 'name or phone number' };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -102,8 +106,8 @@
 | 
				
			||||||
      'remove .recipient': 'removeRecipient',
 | 
					      'remove .recipient': 'removeRecipient',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    filterContacts: function(e) {
 | 
					    filterContacts() {
 | 
				
			||||||
      var query = this.$input.val();
 | 
					      const query = this.$input.val();
 | 
				
			||||||
      if (query.length) {
 | 
					      if (query.length) {
 | 
				
			||||||
        if (this.maybeNumber(query)) {
 | 
					        if (this.maybeNumber(query)) {
 | 
				
			||||||
          this.new_contact_view.model.set('id', query);
 | 
					          this.new_contact_view.model.set('id', query);
 | 
				
			||||||
| 
						 | 
					@ -117,7 +121,7 @@
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    initNewContact: function() {
 | 
					    initNewContact() {
 | 
				
			||||||
      if (this.new_contact_view) {
 | 
					      if (this.new_contact_view) {
 | 
				
			||||||
        this.new_contact_view.undelegateEvents();
 | 
					        this.new_contact_view.undelegateEvents();
 | 
				
			||||||
        this.new_contact_view.$el.hide();
 | 
					        this.new_contact_view.$el.hide();
 | 
				
			||||||
| 
						 | 
					@ -132,47 +136,45 @@
 | 
				
			||||||
      }).render();
 | 
					      }).render();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    addNewRecipient: function() {
 | 
					    addNewRecipient() {
 | 
				
			||||||
      this.recipients.add(this.new_contact_view.model);
 | 
					      this.recipients.add(this.new_contact_view.model);
 | 
				
			||||||
      this.initNewContact();
 | 
					      this.initNewContact();
 | 
				
			||||||
      this.resetTypeahead();
 | 
					      this.resetTypeahead();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    addRecipient: function(e, conversation) {
 | 
					    addRecipient(e, conversation) {
 | 
				
			||||||
      this.recipients.add(this.typeahead.remove(conversation.id));
 | 
					      this.recipients.add(this.typeahead.remove(conversation.id));
 | 
				
			||||||
      this.resetTypeahead();
 | 
					      this.resetTypeahead();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    removeRecipient: function(e, data) {
 | 
					    removeRecipient(e, data) {
 | 
				
			||||||
      var model = this.recipients.remove(data.modelId);
 | 
					      const model = this.recipients.remove(data.modelId);
 | 
				
			||||||
      if (!model.get('newContact')) {
 | 
					      if (!model.get('newContact')) {
 | 
				
			||||||
        this.typeahead.add(model);
 | 
					        this.typeahead.add(model);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this.filterContacts();
 | 
					      this.filterContacts();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    reset: function() {
 | 
					    reset() {
 | 
				
			||||||
      this.delegateEvents();
 | 
					      this.delegateEvents();
 | 
				
			||||||
      this.typeahead_view.delegateEvents();
 | 
					      this.typeahead_view.delegateEvents();
 | 
				
			||||||
      this.recipients_view.delegateEvents();
 | 
					      this.recipients_view.delegateEvents();
 | 
				
			||||||
      this.new_contact_view.delegateEvents();
 | 
					      this.new_contact_view.delegateEvents();
 | 
				
			||||||
      this.typeahead.add(
 | 
					      this.typeahead.add(
 | 
				
			||||||
        this.recipients.filter(function(model) {
 | 
					        this.recipients.filter(model => !model.get('newContact'))
 | 
				
			||||||
          return !model.get('newContact');
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      this.recipients.reset([]);
 | 
					      this.recipients.reset([]);
 | 
				
			||||||
      this.resetTypeahead();
 | 
					      this.resetTypeahead();
 | 
				
			||||||
      this.typeahead.fetchContacts();
 | 
					      this.typeahead.fetchContacts();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    resetTypeahead: function() {
 | 
					    resetTypeahead() {
 | 
				
			||||||
      this.new_contact_view.$el.hide();
 | 
					      this.new_contact_view.$el.hide();
 | 
				
			||||||
      this.$input.val('').focus();
 | 
					      this.$input.val('').focus();
 | 
				
			||||||
      this.typeahead_view.collection.reset([]);
 | 
					      this.typeahead_view.collection.reset([]);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    maybeNumber: function(number) {
 | 
					    maybeNumber(number) {
 | 
				
			||||||
      return number.match(/^\+?[0-9]*$/);
 | 
					      return number.match(/^\+?[0-9]*$/);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,17 @@
 | 
				
			||||||
 | 
					/* global Whisper, moment, WebAudioRecorder */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* eslint-disable more/no-then */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Whisper.RecorderView = Whisper.View.extend({
 | 
					  Whisper.RecorderView = Whisper.View.extend({
 | 
				
			||||||
    className: 'recorder clearfix',
 | 
					    className: 'recorder clearfix',
 | 
				
			||||||
    templateName: 'recorder',
 | 
					    templateName: 'recorder',
 | 
				
			||||||
    initialize: function() {
 | 
					    initialize() {
 | 
				
			||||||
      this.startTime = Date.now();
 | 
					      this.startTime = Date.now();
 | 
				
			||||||
      this.interval = setInterval(this.updateTime.bind(this), 1000);
 | 
					      this.interval = setInterval(this.updateTime.bind(this), 1000);
 | 
				
			||||||
      this.start();
 | 
					      this.start();
 | 
				
			||||||
| 
						 | 
					@ -15,16 +21,16 @@
 | 
				
			||||||
      'click .finish': 'finish',
 | 
					      'click .finish': 'finish',
 | 
				
			||||||
      close: 'close',
 | 
					      close: 'close',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    updateTime: function() {
 | 
					    updateTime() {
 | 
				
			||||||
      var duration = moment.duration(Date.now() - this.startTime, 'ms');
 | 
					      const duration = moment.duration(Date.now() - this.startTime, 'ms');
 | 
				
			||||||
      var minutes = '' + Math.trunc(duration.asMinutes());
 | 
					      const minutes = `${Math.trunc(duration.asMinutes())}`;
 | 
				
			||||||
      var seconds = '' + duration.seconds();
 | 
					      let seconds = `${duration.seconds()}`;
 | 
				
			||||||
      if (seconds.length < 2) {
 | 
					      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
 | 
					      // Note: the 'close' event can be triggered by InboxView, when the user clicks
 | 
				
			||||||
      //   anywhere outside the recording pane.
 | 
					      //   anywhere outside the recording pane.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,7 +50,7 @@
 | 
				
			||||||
      this.source = null;
 | 
					      this.source = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (this.context) {
 | 
					      if (this.context) {
 | 
				
			||||||
        this.context.close().then(function() {
 | 
					        this.context.close().then(() => {
 | 
				
			||||||
          console.log('audio context closed');
 | 
					          console.log('audio context closed');
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
| 
						 | 
					@ -53,16 +59,16 @@
 | 
				
			||||||
      this.remove();
 | 
					      this.remove();
 | 
				
			||||||
      this.trigger('closed');
 | 
					      this.trigger('closed');
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    finish: function() {
 | 
					    finish() {
 | 
				
			||||||
      this.recorder.finishRecording();
 | 
					      this.recorder.finishRecording();
 | 
				
			||||||
      this.close();
 | 
					      this.close();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    handleBlob: function(recorder, blob) {
 | 
					    handleBlob(recorder, blob) {
 | 
				
			||||||
      if (blob) {
 | 
					      if (blob) {
 | 
				
			||||||
        this.trigger('send', blob);
 | 
					        this.trigger('send', blob);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    start: function() {
 | 
					    start() {
 | 
				
			||||||
      this.context = new AudioContext();
 | 
					      this.context = new AudioContext();
 | 
				
			||||||
      this.input = this.context.createGain();
 | 
					      this.input = this.context.createGain();
 | 
				
			||||||
      this.recorder = new WebAudioRecorder(this.input, {
 | 
					      this.recorder = new WebAudioRecorder(this.input, {
 | 
				
			||||||
| 
						 | 
					@ -73,15 +79,15 @@
 | 
				
			||||||
      this.recorder.onError = this.onError;
 | 
					      this.recorder.onError = this.onError;
 | 
				
			||||||
      navigator.webkitGetUserMedia(
 | 
					      navigator.webkitGetUserMedia(
 | 
				
			||||||
        { audio: true },
 | 
					        { audio: true },
 | 
				
			||||||
        function(stream) {
 | 
					        stream => {
 | 
				
			||||||
          this.source = this.context.createMediaStreamSource(stream);
 | 
					          this.source = this.context.createMediaStreamSource(stream);
 | 
				
			||||||
          this.source.connect(this.input);
 | 
					          this.source.connect(this.input);
 | 
				
			||||||
        }.bind(this),
 | 
					        },
 | 
				
			||||||
        this.onError.bind(this)
 | 
					        this.onError.bind(this)
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      this.recorder.startRecording();
 | 
					      this.recorder.startRecording();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    onError: function(error) {
 | 
					    onError(error) {
 | 
				
			||||||
      // Protect against out-of-band errors, which can happen if the user revokes media
 | 
					      // Protect against out-of-band errors, which can happen if the user revokes media
 | 
				
			||||||
      //   permissions after successfully accessing the microphone.
 | 
					      //   permissions after successfully accessing the microphone.
 | 
				
			||||||
      if (!this.recorder) {
 | 
					      if (!this.recorder) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,26 +1,28 @@
 | 
				
			||||||
 | 
					/* global Whisper, i18n */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Whisper.ScrollDownButtonView = Whisper.View.extend({
 | 
					  Whisper.ScrollDownButtonView = Whisper.View.extend({
 | 
				
			||||||
    className: 'scroll-down-button-view',
 | 
					    className: 'scroll-down-button-view',
 | 
				
			||||||
    templateName: 'scroll-down-button-view',
 | 
					    templateName: 'scroll-down-button-view',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    initialize: function(options) {
 | 
					    initialize(options = {}) {
 | 
				
			||||||
      options = options || {};
 | 
					 | 
				
			||||||
      this.count = options.count || 0;
 | 
					      this.count = options.count || 0;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    increment: function(count) {
 | 
					    increment(count = 0) {
 | 
				
			||||||
      count = count || 0;
 | 
					 | 
				
			||||||
      this.count += count;
 | 
					      this.count += count;
 | 
				
			||||||
      this.render();
 | 
					      this.render();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    render_attributes: function() {
 | 
					    render_attributes() {
 | 
				
			||||||
      var cssClass = this.count > 0 ? 'new-messages' : '';
 | 
					      const cssClass = this.count > 0 ? 'new-messages' : '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var moreBelow = i18n('scrollDown');
 | 
					      let moreBelow = i18n('scrollDown');
 | 
				
			||||||
      if (this.count > 1) {
 | 
					      if (this.count > 1) {
 | 
				
			||||||
        moreBelow = i18n('messagesBelow');
 | 
					        moreBelow = i18n('messagesBelow');
 | 
				
			||||||
      } else if (this.count === 1) {
 | 
					      } else if (this.count === 1) {
 | 
				
			||||||
| 
						 | 
					@ -28,8 +30,8 @@
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        cssClass: cssClass,
 | 
					        cssClass,
 | 
				
			||||||
        moreBelow: moreBelow,
 | 
					        moreBelow,
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,16 +1,22 @@
 | 
				
			||||||
 | 
					/* global Whisper, $, getAccountManager, textsecure */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* eslint-disable more/no-then */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Whisper.StandaloneRegistrationView = Whisper.View.extend({
 | 
					  Whisper.StandaloneRegistrationView = Whisper.View.extend({
 | 
				
			||||||
    templateName: 'standalone',
 | 
					    templateName: 'standalone',
 | 
				
			||||||
    className: 'full-screen-flow',
 | 
					    className: 'full-screen-flow',
 | 
				
			||||||
    initialize: function() {
 | 
					    initialize() {
 | 
				
			||||||
      this.accountManager = getAccountManager();
 | 
					      this.accountManager = getAccountManager();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.render();
 | 
					      this.render();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var number = textsecure.storage.user.getNumber();
 | 
					      const number = textsecure.storage.user.getNumber();
 | 
				
			||||||
      if (number) {
 | 
					      if (number) {
 | 
				
			||||||
        this.$('input.number').val(number);
 | 
					        this.$('input.number').val(number);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
| 
						 | 
					@ -26,58 +32,59 @@
 | 
				
			||||||
      'change #code': 'onChangeCode',
 | 
					      'change #code': 'onChangeCode',
 | 
				
			||||||
      'click #verifyCode': 'verifyCode',
 | 
					      'click #verifyCode': 'verifyCode',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    verifyCode: function(e) {
 | 
					    verifyCode() {
 | 
				
			||||||
      var number = this.phoneView.validateNumber();
 | 
					      const number = this.phoneView.validateNumber();
 | 
				
			||||||
      var verificationCode = $('#code')
 | 
					      const verificationCode = $('#code')
 | 
				
			||||||
        .val()
 | 
					        .val()
 | 
				
			||||||
        .replace(/\D+/g, '');
 | 
					        .replace(/\D+/g, '');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.accountManager
 | 
					      this.accountManager
 | 
				
			||||||
        .registerSingleDevice(number, verificationCode)
 | 
					        .registerSingleDevice(number, verificationCode)
 | 
				
			||||||
        .then(
 | 
					        .then(() => {
 | 
				
			||||||
          function() {
 | 
					 | 
				
			||||||
          this.$el.trigger('openInbox');
 | 
					          this.$el.trigger('openInbox');
 | 
				
			||||||
          }.bind(this)
 | 
					        })
 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        .catch(this.log.bind(this));
 | 
					        .catch(this.log.bind(this));
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    log: function(s) {
 | 
					    log(s) {
 | 
				
			||||||
      console.log(s);
 | 
					      console.log(s);
 | 
				
			||||||
      this.$('#status').text(s);
 | 
					      this.$('#status').text(s);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    validateCode: function() {
 | 
					    validateCode() {
 | 
				
			||||||
      var verificationCode = $('#code')
 | 
					      const verificationCode = $('#code')
 | 
				
			||||||
        .val()
 | 
					        .val()
 | 
				
			||||||
        .replace(/\D/g, '');
 | 
					        .replace(/\D/g, '');
 | 
				
			||||||
      if (verificationCode.length == 6) {
 | 
					
 | 
				
			||||||
 | 
					      if (verificationCode.length === 6) {
 | 
				
			||||||
        return verificationCode;
 | 
					        return verificationCode;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    displayError: function(error) {
 | 
					    displayError(error) {
 | 
				
			||||||
      this.$('#error')
 | 
					      this.$('#error')
 | 
				
			||||||
        .hide()
 | 
					        .hide()
 | 
				
			||||||
        .text(error)
 | 
					        .text(error)
 | 
				
			||||||
        .addClass('in')
 | 
					        .addClass('in')
 | 
				
			||||||
        .fadeIn();
 | 
					        .fadeIn();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    onValidation: function() {
 | 
					    onValidation() {
 | 
				
			||||||
      if (this.$('#number-container').hasClass('valid')) {
 | 
					      if (this.$('#number-container').hasClass('valid')) {
 | 
				
			||||||
        this.$('#request-sms, #request-voice').removeAttr('disabled');
 | 
					        this.$('#request-sms, #request-voice').removeAttr('disabled');
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        this.$('#request-sms, #request-voice').prop('disabled', 'disabled');
 | 
					        this.$('#request-sms, #request-voice').prop('disabled', 'disabled');
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    onChangeCode: function() {
 | 
					    onChangeCode() {
 | 
				
			||||||
      if (!this.validateCode()) {
 | 
					      if (!this.validateCode()) {
 | 
				
			||||||
        this.$('#code').addClass('invalid');
 | 
					        this.$('#code').addClass('invalid');
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        this.$('#code').removeClass('invalid');
 | 
					        this.$('#code').removeClass('invalid');
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    requestVoice: function() {
 | 
					    requestVoice() {
 | 
				
			||||||
      window.removeSetupMenuItems();
 | 
					      window.removeSetupMenuItems();
 | 
				
			||||||
      this.$('#error').hide();
 | 
					      this.$('#error').hide();
 | 
				
			||||||
      var number = this.phoneView.validateNumber();
 | 
					      const number = this.phoneView.validateNumber();
 | 
				
			||||||
      if (number) {
 | 
					      if (number) {
 | 
				
			||||||
        this.accountManager
 | 
					        this.accountManager
 | 
				
			||||||
          .requestVoiceVerification(number)
 | 
					          .requestVoiceVerification(number)
 | 
				
			||||||
| 
						 | 
					@ -89,10 +96,10 @@
 | 
				
			||||||
        this.$('#number-container').addClass('invalid');
 | 
					        this.$('#number-container').addClass('invalid');
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    requestSMSVerification: function() {
 | 
					    requestSMSVerification() {
 | 
				
			||||||
      window.removeSetupMenuItems();
 | 
					      window.removeSetupMenuItems();
 | 
				
			||||||
      $('#error').hide();
 | 
					      $('#error').hide();
 | 
				
			||||||
      var number = this.phoneView.validateNumber();
 | 
					      const number = this.phoneView.validateNumber();
 | 
				
			||||||
      if (number) {
 | 
					      if (number) {
 | 
				
			||||||
        this.accountManager
 | 
					        this.accountManager
 | 
				
			||||||
          .requestSMSVerification(number)
 | 
					          .requestSMSVerification(number)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,19 +1,23 @@
 | 
				
			||||||
 | 
					/* global Whisper, Mustache, _ */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Whisper.ToastView = Whisper.View.extend({
 | 
					  Whisper.ToastView = Whisper.View.extend({
 | 
				
			||||||
    className: 'toast',
 | 
					    className: 'toast',
 | 
				
			||||||
    templateName: 'toast',
 | 
					    templateName: 'toast',
 | 
				
			||||||
    initialize: function() {
 | 
					    initialize() {
 | 
				
			||||||
      this.$el.hide();
 | 
					      this.$el.hide();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    close: function() {
 | 
					    close() {
 | 
				
			||||||
      this.$el.fadeOut(this.remove.bind(this));
 | 
					      this.$el.fadeOut(this.remove.bind(this));
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    render: function() {
 | 
					    render() {
 | 
				
			||||||
      this.$el.html(
 | 
					      this.$el.html(
 | 
				
			||||||
        Mustache.render(
 | 
					        Mustache.render(
 | 
				
			||||||
          _.result(this, 'template', ''),
 | 
					          _.result(this, 'template', ''),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,5 @@
 | 
				
			||||||
 | 
					/* global Whisper, Backbone, Mustache, _, $ */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 * Whisper.View
 | 
					 * Whisper.View
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
| 
						 | 
					@ -17,64 +19,57 @@
 | 
				
			||||||
 * 4. Provides some common functionality, e.g. confirmation dialog
 | 
					 * 4. Provides some common functionality, e.g. confirmation dialog
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Whisper.View = Backbone.View.extend(
 | 
					  Whisper.View = Backbone.View.extend(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      constructor: function() {
 | 
					      constructor(...params) {
 | 
				
			||||||
        Backbone.View.apply(this, arguments);
 | 
					        Backbone.View.call(this, ...params);
 | 
				
			||||||
        Mustache.parse(_.result(this, 'template'));
 | 
					        Mustache.parse(_.result(this, 'template'));
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      render_attributes: function() {
 | 
					      render_attributes() {
 | 
				
			||||||
        return _.result(this.model, 'attributes', {});
 | 
					        return _.result(this.model, 'attributes', {});
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      render_partials: function() {
 | 
					      render_partials() {
 | 
				
			||||||
        return Whisper.View.Templates;
 | 
					        return Whisper.View.Templates;
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      template: function() {
 | 
					      template() {
 | 
				
			||||||
        if (this.templateName) {
 | 
					        if (this.templateName) {
 | 
				
			||||||
          return Whisper.View.Templates[this.templateName];
 | 
					          return Whisper.View.Templates[this.templateName];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return '';
 | 
					        return '';
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      render: function() {
 | 
					      render() {
 | 
				
			||||||
        var attrs = _.result(this, 'render_attributes', {});
 | 
					        const attrs = _.result(this, 'render_attributes', {});
 | 
				
			||||||
        var template = _.result(this, 'template', '');
 | 
					        const template = _.result(this, 'template', '');
 | 
				
			||||||
        var partials = _.result(this, 'render_partials', '');
 | 
					        const partials = _.result(this, 'render_partials', '');
 | 
				
			||||||
        this.$el.html(Mustache.render(template, attrs, partials));
 | 
					        this.$el.html(Mustache.render(template, attrs, partials));
 | 
				
			||||||
        return this;
 | 
					        return this;
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      confirm: function(message, okText) {
 | 
					      confirm(message, okText) {
 | 
				
			||||||
        return new Promise(
 | 
					        return new Promise((resolve, reject) => {
 | 
				
			||||||
          function(resolve, reject) {
 | 
					          const dialog = new Whisper.ConfirmationDialogView({
 | 
				
			||||||
            var dialog = new Whisper.ConfirmationDialogView({
 | 
					            message,
 | 
				
			||||||
              message: message,
 | 
					            okText,
 | 
				
			||||||
              okText: okText,
 | 
					            resolve,
 | 
				
			||||||
              resolve: resolve,
 | 
					            reject,
 | 
				
			||||||
              reject: reject,
 | 
					 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
          this.$el.append(dialog.el);
 | 
					          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));
 | 
					 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      // Class attributes
 | 
					      // Class attributes
 | 
				
			||||||
      Templates: (function() {
 | 
					      Templates: (() => {
 | 
				
			||||||
        var templates = {};
 | 
					        const templates = {};
 | 
				
			||||||
        $('script[type="text/x-tmpl-mustache"]').each(function(i, el) {
 | 
					        $('script[type="text/x-tmpl-mustache"]').each((i, el) => {
 | 
				
			||||||
          var $el = $(el);
 | 
					          const $el = $(el);
 | 
				
			||||||
          var id = $el.attr('id');
 | 
					          const id = $el.attr('id');
 | 
				
			||||||
          templates[id] = $el.html();
 | 
					          templates[id] = $el.html();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        return templates;
 | 
					        return templates;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,16 @@
 | 
				
			||||||
 | 
					/* global Whisper */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line func-names
 | 
				
			||||||
(function() {
 | 
					(function() {
 | 
				
			||||||
  'use strict';
 | 
					  'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  window.Whisper = window.Whisper || {};
 | 
					  window.Whisper = window.Whisper || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var lastTime;
 | 
					  let lastTime;
 | 
				
			||||||
  var interval = 1000;
 | 
					  const interval = 1000;
 | 
				
			||||||
  var events;
 | 
					  let events;
 | 
				
			||||||
  function checkTime() {
 | 
					  function checkTime() {
 | 
				
			||||||
    var currentTime = Date.now();
 | 
					    const currentTime = Date.now();
 | 
				
			||||||
    if (currentTime > lastTime + interval * 2) {
 | 
					    if (currentTime > lastTime + interval * 2) {
 | 
				
			||||||
      events.trigger('timetravel');
 | 
					      events.trigger('timetravel');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -14,7 +18,7 @@
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Whisper.WallClockListener = {
 | 
					  Whisper.WallClockListener = {
 | 
				
			||||||
    init: function(_events) {
 | 
					    init(_events) {
 | 
				
			||||||
      events = _events;
 | 
					      events = _events;
 | 
				
			||||||
      lastTime = Date.now();
 | 
					      lastTime = Date.now();
 | 
				
			||||||
      setInterval(checkTime, interval);
 | 
					      setInterval(checkTime, interval);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,9 +30,8 @@
 | 
				
			||||||
    "test-node": "mocha --recursive test/app test/modules ts/test",
 | 
					    "test-node": "mocha --recursive test/app test/modules ts/test",
 | 
				
			||||||
    "test-node-coverage": "nyc --reporter=lcov --reporter=text mocha --recursive test/app test/modules ts/test",
 | 
					    "test-node-coverage": "nyc --reporter=lcov --reporter=text mocha --recursive test/app test/modules ts/test",
 | 
				
			||||||
    "eslint": "eslint .",
 | 
					    "eslint": "eslint .",
 | 
				
			||||||
    "jshint": "yarn grunt jshint",
 | 
					 | 
				
			||||||
    "lint": "yarn format --list-different && yarn lint-windows",
 | 
					    "lint": "yarn format --list-different && yarn lint-windows",
 | 
				
			||||||
    "lint-windows": "yarn eslint && yarn grunt lint && yarn tslint",
 | 
					    "lint-windows": "yarn eslint && yarn tslint",
 | 
				
			||||||
    "tslint": "tslint --format stylish --project .",
 | 
					    "tslint": "tslint --format stylish --project .",
 | 
				
			||||||
    "format": "prettier --write \"*.{css,js,json,md,scss,ts,tsx}\" \"./**/*.{css,js,json,md,scss,ts,tsx}\"",
 | 
					    "format": "prettier --write \"*.{css,js,json,md,scss,ts,tsx}\" \"./**/*.{css,js,json,md,scss,ts,tsx}\"",
 | 
				
			||||||
    "transpile": "tsc",
 | 
					    "transpile": "tsc",
 | 
				
			||||||
| 
						 | 
					@ -122,7 +121,6 @@
 | 
				
			||||||
    "grunt-cli": "^1.2.0",
 | 
					    "grunt-cli": "^1.2.0",
 | 
				
			||||||
    "grunt-contrib-concat": "^1.0.1",
 | 
					    "grunt-contrib-concat": "^1.0.1",
 | 
				
			||||||
    "grunt-contrib-copy": "^1.0.0",
 | 
					    "grunt-contrib-copy": "^1.0.0",
 | 
				
			||||||
    "grunt-contrib-jshint": "^1.1.0",
 | 
					 | 
				
			||||||
    "grunt-contrib-watch": "^1.0.0",
 | 
					    "grunt-contrib-watch": "^1.0.0",
 | 
				
			||||||
    "grunt-exec": "^3.0.0",
 | 
					    "grunt-exec": "^3.0.0",
 | 
				
			||||||
    "grunt-gitinfo": "^0.1.7",
 | 
					    "grunt-gitinfo": "^0.1.7",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,5 @@
 | 
				
			||||||
 | 
					/* global window */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { ipcRenderer } = require('electron');
 | 
					const { ipcRenderer } = require('electron');
 | 
				
			||||||
const url = require('url');
 | 
					const url = require('url');
 | 
				
			||||||
const i18n = require('./js/modules/i18n');
 | 
					const i18n = require('./js/modules/i18n');
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -41,11 +41,15 @@ _.set(defaultConfig, IMPORT_PATH, IMPORT_END_VALUE);
 | 
				
			||||||
console.log('prepare_import_build: updating package.json');
 | 
					console.log('prepare_import_build: updating package.json');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const MAC_ASSET_PATH = 'build.mac.artifactName';
 | 
					const MAC_ASSET_PATH = 'build.mac.artifactName';
 | 
				
			||||||
 | 
					// eslint-disable-next-line no-template-curly-in-string
 | 
				
			||||||
const MAC_ASSET_START_VALUE = '${name}-mac-${version}.${ext}';
 | 
					const MAC_ASSET_START_VALUE = '${name}-mac-${version}.${ext}';
 | 
				
			||||||
 | 
					// eslint-disable-next-line no-template-curly-in-string
 | 
				
			||||||
const MAC_ASSET_END_VALUE = '${name}-mac-${version}-import.${ext}';
 | 
					const MAC_ASSET_END_VALUE = '${name}-mac-${version}-import.${ext}';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const WIN_ASSET_PATH = 'build.win.artifactName';
 | 
					const WIN_ASSET_PATH = 'build.win.artifactName';
 | 
				
			||||||
 | 
					// eslint-disable-next-line no-template-curly-in-string
 | 
				
			||||||
const WIN_ASSET_START_VALUE = '${name}-win-${version}.${ext}';
 | 
					const WIN_ASSET_START_VALUE = '${name}-win-${version}.${ext}';
 | 
				
			||||||
 | 
					// eslint-disable-next-line no-template-curly-in-string
 | 
				
			||||||
const WIN_ASSET_END_VALUE = '${name}-win-${version}-import.${ext}';
 | 
					const WIN_ASSET_END_VALUE = '${name}-win-${version}-import.${ext}';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
checkValue(packageJson, MAC_ASSET_PATH, MAC_ASSET_START_VALUE);
 | 
					checkValue(packageJson, MAC_ASSET_PATH, MAC_ASSET_START_VALUE);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,5 @@
 | 
				
			||||||
 | 
					/* global window */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { ipcRenderer } = require('electron');
 | 
					const { ipcRenderer } = require('electron');
 | 
				
			||||||
const url = require('url');
 | 
					const url = require('url');
 | 
				
			||||||
const i18n = require('./js/modules/i18n');
 | 
					const i18n = require('./js/modules/i18n');
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
const webpack = require('webpack');
 | 
					 | 
				
			||||||
const path = require('path');
 | 
					const path = require('path');
 | 
				
			||||||
 | 
					// eslint-disable-next-line import/no-extraneous-dependencies
 | 
				
			||||||
const typescriptSupport = require('react-docgen-typescript');
 | 
					const typescriptSupport = require('react-docgen-typescript');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const propsParser = typescriptSupport.withCustomConfig('./tsconfig.json').parse;
 | 
					const propsParser = typescriptSupport.withCustomConfig('./tsconfig.json').parse;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -511,7 +511,6 @@
 | 
				
			||||||
  <script type='text/javascript' src='../js/views/new_group_update_view.js' data-cover></script>
 | 
					  <script type='text/javascript' src='../js/views/new_group_update_view.js' data-cover></script>
 | 
				
			||||||
  <script type="text/javascript" src="../js/views/group_update_view.js"></script>
 | 
					  <script type="text/javascript" src="../js/views/group_update_view.js"></script>
 | 
				
			||||||
  <script type='text/javascript' src='../js/views/attachment_view.js' data-cover></script>
 | 
					  <script type='text/javascript' src='../js/views/attachment_view.js' data-cover></script>
 | 
				
			||||||
  <script type='text/javascript' src='../js/views/error_view.js' data-cover></script>
 | 
					 | 
				
			||||||
  <script type='text/javascript' src='../js/views/timestamp_view.js' data-cover></script>
 | 
					  <script type='text/javascript' src='../js/views/timestamp_view.js' data-cover></script>
 | 
				
			||||||
  <script type='text/javascript' src='../js/views/message_view.js' data-cover></script>
 | 
					  <script type='text/javascript' src='../js/views/message_view.js' data-cover></script>
 | 
				
			||||||
  <script type='text/javascript' src='../js/views/key_verification_view.js' data-cover></script>
 | 
					  <script type='text/javascript' src='../js/views/key_verification_view.js' data-cover></script>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
import React from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
import classnames from 'classnames';
 | 
					import classNames from 'classnames';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Emojify } from './conversation/Emojify';
 | 
					import { Emojify } from './conversation/Emojify';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,7 +37,7 @@ export class ContactListItem extends React.Component<Props> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
        className={classnames(
 | 
					        className={classNames(
 | 
				
			||||||
          'module-contact-list-item__avatar-default',
 | 
					          'module-contact-list-item__avatar-default',
 | 
				
			||||||
          `module-contact-list-item__avatar-default--${color}`
 | 
					          `module-contact-list-item__avatar-default--${color}`
 | 
				
			||||||
        )}
 | 
					        )}
 | 
				
			||||||
| 
						 | 
					@ -77,7 +77,7 @@ export class ContactListItem extends React.Component<Props> {
 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
        role="button"
 | 
					        role="button"
 | 
				
			||||||
        onClick={onClick}
 | 
					        onClick={onClick}
 | 
				
			||||||
        className={classnames(
 | 
					        className={classNames(
 | 
				
			||||||
          'module-contact-list-item',
 | 
					          'module-contact-list-item',
 | 
				
			||||||
          onClick ? 'module-contact-list-item--with-click-handler' : null
 | 
					          onClick ? 'module-contact-list-item--with-click-handler' : null
 | 
				
			||||||
        )}
 | 
					        )}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -93,6 +93,7 @@ export class ContactDetail extends React.Component<Props> {
 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
        className="module-contact-detail__send-message"
 | 
					        className="module-contact-detail__send-message"
 | 
				
			||||||
        role="button"
 | 
					        role="button"
 | 
				
			||||||
 | 
					        // tslint:disable-next-line react-this-binding-issue
 | 
				
			||||||
        onClick={onClick}
 | 
					        onClick={onClick}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <button className="module-contact-detail__send-message__inner">
 | 
					        <button className="module-contact-detail__send-message__inner">
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
import React from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
import classnames from 'classnames';
 | 
					import classNames from 'classnames';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Contact, getName } from '../../types/Contact';
 | 
					import { Contact, getName } from '../../types/Contact';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,7 +30,7 @@ export class EmbeddedContact extends React.Component<Props> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
        className={classnames(
 | 
					        className={classNames(
 | 
				
			||||||
          'module-embedded-contact',
 | 
					          'module-embedded-contact',
 | 
				
			||||||
          withContentAbove
 | 
					          withContentAbove
 | 
				
			||||||
            ? 'module-embedded-contact--with-content-above'
 | 
					            ? 'module-embedded-contact--with-content-above'
 | 
				
			||||||
| 
						 | 
					@ -102,7 +102,7 @@ export function renderName({
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div
 | 
					    <div
 | 
				
			||||||
      className={classnames(
 | 
					      className={classNames(
 | 
				
			||||||
        `module-${module}__contact-name`,
 | 
					        `module-${module}__contact-name`,
 | 
				
			||||||
        isIncoming ? `module-${module}__contact-name--incoming` : null
 | 
					        isIncoming ? `module-${module}__contact-name--incoming` : null
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
| 
						 | 
					@ -127,7 +127,7 @@ export function renderContactShorthand({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div
 | 
					    <div
 | 
				
			||||||
      className={classnames(
 | 
					      className={classNames(
 | 
				
			||||||
        `module-${module}__contact-method`,
 | 
					        `module-${module}__contact-method`,
 | 
				
			||||||
        isIncoming ? `module-${module}__contact-method--incoming` : null
 | 
					        isIncoming ? `module-${module}__contact-method--incoming` : null
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,9 +1,13 @@
 | 
				
			||||||
import React from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
import classnames from 'classnames';
 | 
					import classNames from 'classnames';
 | 
				
			||||||
import moment from 'moment';
 | 
					import moment from 'moment';
 | 
				
			||||||
import { padStart } from 'lodash';
 | 
					import { padStart } from 'lodash';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { formatRelativeTime } from '../../util/formatRelativeTime';
 | 
					import { formatRelativeTime } from '../../util/formatRelativeTime';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  isImageTypeSupported,
 | 
				
			||||||
 | 
					  isVideoTypeSupported,
 | 
				
			||||||
 | 
					} from '../../util/GoogleChrome';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { MessageBody } from './MessageBody';
 | 
					import { MessageBody } from './MessageBody';
 | 
				
			||||||
import { Emojify } from './Emojify';
 | 
					import { Emojify } from './Emojify';
 | 
				
			||||||
| 
						 | 
					@ -69,15 +73,18 @@ interface Props {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function isImage(attachment?: Attachment) {
 | 
					function isImage(attachment?: Attachment) {
 | 
				
			||||||
  // TODO: exclude svg and tiff here
 | 
					 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    attachment && attachment.contentType && MIME.isImage(attachment.contentType)
 | 
					    attachment &&
 | 
				
			||||||
 | 
					    attachment.contentType &&
 | 
				
			||||||
 | 
					    isImageTypeSupported(attachment.contentType)
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function isVideo(attachment?: Attachment) {
 | 
					function isVideo(attachment?: Attachment) {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    attachment && attachment.contentType && MIME.isVideo(attachment.contentType)
 | 
					    attachment &&
 | 
				
			||||||
 | 
					    attachment.contentType &&
 | 
				
			||||||
 | 
					    isVideoTypeSupported(attachment.contentType)
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -143,7 +150,7 @@ export class Message extends React.Component<Props> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
        className={classnames(
 | 
					        className={classNames(
 | 
				
			||||||
          'module-message__metadata__timer',
 | 
					          'module-message__metadata__timer',
 | 
				
			||||||
          `module-message__metadata__timer--${bucket}`,
 | 
					          `module-message__metadata__timer--${bucket}`,
 | 
				
			||||||
          `module-message__metadata__timer--${direction}`,
 | 
					          `module-message__metadata__timer--${direction}`,
 | 
				
			||||||
| 
						 | 
					@ -179,7 +186,7 @@ export class Message extends React.Component<Props> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
        className={classnames(
 | 
					        className={classNames(
 | 
				
			||||||
          'module-message__metadata',
 | 
					          'module-message__metadata',
 | 
				
			||||||
          withImageNoCaption
 | 
					          withImageNoCaption
 | 
				
			||||||
            ? 'module-message__metadata--with-image-no-caption'
 | 
					            ? 'module-message__metadata--with-image-no-caption'
 | 
				
			||||||
| 
						 | 
					@ -187,7 +194,7 @@ export class Message extends React.Component<Props> {
 | 
				
			||||||
        )}
 | 
					        )}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <span
 | 
					        <span
 | 
				
			||||||
          className={classnames(
 | 
					          className={classNames(
 | 
				
			||||||
            'module-message__metadata__date',
 | 
					            'module-message__metadata__date',
 | 
				
			||||||
            `module-message__metadata__date--${direction}`,
 | 
					            `module-message__metadata__date--${direction}`,
 | 
				
			||||||
            withImageNoCaption
 | 
					            withImageNoCaption
 | 
				
			||||||
| 
						 | 
					@ -202,7 +209,7 @@ export class Message extends React.Component<Props> {
 | 
				
			||||||
        <span className="module-message__metadata__spacer" />
 | 
					        <span className="module-message__metadata__spacer" />
 | 
				
			||||||
        {direction === 'outgoing' ? (
 | 
					        {direction === 'outgoing' ? (
 | 
				
			||||||
          <div
 | 
					          <div
 | 
				
			||||||
            className={classnames(
 | 
					            className={classNames(
 | 
				
			||||||
              'module-message__metadata__status-icon',
 | 
					              'module-message__metadata__status-icon',
 | 
				
			||||||
              `module-message__metadata__status-icon-${status}`,
 | 
					              `module-message__metadata__status-icon-${status}`,
 | 
				
			||||||
              status === 'read'
 | 
					              status === 'read'
 | 
				
			||||||
| 
						 | 
					@ -251,6 +258,7 @@ export class Message extends React.Component<Props> {
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // tslint:disable-next-line max-func-body-length
 | 
				
			||||||
  public renderAttachment() {
 | 
					  public renderAttachment() {
 | 
				
			||||||
    const {
 | 
					    const {
 | 
				
			||||||
      i18n,
 | 
					      i18n,
 | 
				
			||||||
| 
						 | 
					@ -277,7 +285,7 @@ export class Message extends React.Component<Props> {
 | 
				
			||||||
      return (
 | 
					      return (
 | 
				
			||||||
        <div className="module-message__attachment-container">
 | 
					        <div className="module-message__attachment-container">
 | 
				
			||||||
          <img
 | 
					          <img
 | 
				
			||||||
            className={classnames(
 | 
					            className={classNames(
 | 
				
			||||||
              'module-message__img-attachment',
 | 
					              'module-message__img-attachment',
 | 
				
			||||||
              withCaption
 | 
					              withCaption
 | 
				
			||||||
                ? 'module-message__img-attachment--with-content-below'
 | 
					                ? 'module-message__img-attachment--with-content-below'
 | 
				
			||||||
| 
						 | 
					@ -299,7 +307,7 @@ export class Message extends React.Component<Props> {
 | 
				
			||||||
      return (
 | 
					      return (
 | 
				
			||||||
        <video
 | 
					        <video
 | 
				
			||||||
          controls={true}
 | 
					          controls={true}
 | 
				
			||||||
          className={classnames(
 | 
					          className={classNames(
 | 
				
			||||||
            'module-message__img-attachment',
 | 
					            'module-message__img-attachment',
 | 
				
			||||||
            withCaption
 | 
					            withCaption
 | 
				
			||||||
              ? 'module-message__img-attachment--with-content-below'
 | 
					              ? 'module-message__img-attachment--with-content-below'
 | 
				
			||||||
| 
						 | 
					@ -316,7 +324,7 @@ export class Message extends React.Component<Props> {
 | 
				
			||||||
      return (
 | 
					      return (
 | 
				
			||||||
        <audio
 | 
					        <audio
 | 
				
			||||||
          controls={true}
 | 
					          controls={true}
 | 
				
			||||||
          className={classnames(
 | 
					          className={classNames(
 | 
				
			||||||
            'module-message__audio-attachment',
 | 
					            'module-message__audio-attachment',
 | 
				
			||||||
            withContentBelow
 | 
					            withContentBelow
 | 
				
			||||||
              ? 'module-message__audio-attachment--with-content-below'
 | 
					              ? 'module-message__audio-attachment--with-content-below'
 | 
				
			||||||
| 
						 | 
					@ -335,7 +343,7 @@ export class Message extends React.Component<Props> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return (
 | 
					      return (
 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
          className={classnames(
 | 
					          className={classNames(
 | 
				
			||||||
            'module-message__generic-attachment',
 | 
					            'module-message__generic-attachment',
 | 
				
			||||||
            withContentBelow
 | 
					            withContentBelow
 | 
				
			||||||
              ? 'module-message__generic-attachment--with-content-below'
 | 
					              ? 'module-message__generic-attachment--with-content-below'
 | 
				
			||||||
| 
						 | 
					@ -354,7 +362,7 @@ export class Message extends React.Component<Props> {
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          <div className="module-message__generic-attachment__text">
 | 
					          <div className="module-message__generic-attachment__text">
 | 
				
			||||||
            <div
 | 
					            <div
 | 
				
			||||||
              className={classnames(
 | 
					              className={classNames(
 | 
				
			||||||
                'module-message__generic-attachment__file-name',
 | 
					                'module-message__generic-attachment__file-name',
 | 
				
			||||||
                `module-message__generic-attachment__file-name--${direction}`
 | 
					                `module-message__generic-attachment__file-name--${direction}`
 | 
				
			||||||
              )}
 | 
					              )}
 | 
				
			||||||
| 
						 | 
					@ -362,7 +370,7 @@ export class Message extends React.Component<Props> {
 | 
				
			||||||
              {fileName}
 | 
					              {fileName}
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div
 | 
					            <div
 | 
				
			||||||
              className={classnames(
 | 
					              className={classNames(
 | 
				
			||||||
                'module-message__generic-attachment__file-size',
 | 
					                'module-message__generic-attachment__file-size',
 | 
				
			||||||
                `module-message__generic-attachment__file-size--${direction}`
 | 
					                `module-message__generic-attachment__file-size--${direction}`
 | 
				
			||||||
              )}
 | 
					              )}
 | 
				
			||||||
| 
						 | 
					@ -503,7 +511,7 @@ export class Message extends React.Component<Props> {
 | 
				
			||||||
    if (!authorAvatarPath) {
 | 
					    if (!authorAvatarPath) {
 | 
				
			||||||
      return (
 | 
					      return (
 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
          className={classnames(
 | 
					          className={classNames(
 | 
				
			||||||
            'module-message__author-default-avatar',
 | 
					            'module-message__author-default-avatar',
 | 
				
			||||||
            `module-message__author-default-avatar--${color}`
 | 
					            `module-message__author-default-avatar--${color}`
 | 
				
			||||||
          )}
 | 
					          )}
 | 
				
			||||||
| 
						 | 
					@ -529,7 +537,7 @@ export class Message extends React.Component<Props> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
        className={classnames(
 | 
					        className={classNames(
 | 
				
			||||||
          'module-message__text',
 | 
					          'module-message__text',
 | 
				
			||||||
          `module-message__text--${direction}`
 | 
					          `module-message__text--${direction}`
 | 
				
			||||||
        )}
 | 
					        )}
 | 
				
			||||||
| 
						 | 
					@ -557,7 +565,7 @@ export class Message extends React.Component<Props> {
 | 
				
			||||||
      <li>
 | 
					      <li>
 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
          id={id}
 | 
					          id={id}
 | 
				
			||||||
          className={classnames(
 | 
					          className={classNames(
 | 
				
			||||||
            'module-message',
 | 
					            'module-message',
 | 
				
			||||||
            `module-message--${direction}`,
 | 
					            `module-message--${direction}`,
 | 
				
			||||||
            imageAndNothingElse ? 'module-message--with-image-only' : null,
 | 
					            imageAndNothingElse ? 'module-message--with-image-only' : null,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
import React from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
import classnames from 'classnames';
 | 
					import classNames from 'classnames';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface Props {
 | 
					interface Props {
 | 
				
			||||||
  type: string;
 | 
					  type: string;
 | 
				
			||||||
| 
						 | 
					@ -20,7 +20,7 @@ export class Notification extends React.Component<Props> {
 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
        role="button"
 | 
					        role="button"
 | 
				
			||||||
        onClick={onClick}
 | 
					        onClick={onClick}
 | 
				
			||||||
        className={classnames(
 | 
					        className={classNames(
 | 
				
			||||||
          'module-notification',
 | 
					          'module-notification',
 | 
				
			||||||
          onClick ? 'module-notification--with-click-handler' : null
 | 
					          onClick ? 'module-notification--with-click-handler' : null
 | 
				
			||||||
        )}
 | 
					        )}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
// tslint:disable:react-this-binding-issue
 | 
					// tslint:disable:react-this-binding-issue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
import classnames from 'classnames';
 | 
					import classNames from 'classnames';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import * as MIME from '../../../ts/types/MIME';
 | 
					import * as MIME from '../../../ts/types/MIME';
 | 
				
			||||||
import * as GoogleChrome from '../../../ts/util/GoogleChrome';
 | 
					import * as GoogleChrome from '../../../ts/util/GoogleChrome';
 | 
				
			||||||
| 
						 | 
					@ -89,7 +89,7 @@ export class Quote extends React.Component<Props> {
 | 
				
			||||||
      <div className="module-quote__icon-container__inner">
 | 
					      <div className="module-quote__icon-container__inner">
 | 
				
			||||||
        <div className="module-quote__icon-container__circle-background">
 | 
					        <div className="module-quote__icon-container__circle-background">
 | 
				
			||||||
          <div
 | 
					          <div
 | 
				
			||||||
            className={classnames(
 | 
					            className={classNames(
 | 
				
			||||||
              'module-quote__icon-container__icon',
 | 
					              'module-quote__icon-container__icon',
 | 
				
			||||||
              `module-quote__icon-container__icon--${icon}`
 | 
					              `module-quote__icon-container__icon--${icon}`
 | 
				
			||||||
            )}
 | 
					            )}
 | 
				
			||||||
| 
						 | 
					@ -112,7 +112,7 @@ export class Quote extends React.Component<Props> {
 | 
				
			||||||
        <div className="module-quote__icon-container__inner">
 | 
					        <div className="module-quote__icon-container__inner">
 | 
				
			||||||
          <div className="module-quote__icon-container__circle-background">
 | 
					          <div className="module-quote__icon-container__circle-background">
 | 
				
			||||||
            <div
 | 
					            <div
 | 
				
			||||||
              className={classnames(
 | 
					              className={classNames(
 | 
				
			||||||
                'module-quote__icon-container__icon',
 | 
					                'module-quote__icon-container__icon',
 | 
				
			||||||
                `module-quote__icon-container__icon--${icon}`
 | 
					                `module-quote__icon-container__icon--${icon}`
 | 
				
			||||||
              )}
 | 
					              )}
 | 
				
			||||||
| 
						 | 
					@ -263,7 +263,7 @@ export class Quote extends React.Component<Props> {
 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
        onClick={onClick}
 | 
					        onClick={onClick}
 | 
				
			||||||
        role="button"
 | 
					        role="button"
 | 
				
			||||||
        className={classnames(
 | 
					        className={classNames(
 | 
				
			||||||
          'module-quote',
 | 
					          'module-quote',
 | 
				
			||||||
          isIncoming ? 'module-quote--incoming' : 'module-quote--outgoing',
 | 
					          isIncoming ? 'module-quote--incoming' : 'module-quote--outgoing',
 | 
				
			||||||
          !isIncoming ? `module-quote--outgoing-${color}` : null,
 | 
					          !isIncoming ? `module-quote--outgoing-${color}` : null,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										110
									
								
								yarn.lock
									
										
									
									
									
								
							
							
						
						
									
										110
									
								
								yarn.lock
									
										
									
									
									
								
							| 
						 | 
					@ -1401,13 +1401,6 @@ cli-width@^2.0.0:
 | 
				
			||||||
  version "2.1.0"
 | 
					  version "2.1.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a"
 | 
					  resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
cli@~1.0.0:
 | 
					 | 
				
			||||||
  version "1.0.1"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/cli/-/cli-1.0.1.tgz#22817534f24bfa4950c34d532d48ecbc621b8c14"
 | 
					 | 
				
			||||||
  dependencies:
 | 
					 | 
				
			||||||
    exit "0.1.2"
 | 
					 | 
				
			||||||
    glob "^7.1.1"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
clipboard-copy@^2.0.0:
 | 
					clipboard-copy@^2.0.0:
 | 
				
			||||||
  version "2.0.0"
 | 
					  version "2.0.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/clipboard-copy/-/clipboard-copy-2.0.0.tgz#663abcd8be9c641de6e92a2eb9afef6e0afa727e"
 | 
					  resolved "https://registry.yarnpkg.com/clipboard-copy/-/clipboard-copy-2.0.0.tgz#663abcd8be9c641de6e92a2eb9afef6e0afa727e"
 | 
				
			||||||
| 
						 | 
					@ -1678,7 +1671,7 @@ connect-history-api-fallback@^1.3.0:
 | 
				
			||||||
  version "1.5.0"
 | 
					  version "1.5.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz#b06873934bc5e344fef611a196a6faae0aee015a"
 | 
					  resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz#b06873934bc5e344fef611a196a6faae0aee015a"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
console-browserify@1.1.x, console-browserify@^1.1.0:
 | 
					console-browserify@^1.1.0:
 | 
				
			||||||
  version "1.1.0"
 | 
					  version "1.1.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10"
 | 
					  resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10"
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
| 
						 | 
					@ -2294,13 +2287,6 @@ doctrine@^2.0.2:
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    esutils "^2.0.2"
 | 
					    esutils "^2.0.2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dom-serializer@0:
 | 
					 | 
				
			||||||
  version "0.1.0"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
 | 
					 | 
				
			||||||
  dependencies:
 | 
					 | 
				
			||||||
    domelementtype "~1.1.1"
 | 
					 | 
				
			||||||
    entities "~1.1.1"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
dom-walk@^0.1.0:
 | 
					dom-walk@^0.1.0:
 | 
				
			||||||
  version "0.1.1"
 | 
					  version "0.1.1"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018"
 | 
					  resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018"
 | 
				
			||||||
| 
						 | 
					@ -2309,27 +2295,6 @@ domain-browser@^1.1.1:
 | 
				
			||||||
  version "1.2.0"
 | 
					  version "1.2.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
 | 
					  resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
domelementtype@1:
 | 
					 | 
				
			||||||
  version "1.3.0"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
domelementtype@~1.1.1:
 | 
					 | 
				
			||||||
  version "1.1.3"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
domhandler@2.3:
 | 
					 | 
				
			||||||
  version "2.3.0"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.3.0.tgz#2de59a0822d5027fabff6f032c2b25a2a8abe738"
 | 
					 | 
				
			||||||
  dependencies:
 | 
					 | 
				
			||||||
    domelementtype "1"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
domutils@1.5:
 | 
					 | 
				
			||||||
  version "1.5.1"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"
 | 
					 | 
				
			||||||
  dependencies:
 | 
					 | 
				
			||||||
    dom-serializer "0"
 | 
					 | 
				
			||||||
    domelementtype "1"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
dot-prop@^4.1.0:
 | 
					dot-prop@^4.1.0:
 | 
				
			||||||
  version "4.1.1"
 | 
					  version "4.1.1"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.1.1.tgz#a8493f0b7b5eeec82525b5c7587fa7de7ca859c1"
 | 
					  resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.1.1.tgz#a8493f0b7b5eeec82525b5c7587fa7de7ca859c1"
 | 
				
			||||||
| 
						 | 
					@ -2640,14 +2605,6 @@ enhanced-resolve@^4.0.0:
 | 
				
			||||||
    memory-fs "^0.4.0"
 | 
					    memory-fs "^0.4.0"
 | 
				
			||||||
    tapable "^1.0.0"
 | 
					    tapable "^1.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
entities@1.0:
 | 
					 | 
				
			||||||
  version "1.0.0"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/entities/-/entities-1.0.0.tgz#b2987aa3821347fcde642b24fdfc9e4fb712bf26"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
entities@~1.1.1:
 | 
					 | 
				
			||||||
  version "1.1.1"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
env-paths@^1.0.0:
 | 
					env-paths@^1.0.0:
 | 
				
			||||||
  version "1.0.0"
 | 
					  version "1.0.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0"
 | 
					  resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0"
 | 
				
			||||||
| 
						 | 
					@ -2931,7 +2888,7 @@ exif-parser@^0.1.9:
 | 
				
			||||||
  version "0.1.9"
 | 
					  version "0.1.9"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/exif-parser/-/exif-parser-0.1.9.tgz#1d087e05fd2b079e3a8eaf8ff249978cb5f6fba7"
 | 
					  resolved "https://registry.yarnpkg.com/exif-parser/-/exif-parser-0.1.9.tgz#1d087e05fd2b079e3a8eaf8ff249978cb5f6fba7"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
exit@0.1.2, exit@0.1.x, exit@~0.1.1:
 | 
					exit@~0.1.1:
 | 
				
			||||||
  version "0.1.2"
 | 
					  version "0.1.2"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
 | 
					  resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3774,14 +3731,6 @@ grunt-contrib-copy@^1.0.0:
 | 
				
			||||||
    chalk "^1.1.1"
 | 
					    chalk "^1.1.1"
 | 
				
			||||||
    file-sync-cmp "^0.1.0"
 | 
					    file-sync-cmp "^0.1.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
grunt-contrib-jshint@^1.1.0:
 | 
					 | 
				
			||||||
  version "1.1.0"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/grunt-contrib-jshint/-/grunt-contrib-jshint-1.1.0.tgz#369d909b2593c40e8be79940b21340850c7939ac"
 | 
					 | 
				
			||||||
  dependencies:
 | 
					 | 
				
			||||||
    chalk "^1.1.1"
 | 
					 | 
				
			||||||
    hooker "^0.2.3"
 | 
					 | 
				
			||||||
    jshint "~2.9.4"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
grunt-contrib-watch@^1.0.0:
 | 
					grunt-contrib-watch@^1.0.0:
 | 
				
			||||||
  version "1.0.0"
 | 
					  version "1.0.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/grunt-contrib-watch/-/grunt-contrib-watch-1.0.0.tgz#84a1a7a1d6abd26ed568413496c73133e990018f"
 | 
					  resolved "https://registry.yarnpkg.com/grunt-contrib-watch/-/grunt-contrib-watch-1.0.0.tgz#84a1a7a1d6abd26ed568413496c73133e990018f"
 | 
				
			||||||
| 
						 | 
					@ -4051,7 +4000,7 @@ homedir-polyfill@^1.0.1:
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    parse-passwd "^1.0.0"
 | 
					    parse-passwd "^1.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
hooker@^0.2.3, hooker@~0.2.3:
 | 
					hooker@~0.2.3:
 | 
				
			||||||
  version "0.2.3"
 | 
					  version "0.2.3"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/hooker/-/hooker-0.2.3.tgz#b834f723cc4a242aa65963459df6d984c5d3d959"
 | 
					  resolved "https://registry.yarnpkg.com/hooker/-/hooker-0.2.3.tgz#b834f723cc4a242aa65963459df6d984c5d3d959"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4080,16 +4029,6 @@ html-entities@^1.2.0:
 | 
				
			||||||
  version "1.2.1"
 | 
					  version "1.2.1"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f"
 | 
					  resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
htmlparser2@3.8.x:
 | 
					 | 
				
			||||||
  version "3.8.3"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.8.3.tgz#996c28b191516a8be86501a7d79757e5c70c1068"
 | 
					 | 
				
			||||||
  dependencies:
 | 
					 | 
				
			||||||
    domelementtype "1"
 | 
					 | 
				
			||||||
    domhandler "2.3"
 | 
					 | 
				
			||||||
    domutils "1.5"
 | 
					 | 
				
			||||||
    entities "1.0"
 | 
					 | 
				
			||||||
    readable-stream "1.1"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
http-cache-semantics@3.8.1:
 | 
					http-cache-semantics@3.8.1:
 | 
				
			||||||
  version "3.8.1"
 | 
					  version "3.8.1"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2"
 | 
					  resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2"
 | 
				
			||||||
| 
						 | 
					@ -4845,19 +4784,6 @@ jsesc@~0.5.0:
 | 
				
			||||||
  version "0.5.0"
 | 
					  version "0.5.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
 | 
					  resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jshint@~2.9.4:
 | 
					 | 
				
			||||||
  version "2.9.4"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/jshint/-/jshint-2.9.4.tgz#5e3ba97848d5290273db514aee47fe24cf592934"
 | 
					 | 
				
			||||||
  dependencies:
 | 
					 | 
				
			||||||
    cli "~1.0.0"
 | 
					 | 
				
			||||||
    console-browserify "1.1.x"
 | 
					 | 
				
			||||||
    exit "0.1.x"
 | 
					 | 
				
			||||||
    htmlparser2 "3.8.x"
 | 
					 | 
				
			||||||
    lodash "3.7.x"
 | 
					 | 
				
			||||||
    minimatch "~3.0.2"
 | 
					 | 
				
			||||||
    shelljs "0.3.x"
 | 
					 | 
				
			||||||
    strip-json-comments "1.0.x"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
json-buffer@3.0.0:
 | 
					json-buffer@3.0.0:
 | 
				
			||||||
  version "3.0.0"
 | 
					  version "3.0.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
 | 
					  resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
 | 
				
			||||||
| 
						 | 
					@ -5170,10 +5096,6 @@ lodash.uniq@^4.5.0:
 | 
				
			||||||
  version "4.5.0"
 | 
					  version "4.5.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
 | 
					  resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
lodash@3.7.x:
 | 
					 | 
				
			||||||
  version "3.7.0"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.7.0.tgz#3678bd8ab995057c07ade836ed2ef087da811d45"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
lodash@^3.10.1, lodash@~3.10.1:
 | 
					lodash@^3.10.1, lodash@~3.10.1:
 | 
				
			||||||
  version "3.10.1"
 | 
					  version "3.10.1"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
 | 
					  resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
 | 
				
			||||||
| 
						 | 
					@ -7240,15 +7162,6 @@ read-pkg@^2.0.0:
 | 
				
			||||||
    string_decoder "~1.0.3"
 | 
					    string_decoder "~1.0.3"
 | 
				
			||||||
    util-deprecate "~1.0.1"
 | 
					    util-deprecate "~1.0.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
readable-stream@1.1, readable-stream@^1.1.8, readable-stream@~1.1.9:
 | 
					 | 
				
			||||||
  version "1.1.13"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e"
 | 
					 | 
				
			||||||
  dependencies:
 | 
					 | 
				
			||||||
    core-util-is "~1.0.0"
 | 
					 | 
				
			||||||
    inherits "~2.0.1"
 | 
					 | 
				
			||||||
    isarray "0.0.1"
 | 
					 | 
				
			||||||
    string_decoder "~0.10.x"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
readable-stream@1.1.x:
 | 
					readable-stream@1.1.x:
 | 
				
			||||||
  version "1.1.14"
 | 
					  version "1.1.14"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
 | 
					  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
 | 
				
			||||||
| 
						 | 
					@ -7270,6 +7183,15 @@ readable-stream@2, readable-stream@^2.2.2:
 | 
				
			||||||
    string_decoder "~1.0.3"
 | 
					    string_decoder "~1.0.3"
 | 
				
			||||||
    util-deprecate "~1.0.1"
 | 
					    util-deprecate "~1.0.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					readable-stream@^1.1.8, readable-stream@~1.1.9:
 | 
				
			||||||
 | 
					  version "1.1.13"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e"
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    core-util-is "~1.0.0"
 | 
				
			||||||
 | 
					    inherits "~2.0.1"
 | 
				
			||||||
 | 
					    isarray "0.0.1"
 | 
				
			||||||
 | 
					    string_decoder "~0.10.x"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@~2.0.0:
 | 
					readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@~2.0.0:
 | 
				
			||||||
  version "2.0.6"
 | 
					  version "2.0.6"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e"
 | 
					  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e"
 | 
				
			||||||
| 
						 | 
					@ -7859,10 +7781,6 @@ shell-quote@1.6.1:
 | 
				
			||||||
    array-reduce "~0.0.0"
 | 
					    array-reduce "~0.0.0"
 | 
				
			||||||
    jsonify "~0.0.0"
 | 
					    jsonify "~0.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
shelljs@0.3.x:
 | 
					 | 
				
			||||||
  version "0.3.0"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.3.0.tgz#3596e6307a781544f591f37da618360f31db57b1"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
signal-exit@^3.0.0, signal-exit@^3.0.1, signal-exit@^3.0.2:
 | 
					signal-exit@^3.0.0, signal-exit@^3.0.1, signal-exit@^3.0.2:
 | 
				
			||||||
  version "3.0.2"
 | 
					  version "3.0.2"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
 | 
					  resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
 | 
				
			||||||
| 
						 | 
					@ -8338,10 +8256,6 @@ strip-indent@^1.0.1:
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    get-stdin "^4.0.1"
 | 
					    get-stdin "^4.0.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
strip-json-comments@1.0.x:
 | 
					 | 
				
			||||||
  version "1.0.4"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
strip-json-comments@~2.0.1:
 | 
					strip-json-comments@~2.0.1:
 | 
				
			||||||
  version "2.0.1"
 | 
					  version "2.0.1"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
 | 
					  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue