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