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!
-->
* [ ] I have searched open and closed issues for duplicates
- [ ] I have searched open and closed issues for duplicates
<!--
You can search all issues here:
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:
* [ ] 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 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/)
### 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 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
* [ ] 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 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 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))
- [ ] My changes are ready to be shipped to users
### Description

View File

@ -31,9 +31,9 @@ Then you need `git`, if you don't have that yet: https://git-scm.com/
### Windows
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
* 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>)
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:
* macOS: `~/Library/Application Support/Signal`
* Linux: `~/.config/Signal`
* Windows 10: `C:\Users\<YourName>\AppData\Roaming\Signal`
- macOS: `~/Library/Application Support/Signal`
- Linux: `~/.config/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
`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.
* 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.
* 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
[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
[`_locales/en/messages.json`](_locales/en/messages.json). Other locales are generated
automatically based on that file and then periodically uploaded to Transifex for
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.
This ensures that your changes will merge cleanly when you open your PR.
* Be sure to add and run tests!
* Make sure the diff between our master and your branch contains only the
- Be sure to add and run tests!
- 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
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.
* 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
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
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/)
for some tips on formatting. As far as content, try to include in your
summary

View File

@ -150,18 +150,12 @@ module.exports = grunt => {
},
'test-release': {
osx: {
archive: `mac/${
packageJson.productName
}.app/Contents/Resources/app.asar`,
exe: `mac/${packageJson.productName}.app/Contents/MacOS/${
packageJson.productName
}`,
archive: `mac/${packageJson.productName}.app/Contents/Resources/app.asar`,
exe: `mac/${packageJson.productName}.app/Contents/MacOS/${packageJson.productName}`,
},
mas: {
archive: 'mas/Signal.app/Contents/Resources/app.asar',
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',

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.
* _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.
* _Linux:_ Follow the production instructions to set up the APT repository and run `apt install signal-desktop-beta`.
- _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.
- _Linux:_ Follow the production instructions to set up the APT repository and run `apt install signal-desktop-beta`.
## Got a question?

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -3,13 +3,11 @@
"cdnUrl": "https://cdn-staging.signal.org",
"contentProxyUrl": "http://contentproxy.signal.org:443",
"updatesUrl": "https://updates2.signal.org/desktop",
"updatesPublicKey":
"fd7dd3de7149dc0a127909fee7de0f7620ddd0de061b37a2c303e37de802a401",
"updatesPublicKey": "fd7dd3de7149dc0a127909fee7de0f7620ddd0de061b37a2c303e37de802a401",
"updatesEnabled": false,
"openDevTools": false,
"buildExpiration": 0,
"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",
"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",
"import": false,
"serverTrustRoot": "BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx"
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1098,9 +1098,7 @@
const data = await readDraftData(attachment.path);
if (data.byteLength !== attachment.size) {
window.log.error(
`Attachment size from disk ${
data.byteLength
} did not match attachment size ${attachment.size}`
`Attachment size from disk ${data.byteLength} did not match attachment size ${attachment.size}`
);
return null;
}
@ -1411,7 +1409,7 @@
blob = window.dataURLToBlobSync(
canvas.toDataURL(targetContentType, quality)
);
quality = quality * maxSize / blob.size;
quality = (quality * maxSize) / blob.size;
// NOTE: During testing with a large image, we observed the
// `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

View File

@ -7,8 +7,8 @@
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({
templateName: 'identicon-svg',
initialize(options) {

View File

@ -7,9 +7,9 @@
window.Whisper = window.Whisper || {};
/*
* 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.
*/
* 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.
*/
Whisper.ListView = Backbone.View.extend({
tagName: 'ul',
itemView: Backbone.View,

View File

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

View File

@ -207,7 +207,7 @@ OutgoingMessage.prototype = {
}
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
// 404 should throw UnregisteredUserError
// all other network errors can be retried later.

View File

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

View File

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

View File

@ -6,12 +6,13 @@
(function() {
window.StringView = {
/*
* These functions from the Mozilla Developer Network
* 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/MDN/About#Copyrights_and_licenses
*/
* These functions from the Mozilla Developer Network
* 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/MDN/About#Copyrights_and_licenses
*/
// prettier-ignore
b64ToUint6(nChr) {
return nChr > 64 && nChr < 91
? nChr - 65
@ -59,6 +60,7 @@
return aBBytes;
},
// prettier-ignore
uint6ToB64(nUint6) {
return nUint6 < 26
? nUint6 + 65
@ -82,7 +84,7 @@
nIdx += 1
) {
nMod3 = nIdx % 3;
if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) {
if (nIdx > 0 && ((nIdx * 4) / 3) % 76 === 0) {
sB64Enc += '\r\n';
}
nUint24 |= aBytes[nIdx] << ((16 >>> nMod3) & 24);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,46 +9,45 @@ export type Props = {
export const ShareButtons = React.memo(({ value }: Props) => {
const i18n = useI18n();
const buttonPaths = React.useMemo<Array<[string, string, string, string]>>(
() => {
const packUrl = encodeURIComponent(value);
const text = encodeURIComponent(
`${i18n('StickerCreator--ShareStage--socialMessage')} ${value}`
);
const buttonPaths = React.useMemo<
Array<[string, string, string, string]>
>(() => {
const packUrl = encodeURIComponent(value);
const text = encodeURIComponent(
`${i18n('StickerCreator--ShareStage--socialMessage')} ${value}`
);
return [
// Facebook
[
i18n('StickerCreator--ShareButtons--facebook'),
'#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',
`https://www.facebook.com/sharer/sharer.php?u=${packUrl}`,
],
// Twitter
[
i18n('StickerCreator--ShareButtons--twitter'),
'#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',
`https://twitter.com/intent/tweet?text=${text}`,
],
// Pinterest
// [
// i18n('StickerCreator--ShareButtons--pinterest'),
// '#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',
// `https://pinterest.com/pin/create/button/?url=${packUrl}`,
// ],
// Whatsapp
[
i18n('StickerCreator--ShareButtons--whatsapp'),
'#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',
`https://wa.me?text=${text}`,
],
];
},
[i18n, value]
);
return [
// Facebook
[
i18n('StickerCreator--ShareButtons--facebook'),
'#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',
`https://www.facebook.com/sharer/sharer.php?u=${packUrl}`,
],
// Twitter
[
i18n('StickerCreator--ShareButtons--twitter'),
'#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',
`https://twitter.com/intent/tweet?text=${text}`,
],
// Pinterest
// [
// i18n('StickerCreator--ShareButtons--pinterest'),
// '#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',
// `https://pinterest.com/pin/create/button/?url=${packUrl}`,
// ],
// Whatsapp
[
i18n('StickerCreator--ShareButtons--whatsapp'),
'#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',
`https://wa.me?text=${text}`,
],
];
}, [i18n, value]);
return (
<div className={styles.container}>

View File

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

View File

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

View File

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

View File

@ -16,12 +16,9 @@ const checkSvg = (
export const LabeledCheckbox = React.memo(
({ children, value, onChange }: Props) => {
const handleChange = React.useCallback(
() => {
onChange(!value);
},
[onChange, value]
);
const handleChange = React.useCallback(() => {
onChange(!value);
}, [onChange, value]);
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={styles.bar}
style={{ width: `${Math.floor(count / total * 100)}%` }}
style={{ width: `${Math.floor((count / total) * 100)}%` }}
/>
</div>
));

View File

@ -136,7 +136,7 @@ $conversation-colors: (
'teal': $color-conversation-teal,
'green': $color-conversation-green,
'light_green': $color-conversation-light_green,
'blue_grey': $color-conversation-blue_grey
'blue_grey': $color-conversation-blue_grey,
);
$conversation-colors-tint: (
'red': $color-conversation-red-tint,
@ -149,7 +149,7 @@ $conversation-colors-tint: (
'teal': $color-conversation-teal-tint,
'green': $color-conversation-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: (
'red': $color-conversation-red-shade,
@ -162,7 +162,7 @@ $conversation-colors-shade: (
'teal': $color-conversation-teal-shade,
'green': $color-conversation-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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -37,80 +37,68 @@ export const EmojiButton = React.memo(
null
);
const handleClickButton = React.useCallback(
() => {
if (popperRoot) {
setOpen(false);
} else {
setOpen(true);
}
},
[popperRoot, setOpen]
);
const handleClose = React.useCallback(
() => {
const handleClickButton = React.useCallback(() => {
if (popperRoot) {
setOpen(false);
},
[setOpen]
);
} else {
setOpen(true);
}
}, [popperRoot, setOpen]);
const handleClose = React.useCallback(() => {
setOpen(false);
}, [setOpen]);
// Create popper root and handle outside clicks
React.useEffect(
() => {
if (open) {
const root = document.createElement('div');
setPopperRoot(root);
document.body.appendChild(root);
const handleOutsideClick = ({ target }: MouseEvent) => {
if (!root.contains(target as Node)) {
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);
React.useEffect(() => {
if (open) {
const root = document.createElement('div');
setPopperRoot(root);
document.body.appendChild(root);
const handleOutsideClick = ({ target }: MouseEvent) => {
if (!root.contains(target as Node)) {
setOpen(false);
}
};
document.addEventListener('keydown', handleKeydown);
document.addEventListener('click', handleOutsideClick);
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 (
<Manager>

View File

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

View File

@ -52,29 +52,26 @@ export const StickerButton = React.memo(
null
);
const handleClickButton = React.useCallback(
() => {
// Clear tooltip state
clearInstalledStickerPack();
clearShowIntroduction();
const handleClickButton = React.useCallback(() => {
// Clear tooltip state
clearInstalledStickerPack();
clearShowIntroduction();
// Handle button click
if (installedPacks.length === 0) {
onClickAddPack();
} else if (popperRoot) {
setOpen(false);
} else {
setOpen(true);
}
},
[
clearInstalledStickerPack,
onClickAddPack,
installedPacks,
popperRoot,
setOpen,
]
);
// Handle button click
if (installedPacks.length === 0) {
onClickAddPack();
} else if (popperRoot) {
setOpen(false);
} else {
setOpen(true);
}
}, [
clearInstalledStickerPack,
onClickAddPack,
installedPacks,
popperRoot,
setOpen,
]);
const handlePickSticker = React.useCallback(
(packId: string, stickerId: number) => {
@ -84,116 +81,96 @@ export const StickerButton = React.memo(
[setOpen, onPickSticker]
);
const handleClose = React.useCallback(
() => {
setOpen(false);
},
[setOpen]
);
const handleClose = React.useCallback(() => {
setOpen(false);
}, [setOpen]);
const handleClickAddPack = React.useCallback(
() => {
setOpen(false);
if (showPickerHint) {
clearShowPickerHint();
}
onClickAddPack();
},
[onClickAddPack, showPickerHint, clearShowPickerHint]
);
const handleClickAddPack = React.useCallback(() => {
setOpen(false);
if (showPickerHint) {
clearShowPickerHint();
}
onClickAddPack();
}, [onClickAddPack, showPickerHint, clearShowPickerHint]);
const handleClearIntroduction = React.useCallback(
() => {
clearInstalledStickerPack();
clearShowIntroduction();
},
[clearInstalledStickerPack, clearShowIntroduction]
);
const handleClearIntroduction = React.useCallback(() => {
clearInstalledStickerPack();
clearShowIntroduction();
}, [clearInstalledStickerPack, clearShowIntroduction]);
// Create popper root and handle outside clicks
React.useEffect(
() => {
if (open) {
const root = document.createElement('div');
setPopperRoot(root);
document.body.appendChild(root);
const handleOutsideClick = ({ target }: MouseEvent) => {
const targetElement = target as HTMLElement;
const className = targetElement
? targetElement.className || ''
: '';
React.useEffect(() => {
if (open) {
const root = document.createElement('div');
setPopperRoot(root);
document.body.appendChild(root);
const handleOutsideClick = ({ target }: MouseEvent) => {
const targetElement = target as HTMLElement;
const className = targetElement ? targetElement.className || '' : '';
// We need to special-case sticker picker header buttons, because they can
// disappear after being clicked, which breaks the .contains() check below.
const isMissingButtonClass =
!className ||
className.indexOf('module-sticker-picker__header__button') < 0;
// We need to special-case sticker picker header buttons, because they can
// disappear after being clicked, which breaks the .contains() check below.
const isMissingButtonClass =
!className ||
className.indexOf('module-sticker-picker__header__button') < 0;
if (!root.contains(targetElement) && isMissingButtonClass) {
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);
if (!root.contains(targetElement) && isMissingButtonClass) {
setOpen(false);
}
};
document.addEventListener('keydown', handleKeydown);
document.addEventListener('click', handleOutsideClick);
return () => {
document.removeEventListener('keydown', handleKeydown);
document.body.removeChild(root);
document.removeEventListener('click', handleOutsideClick);
setPopperRoot(null);
};
},
[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 noop;
}, [open, setOpen, setPopperRoot]);
return () => {
clearTimeout(timerId);
};
// 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;
}
return noop;
},
[installedPack, clearInstalledStickerPack]
);
if (commandOrCtrl && shiftKey && (key === 's' || key === 'S')) {
event.stopPropagation();
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 (
countStickers({

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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