Upgrade Prettier
This commit is contained in:
parent
d14c8e2277
commit
0d3b390129
57 changed files with 1074 additions and 1574 deletions
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
|
@ -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
|
||||
|
|
14
.github/PULL_REQUEST_TEMPLATE.md
vendored
14
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
12
Gruntfile.js
12
Gruntfile.js
|
@ -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',
|
||||
|
|
|
@ -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
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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}`
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ function _getString(thing) {
|
|||
return thing;
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
function _b64ToUint6(nChr) {
|
||||
return nChr > 64 && nChr < 91
|
||||
? nChr - 65
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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}`
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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;
|
||||
|
|
2
main.js
2
main.js
|
@ -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'
|
||||
];
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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) })}>
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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>
|
||||
));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue