Upgrade Prettier

This commit is contained in:
Ken Powers 2020-01-08 12:44:54 -05:00 committed by Scott Nonnenberg
parent d14c8e2277
commit 0d3b390129
57 changed files with 1074 additions and 1574 deletions

View File

@ -12,7 +12,7 @@ locations if you have a question or comment:
Lastly, be sure to preview your issue before saving. Thanks! Lastly, be sure to preview your issue before saving. Thanks!
--> -->
* [ ] I have searched open and closed issues for duplicates - [ ] I have searched open and closed issues for duplicates
<!-- <!--
You can search all issues here: You can search all issues here:
https://github.com/signalapp/Signal-Desktop/issues?utf8=%E2%9C%93&q=is%3Aissue https://github.com/signalapp/Signal-Desktop/issues?utf8=%E2%9C%93&q=is%3Aissue

View File

@ -9,16 +9,16 @@ Remember, you can preview this before saving it.
### First time contributor checklist: ### First time contributor checklist:
* [ ] I have read the [README](https://github.com/signalapp/Signal-Desktop/blob/master/README.md) and [Contributor Guidelines](https://github.com/signalapp/Signal-Desktop/blob/master/CONTRIBUTING.md) - [ ] I have read the [README](https://github.com/signalapp/Signal-Desktop/blob/master/README.md) and [Contributor Guidelines](https://github.com/signalapp/Signal-Desktop/blob/master/CONTRIBUTING.md)
* [ ] I have signed the [Contributor Licence Agreement](https://signal.org/cla/) - [ ] I have signed the [Contributor Licence Agreement](https://signal.org/cla/)
### Contributor checklist: ### Contributor checklist:
* [ ] My contribution is **not** related to translations. _Please submit translation changes via our [Signal Desktop Transifex project](https://www.transifex.com/signalapp/signal-desktop/)._ - [ ] My contribution is **not** related to translations. _Please submit translation changes via our [Signal Desktop Transifex project](https://www.transifex.com/signalapp/signal-desktop/)._
* [ ] My commits are in nice logical chunks with [good commit messages](http://chris.beams.io/posts/git-commit/) - [ ] My commits are in nice logical chunks with [good commit messages](http://chris.beams.io/posts/git-commit/)
* [ ] My changes are [rebased](https://medium.freecodecamp.org/git-rebase-and-the-golden-rule-explained-70715eccc372) on the latest [`development`](https://github.com/signalapp/Signal-Desktop/tree/development) branch - [ ] My changes are [rebased](https://medium.freecodecamp.org/git-rebase-and-the-golden-rule-explained-70715eccc372) on the latest [`development`](https://github.com/signalapp/Signal-Desktop/tree/development) branch
* [ ] A `yarn ready` run passes successfully ([more about tests here](https://github.com/signalapp/Signal-Desktop/blob/master/CONTRIBUTING.md#tests)) - [ ] A `yarn ready` run passes successfully ([more about tests here](https://github.com/signalapp/Signal-Desktop/blob/master/CONTRIBUTING.md#tests))
* [ ] My changes are ready to be shipped to users - [ ] My changes are ready to be shipped to users
### Description ### Description

View File

@ -31,9 +31,9 @@ Then you need `git`, if you don't have that yet: https://git-scm.com/
### Windows ### Windows
1. **Windows 7 only:** 1. **Windows 7 only:**
* Install Microsoft .NET Framework 4.5.1: - Install Microsoft .NET Framework 4.5.1:
https://www.microsoft.com/en-us/download/details.aspx?id=40773 https://www.microsoft.com/en-us/download/details.aspx?id=40773
* Install Windows SDK version 8.1: https://developer.microsoft.com/en-us/windows/downloads/sdk-archive - Install Windows SDK version 8.1: https://developer.microsoft.com/en-us/windows/downloads/sdk-archive
1. Install _Windows Build Tools_: Open the [Command Prompt (`cmd.exe`) as Administrator](<https://technet.microsoft.com/en-us/library/cc947813(v=ws.10).aspx>) 1. Install _Windows Build Tools_: Open the [Command Prompt (`cmd.exe`) as Administrator](<https://technet.microsoft.com/en-us/library/cc947813(v=ws.10).aspx>)
and run: `npm install --global --production --add-python-to-path windows-build-tools` and run: `npm install --global --production --add-python-to-path windows-build-tools`
@ -115,9 +115,9 @@ Desktop to populate your testing application!
First, find your application data: First, find your application data:
* macOS: `~/Library/Application Support/Signal` - macOS: `~/Library/Application Support/Signal`
* Linux: `~/.config/Signal` - Linux: `~/.config/Signal`
* Windows 10: `C:\Users\<YourName>\AppData\Roaming\Signal` - Windows 10: `C:\Users\<YourName>\AppData\Roaming\Signal`
Now make a copy of this production data directory in the same place, and call it Now make a copy of this production data directory in the same place, and call it
`Signal-development`. Now start up the development version of the app as normal, `Signal-development`. Now start up the development version of the app as normal,
@ -189,31 +189,31 @@ the report with `yarn open-coverage`.
So you wanna make a pull request? Please observe the following guidelines. So you wanna make a pull request? Please observe the following guidelines.
* First, make sure that your `yarn ready` run passes - it's very similar to what our - First, make sure that your `yarn ready` run passes - it's very similar to what our
Continuous Integration servers do to test the app. Continuous Integration servers do to test the app.
* Please do not submit pull requests for translation fixes. Anyone can update - Please do not submit pull requests for translation fixes. Anyone can update
the translations in the translations in
[Transifex](https://www.transifex.com/projects/p/signal-desktop). [Transifex](https://www.transifex.com/projects/p/signal-desktop).
* Never use plain strings right in the source code - pull them from `messages.json`! - Never use plain strings right in the source code - pull them from `messages.json`!
You **only** need to modify the default locale You **only** need to modify the default locale
[`_locales/en/messages.json`](_locales/en/messages.json). Other locales are generated [`_locales/en/messages.json`](_locales/en/messages.json). Other locales are generated
automatically based on that file and then periodically uploaded to Transifex for automatically based on that file and then periodically uploaded to Transifex for
translation. translation.
* [Rebase](https://nathanleclaire.com/blog/2014/09/14/dont-be-scared-of-git-rebase/) your - [Rebase](https://nathanleclaire.com/blog/2014/09/14/dont-be-scared-of-git-rebase/) your
changes on the latest `development` branch, resolving any conflicts. changes on the latest `development` branch, resolving any conflicts.
This ensures that your changes will merge cleanly when you open your PR. This ensures that your changes will merge cleanly when you open your PR.
* Be sure to add and run tests! - Be sure to add and run tests!
* Make sure the diff between our master and your branch contains only the - Make sure the diff between our master and your branch contains only the
minimal set of changes needed to implement your feature or bugfix. This will minimal set of changes needed to implement your feature or bugfix. This will
make it easier for the person reviewing your code to approve the changes. make it easier for the person reviewing your code to approve the changes.
Please do not submit a PR with commented out code or unfinished features. Please do not submit a PR with commented out code or unfinished features.
* Avoid meaningless or too-granular commits. If your branch contains commits like - Avoid meaningless or too-granular commits. If your branch contains commits like
the lines of "Oops, reverted this change" or "Just experimenting, will the lines of "Oops, reverted this change" or "Just experimenting, will
delete this later", please [squash or rebase those changes away](https://robots.thoughtbot.com/git-interactive-rebase-squash-amend-rewriting-history). delete this later", please [squash or rebase those changes away](https://robots.thoughtbot.com/git-interactive-rebase-squash-amend-rewriting-history).
* Don't have too few commits. If you have a complicated or long lived feature - Don't have too few commits. If you have a complicated or long lived feature
branch, it may make sense to break the changes up into logical atomic chunks branch, it may make sense to break the changes up into logical atomic chunks
to aid in the review process. to aid in the review process.
* Provide a well written and nicely formatted commit message. See [this - Provide a well written and nicely formatted commit message. See [this
link](http://chris.beams.io/posts/git-commit/) link](http://chris.beams.io/posts/git-commit/)
for some tips on formatting. As far as content, try to include in your for some tips on formatting. As far as content, try to include in your
summary summary

View File

@ -150,18 +150,12 @@ module.exports = grunt => {
}, },
'test-release': { 'test-release': {
osx: { osx: {
archive: `mac/${ archive: `mac/${packageJson.productName}.app/Contents/Resources/app.asar`,
packageJson.productName exe: `mac/${packageJson.productName}.app/Contents/MacOS/${packageJson.productName}`,
}.app/Contents/Resources/app.asar`,
exe: `mac/${packageJson.productName}.app/Contents/MacOS/${
packageJson.productName
}`,
}, },
mas: { mas: {
archive: 'mas/Signal.app/Contents/Resources/app.asar', archive: 'mas/Signal.app/Contents/Resources/app.asar',
exe: `mas/${packageJson.productName}.app/Contents/MacOS/${ exe: `mas/${packageJson.productName}.app/Contents/MacOS/${packageJson.productName}`,
packageJson.productName
}`,
}, },
linux: { linux: {
archive: 'linux-unpacked/resources/app.asar', archive: 'linux-unpacked/resources/app.asar',

View File

@ -13,9 +13,9 @@ or [iOS](https://github.com/signalapp/Signal-iOS).
You can install the beta version of Signal Desktop alongside the production version. The beta uses different data and install locations. You can install the beta version of Signal Desktop alongside the production version. The beta uses different data and install locations.
* _Windows:_ First, download [this file](https://updates.signal.org/desktop/beta.yml) and look for the `url` property that specifies the location for the latest beta installer. Download the installer by constructing a final URL that looks like this: `https://updates.signal.org/desktop/<installer location>`. Then run the installer. - _Windows:_ First, download [this file](https://updates.signal.org/desktop/beta.yml) and look for the `url` property that specifies the location for the latest beta installer. Download the installer by constructing a final URL that looks like this: `https://updates.signal.org/desktop/<installer location>`. Then run the installer.
* _macOS:_ First, download [this file](https://updates.signal.org/desktop/beta-mac.yml) and look for the `url` property that specifies the location for the latest beta installer. Download the installer by constructing a final URL that looks like this: `https://updates.signal.org/desktop/<package location>`. Then unzip that package and copy the `.app` file into the `/Applications` folder using Finder. - _macOS:_ First, download [this file](https://updates.signal.org/desktop/beta-mac.yml) and look for the `url` property that specifies the location for the latest beta installer. Download the installer by constructing a final URL that looks like this: `https://updates.signal.org/desktop/<package location>`. Then unzip that package and copy the `.app` file into the `/Applications` folder using Finder.
* _Linux:_ Follow the production instructions to set up the APT repository and run `apt install signal-desktop-beta`. - _Linux:_ Follow the production instructions to set up the APT repository and run `apt install signal-desktop-beta`.
## Got a question? ## Got a question?

File diff suppressed because it is too large Load Diff

View File

@ -43,9 +43,7 @@ async function initialize() {
try { try {
await cleanupLogs(logPath); await cleanupLogs(logPath);
} catch (error) { } catch (error) {
const errorString = `Failed to clean logs; deleting all. Error: ${ const errorString = `Failed to clean logs; deleting all. Error: ${error.stack}`;
error.stack
}`;
console.error(errorString); console.error(errorString);
await deleteAllLogs(logPath); await deleteAllLogs(logPath);
mkdirp.sync(logPath); mkdirp.sync(logPath);

View File

@ -3,13 +3,11 @@
"cdnUrl": "https://cdn-staging.signal.org", "cdnUrl": "https://cdn-staging.signal.org",
"contentProxyUrl": "http://contentproxy.signal.org:443", "contentProxyUrl": "http://contentproxy.signal.org:443",
"updatesUrl": "https://updates2.signal.org/desktop", "updatesUrl": "https://updates2.signal.org/desktop",
"updatesPublicKey": "updatesPublicKey": "fd7dd3de7149dc0a127909fee7de0f7620ddd0de061b37a2c303e37de802a401",
"fd7dd3de7149dc0a127909fee7de0f7620ddd0de061b37a2c303e37de802a401",
"updatesEnabled": false, "updatesEnabled": false,
"openDevTools": false, "openDevTools": false,
"buildExpiration": 0, "buildExpiration": 0,
"certificateAuthority": "certificateAuthority": "-----BEGIN CERTIFICATE-----\nMIID7zCCAtegAwIBAgIJAIm6LatK5PNiMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYD\nVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5j\naXNjbzEdMBsGA1UECgwUT3BlbiBXaGlzcGVyIFN5c3RlbXMxHTAbBgNVBAsMFE9w\nZW4gV2hpc3BlciBTeXN0ZW1zMRMwEQYDVQQDDApUZXh0U2VjdXJlMB4XDTEzMDMy\nNTIyMTgzNVoXDTIzMDMyMzIyMTgzNVowgY0xCzAJBgNVBAYTAlVTMRMwEQYDVQQI\nDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMR0wGwYDVQQKDBRP\ncGVuIFdoaXNwZXIgU3lzdGVtczEdMBsGA1UECwwUT3BlbiBXaGlzcGVyIFN5c3Rl\nbXMxEzARBgNVBAMMClRleHRTZWN1cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\nggEKAoIBAQDBSWBpOCBDF0i4q2d4jAXkSXUGpbeWugVPQCjaL6qD9QDOxeW1afvf\nPo863i6Crq1KDxHpB36EwzVcjwLkFTIMeo7t9s1FQolAt3mErV2U0vie6Ves+yj6\ngrSfxwIDAcdsKmI0a1SQCZlr3Q1tcHAkAKFRxYNawADyps5B+Zmqcgf653TXS5/0\nIPPQLocLn8GWLwOYNnYfBvILKDMItmZTtEbucdigxEA9mfIvvHADEbteLtVgwBm9\nR5vVvtwrD6CCxI3pgH7EH7kMP0Od93wLisvn1yhHY7FuYlrkYqdkMvWUrKoASVw4\njb69vaeJCUdU+HCoXOSP1PQcL6WenNCHAgMBAAGjUDBOMB0GA1UdDgQWBBQBixjx\nP/s5GURuhYa+lGUypzI8kDAfBgNVHSMEGDAWgBQBixjxP/s5GURuhYa+lGUypzI8\nkDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQB+Hr4hC56m0LvJAu1R\nK6NuPDbTMEN7/jMojFHxH4P3XPFfupjR+bkDq0pPOU6JjIxnrD1XD/EVmTTaTVY5\niOheyv7UzJOefb2pLOc9qsuvI4fnaESh9bhzln+LXxtCrRPGhkxA1IMIo3J/s2WF\n/KVYZyciu6b4ubJ91XPAuBNZwImug7/srWvbpk0hq6A6z140WTVSKtJG7EP41kJe\n/oF4usY5J7LPkxK3LWzMJnb5EIJDmRvyH8pyRwWg6Qm6qiGFaI4nL8QU4La1x2en\n4DGXRaLMPRwjELNgQPodR38zoCMuA8gHZfZYYoZ7D7Q1wNUiVHcxuFrEeBaYJbLE\nrwLV\n-----END CERTIFICATE-----\n",
"-----BEGIN CERTIFICATE-----\nMIID7zCCAtegAwIBAgIJAIm6LatK5PNiMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYD\nVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5j\naXNjbzEdMBsGA1UECgwUT3BlbiBXaGlzcGVyIFN5c3RlbXMxHTAbBgNVBAsMFE9w\nZW4gV2hpc3BlciBTeXN0ZW1zMRMwEQYDVQQDDApUZXh0U2VjdXJlMB4XDTEzMDMy\nNTIyMTgzNVoXDTIzMDMyMzIyMTgzNVowgY0xCzAJBgNVBAYTAlVTMRMwEQYDVQQI\nDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMR0wGwYDVQQKDBRP\ncGVuIFdoaXNwZXIgU3lzdGVtczEdMBsGA1UECwwUT3BlbiBXaGlzcGVyIFN5c3Rl\nbXMxEzARBgNVBAMMClRleHRTZWN1cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\nggEKAoIBAQDBSWBpOCBDF0i4q2d4jAXkSXUGpbeWugVPQCjaL6qD9QDOxeW1afvf\nPo863i6Crq1KDxHpB36EwzVcjwLkFTIMeo7t9s1FQolAt3mErV2U0vie6Ves+yj6\ngrSfxwIDAcdsKmI0a1SQCZlr3Q1tcHAkAKFRxYNawADyps5B+Zmqcgf653TXS5/0\nIPPQLocLn8GWLwOYNnYfBvILKDMItmZTtEbucdigxEA9mfIvvHADEbteLtVgwBm9\nR5vVvtwrD6CCxI3pgH7EH7kMP0Od93wLisvn1yhHY7FuYlrkYqdkMvWUrKoASVw4\njb69vaeJCUdU+HCoXOSP1PQcL6WenNCHAgMBAAGjUDBOMB0GA1UdDgQWBBQBixjx\nP/s5GURuhYa+lGUypzI8kDAfBgNVHSMEGDAWgBQBixjxP/s5GURuhYa+lGUypzI8\nkDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQB+Hr4hC56m0LvJAu1R\nK6NuPDbTMEN7/jMojFHxH4P3XPFfupjR+bkDq0pPOU6JjIxnrD1XD/EVmTTaTVY5\niOheyv7UzJOefb2pLOc9qsuvI4fnaESh9bhzln+LXxtCrRPGhkxA1IMIo3J/s2WF\n/KVYZyciu6b4ubJ91XPAuBNZwImug7/srWvbpk0hq6A6z140WTVSKtJG7EP41kJe\n/oF4usY5J7LPkxK3LWzMJnb5EIJDmRvyH8pyRwWg6Qm6qiGFaI4nL8QU4La1x2en\n4DGXRaLMPRwjELNgQPodR38zoCMuA8gHZfZYYoZ7D7Q1wNUiVHcxuFrEeBaYJbLE\nrwLV\n-----END CERTIFICATE-----\n",
"import": false, "import": false,
"serverTrustRoot": "BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx" "serverTrustRoot": "BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx"
} }

View File

@ -498,9 +498,7 @@
idleDetector = new IdleDetector(); idleDetector = new IdleDetector();
let isMigrationWithIndexComplete = false; let isMigrationWithIndexComplete = false;
window.log.info( window.log.info(
`Starting background data migration. Target version: ${ `Starting background data migration. Target version: ${Message.CURRENT_SCHEMA_VERSION}`
Message.CURRENT_SCHEMA_VERSION
}`
); );
idleDetector.on('idle', async () => { idleDetector.on('idle', async () => {
const NUM_MESSAGES_PER_BATCH = 1; const NUM_MESSAGES_PER_BATCH = 1;
@ -1611,7 +1609,9 @@
const ourNumber = textsecure.storage.user.getNumber(); const ourNumber = textsecure.storage.user.getNumber();
const { wrap, sendOptions } = ConversationController.prepareForSend( const { wrap, sendOptions } = ConversationController.prepareForSend(
ourNumber, ourNumber,
{ syncMessage: true } {
syncMessage: true,
}
); );
const installedStickerPacks = window.Signal.Stickers.getInstalledStickerPacks(); const installedStickerPacks = window.Signal.Stickers.getInstalledStickerPacks();

View File

@ -2,10 +2,10 @@
'use strict'; 'use strict';
/* /*
* This file extends the libphonenumber object with a set of phonenumbery * This file extends the libphonenumber object with a set of phonenumbery
* utility functions. libphonenumber must be included before you call these * utility functions. libphonenumber must be included before you call these
* functions, but the order of the files/script-tags doesn't matter. * functions, but the order of the files/script-tags doesn't matter.
*/ */
window.libphonenumber = window.libphonenumber || {}; window.libphonenumber = window.libphonenumber || {};
window.libphonenumber.util = { window.libphonenumber.util = {

View File

@ -955,7 +955,9 @@
const ourNumber = textsecure.storage.user.getNumber(); const ourNumber = textsecure.storage.user.getNumber();
const { wrap, sendOptions } = ConversationController.prepareForSend( const { wrap, sendOptions } = ConversationController.prepareForSend(
ourNumber, ourNumber,
{ syncMessage: true } {
syncMessage: true,
}
); );
await wrap( await wrap(
@ -1480,7 +1482,9 @@
const ourNumber = textsecure.storage.user.getNumber(); const ourNumber = textsecure.storage.user.getNumber();
const { wrap, sendOptions } = ConversationController.prepareForSend( const { wrap, sendOptions } = ConversationController.prepareForSend(
ourNumber, ourNumber,
{ syncMessage: true } {
syncMessage: true,
}
); );
this.syncPromise = this.syncPromise || Promise.resolve(); this.syncPromise = this.syncPromise || Promise.resolve();

View File

@ -396,7 +396,7 @@ function getRandomValue(low, high) {
// Because high and low are inclusive // Because high and low are inclusive
const mod = diff + 1; const mod = diff + 1;
return bytes[0] % mod + low; return (bytes[0] % mod) + low;
} }
function getZeroes(n) { function getZeroes(n) {
@ -454,9 +454,7 @@ function splitBytes(buffer, ...lengths) {
if (total !== buffer.byteLength) { if (total !== buffer.byteLength) {
throw new Error( throw new Error(
`Requested lengths total ${total} does not match source total ${ `Requested lengths total ${total} does not match source total ${buffer.byteLength}`
buffer.byteLength
}`
); );
} }

View File

@ -37,6 +37,7 @@ function _getString(thing) {
return thing; return thing;
} }
// prettier-ignore
function _b64ToUint6(nChr) { function _b64ToUint6(nChr) {
return nChr > 64 && nChr < 91 return nChr > 64 && nChr < 91
? nChr - 65 ? nChr - 65

View File

@ -1098,9 +1098,7 @@
const data = await readDraftData(attachment.path); const data = await readDraftData(attachment.path);
if (data.byteLength !== attachment.size) { if (data.byteLength !== attachment.size) {
window.log.error( window.log.error(
`Attachment size from disk ${ `Attachment size from disk ${data.byteLength} did not match attachment size ${attachment.size}`
data.byteLength
} did not match attachment size ${attachment.size}`
); );
return null; return null;
} }
@ -1411,7 +1409,7 @@
blob = window.dataURLToBlobSync( blob = window.dataURLToBlobSync(
canvas.toDataURL(targetContentType, quality) canvas.toDataURL(targetContentType, quality)
); );
quality = quality * maxSize / blob.size; quality = (quality * maxSize) / blob.size;
// NOTE: During testing with a large image, we observed the // NOTE: During testing with a large image, we observed the
// `quality` value being > 1. Should we clamp it to [0.5, 1.0]? // `quality` value being > 1. Should we clamp it to [0.5, 1.0]?
// See: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob#Syntax // See: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob#Syntax

View File

@ -7,8 +7,8 @@
window.Whisper = window.Whisper || {}; window.Whisper = window.Whisper || {};
/* /*
* Render an avatar identicon to an svg for use in a notification. * Render an avatar identicon to an svg for use in a notification.
*/ */
Whisper.IdenticonSVGView = Whisper.View.extend({ Whisper.IdenticonSVGView = Whisper.View.extend({
templateName: 'identicon-svg', templateName: 'identicon-svg',
initialize(options) { initialize(options) {

View File

@ -7,9 +7,9 @@
window.Whisper = window.Whisper || {}; window.Whisper = window.Whisper || {};
/* /*
* Generic list view that watches a given collection, wraps its members in * Generic list view that watches a given collection, wraps its members in
* a given child view and adds the child view elements to its own element. * a given child view and adds the child view elements to its own element.
*/ */
Whisper.ListView = Backbone.View.extend({ Whisper.ListView = Backbone.View.extend({
tagName: 'ul', tagName: 'ul',
itemView: Backbone.View, itemView: Backbone.View,

View File

@ -1198,9 +1198,7 @@ MessageReceiver.prototype.extend({
if (!_.isNumber(size)) { if (!_.isNumber(size)) {
throw new Error( throw new Error(
`downloadAttachment: Size was not provided, actual size was ${ `downloadAttachment: Size was not provided, actual size was ${data.byteLength}`
data.byteLength
}`
); );
} }
@ -1251,11 +1249,7 @@ MessageReceiver.prototype.extend({
if (envelopeTimestamp !== decryptedTimestamp) { if (envelopeTimestamp !== decryptedTimestamp) {
throw new Error( throw new Error(
`Timestamp ${ `Timestamp ${decrypted.timestamp} in DataMessage did not match envelope timestamp ${envelope.timestamp}`
decrypted.timestamp
} in DataMessage did not match envelope timestamp ${
envelope.timestamp
}`
); );
} }
} }

View File

@ -207,7 +207,7 @@ OutgoingMessage.prototype = {
} }
return promise.catch(e => { return promise.catch(e => {
if (e.name === 'HTTPError' && (e.code !== 409 && e.code !== 410)) { if (e.name === 'HTTPError' && e.code !== 409 && e.code !== 410) {
// 409 and 410 should bubble and be handled by doSendMessage // 409 and 410 should bubble and be handled by doSendMessage
// 404 should throw UnregisteredUserError // 404 should throw UnregisteredUserError
// all other network errors can be retried later. // all other network errors can be retried later.

View File

@ -18,9 +18,7 @@
} }
const protos = result.build('signalservice'); const protos = result.build('signalservice');
if (!protos) { if (!protos) {
const text = `Error loading protos from ${filename} (root: ${ const text = `Error loading protos from ${filename} (root: ${window.PROTO_ROOT})`;
window.PROTO_ROOT
})`;
window.log.error(text); window.log.error(text);
throw new Error(text); throw new Error(text);
} }

View File

@ -209,9 +209,7 @@ MessageSender.prototype = {
} }
if (data.byteLength !== size) { if (data.byteLength !== size) {
throw new Error( throw new Error(
`makeAttachmentPointer: Size ${size} did not match data.byteLength ${ `makeAttachmentPointer: Size ${size} did not match data.byteLength ${data.byteLength}`
data.byteLength
}`
); );
} }

View File

@ -6,12 +6,13 @@
(function() { (function() {
window.StringView = { window.StringView = {
/* /*
* These functions from the Mozilla Developer Network * These functions from the Mozilla Developer Network
* and have been placed in the public domain. * and have been placed in the public domain.
* https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding * https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
* https://developer.mozilla.org/en-US/docs/MDN/About#Copyrights_and_licenses * https://developer.mozilla.org/en-US/docs/MDN/About#Copyrights_and_licenses
*/ */
// prettier-ignore
b64ToUint6(nChr) { b64ToUint6(nChr) {
return nChr > 64 && nChr < 91 return nChr > 64 && nChr < 91
? nChr - 65 ? nChr - 65
@ -59,6 +60,7 @@
return aBBytes; return aBBytes;
}, },
// prettier-ignore
uint6ToB64(nUint6) { uint6ToB64(nUint6) {
return nUint6 < 26 return nUint6 < 26
? nUint6 + 65 ? nUint6 + 65
@ -82,7 +84,7 @@
nIdx += 1 nIdx += 1
) { ) {
nMod3 = nIdx % 3; nMod3 = nIdx % 3;
if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) { if (nIdx > 0 && ((nIdx * 4) / 3) % 76 === 0) {
sB64Enc += '\r\n'; sB64Enc += '\r\n';
} }
nUint24 |= aBytes[nIdx] << ((16 >>> nMod3) & 24); nUint24 |= aBytes[nIdx] << ((16 >>> nMod3) & 24);

View File

@ -24,10 +24,10 @@
receiver.addEventListener('groupsync', this.ongroup); receiver.addEventListener('groupsync', this.ongroup);
const ourNumber = textsecure.storage.user.getNumber(); const ourNumber = textsecure.storage.user.getNumber();
const { wrap, sendOptions } = ConversationController.prepareForSend( const {
ourNumber, wrap,
{ syncMessage: true } sendOptions,
); } = ConversationController.prepareForSend(ourNumber, { syncMessage: true });
window.log.info('SyncRequest created. Sending config sync request...'); window.log.info('SyncRequest created. Sending config sync request...');
wrap(sender.sendRequestConfigurationSyncMessage(sendOptions)); wrap(sender.sendRequestConfigurationSyncMessage(sendOptions));

View File

@ -50,12 +50,12 @@ describe('Key generation', function thisNeeded() {
describe('the first time', () => { describe('the first time', () => {
let result; let result;
/* result should have this format /* result should have this format
* { * {
* preKeys: [ { keyId, publicKey }, ... ], * preKeys: [ { keyId, publicKey }, ... ],
* signedPreKey: { keyId, publicKey, signature }, * signedPreKey: { keyId, publicKey, signature },
* identityKey: <ArrayBuffer> * identityKey: <ArrayBuffer>
* } * }
*/ */
before(() => { before(() => {
const accountManager = new textsecure.AccountManager(''); const accountManager = new textsecure.AccountManager('');
return accountManager.generateKeys(count).then(res => { return accountManager.generateKeys(count).then(res => {

View File

@ -3,26 +3,26 @@
// eslint-disable-next-line func-names // eslint-disable-next-line func-names
(function() { (function() {
/* /*
* WebSocket-Resources * WebSocket-Resources
* *
* Create a request-response interface over websockets using the * Create a request-response interface over websockets using the
* WebSocket-Resources sub-protocol[1]. * WebSocket-Resources sub-protocol[1].
* *
* var client = new WebSocketResource(socket, function(request) { * var client = new WebSocketResource(socket, function(request) {
* request.respond(200, 'OK'); * request.respond(200, 'OK');
* }); * });
* *
* client.sendRequest({ * client.sendRequest({
* verb: 'PUT', * verb: 'PUT',
* path: '/v1/messages', * path: '/v1/messages',
* body: '{ some: "json" }', * body: '{ some: "json" }',
* success: function(message, status, request) {...}, * success: function(message, status, request) {...},
* error: function(message, status, request) {...} * error: function(message, status, request) {...}
* }); * });
* *
* 1. https://github.com/signalapp/WebSocket-Resources * 1. https://github.com/signalapp/WebSocket-Resources
* *
*/ */
const Request = function Request(options) { const Request = function Request(options) {
this.verb = options.verb || options.type; this.verb = options.verb || options.type;

View File

@ -578,7 +578,7 @@ async function getIsLinked() {
let stickerCreatorWindow; let stickerCreatorWindow;
async function showStickerCreator() { async function showStickerCreator() {
if (!await getIsLinked()) { if (!(await getIsLinked())) {
const { message } = locale.messages[ const { message } = locale.messages[
'StickerCreator--Authentication--error' 'StickerCreator--Authentication--error'
]; ];

View File

@ -224,7 +224,7 @@
"npm-run-all": "4.1.5", "npm-run-all": "4.1.5",
"nyc": "11.4.1", "nyc": "11.4.1",
"patch-package": "6.1.2", "patch-package": "6.1.2",
"prettier": "1.12.0", "prettier": "1.19.1",
"react-docgen-typescript": "1.2.6", "react-docgen-typescript": "1.2.6",
"react-styleguidist": "7.0.1", "react-styleguidist": "7.0.1",
"sass-loader": "7.2.0", "sass-loader": "7.2.0",

View File

@ -45,19 +45,13 @@ export const AppStage = (props: Props) => {
} = props; } = props;
const i18n = useI18n(); const i18n = useI18n();
const handleNext = React.useCallback( const handleNext = React.useCallback(() => {
() => { history.push(next);
history.push(next); }, [next]);
},
[next]
);
const handlePrev = React.useCallback( const handlePrev = React.useCallback(() => {
() => { history.push(prev);
history.push(prev); }, [prev]);
},
[prev]
);
const addMoreCount = stickersDuck.useAddMoreCount(); const addMoreCount = stickersDuck.useAddMoreCount();
const toasts = stickersDuck.useToasts(); const toasts = stickersDuck.useToasts();

View File

@ -37,26 +37,17 @@ export const MetaStage = () => {
accept: ['image/png', 'image/webp'], accept: ['image/png', 'image/webp'],
}); });
const onNext = React.useCallback( const onNext = React.useCallback(() => {
() => { setConfirming(true);
setConfirming(true); }, [setConfirming]);
},
[setConfirming]
);
const onCancel = React.useCallback( const onCancel = React.useCallback(() => {
() => { setConfirming(false);
setConfirming(false); }, [setConfirming]);
},
[setConfirming]
);
const onConfirm = React.useCallback( const onConfirm = React.useCallback(() => {
() => { history.push('/upload');
history.push('/upload'); }, [setConfirming]);
},
[setConfirming]
);
const coverFrameClass = isDragActive const coverFrameClass = isDragActive
? styles.coverFrameActive ? styles.coverFrameActive

View File

@ -23,31 +23,26 @@ export const UploadStage = () => {
const total = orderedData.length; const total = orderedData.length;
const [complete, setComplete] = React.useState(0); const [complete, setComplete] = React.useState(0);
React.useEffect( React.useEffect(() => {
() => { (async () => {
(async () => { const onProgress = () => setComplete(i => i + 1);
const onProgress = () => setComplete(i => i + 1); try {
try { const packMeta = await encryptAndUpload(
const packMeta = await encryptAndUpload( { title, author },
{ title, author }, orderedData,
orderedData, cover,
cover, onProgress
onProgress );
); actions.setPackMeta(packMeta);
actions.setPackMeta(packMeta); history.push('/share');
history.push('/share'); } catch (e) {
} catch (e) { actions.addToast('StickerCreator--Toasts--errorUploading', [e.message]);
actions.addToast('StickerCreator--Toasts--errorUploading', [ history.push('/add-meta');
e.message, }
]); })();
history.push('/add-meta');
}
})();
return noop; return noop;
}, }, [title, author, cover, orderedData]);
[title, author, cover, orderedData]
);
return ( return (
<AppStage empty={true}> <AppStage empty={true}>

View File

@ -12,25 +12,22 @@ export const ConfirmModal = React.memo(
const [popperRoot, setPopperRoot] = React.useState<HTMLDivElement>(); const [popperRoot, setPopperRoot] = React.useState<HTMLDivElement>();
// Create popper root and handle outside clicks // Create popper root and handle outside clicks
React.useEffect( React.useEffect(() => {
() => { const root = document.createElement('div');
const root = document.createElement('div'); setPopperRoot(root);
setPopperRoot(root); document.body.appendChild(root);
document.body.appendChild(root); const handleOutsideClick = ({ target }: MouseEvent) => {
const handleOutsideClick = ({ target }: MouseEvent) => { if (!root.contains(target as Node)) {
if (!root.contains(target as Node)) { onCancel();
onCancel(); }
} };
}; document.addEventListener('click', handleOutsideClick);
document.addEventListener('click', handleOutsideClick);
return () => { return () => {
document.body.removeChild(root); document.body.removeChild(root);
document.removeEventListener('click', handleOutsideClick); document.removeEventListener('click', handleOutsideClick);
}; };
}, }, [onCancel]);
[onCancel]
);
return popperRoot return popperRoot
? createPortal( ? createPortal(

View File

@ -9,46 +9,45 @@ export type Props = {
export const ShareButtons = React.memo(({ value }: Props) => { export const ShareButtons = React.memo(({ value }: Props) => {
const i18n = useI18n(); const i18n = useI18n();
const buttonPaths = React.useMemo<Array<[string, string, string, string]>>( const buttonPaths = React.useMemo<
() => { Array<[string, string, string, string]>
const packUrl = encodeURIComponent(value); >(() => {
const text = encodeURIComponent( const packUrl = encodeURIComponent(value);
`${i18n('StickerCreator--ShareStage--socialMessage')} ${value}` const text = encodeURIComponent(
); `${i18n('StickerCreator--ShareStage--socialMessage')} ${value}`
);
return [ return [
// Facebook // Facebook
[ [
i18n('StickerCreator--ShareButtons--facebook'), i18n('StickerCreator--ShareButtons--facebook'),
'#4267B2', '#4267B2',
'M20.155 10.656l-1.506.001c-1.181 0-1.41.561-1.41 1.384v1.816h2.817l-.367 2.845h-2.45V24h-2.937v-7.298h-2.456v-2.845h2.456V11.76c0-2.435 1.487-3.76 3.658-3.76 1.04 0 1.934.077 2.195.112v2.544z', 'M20.155 10.656l-1.506.001c-1.181 0-1.41.561-1.41 1.384v1.816h2.817l-.367 2.845h-2.45V24h-2.937v-7.298h-2.456v-2.845h2.456V11.76c0-2.435 1.487-3.76 3.658-3.76 1.04 0 1.934.077 2.195.112v2.544z',
`https://www.facebook.com/sharer/sharer.php?u=${packUrl}`, `https://www.facebook.com/sharer/sharer.php?u=${packUrl}`,
], ],
// Twitter // Twitter
[ [
i18n('StickerCreator--ShareButtons--twitter'), i18n('StickerCreator--ShareButtons--twitter'),
'#1CA1F2', '#1CA1F2',
'M22.362 12.737c.006.141.01.282.01.425 0 4.337-3.302 9.339-9.34 9.339A9.294 9.294 0 018 21.027c.257.03.518.045.783.045a6.584 6.584 0 004.077-1.405 3.285 3.285 0 01-3.067-2.279 3.312 3.312 0 001.483-.057 3.283 3.283 0 01-2.633-3.218v-.042c.442.246.949.394 1.487.411a3.282 3.282 0 01-1.016-4.383 9.32 9.32 0 006.766 3.43 3.283 3.283 0 015.593-2.994 6.568 6.568 0 002.085-.796 3.299 3.299 0 01-1.443 1.816A6.587 6.587 0 0024 11.038a6.682 6.682 0 01-1.638 1.699', 'M22.362 12.737c.006.141.01.282.01.425 0 4.337-3.302 9.339-9.34 9.339A9.294 9.294 0 018 21.027c.257.03.518.045.783.045a6.584 6.584 0 004.077-1.405 3.285 3.285 0 01-3.067-2.279 3.312 3.312 0 001.483-.057 3.283 3.283 0 01-2.633-3.218v-.042c.442.246.949.394 1.487.411a3.282 3.282 0 01-1.016-4.383 9.32 9.32 0 006.766 3.43 3.283 3.283 0 015.593-2.994 6.568 6.568 0 002.085-.796 3.299 3.299 0 01-1.443 1.816A6.587 6.587 0 0024 11.038a6.682 6.682 0 01-1.638 1.699',
`https://twitter.com/intent/tweet?text=${text}`, `https://twitter.com/intent/tweet?text=${text}`,
], ],
// Pinterest // Pinterest
// [ // [
// i18n('StickerCreator--ShareButtons--pinterest'), // i18n('StickerCreator--ShareButtons--pinterest'),
// '#BD081C', // '#BD081C',
// 'M17.234 19.563c-.992 0-1.926-.536-2.245-1.146 0 0-.534 2.118-.646 2.527-.398 1.444-1.569 2.889-1.66 3.007-.063.083-.203.057-.218-.052-.025-.184-.324-2.007.028-3.493l1.182-5.008s-.293-.587-.293-1.454c0-1.362.789-2.379 1.772-2.379.836 0 1.239.628 1.239 1.38 0 .84-.535 2.097-.811 3.261-.231.975.489 1.77 1.451 1.77 1.74 0 2.913-2.236 2.913-4.886 0-2.014-1.356-3.522-3.824-3.522-2.787 0-4.525 2.079-4.525 4.402 0 .8.237 1.365.607 1.802.17.201.194.282.132.512-.045.17-.145.576-.188.738-.061.233-.249.316-.46.23-1.283-.524-1.882-1.931-1.882-3.511C9.806 11.13 12.008 8 16.374 8c3.51 0 5.819 2.538 5.819 5.265 0 3.605-2.005 6.298-4.959 6.298', // 'M17.234 19.563c-.992 0-1.926-.536-2.245-1.146 0 0-.534 2.118-.646 2.527-.398 1.444-1.569 2.889-1.66 3.007-.063.083-.203.057-.218-.052-.025-.184-.324-2.007.028-3.493l1.182-5.008s-.293-.587-.293-1.454c0-1.362.789-2.379 1.772-2.379.836 0 1.239.628 1.239 1.38 0 .84-.535 2.097-.811 3.261-.231.975.489 1.77 1.451 1.77 1.74 0 2.913-2.236 2.913-4.886 0-2.014-1.356-3.522-3.824-3.522-2.787 0-4.525 2.079-4.525 4.402 0 .8.237 1.365.607 1.802.17.201.194.282.132.512-.045.17-.145.576-.188.738-.061.233-.249.316-.46.23-1.283-.524-1.882-1.931-1.882-3.511C9.806 11.13 12.008 8 16.374 8c3.51 0 5.819 2.538 5.819 5.265 0 3.605-2.005 6.298-4.959 6.298',
// `https://pinterest.com/pin/create/button/?url=${packUrl}`, // `https://pinterest.com/pin/create/button/?url=${packUrl}`,
// ], // ],
// Whatsapp // Whatsapp
[ [
i18n('StickerCreator--ShareButtons--whatsapp'), i18n('StickerCreator--ShareButtons--whatsapp'),
'#25D366', '#25D366',
'M16.033 23.862h-.003a7.914 7.914 0 01-3.79-.965L8.035 24l1.126-4.109a7.907 7.907 0 01-1.059-3.964C8.104 11.556 11.661 8 16.033 8c2.121 0 4.113.826 5.61 2.325a7.878 7.878 0 012.321 5.609c-.002 4.371-3.56 7.928-7.931 7.928zm3.88-5.101c-.165.463-.957.885-1.338.942a2.727 2.727 0 01-1.248-.078 11.546 11.546 0 01-1.13-.418c-1.987-.858-3.286-2.859-3.385-2.991-.1-.132-.81-1.074-.81-2.049 0-.975.513-1.455.695-1.653a.728.728 0 01.528-.248c.132 0 .264.001.38.007.122.006.285-.046.446.34.165.397.56 1.372.61 1.471.05.099.083.215.017.347-.066.132-.1.215-.198.331-.1.115-.208.258-.297.347-.1.098-.203.206-.087.404.116.198.513.847 1.102 1.372.757.675 1.396.884 1.594.984.198.099.314.082.429-.05.116-.132.496-.578.628-.777.132-.198.264-.165.446-.099.18.066 1.156.545 1.354.645.198.099.33.148.38.231.049.083.049.479-.116.942zm-3.877-9.422c-3.636 0-6.594 2.956-6.595 6.589 0 1.245.348 2.458 1.008 3.507l.157.249-.666 2.432 2.495-.654.24.142a6.573 6.573 0 003.355.919h.003a6.6 6.6 0 006.592-6.59 6.55 6.55 0 00-1.93-4.662 6.549 6.549 0 00-4.66-1.932z', 'M16.033 23.862h-.003a7.914 7.914 0 01-3.79-.965L8.035 24l1.126-4.109a7.907 7.907 0 01-1.059-3.964C8.104 11.556 11.661 8 16.033 8c2.121 0 4.113.826 5.61 2.325a7.878 7.878 0 012.321 5.609c-.002 4.371-3.56 7.928-7.931 7.928zm3.88-5.101c-.165.463-.957.885-1.338.942a2.727 2.727 0 01-1.248-.078 11.546 11.546 0 01-1.13-.418c-1.987-.858-3.286-2.859-3.385-2.991-.1-.132-.81-1.074-.81-2.049 0-.975.513-1.455.695-1.653a.728.728 0 01.528-.248c.132 0 .264.001.38.007.122.006.285-.046.446.34.165.397.56 1.372.61 1.471.05.099.083.215.017.347-.066.132-.1.215-.198.331-.1.115-.208.258-.297.347-.1.098-.203.206-.087.404.116.198.513.847 1.102 1.372.757.675 1.396.884 1.594.984.198.099.314.082.429-.05.116-.132.496-.578.628-.777.132-.198.264-.165.446-.099.18.066 1.156.545 1.354.645.198.099.33.148.38.231.049.083.049.479-.116.942zm-3.877-9.422c-3.636 0-6.594 2.956-6.595 6.589 0 1.245.348 2.458 1.008 3.507l.157.249-.666 2.432 2.495-.654.24.142a6.573 6.573 0 003.355.919h.003a6.6 6.6 0 006.592-6.59 6.55 6.55 0 00-1.93-4.662 6.549 6.549 0 00-4.66-1.932z',
`https://wa.me?text=${text}`, `https://wa.me?text=${text}`,
], ],
]; ];
}, }, [i18n, value]);
[i18n, value]
);
return ( return (
<div className={styles.container}> <div className={styles.container}>

View File

@ -82,12 +82,9 @@ export const StickerFrame = React.memo(
] = React.useState<HTMLElement | null>(null); ] = React.useState<HTMLElement | null>(null);
const timerRef = React.useRef<number>(); const timerRef = React.useRef<number>();
const handleToggleEmojiPicker = React.useCallback( const handleToggleEmojiPicker = React.useCallback(() => {
() => { setEmojiPickerOpen(open => !open);
setEmojiPickerOpen(open => !open); }, [setEmojiPickerOpen]);
},
[setEmojiPickerOpen]
);
const handlePickEmoji = React.useCallback( const handlePickEmoji = React.useCallback(
(emoji: EmojiPickDataType) => { (emoji: EmojiPickDataType) => {
@ -97,30 +94,21 @@ export const StickerFrame = React.memo(
[id, onPickEmoji, setEmojiPickerOpen] [id, onPickEmoji, setEmojiPickerOpen]
); );
const handleRemove = React.useCallback( const handleRemove = React.useCallback(() => {
() => { onRemove(id);
onRemove(id); }, [onRemove, id]);
},
[onRemove, id]
);
const handleMouseEnter = React.useCallback( const handleMouseEnter = React.useCallback(() => {
() => { window.clearTimeout(timerRef.current);
window.clearTimeout(timerRef.current); timerRef.current = window.setTimeout(() => {
timerRef.current = window.setTimeout(() => { setPreviewActive(true);
setPreviewActive(true); }, 500);
}, 500); }, [timerRef, setPreviewActive]);
},
[timerRef, setPreviewActive]
);
const handleMouseLeave = React.useCallback( const handleMouseLeave = React.useCallback(() => {
() => { clearTimeout(timerRef.current);
clearTimeout(timerRef.current); setPreviewActive(false);
setPreviewActive(false); }, [timerRef, setPreviewActive]);
},
[timerRef, setPreviewActive]
);
React.useEffect( React.useEffect(
() => () => { () => () => {
@ -130,46 +118,40 @@ export const StickerFrame = React.memo(
); );
// Create popper root and handle outside clicks // Create popper root and handle outside clicks
React.useEffect( React.useEffect(() => {
() => { if (emojiPickerOpen) {
if (emojiPickerOpen) { const root = document.createElement('div');
const root = document.createElement('div'); setEmojiPopperRoot(root);
setEmojiPopperRoot(root); document.body.appendChild(root);
document.body.appendChild(root); const handleOutsideClick = ({ target }: MouseEvent) => {
const handleOutsideClick = ({ target }: MouseEvent) => { if (!root.contains(target as Node)) {
if (!root.contains(target as Node)) { setEmojiPickerOpen(false);
setEmojiPickerOpen(false); }
} };
}; document.addEventListener('click', handleOutsideClick);
document.addEventListener('click', handleOutsideClick);
return () => { return () => {
document.body.removeChild(root); document.body.removeChild(root);
document.removeEventListener('click', handleOutsideClick); document.removeEventListener('click', handleOutsideClick);
}; };
} }
return noop; return noop;
}, }, [emojiPickerOpen, setEmojiPickerOpen, setEmojiPopperRoot]);
[emojiPickerOpen, setEmojiPickerOpen, setEmojiPopperRoot]
);
React.useEffect( React.useEffect(() => {
() => { if (mode !== 'pick-emoji' && image && previewActive) {
if (mode !== 'pick-emoji' && image && previewActive) { const root = document.createElement('div');
const root = document.createElement('div'); setPreviewPopperRoot(root);
setPreviewPopperRoot(root); document.body.appendChild(root);
document.body.appendChild(root);
return () => { return () => {
document.body.removeChild(root); document.body.removeChild(root);
}; };
} }
return noop; return noop;
}, }, [mode, image, previewActive, setPreviewPopperRoot]);
[mode, image, previewActive, setPreviewPopperRoot]
);
const [dragActive, setDragActive] = React.useState<boolean>(false); const [dragActive, setDragActive] = React.useState<boolean>(false);
const containerClass = dragActive ? styles.dragActive : styles.container; const containerClass = dragActive ? styles.dragActive : styles.container;

View File

@ -12,22 +12,19 @@ const DEFAULT_DISMISS = 1e4;
export const Toaster = React.memo(({ loaf, onDismiss, className }: Props) => { export const Toaster = React.memo(({ loaf, onDismiss, className }: Props) => {
const slice = last(loaf); const slice = last(loaf);
React.useEffect( React.useEffect(() => {
() => { if (!slice) {
if (!slice) { return noop;
return noop; }
}
const timer = setTimeout(() => { const timer = setTimeout(() => {
onDismiss(); onDismiss();
}, DEFAULT_DISMISS); }, DEFAULT_DISMISS);
return () => { return () => {
clearTimeout(timer); clearTimeout(timer);
}; };
}, }, [slice, onDismiss]);
[slice, onDismiss]
);
if (!slice) { if (!slice) {
return null; return null;

View File

@ -12,15 +12,12 @@ export type Props = {
export const CopyText = React.memo(({ label, onCopy, value }: Props) => { export const CopyText = React.memo(({ label, onCopy, value }: Props) => {
const i18n = useI18n(); const i18n = useI18n();
const handleClick = React.useCallback( const handleClick = React.useCallback(() => {
() => { copy(value);
copy(value); if (onCopy) {
if (onCopy) { onCopy();
onCopy(); }
} }, [onCopy, value]);
},
[onCopy, value]
);
return ( return (
<div className={styles.container}> <div className={styles.container}>

View File

@ -37,14 +37,11 @@ export const DropZone = (props: Props) => {
accept: ['image/png', 'image/webp'], accept: ['image/png', 'image/webp'],
}); });
React.useEffect( React.useEffect(() => {
() => { if (onDragActive) {
if (onDragActive) { onDragActive(isDragActive);
onDragActive(isDragActive); }
} }, [isDragActive, onDragActive]);
},
[isDragActive, onDragActive]
);
return ( return (
<div {...getRootProps({ className: getClassName(props, isDragActive) })}> <div {...getRootProps({ className: getClassName(props, isDragActive) })}>

View File

@ -16,12 +16,9 @@ const checkSvg = (
export const LabeledCheckbox = React.memo( export const LabeledCheckbox = React.memo(
({ children, value, onChange }: Props) => { ({ children, value, onChange }: Props) => {
const handleChange = React.useCallback( const handleChange = React.useCallback(() => {
() => { onChange(!value);
onChange(!value); }, [onChange, value]);
},
[onChange, value]
);
const className = value ? styles.checkboxChecked : styles.checkbox; const className = value ? styles.checkboxChecked : styles.checkbox;

View File

@ -11,7 +11,7 @@ export const ProgressBar = React.memo(({ className, count, total }: Props) => (
<div className={classnames(styles.base, className)}> <div className={classnames(styles.base, className)}>
<div <div
className={styles.bar} className={styles.bar}
style={{ width: `${Math.floor(count / total * 100)}%` }} style={{ width: `${Math.floor((count / total) * 100)}%` }}
/> />
</div> </div>
)); ));

View File

@ -136,7 +136,7 @@ $conversation-colors: (
'teal': $color-conversation-teal, 'teal': $color-conversation-teal,
'green': $color-conversation-green, 'green': $color-conversation-green,
'light_green': $color-conversation-light_green, 'light_green': $color-conversation-light_green,
'blue_grey': $color-conversation-blue_grey 'blue_grey': $color-conversation-blue_grey,
); );
$conversation-colors-tint: ( $conversation-colors-tint: (
'red': $color-conversation-red-tint, 'red': $color-conversation-red-tint,
@ -149,7 +149,7 @@ $conversation-colors-tint: (
'teal': $color-conversation-teal-tint, 'teal': $color-conversation-teal-tint,
'green': $color-conversation-green-tint, 'green': $color-conversation-green-tint,
'light_green': $color-conversation-light_green-tint, 'light_green': $color-conversation-light_green-tint,
'blue_grey': $color-conversation-blue_grey-tint 'blue_grey': $color-conversation-blue_grey-tint,
); );
$conversation-colors-shade: ( $conversation-colors-shade: (
'red': $color-conversation-red-shade, 'red': $color-conversation-red-shade,
@ -162,7 +162,7 @@ $conversation-colors-shade: (
'teal': $color-conversation-teal-shade, 'teal': $color-conversation-teal-shade,
'green': $color-conversation-green-shade, 'green': $color-conversation-green-shade,
'light_green': $color-conversation-light_green-shade, 'light_green': $color-conversation-light_green-shade,
'blue_grey': $color-conversation-blue_grey-shade 'blue_grey': $color-conversation-blue_grey-shade,
); );
// -- Non-V3 colors // -- Non-V3 colors

View File

@ -111,15 +111,12 @@ export const CompositionArea = ({
const editorRef = React.useRef<Editor>(null); const editorRef = React.useRef<Editor>(null);
const inputApiRef = React.useRef<InputApi | undefined>(); const inputApiRef = React.useRef<InputApi | undefined>();
const handleForceSend = React.useCallback( const handleForceSend = React.useCallback(() => {
() => { setLarge(false);
setLarge(false); if (inputApiRef.current) {
if (inputApiRef.current) { inputApiRef.current.submit();
inputApiRef.current.submit(); }
} }, [inputApiRef, setLarge]);
},
[inputApiRef, setLarge]
);
const handleSubmit = React.useCallback<typeof onSubmit>( const handleSubmit = React.useCallback<typeof onSubmit>(
(...args) => { (...args) => {
@ -129,14 +126,11 @@ export const CompositionArea = ({
[setLarge, onSubmit] [setLarge, onSubmit]
); );
const focusInput = React.useCallback( const focusInput = React.useCallback(() => {
() => { if (editorRef.current) {
if (editorRef.current) { editorRef.current.focus();
editorRef.current.focus(); }
} }, [editorRef]);
},
[editorRef]
);
const withStickers = const withStickers =
countStickers({ countStickers({
@ -180,40 +174,31 @@ export const CompositionArea = ({
[inputApiRef, onPickEmoji] [inputApiRef, onPickEmoji]
); );
const handleToggleLarge = React.useCallback( const handleToggleLarge = React.useCallback(() => {
() => { setLarge(l => !l);
setLarge(l => !l); }, [setLarge]);
},
[setLarge]
);
// The following is a work-around to allow react to lay-out backbone-managed // The following is a work-around to allow react to lay-out backbone-managed
// dom nodes until those functions are in React // dom nodes until those functions are in React
const micCellRef = React.useRef<HTMLDivElement>(null); const micCellRef = React.useRef<HTMLDivElement>(null);
React.useLayoutEffect( React.useLayoutEffect(() => {
() => { const { current: micCellContainer } = micCellRef;
const { current: micCellContainer } = micCellRef; if (micCellContainer && micCellEl) {
if (micCellContainer && micCellEl) { emptyElement(micCellContainer);
emptyElement(micCellContainer); micCellContainer.appendChild(micCellEl);
micCellContainer.appendChild(micCellEl); }
}
return noop; return noop;
}, }, [micCellRef, micCellEl, large, dirty, showMic]);
[micCellRef, micCellEl, large, dirty, showMic]
);
React.useLayoutEffect( React.useLayoutEffect(() => {
() => { const { current: attSlot } = attSlotRef;
const { current: attSlot } = attSlotRef; if (attSlot && attachmentListEl) {
if (attSlot && attachmentListEl) { attSlot.appendChild(attachmentListEl);
attSlot.appendChild(attachmentListEl); }
}
return noop; return noop;
}, }, [attSlotRef, attachmentListEl]);
[attSlotRef, attachmentListEl]
);
const emojiButtonFragment = ( const emojiButtonFragment = (
<div className="module-composition-area__button-cell"> <div className="module-composition-area__button-cell">
@ -287,31 +272,28 @@ export const CompositionArea = ({
) : null; ) : null;
// Listen for cmd/ctrl-shift-x to toggle large composition mode // Listen for cmd/ctrl-shift-x to toggle large composition mode
React.useEffect( React.useEffect(() => {
() => { const handler = (e: KeyboardEvent) => {
const handler = (e: KeyboardEvent) => { const { key, shiftKey, ctrlKey, metaKey } = e;
const { key, shiftKey, ctrlKey, metaKey } = e; // When using the ctrl key, `key` is `'X'`. When using the cmd key, `key` is `'x'`
// When using the ctrl key, `key` is `'X'`. When using the cmd key, `key` is `'x'` const xKey = key === 'x' || key === 'X';
const xKey = key === 'x' || key === 'X'; const commandKey = get(window, 'platform') === 'darwin' && metaKey;
const commandKey = get(window, 'platform') === 'darwin' && metaKey; const controlKey = get(window, 'platform') !== 'darwin' && ctrlKey;
const controlKey = get(window, 'platform') !== 'darwin' && ctrlKey; const commandOrCtrl = commandKey || controlKey;
const commandOrCtrl = commandKey || controlKey;
// cmd/ctrl-shift-x // cmd/ctrl-shift-x
if (xKey && shiftKey && commandOrCtrl) { if (xKey && shiftKey && commandOrCtrl) {
e.preventDefault(); e.preventDefault();
setLarge(x => !x); setLarge(x => !x);
} }
}; };
document.addEventListener('keydown', handler); document.addEventListener('keydown', handler);
return () => { return () => {
document.removeEventListener('keydown', handler); document.removeEventListener('keydown', handler);
}; };
}, }, [setLarge]);
[setLarge]
);
return ( return (
<div className="module-composition-area"> <div className="module-composition-area">

View File

@ -307,14 +307,11 @@ export const CompositionInput = ({
[onDirtyChange, onEditorStateChange, editorStateRef] [onDirtyChange, onEditorStateChange, editorStateRef]
); );
const resetEmojiResults = React.useCallback( const resetEmojiResults = React.useCallback(() => {
() => { setEmojiResults([]);
setEmojiResults([]); setEmojiResultsIndex(0);
setEmojiResultsIndex(0); setSearchText('');
setSearchText(''); }, [setEmojiResults, setEmojiResultsIndex, setSearchText]);
},
[setEmojiResults, setEmojiResultsIndex, setSearchText]
);
const handleEditorStateChange = React.useCallback( const handleEditorStateChange = React.useCallback(
(newState: EditorState) => { (newState: EditorState) => {
@ -361,26 +358,23 @@ export const CompositionInput = ({
] ]
); );
const handleBeforeInput = React.useCallback( const handleBeforeInput = React.useCallback((): DraftHandleValue => {
(): DraftHandleValue => { if (!editorStateRef.current) {
if (!editorStateRef.current) {
return 'not-handled';
}
const editorState = editorStateRef.current;
const plainText = editorState.getCurrentContent().getPlainText();
const selectedTextLength = getLengthOfSelectedText(editorState);
if (plainText.length - selectedTextLength > MAX_LENGTH - 1) {
onTextTooLong();
return 'handled';
}
return 'not-handled'; return 'not-handled';
}, }
[onTextTooLong, editorStateRef]
); const editorState = editorStateRef.current;
const plainText = editorState.getCurrentContent().getPlainText();
const selectedTextLength = getLengthOfSelectedText(editorState);
if (plainText.length - selectedTextLength > MAX_LENGTH - 1) {
onTextTooLong();
return 'handled';
}
return 'not-handled';
}, [onTextTooLong, editorStateRef]);
const handlePastedText = React.useCallback( const handlePastedText = React.useCallback(
(pastedText: string): DraftHandleValue => { (pastedText: string): DraftHandleValue => {
@ -406,25 +400,19 @@ export const CompositionInput = ({
[onTextTooLong, editorStateRef] [onTextTooLong, editorStateRef]
); );
const resetEditorState = React.useCallback( const resetEditorState = React.useCallback(() => {
() => { const newEmptyState = EditorState.createEmpty(compositeDecorator);
const newEmptyState = EditorState.createEmpty(compositeDecorator); setAndTrackEditorState(newEmptyState);
setAndTrackEditorState(newEmptyState); resetEmojiResults();
resetEmojiResults(); }, [editorStateRef, resetEmojiResults, setAndTrackEditorState]);
},
[editorStateRef, resetEmojiResults, setAndTrackEditorState]
);
const submit = React.useCallback( const submit = React.useCallback(() => {
() => { const { current: state } = editorStateRef;
const { current: state } = editorStateRef; const text = state.getCurrentContent().getPlainText();
const text = state.getCurrentContent().getPlainText(); const emojidText = replaceColons(text);
const emojidText = replaceColons(text); const trimmedText = emojidText.trim();
const trimmedText = emojidText.trim(); onSubmit(trimmedText);
onSubmit(trimmedText); }, [editorStateRef, onSubmit]);
},
[editorStateRef, onSubmit]
);
const handleEditorSizeChange = React.useCallback( const handleEditorSizeChange = React.useCallback(
(rect: ContentRect) => { (rect: ContentRect) => {
@ -716,67 +704,55 @@ export const CompositionInput = ({
); );
// Create popper root // Create popper root
React.useEffect( React.useEffect(() => {
() => { if (emojiResults.length > 0) {
if (emojiResults.length > 0) { const root = document.createElement('div');
const root = document.createElement('div'); setPopperRoot(root);
setPopperRoot(root); document.body.appendChild(root);
document.body.appendChild(root);
return () => { return () => {
document.body.removeChild(root); document.body.removeChild(root);
setPopperRoot(null); setPopperRoot(null);
}; };
} }
return noop; return noop;
}, }, [setPopperRoot, emojiResults]);
[setPopperRoot, emojiResults]
);
const onFocus = React.useCallback( const onFocus = React.useCallback(() => {
() => { focusRef.current = true;
focusRef.current = true; }, [focusRef]);
},
[focusRef]
);
const onBlur = React.useCallback( const onBlur = React.useCallback(() => {
() => { focusRef.current = false;
focusRef.current = false; }, [focusRef]);
},
[focusRef]
);
// Manage focus // Manage focus
// Chromium places the editor caret at the beginning of contenteditable divs on focus // Chromium places the editor caret at the beginning of contenteditable divs on focus
// Here, we force the last known selection on focusin (doing this with onFocus wasn't behaving properly) // Here, we force the last known selection on focusin (doing this with onFocus wasn't behaving properly)
// This needs to be done in an effect because React doesn't support focus{In,Out} // This needs to be done in an effect because React doesn't support focus{In,Out}
// https://github.com/facebook/react/issues/6410 // https://github.com/facebook/react/issues/6410
React.useLayoutEffect( React.useLayoutEffect(() => {
() => { const { current: rootEl } = rootElRef;
const { current: rootEl } = rootElRef;
if (rootEl) { if (rootEl) {
const onFocusIn = () => { const onFocusIn = () => {
const { current: oldState } = editorStateRef; const { current: oldState } = editorStateRef;
// Force selection to be old selection // Force selection to be old selection
setAndTrackEditorState( setAndTrackEditorState(
EditorState.forceSelection(oldState, oldState.getSelection()) EditorState.forceSelection(oldState, oldState.getSelection())
); );
}; };
rootEl.addEventListener('focusin', onFocusIn); rootEl.addEventListener('focusin', onFocusIn);
return () => { return () => {
rootEl.removeEventListener('focusin', onFocusIn); rootEl.removeEventListener('focusin', onFocusIn);
}; };
} }
return noop; return noop;
}, }, [editorStateRef, rootElRef, setAndTrackEditorState]);
[editorStateRef, rootElRef, setAndTrackEditorState]
);
if (inputApi) { if (inputApi) {
inputApi.current = { inputApi.current = {
@ -843,9 +819,7 @@ export const CompositionInput = ({
}} }}
role="listbox" role="listbox"
aria-expanded={true} aria-expanded={true}
aria-activedescendant={`emoji-result--${ aria-activedescendant={`emoji-result--${emojiResults[emojiResultsIndex].short_name}`}
emojiResults[emojiResultsIndex].short_name
}`}
> >
{emojiResults.map((emoji, index) => ( {emojiResults.map((emoji, index) => (
<button <button

View File

@ -30,21 +30,18 @@ export const ConfirmationDialog = React.memo(
affirmativeText, affirmativeText,
negativeText, negativeText,
}: Props) => { }: Props) => {
React.useEffect( React.useEffect(() => {
() => { const handler = ({ key }: KeyboardEvent) => {
const handler = ({ key }: KeyboardEvent) => { if (key === 'Escape') {
if (key === 'Escape') { onClose();
onClose(); }
} };
}; document.addEventListener('keydown', handler);
document.addEventListener('keydown', handler);
return () => { return () => {
document.removeEventListener('keydown', handler); document.removeEventListener('keydown', handler);
}; };
}, }, [onClose]);
[onClose]
);
const handleCancel = React.useCallback( const handleCancel = React.useCallback(
(e: React.MouseEvent) => { (e: React.MouseEvent) => {
@ -55,25 +52,19 @@ export const ConfirmationDialog = React.memo(
[onClose] [onClose]
); );
const handleNegative = React.useCallback( const handleNegative = React.useCallback(() => {
() => { onClose();
onClose(); if (onNegative) {
if (onNegative) { onNegative();
onNegative(); }
} }, [onClose, onNegative]);
},
[onClose, onNegative]
);
const handleAffirmative = React.useCallback( const handleAffirmative = React.useCallback(() => {
() => { onClose();
onClose(); if (onAffirmative) {
if (onAffirmative) { onAffirmative();
onAffirmative(); }
} }, [onClose, onAffirmative]);
},
[onClose, onAffirmative]
);
return ( return (
<div className="module-confirmation-dialog__container"> <div className="module-confirmation-dialog__container">

View File

@ -39,24 +39,21 @@ export const ConfirmationModal = React.memo(
}; };
}, []); }, []);
React.useEffect( React.useEffect(() => {
() => { const handler = (event: KeyboardEvent) => {
const handler = (event: KeyboardEvent) => { if (event.key === 'Escape') {
if (event.key === 'Escape') { onClose();
onClose();
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
} }
}; };
document.addEventListener('keydown', handler); document.addEventListener('keydown', handler);
return () => { return () => {
document.removeEventListener('keydown', handler); document.removeEventListener('keydown', handler);
}; };
}, }, [onClose]);
[onClose]
);
const handleCancel = React.useCallback( const handleCancel = React.useCallback(
(e: React.MouseEvent) => { (e: React.MouseEvent) => {

View File

@ -159,8 +159,8 @@ export class ConversationListItem extends React.PureComponent<Props> {
shouldShowDraft && draftPreview shouldShowDraft && draftPreview
? draftPreview ? draftPreview
: lastMessage && lastMessage.text : lastMessage && lastMessage.text
? lastMessage.text ? lastMessage.text
: ''; : '';
return ( return (
<div className="module-conversation-list-item__message"> <div className="module-conversation-list-item__message">
@ -195,9 +195,7 @@ export class ConversationListItem extends React.PureComponent<Props> {
<div <div
className={classNames( className={classNames(
'module-conversation-list-item__message__status-icon', 'module-conversation-list-item__message__status-icon',
`module-conversation-list-item__message__status-icon--${ `module-conversation-list-item__message__status-icon--${lastMessage.status}`
lastMessage.status
}`
)} )}
/> />
) : null} ) : null}

View File

@ -23,9 +23,11 @@ interface Props {
close: () => void; close: () => void;
i18n: LocalizerType; i18n: LocalizerType;
media: Array<MediaItemType>; media: Array<MediaItemType>;
onSave?: ( onSave?: (options: {
options: { attachment: AttachmentType; message: Message; index: number } attachment: AttachmentType;
) => void; message: Message;
index: number;
}) => void;
selectedIndex: number; selectedIndex: number;
} }

View File

@ -110,29 +110,24 @@ export type PropsActions = {
showMessageDetail: (id: string) => void; showMessageDetail: (id: string) => void;
openConversation: (conversationId: string, messageId?: string) => void; openConversation: (conversationId: string, messageId?: string) => void;
showContactDetail: ( showContactDetail: (options: {
options: { contact: ContactType; signalAccount?: string } contact: ContactType;
) => void; signalAccount?: string;
}) => void;
showVisualAttachment: ( showVisualAttachment: (options: {
options: { attachment: AttachmentType; messageId: string } attachment: AttachmentType;
) => void; messageId: string;
downloadAttachment: ( }) => void;
options: { downloadAttachment: (options: {
attachment: AttachmentType; attachment: AttachmentType;
timestamp: number; timestamp: number;
isDangerous: boolean; isDangerous: boolean;
} }) => void;
) => void;
displayTapToViewMessage: (messageId: string) => unknown; displayTapToViewMessage: (messageId: string) => unknown;
openLink: (url: string) => void; openLink: (url: string) => void;
scrollToQuotedMessage: ( scrollToQuotedMessage: (options: { author: string; sentAt: number }) => void;
options: {
author: string;
sentAt: number;
}
) => void;
selectMessage?: (messageId: string, conversationId: string) => unknown; selectMessage?: (messageId: string, conversationId: string) => unknown;
}; };
@ -1241,8 +1236,8 @@ export class Message extends React.PureComponent<Props, State> {
return isTapToViewError return isTapToViewError
? i18n('incomingError') ? i18n('incomingError')
: direction === 'outgoing' : direction === 'outgoing'
? outgoingString ? outgoingString
: incomingString; : incomingString;
} }
public renderTapToView() { public renderTapToView() {

View File

@ -37,80 +37,68 @@ export const EmojiButton = React.memo(
null null
); );
const handleClickButton = React.useCallback( const handleClickButton = React.useCallback(() => {
() => { if (popperRoot) {
if (popperRoot) {
setOpen(false);
} else {
setOpen(true);
}
},
[popperRoot, setOpen]
);
const handleClose = React.useCallback(
() => {
setOpen(false); setOpen(false);
}, } else {
[setOpen] setOpen(true);
); }
}, [popperRoot, setOpen]);
const handleClose = React.useCallback(() => {
setOpen(false);
}, [setOpen]);
// Create popper root and handle outside clicks // Create popper root and handle outside clicks
React.useEffect( React.useEffect(() => {
() => { if (open) {
if (open) { const root = document.createElement('div');
const root = document.createElement('div'); setPopperRoot(root);
setPopperRoot(root); document.body.appendChild(root);
document.body.appendChild(root); const handleOutsideClick = ({ target }: MouseEvent) => {
const handleOutsideClick = ({ target }: MouseEvent) => { if (!root.contains(target as Node)) {
if (!root.contains(target as Node)) { setOpen(false);
setOpen(false);
}
};
document.addEventListener('click', handleOutsideClick);
return () => {
document.body.removeChild(root);
document.removeEventListener('click', handleOutsideClick);
setPopperRoot(null);
};
}
return noop;
},
[open, setOpen, setPopperRoot]
);
// Install keyboard shortcut to open emoji picker
React.useEffect(
() => {
const handleKeydown = (event: KeyboardEvent) => {
const { ctrlKey, key, metaKey, shiftKey } = event;
const commandKey = get(window, 'platform') === 'darwin' && metaKey;
const controlKey = get(window, 'platform') !== 'darwin' && ctrlKey;
const commandOrCtrl = commandKey || controlKey;
// We don't want to open up if the conversation has any panels open
const panels = document.querySelectorAll('.conversation .panel');
if (panels && panels.length > 1) {
return;
}
if (commandOrCtrl && shiftKey && (key === 'j' || key === 'J')) {
event.stopPropagation();
event.preventDefault();
setOpen(!open);
} }
}; };
document.addEventListener('keydown', handleKeydown); document.addEventListener('click', handleOutsideClick);
return () => { return () => {
document.removeEventListener('keydown', handleKeydown); document.body.removeChild(root);
document.removeEventListener('click', handleOutsideClick);
setPopperRoot(null);
}; };
}, }
[open, setOpen]
); return noop;
}, [open, setOpen, setPopperRoot]);
// Install keyboard shortcut to open emoji picker
React.useEffect(() => {
const handleKeydown = (event: KeyboardEvent) => {
const { ctrlKey, key, metaKey, shiftKey } = event;
const commandKey = get(window, 'platform') === 'darwin' && metaKey;
const controlKey = get(window, 'platform') !== 'darwin' && ctrlKey;
const commandOrCtrl = commandKey || controlKey;
// We don't want to open up if the conversation has any panels open
const panels = document.querySelectorAll('.conversation .panel');
if (panels && panels.length > 1) {
return;
}
if (commandOrCtrl && shiftKey && (key === 'j' || key === 'J')) {
event.stopPropagation();
event.preventDefault();
setOpen(!open);
}
};
document.addEventListener('keydown', handleKeydown);
return () => {
document.removeEventListener('keydown', handleKeydown);
};
}, [open, setOpen]);
return ( return (
<Manager> <Manager>

View File

@ -82,14 +82,11 @@ export const EmojiPicker = React.memo(
const [scrollToRow, setScrollToRow] = React.useState(0); const [scrollToRow, setScrollToRow] = React.useState(0);
const [selectedTone, setSelectedTone] = React.useState(skinTone); const [selectedTone, setSelectedTone] = React.useState(skinTone);
const handleToggleSearch = React.useCallback( const handleToggleSearch = React.useCallback(() => {
() => { setSearchText('');
setSearchText(''); setSelectedCategory(categories[0]);
setSelectedCategory(categories[0]); setSearchMode(m => !m);
setSearchMode(m => !m); }, [setSearchText, setSearchMode]);
},
[setSearchText, setSearchMode]
);
const debounceSearchChange = React.useMemo( const debounceSearchChange = React.useMemo(
() => () =>
@ -141,43 +138,40 @@ export const EmojiPicker = React.memo(
); );
// Handle escape key // Handle escape key
React.useEffect( React.useEffect(() => {
() => { const handler = (event: KeyboardEvent) => {
const handler = (event: KeyboardEvent) => { if (searchMode && event.key === 'Escape') {
if (searchMode && event.key === 'Escape') { setSearchText('');
setSearchText(''); setSearchMode(false);
setSearchMode(false); setScrollToRow(0);
setScrollToRow(0);
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
} else if ( } else if (
!searchMode && !searchMode &&
![ ![
'ArrowUp', 'ArrowUp',
'ArrowDown', 'ArrowDown',
'ArrowLeft', 'ArrowLeft',
'ArrowRight', 'ArrowRight',
'Shift', 'Shift',
'Tab', 'Tab',
' ', // Space ' ', // Space
].includes(event.key) ].includes(event.key)
) { ) {
onClose(); onClose();
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
} }
}; };
document.addEventListener('keydown', handler); document.addEventListener('keydown', handler);
return () => { return () => {
document.removeEventListener('keydown', handler); document.removeEventListener('keydown', handler);
}; };
}, }, [onClose, searchMode]);
[onClose, searchMode]
);
// Restore focus on teardown // Restore focus on teardown
React.useEffect(() => { React.useEffect(() => {
@ -193,50 +187,47 @@ export const EmojiPicker = React.memo(
}; };
}, []); }, []);
const emojiGrid = React.useMemo( const emojiGrid = React.useMemo(() => {
() => { if (searchText) {
if (searchText) { return chunk(
return chunk(search(searchText).map(e => e.short_name), COL_COUNT); search(searchText).map(e => e.short_name),
} COL_COUNT
const [, ...cats] = categories;
const chunks = flatMap(cats, cat =>
chunk(dataByCategory[cat].map(e => e.short_name), COL_COUNT)
); );
}
return [...chunk(firstRecent, COL_COUNT), ...chunks]; const [, ...cats] = categories;
},
[dataByCategory, categories, firstRecent, searchText]
);
const catRowEnds = React.useMemo( const chunks = flatMap(cats, cat =>
() => { chunk(
const rowEnds: Array<number> = [ dataByCategory[cat].map(e => e.short_name),
Math.ceil(firstRecent.length / COL_COUNT) - 1, COL_COUNT
]; )
const [, ...cats] = categories; );
cats.forEach(cat => { return [...chunk(firstRecent, COL_COUNT), ...chunks];
rowEnds.push( }, [dataByCategory, categories, firstRecent, searchText]);
Math.ceil(dataByCategory[cat].length / COL_COUNT) +
(last(rowEnds) as number)
);
});
return rowEnds; const catRowEnds = React.useMemo(() => {
}, const rowEnds: Array<number> = [
[categories, dataByCategory] Math.ceil(firstRecent.length / COL_COUNT) - 1,
); ];
const [, ...cats] = categories;
const catToRowOffsets = React.useMemo( cats.forEach(cat => {
() => { rowEnds.push(
const offsets = initial(catRowEnds).map(i => i + 1); Math.ceil(dataByCategory[cat].length / COL_COUNT) +
(last(rowEnds) as number)
);
});
return zipObject(categories, [0, ...offsets]); return rowEnds;
}, }, [categories, dataByCategory]);
[categories, catRowEnds]
); const catToRowOffsets = React.useMemo(() => {
const offsets = initial(catRowEnds).map(i => i + 1);
return zipObject(categories, [0, ...offsets]);
}, [categories, catRowEnds]);
const catOffsetEntries = React.useMemo( const catOffsetEntries = React.useMemo(
() => Object.entries(catToRowOffsets), () => Object.entries(catToRowOffsets),
@ -331,24 +322,23 @@ export const EmojiPicker = React.memo(
/> />
</div> </div>
) : ( ) : (
categories.map( categories.map(cat =>
cat => cat === 'recents' && firstRecent.length === 0 ? null : (
cat === 'recents' && firstRecent.length === 0 ? null : ( <button
<button key={cat}
key={cat} data-category={cat}
data-category={cat} title={cat}
title={cat} onClick={handleSelectCategory}
onClick={handleSelectCategory} className={classNames(
className={classNames( 'module-emoji-picker__button',
'module-emoji-picker__button', 'module-emoji-picker__button--icon',
'module-emoji-picker__button--icon', `module-emoji-picker__button--icon--${cat}`,
`module-emoji-picker__button--icon--${cat}`, selectedCategory === cat
selectedCategory === cat ? 'module-emoji-picker__button--selected'
? 'module-emoji-picker__button--selected' : null
: null )}
)} />
/> )
)
) )
)} )}
</header> </header>

View File

@ -52,29 +52,26 @@ export const StickerButton = React.memo(
null null
); );
const handleClickButton = React.useCallback( const handleClickButton = React.useCallback(() => {
() => { // Clear tooltip state
// Clear tooltip state clearInstalledStickerPack();
clearInstalledStickerPack(); clearShowIntroduction();
clearShowIntroduction();
// Handle button click // Handle button click
if (installedPacks.length === 0) { if (installedPacks.length === 0) {
onClickAddPack(); onClickAddPack();
} else if (popperRoot) { } else if (popperRoot) {
setOpen(false); setOpen(false);
} else { } else {
setOpen(true); setOpen(true);
} }
}, }, [
[ clearInstalledStickerPack,
clearInstalledStickerPack, onClickAddPack,
onClickAddPack, installedPacks,
installedPacks, popperRoot,
popperRoot, setOpen,
setOpen, ]);
]
);
const handlePickSticker = React.useCallback( const handlePickSticker = React.useCallback(
(packId: string, stickerId: number) => { (packId: string, stickerId: number) => {
@ -84,116 +81,96 @@ export const StickerButton = React.memo(
[setOpen, onPickSticker] [setOpen, onPickSticker]
); );
const handleClose = React.useCallback( const handleClose = React.useCallback(() => {
() => { setOpen(false);
setOpen(false); }, [setOpen]);
},
[setOpen]
);
const handleClickAddPack = React.useCallback( const handleClickAddPack = React.useCallback(() => {
() => { setOpen(false);
setOpen(false); if (showPickerHint) {
if (showPickerHint) { clearShowPickerHint();
clearShowPickerHint(); }
} onClickAddPack();
onClickAddPack(); }, [onClickAddPack, showPickerHint, clearShowPickerHint]);
},
[onClickAddPack, showPickerHint, clearShowPickerHint]
);
const handleClearIntroduction = React.useCallback( const handleClearIntroduction = React.useCallback(() => {
() => { clearInstalledStickerPack();
clearInstalledStickerPack(); clearShowIntroduction();
clearShowIntroduction(); }, [clearInstalledStickerPack, clearShowIntroduction]);
},
[clearInstalledStickerPack, clearShowIntroduction]
);
// Create popper root and handle outside clicks // Create popper root and handle outside clicks
React.useEffect( React.useEffect(() => {
() => { if (open) {
if (open) { const root = document.createElement('div');
const root = document.createElement('div'); setPopperRoot(root);
setPopperRoot(root); document.body.appendChild(root);
document.body.appendChild(root); const handleOutsideClick = ({ target }: MouseEvent) => {
const handleOutsideClick = ({ target }: MouseEvent) => { const targetElement = target as HTMLElement;
const targetElement = target as HTMLElement; const className = targetElement ? targetElement.className || '' : '';
const className = targetElement
? targetElement.className || ''
: '';
// We need to special-case sticker picker header buttons, because they can // We need to special-case sticker picker header buttons, because they can
// disappear after being clicked, which breaks the .contains() check below. // disappear after being clicked, which breaks the .contains() check below.
const isMissingButtonClass = const isMissingButtonClass =
!className || !className ||
className.indexOf('module-sticker-picker__header__button') < 0; className.indexOf('module-sticker-picker__header__button') < 0;
if (!root.contains(targetElement) && isMissingButtonClass) { if (!root.contains(targetElement) && isMissingButtonClass) {
setOpen(false); setOpen(false);
}
};
document.addEventListener('click', handleOutsideClick);
return () => {
document.body.removeChild(root);
document.removeEventListener('click', handleOutsideClick);
setPopperRoot(null);
};
}
return noop;
},
[open, setOpen, setPopperRoot]
);
// Install keyboard shortcut to open sticker picker
React.useEffect(
() => {
const handleKeydown = (event: KeyboardEvent) => {
const { ctrlKey, key, metaKey, shiftKey } = event;
const commandKey = get(window, 'platform') === 'darwin' && metaKey;
const controlKey = get(window, 'platform') !== 'darwin' && ctrlKey;
const commandOrCtrl = commandKey || controlKey;
// We don't want to open up if the conversation has any panels open
const panels = document.querySelectorAll('.conversation .panel');
if (panels && panels.length > 1) {
return;
}
if (commandOrCtrl && shiftKey && (key === 's' || key === 'S')) {
event.stopPropagation();
event.preventDefault();
setOpen(!open);
} }
}; };
document.addEventListener('keydown', handleKeydown); document.addEventListener('click', handleOutsideClick);
return () => { return () => {
document.removeEventListener('keydown', handleKeydown); document.body.removeChild(root);
document.removeEventListener('click', handleOutsideClick);
setPopperRoot(null);
}; };
}, }
[open, setOpen]
);
// Clear the installed pack after one minute return noop;
React.useEffect( }, [open, setOpen, setPopperRoot]);
() => {
if (installedPack) {
// tslint:disable-next-line:no-string-based-set-timeout
const timerId = setTimeout(clearInstalledStickerPack, 10 * 1000);
return () => { // Install keyboard shortcut to open sticker picker
clearTimeout(timerId); React.useEffect(() => {
}; const handleKeydown = (event: KeyboardEvent) => {
const { ctrlKey, key, metaKey, shiftKey } = event;
const commandKey = get(window, 'platform') === 'darwin' && metaKey;
const controlKey = get(window, 'platform') !== 'darwin' && ctrlKey;
const commandOrCtrl = commandKey || controlKey;
// We don't want to open up if the conversation has any panels open
const panels = document.querySelectorAll('.conversation .panel');
if (panels && panels.length > 1) {
return;
} }
return noop; if (commandOrCtrl && shiftKey && (key === 's' || key === 'S')) {
}, event.stopPropagation();
[installedPack, clearInstalledStickerPack] event.preventDefault();
);
setOpen(!open);
}
};
document.addEventListener('keydown', handleKeydown);
return () => {
document.removeEventListener('keydown', handleKeydown);
};
}, [open, setOpen]);
// Clear the installed pack after one minute
React.useEffect(() => {
if (installedPack) {
// tslint:disable-next-line:no-string-based-set-timeout
const timerId = setTimeout(clearInstalledStickerPack, 10 * 1000);
return () => {
clearTimeout(timerId);
};
}
return noop;
}, [installedPack, clearInstalledStickerPack]);
if ( if (
countStickers({ countStickers({

View File

@ -53,12 +53,9 @@ export const StickerManager = React.memo(
}); });
}, []); }, []);
const clearPackToPreview = React.useCallback( const clearPackToPreview = React.useCallback(() => {
() => { setPackToPreview(null);
setPackToPreview(null); }, [setPackToPreview]);
},
[setPackToPreview]
);
const previewPack = React.useCallback( const previewPack = React.useCallback(
(pack: StickerPackType) => { (pack: StickerPackType) => {

View File

@ -26,12 +26,9 @@ export const StickerManagerPackRow = React.memo(
const { id, key, isBlessed } = pack; const { id, key, isBlessed } = pack;
const [uninstalling, setUninstalling] = React.useState(false); const [uninstalling, setUninstalling] = React.useState(false);
const clearUninstalling = React.useCallback( const clearUninstalling = React.useCallback(() => {
() => { setUninstalling(false);
setUninstalling(false); }, [setUninstalling]);
},
[setUninstalling]
);
const handleInstall = React.useCallback( const handleInstall = React.useCallback(
(e: React.MouseEvent) => { (e: React.MouseEvent) => {
@ -55,15 +52,12 @@ export const StickerManagerPackRow = React.memo(
[setUninstalling, id, key, isBlessed] [setUninstalling, id, key, isBlessed]
); );
const handleConfirmUninstall = React.useCallback( const handleConfirmUninstall = React.useCallback(() => {
() => { clearUninstalling();
clearUninstalling(); if (uninstallStickerPack) {
if (uninstallStickerPack) { uninstallStickerPack(id, key);
uninstallStickerPack(id, key); }
} }, [id, key, clearUninstalling]);
},
[id, key, clearUninstalling]
);
const handleKeyDown = React.useCallback( const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent) => { (event: React.KeyboardEvent) => {

View File

@ -42,7 +42,7 @@ function getPacksPageOffset(page: number, packs: number): number {
if (isLastPacksPage(page, packs)) { if (isLastPacksPage(page, packs)) {
return ( return (
PACK_PAGE_WIDTH * (Math.floor(packs / PACKS_PAGE_SIZE) - 1) + PACK_PAGE_WIDTH * (Math.floor(packs / PACKS_PAGE_SIZE) - 1) +
(packs % PACKS_PAGE_SIZE - 1) * PACK_ICON_WIDTH ((packs % PACKS_PAGE_SIZE) - 1) * PACK_ICON_WIDTH
); );
} }
@ -82,54 +82,44 @@ export const StickerPicker = React.memo(
const { const {
stickers = recentStickers, stickers = recentStickers,
title: packTitle = 'Recent Stickers', title: packTitle = 'Recent Stickers',
} = } = selectedPack || {};
selectedPack || {};
const [isUsingKeyboard, setIsUsingKeyboard] = React.useState(false); const [isUsingKeyboard, setIsUsingKeyboard] = React.useState(false);
const [packsPage, setPacksPage] = React.useState(0); const [packsPage, setPacksPage] = React.useState(0);
const onClickPrevPackPage = React.useCallback( const onClickPrevPackPage = React.useCallback(() => {
() => { setPacksPage(i => i - 1);
setPacksPage(i => i - 1); }, [setPacksPage]);
}, const onClickNextPackPage = React.useCallback(() => {
[setPacksPage] setPacksPage(i => i + 1);
); }, [setPacksPage]);
const onClickNextPackPage = React.useCallback(
() => {
setPacksPage(i => i + 1);
},
[setPacksPage]
);
// Handle escape key // Handle escape key
React.useEffect( React.useEffect(() => {
() => { const handler = (event: KeyboardEvent) => {
const handler = (event: KeyboardEvent) => { if (event.key === 'Tab') {
if (event.key === 'Tab') { // We do NOT prevent default here to allow Tab to be used normally
// We do NOT prevent default here to allow Tab to be used normally
setIsUsingKeyboard(true); setIsUsingKeyboard(true);
return; return;
} }
if (event.key === 'Escape') { if (event.key === 'Escape') {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
onClose(); onClose();
return; return;
} }
}; };
document.addEventListener('keydown', handler); document.addEventListener('keydown', handler);
return () => { return () => {
document.removeEventListener('keydown', handler); document.removeEventListener('keydown', handler);
}; };
}, }, [onClose]);
[onClose]
);
// Focus popup on after initial render, restore focus on teardown // Focus popup on after initial render, restore focus on teardown
React.useEffect(() => { React.useEffect(() => {

View File

@ -79,25 +79,22 @@ export const StickerPreviewModal = React.memo(
const [confirmingUninstall, setConfirmingUninstall] = React.useState(false); const [confirmingUninstall, setConfirmingUninstall] = React.useState(false);
// Restore focus on teardown // Restore focus on teardown
React.useEffect( React.useEffect(() => {
() => { if (!root) {
if (!root) { return;
return; }
}
const lastFocused = document.activeElement as any; const lastFocused = document.activeElement as any;
if (focusRef.current) { if (focusRef.current) {
focusRef.current.focus(); focusRef.current.focus();
} }
return () => { return () => {
if (lastFocused && lastFocused.focus) { if (lastFocused && lastFocused.focus) {
lastFocused.focus(); lastFocused.focus();
} }
}; };
}, }, [root]);
[root]
);
React.useEffect(() => { React.useEffect(() => {
const div = document.createElement('div'); const div = document.createElement('div');
@ -126,52 +123,49 @@ export const StickerPreviewModal = React.memo(
}, []); }, []);
const isInstalled = Boolean(pack && pack.status === 'installed'); const isInstalled = Boolean(pack && pack.status === 'installed');
const handleToggleInstall = React.useCallback( const handleToggleInstall = React.useCallback(() => {
() => { if (!pack) {
if (!pack) { return;
return; }
} if (isInstalled) {
if (isInstalled) { setConfirmingUninstall(true);
setConfirmingUninstall(true); } else if (pack.status === 'ephemeral') {
} else if (pack.status === 'ephemeral') { downloadStickerPack(pack.id, pack.key, { finalStatus: 'installed' });
downloadStickerPack(pack.id, pack.key, { finalStatus: 'installed' }); onClose();
onClose(); } else {
} else { installStickerPack(pack.id, pack.key);
installStickerPack(pack.id, pack.key); onClose();
}
}, [
isInstalled,
pack,
setConfirmingUninstall,
installStickerPack,
onClose,
]);
const handleUninstall = React.useCallback(() => {
if (!pack) {
return;
}
uninstallStickerPack(pack.id, pack.key);
setConfirmingUninstall(false);
// onClose is called by the confirmation modal
}, [uninstallStickerPack, setConfirmingUninstall, pack]);
React.useEffect(() => {
const handler = ({ key }: KeyboardEvent) => {
if (key === 'Escape') {
onClose(); onClose();
} }
}, };
[isInstalled, pack, setConfirmingUninstall, installStickerPack, onClose]
);
const handleUninstall = React.useCallback( document.addEventListener('keydown', handler);
() => {
if (!pack) {
return;
}
uninstallStickerPack(pack.id, pack.key);
setConfirmingUninstall(false);
// onClose is called by the confirmation modal
},
[uninstallStickerPack, setConfirmingUninstall, pack]
);
React.useEffect( return () => {
() => { document.removeEventListener('keydown', handler);
const handler = ({ key }: KeyboardEvent) => { };
if (key === 'Escape') { }, [onClose]);
onClose();
}
};
document.addEventListener('keydown', handler);
return () => {
document.removeEventListener('keydown', handler);
};
},
[onClose]
);
const handleClickToClose = React.useCallback( const handleClickToClose = React.useCallback(
(e: React.MouseEvent) => { (e: React.MouseEvent) => {

View File

@ -29,8 +29,8 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
const loadingState: STATE_ENUM = isLoadingMessages const loadingState: STATE_ENUM = isLoadingMessages
? 'loading' ? 'loading'
: isNumber(loadCountdownStart) : isNumber(loadCountdownStart)
? 'countdown' ? 'countdown'
: 'idle'; : 'idle';
const duration = loadingState === 'countdown' ? LOAD_COUNTDOWN : undefined; const duration = loadingState === 'countdown' ? LOAD_COUNTDOWN : undefined;
const expiresAt = const expiresAt =
loadingState === 'countdown' && loadCountdownStart loadingState === 'countdown' && loadCountdownStart

View File

@ -1,9 +1,7 @@
export type RenderTextCallbackType = ( export type RenderTextCallbackType = (options: {
options: { text: string;
text: string; key: number;
key: number; }) => JSX.Element | string;
}
) => JSX.Element | string;
export type LocalizerType = (key: string, values?: Array<string>) => string; export type LocalizerType = (key: string, values?: Array<string>) => string;

View File

@ -9234,9 +9234,9 @@
"rule": "React-createRef", "rule": "React-createRef",
"path": "ts/components/conversation/Message.tsx", "path": "ts/components/conversation/Message.tsx",
"line": " public focusRef: React.RefObject<HTMLDivElement> = React.createRef();", "line": " public focusRef: React.RefObject<HTMLDivElement> = React.createRef();",
"lineNumber": 155, "lineNumber": 150,
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2019-11-01T22:46:33.013Z", "updated": "2020-01-06T17:05:33.013Z",
"reasonDetail": "Used for setting focus only" "reasonDetail": "Used for setting focus only"
}, },
{ {

View File

@ -17,7 +17,7 @@ export function getTimerBucket(expiration: number, length: number): string {
return '60'; return '60';
} }
const bucket = Math.round(delta / length * 12); const bucket = Math.round((delta / length) * 12);
return padStart(String(bucket * 5), 2, '0'); return padStart(String(bucket * 5), 2, '0');
} }

View File

@ -12527,9 +12527,10 @@ preserve@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
prettier@1.12.0: prettier@1.19.1:
version "1.12.0" version "1.19.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.12.0.tgz#d26fc5894b9230de97629b39cae225b503724ce8" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
pretty-bytes@^1.0.2: pretty-bytes@^1.0.2:
version "1.0.4" version "1.0.4"