Compare commits

...

73 Commits
main ... chrome

Author SHA1 Message Date
Scott Nonnenberg e72db5cd08 Remove tests using fixtures 2018-11-16 10:10:36 -08:00
Scott Nonnenberg 84943997a6 v0.48.1 2018-11-16 09:59:52 -08:00
Scott Nonnenberg 326bda9595 Remove use of const for compatibility with old chrome versions 2018-11-16 09:59:25 -08:00
Scott Nonnenberg e06b9878ba v0.48.0 2018-11-15 16:49:29 -08:00
Scott Nonnenberg b8a59507c2 Hardcode display of export screen, of expired banner 2018-11-15 16:49:09 -08:00
Scott Nonnenberg 0378cdff82 Show 'app is expired' screen instead of 'welcome' screen 2018-11-15 16:48:17 -08:00
Scott Nonnenberg af6f32ee05 Introduce final 'uninstall' step to the export process 2018-11-15 16:48:17 -08:00
Scott Nonnenberg acf90adb62 Chrome App no longer sends or receives messages 2018-11-15 16:48:15 -08:00
Scott Nonnenberg 4515ad3db1 v0.47.2 2018-11-15 16:44:13 -08:00
Scott Nonnenberg 4ca502dea0 Remove code to show upgrade screen after expiration 2018-11-15 16:43:43 -08:00
Scott Nonnenberg 260c6f7da0 v0.47.1 2018-10-17 10:07:20 -07:00
Scott Nonnenberg 38b43b350f Ensure that linux install directions pop up on 'Install' click 2018-10-17 10:06:40 -07:00
Scott Nonnenberg c744a81d42 v0.47.0 2018-10-16 14:34:08 -07:00
Scott Nonnenberg 6c8cff56bb Introduce final expiration, streamline export process 2018-10-16 12:08:56 -07:00
Scott Nonnenberg d1d7ab61e0 Close recorder on switch away, only send after finish clicked 2018-10-05 15:34:20 -07:00
Scott Nonnenberg da29423e48 v0.46.6 2018-09-19 18:47:11 -07:00
Scott Nonnenberg d4bf4e189c v0.46.5 2018-09-19 17:57:55 -07:00
Scott Nonnenberg 30745676e8 v0.46.4 2018-06-21 16:52:53 -07:00
Scott Nonnenberg 04cb67fb71 Expire timer updates: don't send if updated via remote message 2018-06-21 16:44:32 -07:00
Scott Nonnenberg 4309938861 v0.46.3 2018-06-14 16:55:11 -07:00
Scott Nonnenberg b0cfb5a264 Ensure timer updates show in convo before initiating message 2018-06-14 16:42:47 -07:00
Scott Nonnenberg fae282cf8e When user cancels filesystem dialog, reset to choice screen 2018-06-13 17:29:56 -07:00
Scott Nonnenberg 327fc7b215
v0.46.2
Hide "It’s Installed" button until user installs standalone version (#2226)
2018-04-11 10:30:17 -07:00
Daniel Gasienica c8fb28c3d9
Hide “It’s Installed” until user installs standalone version (#2226)
Some of our users skipped over the installation screen as it wasn’t clear
installation was a required action before proceeding.

Should reduce confusion as observed in #2130.
2018-04-05 19:10:51 -04:00
Daniel Gasienica eb4865b74c Hide “It’s Installed” until user installs standalone version 2018-04-05 18:59:14 -04:00
Scott Nonnenberg 74c393e893
v0.46.1
Fixed: Buttons on 'export failed' screen were not clickable (#2139)
2018-03-16 09:57:30 -07:00
Scott Nonnenberg 06bcae0fce
Make 'submit debug log'/'try again' clickable on error screen (#2139) 2018-03-16 08:26:23 -07:00
Scott Nonnenberg 53101148a4
v0.46.0
Move to debuglogs.org for debug logs, since GitHub is retiring anonymous
gists (#2118)
  More info: https://blog.github.com/2018-02-18-deprecation-notice-removing-anonymous-gist-creation/
2018-03-13 14:20:55 -07:00
Daniel Gasienica b8a237bb37 Chrome: Move Debug Logs From GitHub Gists to debuglogs.org
In anticipation of GitHub’s deprecation of anonymous gists, we are moving our
debug logs to https://debuglogs.org.

- [x] Publish debug logs to debuglogs.org:
  - ~~Using jQuery v2.1.1-pre results in S3 error about invalid form data
    indicating our jQuery version (added in 2014) doesn’t serialize
    `FormData` correctly.~~
  - [x] Using vanilla XHR ~~results in CORS error indicating our S3 bucket
    doesn’t CORS headers but upload succeeds nonetheless.~~
- [x] Add CORS headers to https://debuglogs.org
- [x] Add CORS headers to S3 bucket (incl. `POST` requests):
      https://s3.amazonaws.com/signal-debug-logs

**Sample log:** https://debuglogs.org/76bf1d7fd9b88ad061d91f79914230e45dc123bc8d169e073f10adb5c1999d4e
2018-03-13 16:48:08 -04:00
Scott Nonnenberg ba02fe6bbe
Ensure that clicks in debug log dialog don't open new dialogs 2018-03-13 13:41:33 -07:00
Daniel Gasienica 206196011f Remove debuglogs S3 bucket from permissions
CORS is now working.
2018-03-13 16:14:35 -04:00
Daniel Gasienica e99a81d56a Remove debuglogs.org from permissions
CORS is now working. Still waiting for S3 POST requests to be CORS enabled.
2018-03-12 23:19:38 -04:00
Daniel Gasienica 2256fed715 Publish debug logs to debuglogs.org 2018-03-09 12:21:31 -05:00
Daniel Gasienica 6621b8ac69 Add `debuglogs.org` and S3 to permissions
Allows cross-origin calls to publish debug logs.
See: https://developer.chrome.com/apps/xhr
2018-03-09 11:33:28 -05:00
Daniel Gasienica 3180b6ca8d Use vanilla XHR for debug log publishing
Chrome still reports CORS error as response from S3 bucket does not include CORS
headers but upload succeeds nonetheless.
2018-03-07 19:10:49 -05:00
Daniel Gasienica 549e9c8d60 Publish debug logs on debuglogs.org 2018-03-07 19:05:33 -05:00
Scott Nonnenberg e9772b66ec
v0.45.0
Updated export flow visuals to match registration/import in Standalone
(#2067)

New dismissable upgrade banner, with automatic display via incremental
rollout (#2067)

After the first export, all subsequent exports will be 'light' -
messages, contacts and groups only (#2067)
2018-02-27 14:16:41 -08:00
Scott Nonnenberg a02612cd11
Updated export flow visuals, new upgrade banner, light export (#2067)
* Backup: Exclude all disappearing messages from export

* Reset overall statistics on start of backup

* Updated visual design for the export process

* On any export after first, don't include encryption info

* Wire up the upgrade banner with dismiss button

Unlike the previous implementation of this banner, it does not go away
when the user clicks on a conversation.

* Small tweak to the export error page: text width, error->isError

* Responding to PR feedback: some refactors, typo fix, dev server

- backupToDirectory -> exportToDirectory
- backup.js attachments -> numAttachments
- migration_view.js comment typo fix
- background.js revert back to the staging server url
- database.js remove dev-only commented-out migration

* Incremental rollout for upgrade banner

* Fix timing, address other PR feedback

* exportingToDirectory -> exportToDirectory

* inboxView: call proper method to show export process

* Remove unnecessary cancel() if first export was never finished

Since we do want to keep track of whether previous exports were even
attempted, this works against our 'light export/import' strategy. It's
also unnecessary, because the user will start at the first step of the
process.

* Log if we get the same value back from the s3 trigger file

* Migration.cancel() - don't reset 'everAttempted' flag

This would prevent us from doing the light export that second time
through.
2018-02-27 14:10:57 -08:00
Scott Nonnenberg 636fae1857
v0.44.14
Fix bug where disconnect/reconnect from websocket could disrupt signed
key rotation schedule (16a613066f)
2017-12-01 10:25:12 -08:00
Scott Nonnenberg 16a613066f
Prevent key rotation listener from weirdness on reconnect 2017-12-01 10:21:11 -08:00
Scott Nonnenberg 246f958616
v0.44.13
Fix an issue where a mid-export or exported Chrome app would interfere
with Standalone app which imported its data

00ca91bc9b
and
a246e1a614

Dev:
  - Harden top-level handler to strange incoming error shapes
2017-11-30 10:40:53 -08:00
Scott Nonnenberg f158220053
Harden top-level handler to strange incoming error shapes 2017-11-30 10:10:42 -08:00
Scott Nonnenberg a246e1a614
Stop signed key rotation as export process starts 2017-11-30 10:10:24 -08:00
Scott Nonnenberg 00ca91bc9b
Don't start key rotation listener until we connect to socket
We're seeing evidence of post-export Chrome apps causing problems with
the Standalone install that imported the data. They both try to rotate
signed prekeys.
2017-11-30 10:01:05 -08:00
Scott Nonnenberg 10ad8b4008
v0.44.12
Fix issue where import complained of malformed JSON (#1764)
2017-11-14 12:21:07 -08:00
Scott Nonnenberg 55614c245f
Poll for well-formed messages.json for five minutes, fail if not (#1764)
Previously we just waited 5 or 10 seconds, and if it didn't work out,
then we just noted in the log.

Now we check for longer, and if any conversations are still not
well-formed after that long, we fail the export.
2017-11-14 11:46:54 -08:00
Scott Nonnenberg 8bde6ec1cd
v0.44.11
Fix issue where export fails to finish
2017-11-09 12:31:20 -08:00
Scott Nonnenberg 30c26cf210
v0.44.10
Export: Fix issue where export would never finish (#1740)
2017-11-09 07:46:46 -08:00
Scott Nonnenberg 5e186cb50d
Manually check for existence of deep prop - no _.get() (#1740) 2017-11-08 21:20:43 -08:00
Scott Nonnenberg b168dab826
v0.44.9
Export: Eliminate attachment when error saved full message details
(#1731)

Export: Make messages.json checking stat-collection only, don't block
export (#1732)

Export: Handle attachments previously saved in base64 format (#1721)
2017-11-08 14:42:57 -08:00
Scott Nonnenberg 86598f2a6a
Eliminate attachment when errors saved full message details (#1731) 2017-11-08 14:22:55 -08:00
Scott Nonnenberg 539c13871e
Make messages.json checking stats only, don't block export (#1732) 2017-11-08 14:08:53 -08:00
Scott Nonnenberg 3b7248c09a
Handle attachments previously saved in base64 format (#1721)
It appears that at some point, attachments were saved to IndexedDB in
the same way that we prepare binary files for JSON:

```
{
  type: 'ArrayBuffer',
  encoding: 'base64',
  data: '<data>'
}
```
2017-11-08 12:12:55 -08:00
Scott Nonnenberg a375d132dd
v0.44.8
Fix bug where a failed export could look like it succeeded (#1718)
2017-11-07 14:35:23 -08:00
Scott Nonnenberg 9942499230
Migration View: Handle non-errors from export process (#1718) 2017-11-07 14:34:04 -08:00
Scott Nonnenberg 727be306ea
v0.44.7
Export: Wait for file writes, handle/log strange attachment data (#1712)

Export: track attachment/conversation metrics (#1712)

Show new versions in log (#1712)
2017-11-07 09:50:56 -08:00
Scott Nonnenberg bf02375212
Export fixes, tracking export metrics, show new versions in log (#1712)
* Show the current version when we get an onInstalled event

* Don't fail export immediately on attachment problem; track stats

* Attempt to handle strange attachments via Uint8Array, empty str

* Remove unused count variable

* Validate messages.json after writing - manual wait for writes!

* Export: Log additional information about strange attachment data
2017-11-07 09:49:36 -08:00
Scott Nonnenberg 16a3763030
v0.44.6
Export: Make more resilient, add debugging information (#1697)

Migration: show install step first, remove unneeded buttons (#1697)
2017-11-03 16:02:18 -07:00
Scott Nonnenberg adb30e6490
Export: Fixes and debugging, Migration: show install step first (#1697)
* Export: Allow for duplicate folders and filenames

This should account for messages with duplicate received_at times. It is
unlikely that their attachment filenames overlap as well, but in that
case the more recent attachment will win. On import, both messages will
end up with the same file.

* Export: Throw informative error if message had no received_at

* Migration: First step is to install the new signal desktop

Once that's installed, we move on to the choose directory step, which
actually does the export. You can cancel out of the first step if you
can't install the new Signal Desktop at the moment.

Also: This removes the cancel button on the 'complete' step, since it
has the potential to very easily cause conflicts between the new Signal
Desktop and the chrome app.

* Refine our duplicate resilience: throw on dupe in some cases

* Migration: Remove later step install buttons; should be complete

* Export: Check type of data destined for disk, throw error
2017-11-03 16:01:27 -07:00
Scott Nonnenberg 9585f14f4e
v0.44.5
Cancel out of a complete migration, with confirmation dialog (#1686)
2017-11-02 18:39:16 -07:00
Scott Nonnenberg 702ace15ec
Cancel out of a complete migration (#1686)
* Allow cancellation of migration process; show warning beforehand

* Export: Properly handle errors that happen during a write
2017-11-02 18:36:13 -07:00
Scott Nonnenberg ac7a346412
v0.44.4
Fix broken attachment filtering
2017-11-02 15:07:47 -07:00
Scott Nonnenberg cc5230f0b0
v0.44.3
Export: remove problematic zero-length attachments (#1683)

Export: devtools-only logging (#1683)
2017-11-02 14:25:32 -07:00
Scott Nonnenberg f047d36fc1
Export: remove zero-length attachments, devtools-only logging (#1683)
* Database: Eliminate zero-length attachments from all messages

* DevTools-only logging to help with export debugging
2017-11-02 13:01:32 -07:00
Scott Nonnenberg b3091e77f6
v0.44.2
Clean attachment structure to enable successful export (#1631)

If export fails, add cancel button to go back to using app (#1631)
2017-11-01 12:34:35 -07:00
Scott Nonnenberg d4a77bded2
Clean attachment structure, cancel out of failed migration (#1631)
* Database migration: Fix attachments with unexpected structure

* Attachment cleanup: Remove all attachments with no data field

* Export: Allow user to cancel out on error if never succeeded

* Only update attachment id if fileName not present

Turns out that modern attachments only have fileName, without an id.

* A small tweak to the attachment fix-up migration, test coverage

* Take migration version back down to 16

* Jshint fix in database.js
2017-11-01 12:30:20 -07:00
Scott Nonnenberg e9e4f168a3
v0.44.1
New menu option to start migration process, eliminate banner
2017-10-31 11:12:18 -07:00
Scott Nonnenberg 46e9ad3317
New menu option to start migration process, eliminate banner 2017-10-31 11:11:45 -07:00
Scott Nonnenberg 16a09a8f94
v0.44.0 2017-10-20 17:55:51 -07:00
Scott Nonnenberg d22e678d18
Update el, es, and kn translations 2017-10-20 17:55:26 -07:00
Scott Nonnenberg 3cbf67ceb0 Remotely deprecate Chrome app via file on updates.signal.org 2017-10-20 17:53:20 -07:00
Scott Nonnenberg e9d2694f8f Update strings for da, fa, hr, pt_BR, pt_PT, ro, and zh_TW (#1509)
FREEBIE
2017-09-28 11:59:08 -07:00
Scott Nonnenberg a064e4b9b2
Travis: Update to node 6
FREEBIE
2017-09-26 10:38:49 -07:00
48 changed files with 2816 additions and 4060 deletions

View File

@ -1,6 +1,6 @@
language: node_js
node_js:
- '0.10'
- '6'
install:
- npm install
script:

View File

@ -142,7 +142,7 @@
"description": "Explain the purpose of the notification settings"
},
"timestamp_h": {
"message": "1 hour",
"message": "1 time",
"description": "Brief timestamp for messages sent about one minute ago. Displayed in the conversation list and message bubble."
},
"timerOption_0_seconds_abbreviated": {
@ -174,7 +174,7 @@
"description": ""
},
"delete": {
"message": "Delete",
"message": "Slet",
"description": ""
},
"verified": {
@ -246,7 +246,7 @@
}
},
"timestamp_s": {
"message": "now",
"message": "nu",
"description": "Brief timestamp for messages sent less than a minute ago. Displayed in the conversation list and message bubble."
},
"changedRecentlyMultiple": {
@ -486,7 +486,7 @@
"description": "Very short format indicating current timer setting in the conversation header"
},
"sync": {
"message": "Contacts",
"message": "Kontakter",
"description": "Label for contact and group sync settings"
},
"timerOption_1_week": {
@ -550,7 +550,7 @@
"description": ""
},
"deleteMessage": {
"message": "Delete this message",
"message": "Slet besked",
"description": ""
},
"timerOption_0_seconds": {

View File

@ -1,6 +1,6 @@
{
"scrollDown": {
"message": "Scroll to bottom of conversation",
"message": "Κύλιση στο τέρμα της συζήτησης",
"description": "Alt text for button to take user down to bottom of conversation, shown when user scrolls up"
},
"timerOption_10_seconds": {
@ -8,11 +8,11 @@
"description": "Label for a selectable option in the message expiration timer menu"
},
"view": {
"message": "View",
"message": "Εμφάνιση",
"description": "Used as a label on a button allowing user to see more information"
},
"upgradingDatabase": {
"message": "Upgrading database. This may take some time...",
"message": "Αναβάθμιση βάσης δεδομένων. Αυτό μπορεί να πάρει κάποια ώρα...",
"description": "Message shown on the loading screen when we're changing database structure on first run of a new version"
},
"lastSynced": {
@ -20,7 +20,7 @@
"description": "Label for date and time of last sync operation"
},
"attemptingReconnection": {
"message": "Attempting reconnect in $reconnect_duration_in_seconds$ seconds",
"message": "Απόπειρα επανασύνδεσης σε $reconnect_duration_in_seconds$ δευτερόλεπτα",
"description": "",
"placeholders": {
"reconnect_duration_in_seconds": {
@ -30,15 +30,15 @@
}
},
"unsupportedAttachment": {
"message": "Μη υποστηριζόμενος τύπος συνημμένου. Πατήστε για αποθήκευση.",
"message": "Μη υποστηριζόμενος τύπος συνημμένου. Κλικ για αποθήκευση.",
"description": "Displayed for incoming unsupported attachment"
},
"showSafetyNumber": {
"message": "Show safety number",
"message": "Προβολή αριθμού ασφαλείας",
"description": ""
},
"androidMessageLengthWarning": {
"message": "Android clients will only receive the first 2000 characters of this message.",
"message": "Οι παραλήπτες σε Android θα παραλάβουν μόνο τους πρώτους 2000 χαρακτήρες αυτού του μηνύματος.",
"description": "Warning that long messages could not get received completely by Android clients."
},
"youChangedTheTimer": {
@ -76,7 +76,7 @@
"description": "Conversation menu option to enable disappearing messages"
},
"deleteWarning": {
"message": "Are you sure? Clicking 'delete' will permanently remove this message from this device.",
"message": "Είσαι σίγουρος/η; Πατώντας \"διαγραφή\", αυτό το μήνυμα θα αφαιρεθεί μόνιμα από αυτή την συσκευή.",
"description": ""
},
"showMore": {
@ -84,7 +84,7 @@
"description": "Displays the details of a key change"
},
"verifyHelp": {
"message": "If you wish to verify the security of your end-to-end encryption with $name$, compare the numbers above with the numbers on their device.",
"message": "Αν επιθυμείς να επιβεβαιώσεις την ασφάλεια της κρυπτογράφηση από άκρο σε άκρο με τον/την $name$, σύγκρινε τους παραπάνω αριθμούς με τους αριθμούς στην συσκευή του/της.",
"description": "",
"placeholders": {
"name": {
@ -94,7 +94,7 @@
}
},
"membersNeedingVerification": {
"message": "Your safety numbers with these group members have changed since you last verified. Click a group member to see your new safety number with them.",
"message": "Οι αριθμοί ασφαλείας με αυτά τα μέλη της ομάδας άλλαξαν από την τελευταία φορά που επιβεβαιώθηκαν. Κάνε κλικ σε ένα μέλος της ομάδας για να δεις τον νέο αριθμό ασφαλείας με αυτόν/ήν.",
"description": "When there are multiple previously-verified group members with safety number changes, a banner will be shown. The list of contacts with safety number changes is shown, and this text introduces that list."
},
"timerOption_5_minutes_abbreviated": {
@ -106,19 +106,19 @@
"description": "Displayed for outgoing unsupported attachment"
},
"exportAgain": {
"message": "Export again",
"message": "Εξαγωγή ξανά",
"description": "If user has already exported once, this button allows user to do it again if needed"
},
"clickToSave": {
"message": "Click to save",
"message": "Κλικ για αποθήκευση",
"description": "Hover text for attachment filenames"
},
"messagesBelow": {
"message": "New messages below",
"message": "Νέα μηνύματα παρακάτω",
"description": "Alt text for button to take user down to bottom of conversation with more than one message out of screen"
},
"installGeneratingKeys": {
"message": "Δημιουργούνται Κλειδιά",
"message": "Παράγονται Κλειδιά",
"description": ""
},
"resetSession": {
@ -126,15 +126,15 @@
"description": "This is a menu item for resetting the session, using the imperative case, as in a command."
},
"offline": {
"message": "Offline",
"message": "Εκτός σύνδεσης",
"description": "Displayed when the desktop client has no network connection."
},
"welcomeToSignal": {
"message": "Καλώς ήρθατε στο Signal",
"message": "Καλωσόρισες στο Signal",
"description": ""
},
"checkNetworkConnection": {
"message": "Check your network connection.",
"message": "Έλεγξε την σύνδεση στο δίκτυο.",
"description": "Obvious instructions for when a user's computer loses its network connection"
},
"notificationSettingsDialog": {
@ -146,11 +146,11 @@
"description": "Brief timestamp for messages sent about one minute ago. Displayed in the conversation list and message bubble."
},
"timerOption_0_seconds_abbreviated": {
"message": "Σβησμένο",
"message": "κλειστό",
"description": "Short format indicating current timer setting in the conversation list snippet"
},
"syncExplanation": {
"message": "Εισαγωγή όλων των Signal ομάδων και επαφών από την φορητή συσκευή σου.",
"message": "Εισαγωγή όλων των ομάδων και επαφών Signal από την φορητή συσκευή σου.",
"description": "Explanatory text for sync settings"
},
"restartSignal": {
@ -158,7 +158,7 @@
"description": "Menu item for restarting the program."
},
"youLeftTheGroup": {
"message": "You left the group",
"message": "Αποχώρησες από την ομάδα",
"description": "Displayed when a user can't send a message because they have left the group"
},
"deleteMessages": {
@ -166,7 +166,7 @@
"description": "Menu item for deleting messages, title case."
},
"confirmMigration": {
"message": "Start migration process? You will not be able to send or receive Signal messages from this application while the migration is in progress.",
"message": "Εκκίνηση της διαδικασίας μεταφοράς; Δεν θα μπορείς να στείλεις ή να παραλάβεις μηνύματα Signal μέσω αυτής της εφαρμογής όσο η μεταφορά είναι σε εξέλιξη.",
"description": "Confirmation dialogue when beginning migration"
},
"incomingError": {
@ -174,19 +174,19 @@
"description": ""
},
"delete": {
"message": "Delete",
"message": "Διαγραφή",
"description": ""
},
"verified": {
"message": "Verified",
"message": "Επιβεβαιωμένο",
"description": ""
},
"selectAContact": {
"message": "Επιλέξτε μία επαφή ή ομάδα για να ξεκινήσετε συζήτηση.",
"message": "Επέλεξε μια επαφή ή ομάδα για να ξεκινήσεις να συζητάς.",
"description": ""
},
"exportError": {
"message": "Unfortunately, something went wrong during the export. First, double-check your target empty directory for write access and enough space. Then, please submit a debug log so we can help you get migrated!",
"message": "Δυστυχώς, κάτι πήγε στραβά κατά την εξαγωγή. Πρώτα, έλεγξε τα δικαιώματα εγγραφής και τον διαθέσιμο χώρο στον φάκελο που διάλεξες. Έπειτα, παρακαλώ καταχώρησε ένα αρχείο καταγραφής αποσφαλμάτωσης ώστε να σε βοηθήσουμε να μεταφερθείς!",
"description": "Helper text if the user went forward on migrating the app, but ran into an error"
},
"installConnecting": {
@ -194,11 +194,11 @@
"description": "Displayed when waiting for the QR Code"
},
"selectedLocation": {
"message": "your selected location",
"message": "επιλεγμένη τοποθεσία σου",
"description": "Message shown as the export location if we didn't capture the target directory"
},
"verifyContact": {
"message": "Ίσως επιθυμείς να $tag_start$ επιβεβαιώσεις $tag_end$ τον κωδικό ασφαλείας σου με αυτή την επαφή.",
"message": "Ίσως επιθυμείς να $tag_start$ επιβεβαιώσεις $tag_end$ τον αριθμό ασφαλείας σου με αυτή την επαφή.",
"description": "Use $tag_start$ and $tag_end$ to wrap the word or phrase in this sentence that the user should click on in order to navigate to the verification screen. These placeholders will be replaced with appropriate HTML code.",
"placeholders": {
"tag_start": {
@ -210,7 +210,7 @@
}
},
"changedRecently": {
"message": "Your safety number with $name$ has changed recently. This could mean that someone is trying to intercept your communication or that $name$ has simply reinstalled Signal.",
"message": "Ο αριθμός ασφαλείας με τον/την $name$ άλλαξε πρόσφατα. Αυτό μπορεί να σημαίνει πως κάποιος προσπαθεί να υποκλέψει την επικοινωνία σου ή ότι απλά ο/η $name$ επανεγκατέστησε το Signal.",
"description": "Shown on confirmation dialog when user attempts to send a message",
"placeholders": {
"name": {
@ -224,15 +224,15 @@
"description": "Label for the time a message was sent"
},
"migrationWarning": {
"message": "The Signal Desktop Chrome app has been deprecated. Would you like to migrate to the new Signal Desktop now?",
"message": "Η εφαρμογή Signal Desktop για τον Chrome είναι παρωχημένη. Θέλεις να μεταφερθείς στο νέο Signal Desktop τωρα;",
"description": "Warning notification that this version of the app has been deprecated and the user must migrate"
},
"migrate": {
"message": "Migrate",
"message": "Μεταφορά",
"description": "Button label to begin migrating this client to Electron"
},
"theyChangedTheTimer": {
"message": "$name$ ρύθμισε την αντίστροφη μέτρηση ίση με $time$",
"message": "Ο/Η $name$ ρύθμισε την αντίστροφη μέτρηση ίση με $time$",
"description": "Message displayed when someone else changes the message expiration timer in a conversation.",
"placeholders": {
"name": {
@ -250,7 +250,7 @@
"description": "Brief timestamp for messages sent less than a minute ago. Displayed in the conversation list and message bubble."
},
"changedRecentlyMultiple": {
"message": "Your safety numbers with multiple group members have changed recently. This could mean that someone is trying to intercept your communication or that they have simply reinstalled Signal.",
"message": "Οι αριθμοί ασφαλείας με πολλαπλά μέλη της ομάδας άλλαξαν πρόσφατα. Αυτό μπορεί να σημαίνει πως κάποιος προσπαθεί να υποκλέψει την επικοινωνία σου ή ότι απλά επανεγκατέστησαν το Signal.",
"description": "Shown on confirmation dialog when user attempts to send a message"
},
"timerOption_1_day": {
@ -258,7 +258,7 @@
"description": "Label for a selectable option in the message expiration timer menu"
},
"exporting": {
"message": "Please wait while we export your data. It may take several minutes. You can still use Signal on your phone and other devices during this time.",
"message": "Παρακαλώ περίμενε όσο εξάγουμε τα δεδομένα σου. Αυτό μπορεί να πάρει αρκετά λεπτά. Μπορείς ακόμα να χρησιμοποιείς το Signal στο κινητό σου και σε άλλες συσκευές.",
"description": "Message shown on the migration screen while we export data"
},
"reportIssue": {
@ -270,11 +270,11 @@
"description": "Displayed in notifications for only 1 message"
},
"nameOnly": {
"message": "Μόνο το όνομα του αποστολέα",
"message": "Μόνο όνομα αποστολέα",
"description": "Label for setting notifications to display sender name only"
},
"from": {
"message": "Απο",
"message": "Από",
"description": "Label for the sender of a message"
},
"deleteConversationConfirmation": {
@ -282,19 +282,19 @@
"description": "Confirmation dialog text that asks the user if they really wish to delete the conversation. Answer buttons use the strings 'ok' and 'cancel'. The deletion is permanent, i.e. it cannot be undone."
},
"unlinkedWarning": {
"message": "Relink Signal Desktop to your mobile device to continue messaging.",
"message": "Επανασύνδεσε το Signal Desktop με την φορητή συσκευή σου για να συνεχίσεις να συνομιλείς.",
"description": ""
},
"debugLogExplanation": {
"message": "Αυτό το αρχείο καταγραφής συμβάντων θα δημοσιευτεί στο internet ώστε να το εξετάσουν εθελοντές. Μπορείς να το μελετήσεις και να το αλλάξεις πριν το υποβάλλεις.",
"message": "Αυτό το αρχείο καταγραφής συμβάντων θα δημοσιευτεί στο διαδίκτυο ώστε να το εξετάσουν εθελοντές. Μπορείς να το μελετήσεις και να το αλλάξεις πριν το υποβάλλεις.",
"description": ""
},
"newPhoneNumber": {
"message": "Βάλτε νούμερο τηλεφώνου για να προσθέσετε επαφή",
"message": "Βάλε αριθμό τηλεφώνου για να προσθέσεις μια επαφή",
"description": "Placeholder for adding a new number to a contact"
},
"noLongerVerified": {
"message": "Your safety number with $name$ has changed and is no longer verified. Click to show.",
"message": "Ο αριθμός ασφαλείας με τον/την $name$ άλλαξε και δεν είναι πια επιβεβαιωμένος. Κλικ για εμφάνιση.",
"description": "Shown in converation banner when user's safety number has changed, but they were previously verified.",
"placeholders": {
"name": {
@ -308,11 +308,11 @@
"description": "Placeholder text in the search input"
},
"someRecipientsFailed": {
"message": "Some recipients failed.",
"message": "Κάποιοι παραλήπτες απέτυχαν.",
"description": "When you send to multiple recipients via a group, and the message went to some recipients but not others."
},
"noNameOrMessage": {
"message": "Ούτε το όνομα αποστολέα, ούτε το μήνυμα",
"message": "Ούτε όνομα αποστολέα, ούτε μήνυμα",
"description": "Label for setting notifications to display no name and no message text"
},
"syncNow": {
@ -324,7 +324,7 @@
"description": "Informational text displayed if a sync operation times out."
},
"unlinked": {
"message": "Unlinked",
"message": "Αποσυνδεδεμένο",
"description": ""
},
"installFollowUs": {
@ -338,11 +338,11 @@
}
},
"learnMore": {
"message": "Πληροφορίες για την επαλήθευση κωδικών ασφαλείας",
"message": "Μάθε περισσότερα για την επαλήθευση αριθμών ασφαλείας",
"description": "Text that links to a support article on verifying safety numbers"
},
"unreadMessages": {
"message": "$count$ Unread Messages",
"message": "$count$ μη αναγνωσμένα μηνύματα",
"description": "Text for unread message separator, with count",
"placeholders": {
"count": {
@ -352,7 +352,7 @@
}
},
"installTooManyDevices": {
"message": "Δυστυχώς έχετε ήδη πολλές συνδεδεμένες συσκευές. Προσπαθήστε να αφαιρέσετε μερικές.",
"message": "Δυστυχώς έχεις ήδη πολλές συνδεδεμένες συσκευές. Προσπάθησε να αφαιρέσεις μερικές.",
"description": ""
},
"installSignalLinks": {
@ -386,7 +386,7 @@
"description": "Very short format indicating current timer setting in the conversation header"
},
"loadingMessages": {
"message": "Loading messages. $count$ so far...",
"message": "Φόρτωση μηνυμάτων. $count$ μέχρι στιγμής...",
"description": "Message shown on the loading screen when we're catching up on the backlog of messages",
"placeholders": {
"count": {
@ -396,7 +396,7 @@
}
},
"changedSinceVerified": {
"message": "Your safety number with $name$ has changed since you last verified. This could mean that someone is trying to intercept your communication or that $name$ has simply reinstalled Signal.",
"message": "Ο αριθμός ασφαλείας με τον/την $name$ άλλαξε από την τελευταία φορά που επιβεβαιώθηκε. Αυτό μπορεί να σημαίνει πως κάποιος προσπαθεί να υποκλέψει την επικοινωνία σου ή ότι απλά ο/η $name$ επανεγκατέστησε το Signal.",
"description": "Shown on confirmation dialog when user attempts to send a message",
"placeholders": {
"name": {
@ -410,7 +410,7 @@
"description": "Label text for button to upgrade the app to the latest version"
},
"yourSafetyNumberWith": {
"message": "Your safety number with $name$:",
"message": "Ο αριθμός ασφαλείας με τον/την $name$:",
"description": "Heading for safety number view",
"placeholders": {
"name": {
@ -428,7 +428,7 @@
"description": "Header for theme settings"
},
"newIdentity": {
"message": "Νέος κωδικός ασφαλείας",
"message": "Νέος αριθμός ασφαλείας",
"description": "Header for a key change dialog"
},
"installTagline": {
@ -440,7 +440,7 @@
"description": "Description for audio notification setting"
},
"isNotVerified": {
"message": "You have not verified your safety number with $name$.",
"message": "Δεν έχεις επιβεβαιώσει τον αριθμό ασφαλείας με τον/την $name$.",
"description": "Summary state shown at top of the safety number screen if user has not verified contact.",
"placeholders": {
"name": {
@ -458,7 +458,7 @@
"description": "Label for a disabled sync button while sync is in progress."
},
"multipleNoLongerVerified": {
"message": "Your safety numbers with multiple members of this group have changed and are no longer verified. Click to show.",
"message": "Οι αριθμοί ασφαλείας με πολλαπλά μέλη αυτής της ομάδας άλλαξαν και δεν είναι πια επιβεβαιωμένοι. Κλικ για εμφάνιση.",
"description": "Shown in conversation banner when more than one group member's safety number has changed, but they were previously verified."
},
"acceptNewKey": {
@ -502,7 +502,7 @@
"description": "Label for the time a message was received"
},
"youMarkedAsNotVerified": {
"message": "You marked your safety number with $name$ as unverified.",
"message": "Σημείωσες τον αριθμό ασφαλείας με τον/την $name$ ως μη επιβεβαιωμένο.",
"description": "Shown in the conversation history when the user marks a contact as not verified, whether on the safety number screen or by dismissing a banner or dialog.",
"placeholders": {
"name": {
@ -512,7 +512,7 @@
}
},
"isVerified": {
"message": "You have verified your safety number with $name$.",
"message": "Επιβεβαίωσες τον αριθμό ασφαλείας με τον/την $name$.",
"description": "Summary state shown at top of the safety number screen if user has verified contact.",
"placeholders": {
"name": {
@ -526,11 +526,11 @@
"description": "Label for a selectable option in the message expiration timer menu"
},
"sendMessage": {
"message": "Νέο Μήνυμα",
"message": "Στείλε ένα μήνυμα",
"description": "Placeholder text in the message entry field"
},
"me": {
"message": "Me",
"message": "Εγώ",
"description": "The label for yourself when shown in a group member list"
},
"mediaMessage": {
@ -538,7 +538,7 @@
"description": "Description of a message that has an attachment and no text, displayed in the conversation list as a preview."
},
"unreadMessage": {
"message": "1 Unread Message",
"message": "1 μη αναγνωσμένο μήνυμα",
"description": "Text for unread message separator, just one message"
},
"expiredWarning": {
@ -550,35 +550,35 @@
"description": ""
},
"deleteMessage": {
"message": "Delete this message",
"message": "Διαγραφή αυτού του μηνύματος",
"description": ""
},
"timerOption_0_seconds": {
"message": "Σβησμένο",
"message": "κλειστό",
"description": "Label for option to turn off message expiration in the timer menu"
},
"installAndroidInstructions": {
"message": "Ανοίξτε το Signal στο κινητό σας και πηγαίνετε στις Ρυθμίσεις > Συνδεδεμένες συσκευές. Πιέστε το πλήκτρο προσθήκης νέας συσκευής και σαρώστε το παραπάνω γραφικό.",
"message": "Άνοιξε το Signal στο κινητό σου και πήγαινε στις Ρυθμίσεις > Συνδεδεμένες συσκευές. Πάτα το πλήκτρο προσθήκης νέας συσκευής και σάρωσε το παραπάνω γραφικό.",
"description": ""
},
"migrationDisconnecting": {
"message": "Disconnecting...",
"message": "Αποσυνδέεται...",
"description": "Displayed while we wait for pending incoming messages to process"
},
"invalidNumberError": {
"message": "Λάθος νούμερο",
"message": "Μη έγκυρος αριθμός",
"description": "When a person inputs a number that is invalid"
},
"installWelcome": {
"message": "Καλώς ήρθατε στο Signal Desktop",
"message": "Καλωσόρισες στο Signal Desktop",
"description": "Welcome title on the install page"
},
"messageBelow": {
"message": "New message below",
"message": "Νέο μήνυμα παρακάτω",
"description": "Alt text for button to take user down to bottom of conversation with a new message out of screen"
},
"exportComplete": {
"message": "Your data has been exported to: <p><b>$location$</b></p> You'll be able to import this data as you set up <a target='_blank' href='https://support.whispersystems.org/hc/en-us/articles/214507138'>the new Signal Desktop</a>.",
"message": "Τα δεδομένα σου εξήχθησαν σε: <p><b>$location$</b></p> Θα μπορέσεις να εισάγεις αυτά τα δεδομένα κατά την εγκατάσταση του <a target='_blank' href='https://support.whispersystems.org/hc/en-us/articles/214507138'>νέου Signal Desktop</a>.",
"description": "Message shown on the migration screen when we are done exporting data",
"placeholders": {
"location": {
@ -592,7 +592,7 @@
"description": "Displayed when we can't connect to the server."
},
"unverify": {
"message": "Mark as not verified",
"message": "Σημείωση ως μη επιβεβαιωμένο",
"description": ""
},
"messageNotSent": {
@ -608,7 +608,7 @@
"description": ""
},
"unblockToSend": {
"message": "Ξεκλείδωσε αυτή την επαφή για να στείλεις ένα μήνυμα",
"message": "Ξεμπλόκαρε αυτή την επαφή για να στείλεις ένα μήνυμα",
"description": "Brief message shown when trying to message a blocked number"
},
"installIHaveSignalButton": {
@ -616,15 +616,15 @@
"description": "Button for the user to confirm that they have Signal for Android"
},
"unnamedFile": {
"message": "Unnamed File",
"message": "Ανώνυμο αρχείο",
"description": "Hover text for attachment filenames"
},
"sendAnyway": {
"message": "Send Anyway",
"message": "Αποστολή όπως και να 'χει",
"description": "Used on a warning dialog to make it clear that it might be risky to send the message."
},
"youMarkedAsVerified": {
"message": "You marked your safety number with $name$ as verified.",
"message": "Σημείωσες τον αριθμό ασφαλείας με τον/την $name$ ως επιβεβαιωμένο.",
"description": "Shown in the conversation history when the user marks a contact as verified.",
"placeholders": {
"name": {
@ -634,11 +634,11 @@
}
},
"connecting": {
"message": "Connecting",
"message": "Συνδέεται",
"description": "Displayed when the desktop client is currently connecting to the server."
},
"sessionEnded": {
"message": "Η ασφαλής σύνδεση επανεκκινήθηκε",
"message": "Η ασφαλής συνεδρία επανεκκινήθηκε",
"description": "This is a past tense, informational message. In other words, your secure session has been reset."
},
"installGetStartedButton": {
@ -646,7 +646,7 @@
"description": ""
},
"relink": {
"message": "Relink",
"message": "Επανασύνδεση",
"description": ""
},
"timerOption_1_week_abbreviated": {
@ -658,11 +658,11 @@
"description": "Label for a selectable option in the message expiration timer menu"
},
"showMembers": {
"message": "Show members",
"message": "Εμφάνιση μελών",
"description": ""
},
"youMarkedAsNotVerifiedOtherDevice": {
"message": "You marked your safety number with $name$ as not verified from another device.",
"message": "Σημείωσες τον αριθμό ασφαλείας με τον/την $name$ ως μη επιβεβαιωμένο από άλλη συσκευή.",
"description": "Shown in the conversation history when we discover that the user marked a contact as not verified on another device.",
"placeholders": {
"name": {
@ -686,11 +686,11 @@
"description": "Text displayed before the phone number that the user is in the process of linking with"
},
"groupMembers": {
"message": "Group members",
"message": "Μέλη ομάδας",
"description": ""
},
"loading": {
"message": "Loading...",
"message": "Φορτώνει...",
"description": "Message shown on the loading screen before we've loaded any messages"
},
"newMessages": {
@ -698,15 +698,15 @@
"description": "Displayed in notifications for multiple messages"
},
"newContact": {
"message": "Πατήστε για να προσθέσετε καινούρια επαφή",
"message": "Κλικ για προσθήκη νέας επαφής",
"description": ""
},
"theirIdentityUnknown": {
"message": "Δεν έχετε ανταλλάξει μηνύματα με αυτή την επαφή ακόμα. Ο κωδικός ασφαλείας σας με αυτή την επαφή θα είναι διαθέσιμος μετά το πρώτο μήνυμα.",
"message": "Δεν έχεις ανταλλάξει ακόμα μηνύματα με αυτή την επαφή. Ο αριθμός ασφαλείας με αυτή την επαφή θα είναι διαθέσιμος μετά το πρώτο μήνυμα.",
"description": ""
},
"voiceMessage": {
"message": "Voice Message",
"message": "Μήνυμα φωνής",
"description": "Name for a voice message attachment"
},
"submit": {
@ -714,7 +714,7 @@
"description": ""
},
"keychanged": {
"message": "Your safety number with $name$ has changed. Click to show.",
"message": "Ο αριθμός ασφαλείας με τον/την $name$ άλλαξε. Κλικ για εμφάνιση.",
"description": "",
"placeholders": {
"name": {
@ -724,7 +724,7 @@
}
},
"identityKeyErrorOnSend": {
"message": "Your safety number with $name$ has changed. This could either mean that someone is trying to intercept your communication or that $name$ has simply reinstalled Signal. You may wish to verify your saftey number with this contact.",
"message": "Ο αριθμός ασφαλείας με τον/την $name$ έχει αλλάξει. Αυτό σημαίνει ότι είτε κάποιος προσπαθεί να υποκλέψει την επικοινωνία σου είτε απλά ο/η $name$ επανεγκατέστησε το Signal. Ίσως επιθυμείς να επαληθεύσεις τον αριθμό ασφαλείας με αυτή την επαφή.",
"description": "Shown when user clicks on a failed recipient in the message detail view after an identity key change",
"placeholders": {
"name": {
@ -750,11 +750,11 @@
"description": ""
},
"installNewSignal": {
"message": "Install new Signal Desktop",
"message": "Εγκατάσταση του νέου Signal Desktop",
"description": "When export is complete, a button shows which sends user to Signal Desktop install instructions"
},
"verify": {
"message": "Mark as verified",
"message": "Σημείωση ως επιβεβαιωμένο",
"description": ""
},
"timerOption_10_seconds_abbreviated": {
@ -766,7 +766,7 @@
"description": "Label for setting notifications to display name and message text"
},
"failedToSend": {
"message": "Αποτυχία αποστολής σε κάποιους παραλήπτες. Ελέγξτε την σύνδεση του δικτύου σας.",
"message": "Αποτυχία αποστολής σε κάποιους παραλήπτες. Έλεγξε την σύνδεση του δικτύου.",
"description": ""
},
"ok": {
@ -774,19 +774,19 @@
"description": ""
},
"identityChanged": {
"message": "Ο κωδικός ασφαλείας με αυτή την επαφή έχει αλλάξει. Αυτό σημαίνει ότι είτε κάποιος προσπαθεί να υποκλέψει την συνομιλία σας είτε αυτή απλά επανεγκατέστησε το Signal. Ίσως επιθυμείτε να επαληθεύσετε το νέο κωδικό ασφαλείας παρακάτω.",
"message": "Ο αριθμός ασφαλείας με αυτή την επαφή έχει αλλάξει. Αυτό σημαίνει ότι είτε κάποιος προσπαθεί να υποκλέψει την επικοινωνία σου είτε η επαφή απλά επανεγκατέστησε το Signal. Ίσως επιθυμείς να επαληθεύσεις το νέο αριθμό ασφαλείας παρακάτω.",
"description": ""
},
"changedSinceVerifiedMultiple": {
"message": "Your safety numbers with multiple group members have changed since you last verified. This could mean that someone is trying to intercept your communication or that they have simply reinstalled Signal.",
"message": "Οι αριθμοί ασφαλείας με πολλαπλά μέλη αυτής της ομάδας άλλαξαν από την τελευταία φορά που επιβεβαιώθηκαν. Αυτό μπορεί να σημαίνει πως κάποιος προσπαθεί να υποκλέψει την επικοινωνία σου ή ότι απλά τα μέλη επανεγκατέστησαν το Signal.",
"description": "Shown on confirmation dialog when user attempts to send a message"
},
"export": {
"message": "Choose directory",
"message": "Επιλογή φακέλου",
"description": "Button to allow the user to export all data from app as part of migration process"
},
"submitDebugLog": {
"message": "Υποβολή καταχωρήσεων αποσφαλμάτωσης",
"message": "Υποβολή αρχείου καταγραφής αποσφαλμάτωσης",
"description": "Menu item and header text for debug log modal, title case."
},
"error": {
@ -794,7 +794,7 @@
"description": ""
},
"changedRightAfterVerify": {
"message": "The safety number you are trying to verify has changed. Please review your new safety number with $name$. Remember, this change could mean that someone is trying to intercept your communication or that $name$ has simply reinstalled Signal.",
"message": "Ο αριθμός ασφαλείας που προσπαθείς να επιβεβαιώσεις έχει αλλάξει. Παρακαλώ επιβεβαίωσε τον νέο αριθμό ασφαλείας με τον/την $name$. Θυμήσου, αυτή η αλλαγή μπορεί να σημαίνει πως κάποιος προσπαθεί να υποκλέψει την επικοινωνία σου ή ότι απλά ο/η $name$ επανεγκατέστησε το Signal.",
"description": "Shown on the safety number screen when the user has selected to verify/unverify a contact's safety number, and we immediately discover a safety number change",
"placeholders": {
"name": {
@ -804,7 +804,7 @@
}
},
"timerSetTo": {
"message": "Η ρύθμιση της αντίστροφης μέτρησης είναι ίση με $time$",
"message": "Η ρύθμιση της αντίστροφης μέτρησης είναι $time$",
"description": "Displayed in the conversation list when the timer is updated.",
"placeholders": {
"time": {
@ -818,7 +818,7 @@
"description": "Very short format indicating current timer setting in the conversation header"
},
"exportInstructions": {
"message": "The first step is to choose a directory to store this application's exported data. It will contain your message history and sensitive cryptographic data, so be sure to save it somewhere private.",
"message": "Το πρώτο βήμα είναι η επιλογή ενός φακέλου για την αποθήκευση των δεδομένων που θα εξαχθούν από αυτή την εφαρμογή. Αυτά περιέχουν το ιστορικό μηνυμάτων σου και ευαίσθητες κρυπτογραφικές πληροφορίες, γι' αυτό επιβεβαιώσου πως το αποθηκεύεις κάπου όπου θα είναι ασφαλές και μη δημόσιο.",
"description": "Description of the export process"
},
"notifications": {
@ -826,11 +826,11 @@
"description": "Header for notification settings"
},
"resend": {
"message": "Ξαναστείλτε",
"message": "Επαναποστολή",
"description": ""
},
"youMarkedAsVerifiedOtherDevice": {
"message": "You marked your safety number with $name$ as verified from another device.",
"message": "Σημείωσες τον αριθμό ασφαλείας με τον/την $name$ ως επιβεβαιωμένο από άλλη συσκευή.",
"description": "Shown in the conversation history when we discover that the user marked a contact as verified on another device.",
"placeholders": {
"name": {

View File

@ -1,57 +1,173 @@
{
"preLinkExpiredHeader": {
"message": "Time to upgrade"
},
"uninstallHeader": {
"message": "How to uninstall"
},
"uninstallStep1": {
"message": "Open this location in your Chrome browser:"
},
"uninstallStep2": {
"message": "Find 'Signal Private Messenger'"
},
"uninstallStep3": {
"message": "Select 'Remove'"
},
"uninstallStep4": {
"message": "Click 'Remove' to confirm"
},
"uninstallStep5": {
"message": "Delete your previously exported data here:"
},
"uninstallStep6": {
"message": "Remove the Signal Desktop Chrome App shortcut"
},
"linuxInstallInstructions": {
"message": "Debian-based Linux install instructions",
"description": "When exporting data for migration to Electron app, Linux instructions pop up."
},
"readOnlyMode": {
"message": "This application is in read-only mode. Please upgrade to the latest Signal Desktop.",
"description": "Shown when the user tries to send a message"
},
"close": {
"message": "Close",
"description": "Shown on a button to dismiss a dialog box"
},
"loading": {
"message": "Loading...",
"description": "Message shown on the loading screen before we've loaded any messages"
},
"migrationWarning": {
"message": "The Signal Desktop Chrome app has been deprecated. Would you like to migrate to the new Signal Desktop now?",
"description": "Warning notification that this version of the app has been deprecated and the user must migrate"
"installComplete": {
"message": "Install is complete",
"description": "Button to click when user has installed the new Signal Desktop"
},
"exportInstructions": {
"message": "The first step is to choose a directory to store this application's exported data. It will contain your message history and sensitive cryptographic data, so be sure to save it somewhere private.",
"description": "Description of the export process"
"upgradeBanner": {
"message": "This legacy version of Signal Desktop will stop working in:",
"description": "Shown at the top of the application alerting the user that this version of Signal Desktop will soon no longer be available."
},
"migrate": {
"message": "Migrate",
"description": "Button label to begin migrating this client to Electron"
"upgradeBannerExpired": {
"message": "This legacy version of Signal Desktop is in read-only mode",
"description": "Shown at the top of the application alerting the user that they will need to find a new Signal Desktop application."
},
"export": {
"message": "Choose directory",
"description": "Button to allow the user to export all data from app as part of migration process"
},
"exportAgain": {
"message": "Export again",
"description": "If user has already exported once, this button allows user to do it again if needed"
},
"exportError": {
"message": "Unfortunately, something went wrong during the export. First, double-check your target empty directory for write access and enough space. Then, please submit a debug log so we can help you get migrated!",
"description": "Helper text if the user went forward on migrating the app, but ran into an error"
},
"confirmMigration": {
"message": "Start migration process? You will not be able to send or receive Signal messages from this application while the migration is in progress.",
"description": "Confirmation dialogue when beginning migration"
},
"migrationDisconnecting": {
"message": "Disconnecting...",
"description": "Displayed while we wait for pending incoming messages to process"
},
"exporting": {
"message": "Please wait while we export your data. It may take several minutes. You can still use Signal on your phone and other devices during this time.",
"description": "Message shown on the migration screen while we export data"
},
"exportComplete": {
"message": "Your data has been exported to: <p><b>$location$</b></p> You'll be able to import this data as you set up <a target='_blank' href='https://support.whispersystems.org/hc/en-us/articles/214507138'>the new Signal Desktop</a>.",
"description": "Message shown on the migration screen when we are done exporting data",
"upgradeBannerTimespan": {
"message": "$days$ days",
"description": "Shown at the top of the application giving the user the amount of time left before this version of Signal Desktop stops working",
"placeholders": {
"location": {
"days": {
"content": "$1",
"example": "/Users/someone/somewhere"
"example": "5"
}
}
},
"installNewSignal": {
"message": "Install new Signal Desktop",
"description": "When export is complete, a button shows which sends user to Signal Desktop install instructions"
"upgradeNow": {
"message": "Upgrade now",
"description": "The button shown in the upgrade banner, starting the upgrade process."
},
"migrateToStandalone": {
"message": "Migrate to standalone",
"description": "Menu option to begin migrating this client to Electron"
},
"migrateInstallStep": {
"message": "The first step is to install the new Signal Desktop.",
"description": "The first step in the export process; installing the standalone desktop app, to ensure it is available for that platform."
},
"saveDataPrompt": {
"message": "Your messages, contacts, groups, and other information can be seamlessly transferred to the standalone version of Signal Desktop. Select the folder where your exported Signal data should be saved.",
"description": "Explanation of what will happen when the user presses the 'choose folder' button during the upgrade process"
},
"chooseFolder": {
"message": "Choose folder",
"description": "Button which pops up a directory selection dialog, and starts saving data to the selected folder"
},
"startExportHeader": {
"message": "Upgrade to the new Signal Desktop",
"description": "Header shown when starting the export and upgrade process"
},
"startExportIntroParagraph1": {
"message": "You are currently using the legacy Chrome version of Signal that was officially deprecated on October 31, 2017. Since then, the new standalone version of Signal Desktop has received regular updates, numerous feature enhancements, and significant performance improvements.",
"description": "Description of the why and the what of the export and upgrade process"
},
"startExportIntroParagraph2": {
"message": "This migration process will help you switch to the latest release. All of your information will be saved, and the upgrade should only take a few minutes. ",
"description": "Description of the why and the what of the export and upgrade process"
},
"startExportIntroParagraph3": {
"message": "Note: Non-Debian-based Linux distributions, ChromeOS, and 32-bit Windows platforms are not currently supported.",
"description": "Description of the why and the what of the export and upgrade process"
},
"moreInformation": {
"message": "More information",
"description": "Description of the why and the what of the export and upgrade process"
},
"upgradeLater": {
"message": "Upgrade Later",
"description": "Description of the why and the what of the export and upgrade process"
},
"imReady": {
"message": "I'm ready",
"description": "Button to kick off the export and upgrade process"
},
"installHeader": {
"message": "Select your Operating System",
"description": "Header text for the install step of the export and upgrade flow"
},
"installIntro": {
"message": "Download and install the new Signal Desktop for your chosen OS.",
"description": "Introduction text for the install step of the export and upgrade flow"
},
"macOS": {
"message": "macOS",
"description": "The name of Apple's desktop operating system"
},
"windows": {
"message": "Windows",
"description": "The name of Microsoft's desktop operating system"
},
"debianLinux": {
"message": "Debian-based Linux",
"description": "A succinct name for the flavors of linux based on Debian"
},
"installed": {
"message": "It's installed",
"description": "The button indicating that the user is done with the installation step"
},
"saveHeader": {
"message": "Save your data",
"description": "The header for the choose folder step of the export and upgrade flow"
},
"completeHeader": {
"message": "Success!",
"description": "The header for the finish step of the export and upgrade flow"
},
"completeIntro": {
"message": "Your Signal Desktop migration data was successfully saved here. Remember this location:",
"description": "A label for the location of the files we put on disk during the export"
},
"completeNextSteps": {
"message": "You are almost done! Download and run the new version to import your data and finish the move.",
"description": "A summary of the next steps to finish the export and upgrade process"
},
"getNewVersion": {
"message": "Get New Version",
"description": "The button to download the new Signal Desktop version after export is complete"
},
"exportErrorHeader": {
"message": "Something went wrong!",
"description": "Header shown at the top of the error screen if something goes wrong during data export"
},
"exportError": {
"message": "Unfortunately, something went wrong during the export. First, check that you have enough space where you saved the file. Then, please submit a debug log so we can help you get migrated!",
"description": "Helper text if the user went forward on migrating the app, but ran into an error"
},
"chooseFolderAndTryAgain": {
"message": "Choose folder and try again",
"description": "Button shown on the export error screen allowing user to try again"
},
"savingData": {
"message": "Saving your data",
"description": "Message shown during the upgrade process while we export data"
},
"selectedLocation": {
"message": "your selected location",

View File

@ -76,7 +76,7 @@
"description": "Conversation menu option to enable disappearing messages"
},
"deleteWarning": {
"message": "¿Estás seguro? Al hacer clic en 'eliminar' se eliminará permanentemente este mensaje de este dispositivo.",
"message": "¿Estás seguro? Al hacer clic en «eliminar» se eliminará permanentemente este mensaje de este dispositivo.",
"description": ""
},
"showMore": {
@ -232,7 +232,7 @@
"description": "Button label to begin migrating this client to Electron"
},
"theyChangedTheTimer": {
"message": "$name$ ha fijado la caducidad a $time$.",
"message": "$name$ ha fijado la caducidad de mensajes a $time$.",
"description": "Message displayed when someone else changes the message expiration timer in a conversation.",
"placeholders": {
"name": {
@ -258,7 +258,7 @@
"description": "Label for a selectable option in the message expiration timer menu"
},
"exporting": {
"message": "Por favor, espere mientras exportamos sus datos. Puede tomar varios minutos. Todavía puede utilizar Signal en su teléfono y otros dispositivos durante este tiempo.",
"message": "Por favor, espera mientras exportamos tus datos. Puede tomar varios minutos. Mientras tanto puedes utilizar Signal en tu teléfono y otros dispositivos.",
"description": "Message shown on the migration screen while we export data"
},
"reportIssue": {
@ -320,7 +320,7 @@
"description": "Label for a button that syncs contacts and groups from your phone"
},
"syncFailed": {
"message": "Fallo al importar. Asegúrate de que ordenador y teléfono estan conectados a internet.",
"message": "Fallo al importar. Asegúrate de que ordenador y teléfono están conectados a internet.",
"description": "Informational text displayed if a sync operation times out."
},
"unlinked": {
@ -578,7 +578,7 @@
"description": "Alt text for button to take user down to bottom of conversation with a new message out of screen"
},
"exportComplete": {
"message": "Sus datos se han exportado a: <p><b>$location$</b></p> Podrá importar estos datos al configurar <a target='_blank' href='https://support.whispersystems.org/hc/en-us/articles/214507138'>el nuevo Signal Desktop</a>.",
"message": "Tus datos se han exportado a: <p><b>$location$</b></p> Podrás importar estos datos al configurar <a target='_blank' href='https://support.whispersystems.org/hc/en-us/articles/214507138'>el nuevo Signal Desktop</a>.",
"description": "Message shown on the migration screen when we are done exporting data",
"placeholders": {
"location": {
@ -750,7 +750,7 @@
"description": ""
},
"installNewSignal": {
"message": "Instalar nuevo Signal Desktop",
"message": "Instalar el nuevo Signal Desktop",
"description": "When export is complete, a button shows which sends user to Signal Desktop install instructions"
},
"verify": {

View File

@ -8,11 +8,11 @@
"description": "Label for a selectable option in the message expiration timer menu"
},
"view": {
"message": "View",
"message": "نمایش",
"description": "Used as a label on a button allowing user to see more information"
},
"upgradingDatabase": {
"message": "Upgrading database. This may take some time...",
"message": "در حال به‌روزرسانی پابگاه داده. ممکن است کمی طول بکشد...",
"description": "Message shown on the loading screen when we're changing database structure on first run of a new version"
},
"lastSynced": {
@ -174,7 +174,7 @@
"description": ""
},
"delete": {
"message": "Delete",
"message": "پاک کن",
"description": ""
},
"verified": {
@ -308,7 +308,7 @@
"description": "Placeholder text in the search input"
},
"someRecipientsFailed": {
"message": "Some recipients failed.",
"message": "برخی دریافت‌ها با مشکل مواجه شد.",
"description": "When you send to multiple recipients via a group, and the message went to some recipients but not others."
},
"noNameOrMessage": {
@ -386,7 +386,7 @@
"description": "Very short format indicating current timer setting in the conversation header"
},
"loadingMessages": {
"message": "Loading messages. $count$ so far...",
"message": "در حال بارگذاری پیام‌ها. تاکنون $count$ ...",
"description": "Message shown on the loading screen when we're catching up on the backlog of messages",
"placeholders": {
"count": {
@ -530,7 +530,7 @@
"description": "Placeholder text in the message entry field"
},
"me": {
"message": "Me",
"message": "من",
"description": "The label for yourself when shown in a group member list"
},
"mediaMessage": {
@ -550,7 +550,7 @@
"description": ""
},
"deleteMessage": {
"message": "Delete this message",
"message": "پاک کردن این پیام",
"description": ""
},
"timerOption_0_seconds": {
@ -620,7 +620,7 @@
"description": "Hover text for attachment filenames"
},
"sendAnyway": {
"message": "Send Anyway",
"message": "به هر حال بفرست",
"description": "Used on a warning dialog to make it clear that it might be risky to send the message."
},
"youMarkedAsVerified": {
@ -658,7 +658,7 @@
"description": "Label for a selectable option in the message expiration timer menu"
},
"showMembers": {
"message": "Show members",
"message": "نمایش اعضا",
"description": ""
},
"youMarkedAsNotVerifiedOtherDevice": {
@ -686,11 +686,11 @@
"description": "Text displayed before the phone number that the user is in the process of linking with"
},
"groupMembers": {
"message": "Group members",
"message": "اعضای گروه",
"description": ""
},
"loading": {
"message": "Loading...",
"message": "در حال بارگذاری...",
"description": "Message shown on the loading screen before we've loaded any messages"
},
"newMessages": {

View File

@ -42,7 +42,7 @@
"description": "Warning that long messages could not get received completely by Android clients."
},
"youChangedTheTimer": {
"message": "You set the timer to $time$.",
"message": "Postavili ste brojač na $time$.",
"description": "Message displayed when you change the message expiration timer in a conversation.",
"placeholders": {
"time": {
@ -662,7 +662,7 @@
"description": ""
},
"youMarkedAsNotVerifiedOtherDevice": {
"message": "You marked your safety number with $name$ as not verified from another device.",
"message": "Označili ste svoj sigurnosni broj s $name$ kao nepotvrđen s drugog uređaja.",
"description": "Shown in the conversation history when we discover that the user marked a contact as not verified on another device.",
"placeholders": {
"name": {
@ -804,7 +804,7 @@
}
},
"timerSetTo": {
"message": "Timer set to $time$",
"message": "Brojač postavljen na $time$",
"description": "Displayed in the conversation list when the timer is updated.",
"placeholders": {
"time": {

View File

@ -8,7 +8,7 @@
"description": "Label for a selectable option in the message expiration timer menu"
},
"view": {
"message": "View",
"message": "ತೋರಿಸು",
"description": "Used as a label on a button allowing user to see more information"
},
"upgradingDatabase": {
@ -34,7 +34,7 @@
"description": "Displayed for incoming unsupported attachment"
},
"showSafetyNumber": {
"message": "Show safety number",
"message": "ಸುರಕ್ಷತಾ ಸಂಖ್ಯೆ ತೋರಿಸಿ",
"description": ""
},
"androidMessageLengthWarning": {
@ -72,7 +72,7 @@
"description": "Label for a button that dismisses a dialog. The user clicks it to confirm that they understand the message in the dialog."
},
"disappearingMessages": {
"message": "Disappearing messages",
"message": "ಕಣ್ಮರೆಯಾಗುವ ಸಂದೇಶಗಳು",
"description": "Conversation menu option to enable disappearing messages"
},
"deleteWarning": {
@ -98,7 +98,7 @@
"description": "When there are multiple previously-verified group members with safety number changes, a banner will be shown. The list of contacts with safety number changes is shown, and this text introduces that list."
},
"timerOption_5_minutes_abbreviated": {
"message": "5m",
"message": "5 ನಿಮಿಷಗಳು",
"description": "Very short format indicating current timer setting in the conversation header"
},
"unsupportedFileType": {
@ -110,11 +110,11 @@
"description": "If user has already exported once, this button allows user to do it again if needed"
},
"clickToSave": {
"message": "Click to save",
"message": "ಉಳಿಸಲು ಕ್ಲಿಕ್ ಮಾಡಿ",
"description": "Hover text for attachment filenames"
},
"messagesBelow": {
"message": "New messages below",
"message": "ಕೆಳಗೆ ಹೊಸ ಸಂದೇಶಗಳು ಇವೆ",
"description": "Alt text for button to take user down to bottom of conversation with more than one message out of screen"
},
"installGeneratingKeys": {
@ -146,7 +146,7 @@
"description": "Brief timestamp for messages sent about one minute ago. Displayed in the conversation list and message bubble."
},
"timerOption_0_seconds_abbreviated": {
"message": "off",
"message": "ಆರಿಸು",
"description": "Short format indicating current timer setting in the conversation list snippet"
},
"syncExplanation": {
@ -162,7 +162,7 @@
"description": "Displayed when a user can't send a message because they have left the group"
},
"deleteMessages": {
"message": "Delete messages",
"message": "ಸಂದೇಶಗಳನ್ನು ಅಳಿಸಿ",
"description": "Menu item for deleting messages, title case."
},
"confirmMigration": {
@ -174,11 +174,11 @@
"description": ""
},
"delete": {
"message": "Delete",
"message": "ಅಳಿಸು",
"description": ""
},
"verified": {
"message": "Verified",
"message": "ಪರಿಶೀಲಿಸಲಾಗಿದೆ",
"description": ""
},
"selectAContact": {
@ -254,7 +254,7 @@
"description": "Shown on confirmation dialog when user attempts to send a message"
},
"timerOption_1_day": {
"message": "1 day",
"message": "1 ದಿನ",
"description": "Label for a selectable option in the message expiration timer menu"
},
"exporting": {
@ -282,7 +282,7 @@
"description": "Confirmation dialog text that asks the user if they really wish to delete the conversation. Answer buttons use the strings 'ok' and 'cancel'. The deletion is permanent, i.e. it cannot be undone."
},
"unlinkedWarning": {
"message": "Relink Signal Desktop to your mobile device to continue messaging.",
"message": "ಸಂದೇಶವನ್ನು ಮುಂದುವರಿಸಲು ಸಿಗ್ನಲ್ ಡೆಸ್ಕ್ಟಾಪ್ ಅನ್ನು ನಿಮ್ಮ ಮೊಬೈಲ್ ಸಾಧನಕ್ಕೆ ರಿಲೀಂಕ್ ಮಾಡಿ.",
"description": ""
},
"debugLogExplanation": {
@ -374,7 +374,7 @@
"description": ""
},
"timerOption_30_minutes_abbreviated": {
"message": "30m",
"message": "30 ನಿಮಿಷಗಳು",
"description": "Very short format indicating current timer setting in the conversation header"
},
"themeAndroidDark": {
@ -382,7 +382,7 @@
"description": "Label text for dark Android theme"
},
"timerOption_1_minute_abbreviated": {
"message": "1m",
"message": "1 ನಿಮಿಷ",
"description": "Very short format indicating current timer setting in the conversation header"
},
"loadingMessages": {
@ -396,7 +396,7 @@
}
},
"changedSinceVerified": {
"message": "Your safety number with $name$ has changed since you last verified. This could mean that someone is trying to intercept your communication or that $name$ has simply reinstalled Signal.",
"message": "ನೀವು ಕೊನೆಯದಾಗಿ ಪರಿಶೀಲಿಸಿದ ನಂತರ $name$ ನಿಮ್ಮ ಸುರಕ್ಷತೆ ಸಂಖ್ಯೆ ಬದಲಾಗಿದೆ. ನಿಮ್ಮ ಸಂವಹನವನ್ನು ಯಾರಾದರೂ ತಡೆಗಟ್ಟಲು ಪ್ರಯತ್ನಿಸುತ್ತಿದ್ದಾರೆ ಅಥವಾ $name$ ಸಿಗ್ನಲ್ ಅನ್ನು ಪುನಃ ಸ್ಥಾಪಿಸಿದ್ದಾರೆ",
"description": "Shown on confirmation dialog when user attempts to send a message",
"placeholders": {
"name": {
@ -428,7 +428,7 @@
"description": "Header for theme settings"
},
"newIdentity": {
"message": "New safety number",
"message": "ಹೊಸ ಸುರಕ್ಷತೆ ಸಂಖ್ಯೆ",
"description": "Header for a key change dialog"
},
"installTagline": {
@ -466,7 +466,7 @@
"description": "Label for a button to accept a new safety number"
},
"timerOption_12_hours_abbreviated": {
"message": "12h",
"message": "12 ಗಂಟೆ",
"description": "Very short format indicating current timer setting in the conversation header"
},
"timestampFormat_M": {
@ -474,7 +474,7 @@
"description": "Timestamp format string for displaying month and day (but not the year) of a date within the current year, ex: use 'MMM D' for 'Aug 8', or 'D MMM' for '8 Aug'."
},
"timerOption_6_hours_abbreviated": {
"message": "6h",
"message": "6 ಗಂಟೆ",
"description": "Very short format indicating current timer setting in the conversation header"
},
"unregisteredUser": {
@ -482,7 +482,7 @@
"description": "Error message displayed when sending to an unregistered user."
},
"timerOption_1_day_abbreviated": {
"message": "1d",
"message": "1 ದಿನ",
"description": "Very short format indicating current timer setting in the conversation header"
},
"sync": {
@ -490,7 +490,7 @@
"description": "Label for contact and group sync settings"
},
"timerOption_1_week": {
"message": "1 week",
"message": "ಒಂದು ವಾರ",
"description": "Label for a selectable option in the message expiration timer menu"
},
"installGotIt": {
@ -530,7 +530,7 @@
"description": "Placeholder text in the message entry field"
},
"me": {
"message": "Me",
"message": "ನಾನು",
"description": "The label for yourself when shown in a group member list"
},
"mediaMessage": {
@ -538,7 +538,7 @@
"description": "Description of a message that has an attachment and no text, displayed in the conversation list as a preview."
},
"unreadMessage": {
"message": "1 Unread Message",
"message": "1 ಓದದಿರುವ ಸಂದೇಶ",
"description": "Text for unread message separator, just one message"
},
"expiredWarning": {
@ -550,11 +550,11 @@
"description": ""
},
"deleteMessage": {
"message": "Delete this message",
"message": "ಈ ಸಂದೇಶವನ್ನು ಅಳಿಸಿ",
"description": ""
},
"timerOption_0_seconds": {
"message": "off",
"message": "ಆರಿಸು",
"description": "Label for option to turn off message expiration in the timer menu"
},
"installAndroidInstructions": {
@ -566,7 +566,7 @@
"description": "Displayed while we wait for pending incoming messages to process"
},
"invalidNumberError": {
"message": "Invalid number",
"message": "ತಪ್ಪಾದ ಸಂಖ್ಯೆ",
"description": "When a person inputs a number that is invalid"
},
"installWelcome": {
@ -634,7 +634,7 @@
}
},
"connecting": {
"message": "Connecting",
"message": "ಸಂಪರ್ಕಿಸಲಾಗುತ್ತಿದೆ",
"description": "Displayed when the desktop client is currently connecting to the server."
},
"sessionEnded": {
@ -650,7 +650,7 @@
"description": ""
},
"timerOption_1_week_abbreviated": {
"message": "1w",
"message": "1 ವಾರ",
"description": "Very short format indicating current timer setting in the conversation header"
},
"timerOption_5_seconds": {
@ -658,7 +658,7 @@
"description": "Label for a selectable option in the message expiration timer menu"
},
"showMembers": {
"message": "Show members",
"message": "ಸದಸ್ಯರನ್ನು ತೋರಿಸು",
"description": ""
},
"youMarkedAsNotVerifiedOtherDevice": {
@ -698,15 +698,15 @@
"description": "Displayed in notifications for multiple messages"
},
"newContact": {
"message": "Click to create new contact",
"message": "ಹೊಸ ಸಂಪರ್ಕವನ್ನು ಉಳಿಸಲು ಕ್ಲಿಕ್ ಮಾಡಿ",
"description": ""
},
"theirIdentityUnknown": {
"message": "You haven't exchanged any messages with this contact yet. Your safety number with them will be available after the first message.",
"message": "ನೀವು ಇನ್ನೂ ಈ ಸಂಪರ್ಕದೊಂದಿಗೆ ಯಾವುದೇ ಸಂದೇಶಗಳನ್ನು ವಿನಿಮಯ ಮಾಡಿಲ್ಲ. ಅವರೊಂದಿಗೆ ನಿಮ್ಮ ಸುರಕ್ಷತಾ ಸಂಖ್ಯೆ ಮೊದಲ ಸಂದೇಶದ ನಂತರ ಲಭ್ಯವಿರುತ್ತದೆ.",
"description": ""
},
"voiceMessage": {
"message": "Voice Message",
"message": "ಧ್ವನಿ ಸಂದೇಶ",
"description": "Name for a voice message attachment"
},
"submit": {

View File

@ -84,7 +84,7 @@
"description": "Displays the details of a key change"
},
"verifyHelp": {
"message": "Se você desejar verificar a segurança da sua criptografia ponta-a-ponta com $name$, compare os números acima com os números no aparelho desse contato.",
"message": "Se você desejar verificar a segurança da sua criptografia ponta-a-ponta com $name$, compare os números acima com os números no aparelho dessa pessoa.",
"description": "",
"placeholders": {
"name": {
@ -308,7 +308,7 @@
"description": "Placeholder text in the search input"
},
"someRecipientsFailed": {
"message": "Some recipients failed.",
"message": "Falha em alguns envios.",
"description": "When you send to multiple recipients via a group, and the message went to some recipients but not others."
},
"noNameOrMessage": {
@ -324,7 +324,7 @@
"description": "Informational text displayed if a sync operation times out."
},
"unlinked": {
"message": "Não-religado",
"message": "Não religado",
"description": ""
},
"installFollowUs": {
@ -574,7 +574,7 @@
"description": "Welcome title on the install page"
},
"messageBelow": {
"message": "Nova mensagem abaixo",
"message": "Nova mensagem, abaixo",
"description": "Alt text for button to take user down to bottom of conversation with a new message out of screen"
},
"exportComplete": {
@ -724,7 +724,7 @@
}
},
"identityKeyErrorOnSend": {
"message": "O seu número de segurança com $name$mudou. Isso pode significar uma tentativa de interceptação das suas comunicações ou somente que $name$ reinstalou Signal. Recomendamos que você verifique o seu número de segurança com com esse contato.",
"message": "O seu número de segurança com $name$mudou. Isso pode significar uma tentativa de interceptação das suas comunicações ou somente que $name$ reinstalou Signal. Recomendamos que você verifique o seu número de segurança com esse contato.",
"description": "Shown when user clicks on a failed recipient in the message detail view after an identity key change",
"placeholders": {
"name": {
@ -778,7 +778,7 @@
"description": ""
},
"changedSinceVerifiedMultiple": {
"message": "Os seus números de segurança com varios membros mudaram desde a última vez que você os verificou. Isso pode significar uma tentativa de interceptação das suas comunicações ou somente que esses contatos reinstalaram Signal.",
"message": "Os seus números de segurança com vários membros mudaram desde a última vez que você os verificou. Isso pode significar uma tentativa de interceptação das suas comunicações ou somente que esses contatos reinstalaram Signal.",
"description": "Shown on confirmation dialog when user attempts to send a message"
},
"export": {

View File

@ -186,7 +186,7 @@
"description": ""
},
"exportError": {
"message": "Infelizmente, algo falhou durante a exportação. Primeiro, verifique que a pasta de destino tem acesso de escrita e espaço suficiente. Depois, por favor envie um debug log para que o(a) possamos ajudar a migrar!",
"message": "Infelizmente, algo falhou durante a exportação. Primeiro, verifique que a pasta de destino tem acesso de escrita e espaço suficiente. Depois, por favor envie um relatório de erros para que o(a) possamos ajudar a migrar!",
"description": "Helper text if the user went forward on migrating the app, but ran into an error"
},
"installConnecting": {
@ -224,7 +224,7 @@
"description": "Label for the time a message was sent"
},
"migrationWarning": {
"message": "O Signal Desktop para o Chrome foi descontinuada. Pretende migrar para o novo Signal Desktop agora?",
"message": "O Signal Desktop para o Chrome foi descontinuado. Pretende migrar para o novo Signal Desktop agora?",
"description": "Warning notification that this version of the app has been deprecated and the user must migrate"
},
"migrate": {

View File

@ -750,7 +750,7 @@
"description": ""
},
"installNewSignal": {
"message": "Install new Signal Desktop",
"message": "Instalează noul Signal Desktop",
"description": "When export is complete, a button shows which sends user to Signal Desktop install instructions"
},
"verify": {

View File

@ -106,7 +106,7 @@
"description": "Displayed for outgoing unsupported attachment"
},
"exportAgain": {
"message": "Export again",
"message": "再次匯出",
"description": "If user has already exported once, this button allows user to do it again if needed"
},
"clickToSave": {
@ -258,7 +258,7 @@
"description": "Label for a selectable option in the message expiration timer menu"
},
"exporting": {
"message": "Please wait while we export your data. It may take several minutes. You can still use Signal on your phone and other devices during this time.",
"message": "請耐心等待我們匯出你的資料此過程將需數分鐘。你可以繼續在電話及其他裝置上使用Signal。",
"description": "Message shown on the migration screen while we export data"
},
"reportIssue": {
@ -750,7 +750,7 @@
"description": ""
},
"installNewSignal": {
"message": "Install new Signal Desktop",
"message": "安裝新的Signal Desktop",
"description": "When export is complete, a button shows which sends user to Signal Desktop install instructions"
},
"verify": {

View File

@ -2,30 +2,6 @@
<html>
<head>
<meta charset='utf-8'>
<script type='text/x-tmpl-mustache' id='app-migration-screen'>
<div class='content'>
<img src='/images/icon_128.png'>
{{ ^hideProgress }}
<div class='container'>
<span class='dot'></span>
<span class='dot'></span>
<span class='dot'></span>
</div>
{{ /hideProgress }}
<div class='message'>{{& message }}</div>
<div>
{{ #installButton }}
<button class='install grey'>{{ installButton }}</button>
{{ /installButton }}
{{ #exportButton }}
<button class='export grey'>{{ exportButton }}</button>
{{ /exportButton }}
{{ #debugLogButton }}
<button class='debug-log grey'>{{ debugLogButton }}</button>
{{ /debugLogButton }}
</div>
</div>
</script>
<script type='text/x-tmpl-mustache' id='app-loading-screen'>
<div class='content'>
<img src='/images/icon_128.png'>
@ -58,6 +34,7 @@
<ul class='menu-list'>
<li class='showSettings'>{{ settings }}</li>
<li class='show-debug-log'>{{ submitDebugLog }}</li>
<li class='migrate'>{{ migrate }}</li>
<li class='restart-signal'>{{ restartSignal }}</li>
</ul>
</div>
@ -103,12 +80,15 @@
<a target='_blank' href='https://chrome.google.com/webstore/detail/bikioccmkafdpakkkcpdbppfkghcmihk'>
<button class='upgrade'>{{ upgrade }}</button>
</a>
{{ expiredWarning }}
</script>
<script type='text/x-tmpl-mustache' id='migration_alert'>
<button class='migrate'>{{ migrate }}</button>
<div class='message'>
{{ migrationWarning }}
{{ expiredWarning }}
</div>
</script>
<script type='text/x-tmpl-mustache' id='upgrade_banner'>
<span class='x banner-close'></span>
<button class='migrate'>{{ upgradeNow }}</button>
<div class='message'>
{{ upgradeMessage }} <span class='highlight'>{{ highlight }}</span>
</div>
</script>
<script type='text/x-tmpl-mustache' id='banner'>
@ -147,7 +127,6 @@
<div class='conversation-menu menu'>
<button class='hamburger' alt='conversation menu'></button>
<ul class='menu-list'>
<li class='disappearing-messages'>{{ disappearing-messages }}</li>
{{#group}}
<li class='show-members'>{{ show-members }}</li>
<!-- <li class='update-group'>Update group</li> -->
@ -157,7 +136,6 @@
{{ ^isMe }}
<li class='show-identity'>{{ show-identity }}</li>
{{ /isMe }}
<li class='end-session'>{{ end-session }}</li>
{{/group}}
<li class='destroy'>{{ destroy }}</li>
</ul>
@ -612,92 +590,167 @@
{{/action }}
</script>
<script type='text/x-tmpl-mustache' id='linux-install-instructions'>
<div class='confirmation-dialog modal'>
<div class="content" tabindex="2">
<div class='header'>{{ header }}</div>
<pre class='instructions'>curl -s https://updates.signal.org/desktop/apt/keys.asc | sudo apt-key add -
echo "deb [arch=amd64] https://updates.signal.org/desktop/apt xenial main" | sudo tee -a /etc/apt/sources.list.d/signal-xenial.list
sudo apt update && sudo apt install signal-desktop
/opt/Signal/signal-desktop --import</pre>
<div class='buttons'>
<button class='ok' tabindex='1'>{{ ok }}</button>
</div>
</div>
</div>
</script>
<script type='text/x-tmpl-mustache' id='migration-flow-template'>
{{#isStep1}}
<div id='step1' class='step'>
<div class='inner'>
<div class='step-body'>
<img class='banner-image' src='/images/icon_128.png'>
<div class='header'>{{ startHeader }}</div>
<div class='body-text-wide'>{{ startParagraph1 }}</div>
<div class='body-text-wide'>{{ startParagraph2 }}</div>
<div class='body-text-wide red-text'>{{ startParagraph3 }} <a target='_blank' href='https://support.signal.org/hc/en-us/articles/360007320431'>{{ moreInformation }}</div>
</div>
<div class='nav'>
<div>
<a class='button start'>{{ startButton }}</a>
</div>
<div>
<a class='link cancel'>{{ cancelButton }}</a>
</div>
</div>
</div>
</div>
{{/isStep1}}
{{#isStep2}}
<div id='step2' class='step'>
<div class='inner'>
<div class='step-body'>
<span class='banner-icon folder-outline'></span>
<div class='header'>{{ chooseHeader }}</div>
<div class='body-text-wide'>{{ choose }}</div>
</div>
<div class='nav'>
<div>
<a class='button choose'>{{ chooseButton }}</a>
</div>
<div>
<a class='link cancel'>{{ cancelButton }}</a>
</div>
</div>
</div>
</div>
{{/isStep2}}
{{#isStep3}}
<div id='step3' class='step'>
<div class='inner'>
<div class='step-body'>
<span class='banner-icon export'></span>
<div class='header'>{{ exportHeader }}</div>
</div>
<div class='progress'>
<div class='bar-container'>
<div class='bar progress-bar progress-bar-striped active'></div>
</div>
</div>
<div class='nav'>
<div>
<a class='link submit-debug-log'>{{ debugLogButton }}</a>
</div>
</div>
</div>
</div>
{{/isStep3}}
{{#isStep4}}
<div id='step4' class='step'>
<div class='inner'>
<div class='step-body'>
<span class='banner-icon check-circle-outline'></span>
<div class='header'>{{ completeHeader }}</div>
<div class='body-text-wide center'>{{ completeIntro }}</div>
<div class='export-location'>{{ completeLocation }}</div>
<div class='body-text-wide'>{{ completeNextSteps }}</div>
<div class='body-text-wide'>{{ completeSignoff }}</div>
</div>
<div class='nav'>
<div>
<a class='button get-new-version' target='_blank' href='{{ downloadLocation }}'>{{ installButton }}</a>
</div>
<div>
<a class='link submit-debug-log'>{{ debugLogButton }}</a>
</div>
</div>
</div>
</div>
{{/isStep4}}
{{#isStep5}}
<div id='step5' class='step'>
<div class='inner'>
<div class='step-body'>
<span class='banner-icon check-circle-outline'></span>
<div class='header'>{{ uninstallHeader }}</div>
<div class='body-text-wide center'>
<ol class='uninstall-steps'>
<li>{{ uninstallStep5 }}</li>
<div class='export-location'>{{ completeLocation }}</div>
{{ #uninstallStep6 }}
<li><a href="https://support.signal.org/hc/en-us/articles/360007320191" target="_blank">{{ uninstallStep6 }}</a></li>
{{ /uninstallStep6 }}
<li>{{ uninstallStep1 }} <div class='url'>chrome://extensions/</div></li>
<li>{{ uninstallStep2 }}</li>
<li>{{ uninstallStep3 }}</li>
<li>{{ uninstallStep4 }}</li>
</ol>
</div>
<div class='nav'>
<div>
<a class='link submit-debug-log'>{{ debugLogButton }}</a>
</div>
</div>
</div>
</div>
{{/isStep5}}
{{#isError}}
<div id='error' class='step'>
<div class='inner error-dialog clearfix'>
<div class='step-body'>
<span class='banner-icon alert-outline'></span>
<div class='header'>{{ errorHeader }}</div>
<div class='body-text-wide'>{{ error }}</div>
</div>
<div class='nav'>
<div>
<a class='button choose'>{{ tryAgain }}</a>
</div>
<div>
<a class='link submit-debug-log'>{{ debugLogButton }}</a>
</div>
</div>
</div>
</div>
{{/isError}}
</script>
<script type='text/x-tmpl-mustache' id='install_flow_template'>
<div id='step1' class='step'>
<div class='inner'>
<div class='step-body'>
<img id='signal-icon' src='/images/icon_250.png'/>
<h1>{{ installWelcome }}</h1>
<p>{{ installTagline }}</p>
</div>
<div class='nav'>
<div> <a class='button step2'>{{ installGetStartedButton }}</a> </div>
<span class='dot step1 selected'></span>
<span class='dot step2'></span>
<span class='dot step3'></span>
</div>
</div>
</div>
<div id='step2' class='step'>
<div class='inner'>
<div class='step-body'>
<img id='signal-phone' src='/images/signal-phone.png'>
<p>{{{ installSignalLink }}}</p>
</div>
<div class='nav'>
<div> <a class='button step3'>{{ installIHaveSignalButton }}</a> </div>
<span class='dot step1'></span>
<span class='dot step2 selected'></span>
<span class='dot step3'></span>
</div>
</div>
</div>
<div id='step3' class='step'>
<div class='inner'>
<div class='step-body'>
<div id="qr"></div>
<p>{{ installAndroidInstructions }}</p>
</div>
<div class='nav'>
<span class='dot step1'></span>
<span class='dot step2'></span>
<span class='dot step3 selected'></span>
</div>
</div>
</div>
<form id='step4' class='step'>
<div class='inner'>
<div class='step-body'>
<p>{{ installLinkingWithNumber }}</p>
<h2 class='number'></h2>
<img id='signal-computer' src='/images/signal-laptop.png'>
<p>{{ installComputerName }}</p>
<div>
<input type='text' id='device-name' spellcheck='false' maxlength='50' />
</div>
<img class='banner-image' src='/images/icon_128.png'>
<div class='header'>{{ preLinkExpiredHeader }}</div>
<div class='body-text-wide'>{{ startExportIntroParagraph1 }}</div>
</div>
<div class='nav'>
<div>
<input type='submit' class='button' id='sync' value='{{ installFinalButton }}' />
<a class='button get-new-version' target='_blank' href='https://signal.org/download'>{{ getNewVersion }}</a>
</div>
</div>
</div>
</form>
<div id='step5' class='step'>
<div class='inner'>
<div class='step-body'>
<img id='signal-icon' src='/images/icon_250.png'/>
<div class='progress-dialog'>
<p class='status'></p>
<div class='bar-container'><div class='bar progress-bar'></div></div>
</div>
</div>
<div class='nav'>
</div>
</div>
</div>
<div id='stepTooManyDevices' class='step'>
<div class='inner error-dialog clearfix'>
<div class='panel step-body'>{{ installTooManyDevices }}</div>
<div class='nav'>
<button class='ok step3'>{{ ok }}</button>
</div>
</div>
</div>
</script>

1
images/alert-outline.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M12,2L1,21H23M12,6L19.53,19H4.47M11,10V14H13V10M11,16V18H13V16" /></svg>

After

Width:  |  Height:  |  Size: 357 B

1
images/apple.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M18.71,19.5C17.88,20.74 17,21.95 15.66,21.97C14.32,22 13.89,21.18 12.37,21.18C10.84,21.18 10.37,21.95 9.1,22C7.79,22.05 6.8,20.68 5.96,19.47C4.25,17 2.94,12.45 4.7,9.39C5.57,7.87 7.13,6.91 8.82,6.88C10.1,6.86 11.32,7.75 12.11,7.75C12.89,7.75 14.37,6.68 15.92,6.84C16.57,6.87 18.39,7.1 19.56,8.82C19.47,8.88 17.39,10.1 17.41,12.63C17.44,15.65 20.06,16.66 20.09,16.67C20.06,16.74 19.67,18.11 18.71,19.5M13,3.5C13.73,2.67 14.94,2.04 15.94,2C16.07,3.17 15.6,4.35 14.9,5.19C14.21,6.04 13.07,6.7 11.95,6.61C11.8,5.46 12.36,4.26 13,3.5Z" /></svg>

After

Width:  |  Height:  |  Size: 824 B

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4M11,16.5L6.5,12L7.91,10.59L11,13.67L16.59,8.09L18,9.5L11,16.5Z" /></svg>

After

Width:  |  Height:  |  Size: 499 B

1
images/export.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M23,12L19,8V11H10V13H19V16M1,18V6C1,4.89 1.9,4 3,4H15A2,2 0 0,1 17,6V9H15V6H3V18H15V15H17V18A2,2 0 0,1 15,20H3A2,2 0 0,1 1,18Z" /></svg>

After

Width:  |  Height:  |  Size: 421 B

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M20,18H4V8H20M20,6H12L10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6Z" /></svg>

After

Width:  |  Height:  |  Size: 401 B

2
images/linux.svg Normal file
View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M791 411q-11 1-15.5 10.5t-8.5 9.5q-5 1-5-5 0-12 19-15h10zm87 14q-4 1-11.5-6.5t-17.5-4.5q24-11 32 2 3 6-3 9zm-351 427q-4-1-6 3t-4.5 12.5-5.5 13.5-10 13q-10 11-1 12 4 1 12.5-7t12.5-18q1-3 2-7t2-6 1.5-4.5.5-4v-3l-1-2.5-3-2zm855 359q0-18-55-42 4-15 7.5-27.5t5-26 3-21.5.5-22.5-1-19.5-3.5-22-4-20.5-5-25-5.5-26.5q-10-48-47-103t-72-75q24 20 57 83 87 162 54 278-11 40-50 42-31 4-38.5-18.5t-8-83.5-11.5-107q-9-39-19.5-69t-19.5-45.5-15.5-24.5-13-15-7.5-7q-14-62-31-103t-29.5-56-23.5-33-15-40q-4-21 6-53.5t4.5-49.5-44.5-25q-15-3-44.5-18t-35.5-16q-8-1-11-26t8-51 36-27q37-3 51 30t4 58q-11 19-2 26.5t30 .5q13-4 13-36v-37q-5-30-13.5-50t-21-30.5-23.5-15-27-7.5q-107 8-89 134 0 15-1 15-9-9-29.5-10.5t-33 .5-15.5-5q1-57-16-90t-45-34q-27-1-41.5 27.5t-16.5 59.5q-1 15 3.5 37t13 37.5 15.5 13.5q10-3 16-14 4-9-7-8-7 0-15.5-14.5t-9.5-33.5q-1-22 9-37t34-14q17 0 27 21t9.5 39-1.5 22q-22 15-31 29-8 12-27.5 23.5t-20.5 12.5q-13 14-15.5 27t7.5 18q14 8 25 19.5t16 19 18.5 13 35.5 6.5q47 2 102-15 2-1 23-7t34.5-10.5 29.5-13 21-17.5q9-14 20-8 5 3 6.5 8.5t-3 12-16.5 9.5q-20 6-56.5 21.5t-45.5 19.5q-44 19-70 23-25 5-79-2-10-2-9 2t17 19q25 23 67 22 17-1 36-7t36-14 33.5-17.5 30-17 24.5-12 17.5-2.5 8.5 11q0 2-1 4.5t-4 5-6 4.5-8.5 5-9 4.5-10 5-9.5 4.5q-28 14-67.5 44t-66.5 43-49 1q-21-11-63-73-22-31-25-22-1 3-1 10 0 25-15 56.5t-29.5 55.5-21 58 11.5 63q-23 6-62.5 90t-47.5 141q-2 18-1.5 69t-5.5 59q-8 24-29 3-32-31-36-94-2-28 4-56 4-19-1-18-2 1-4 5-36 65 10 166 5 12 25 28t24 20q20 23 104 90.5t93 76.5q16 15 17.5 38t-14 43-45.5 23q8 15 29 44.5t28 54 7 70.5q46-24 7-92-4-8-10.5-16t-9.5-12-2-6q3-5 13-9.5t20 2.5q46 52 166 36 133-15 177-87 23-38 34-30 12 6 10 52-1 25-23 92-9 23-6 37.5t24 15.5q3-19 14.5-77t13.5-90q2-21-6.5-73.5t-7.5-97 23-70.5q15-18 51-18 1-37 34.5-53t72.5-10.5 60 22.5zm-628-827q3-17-2.5-30t-11.5-15q-9-2-9 7 2 5 5 6 10 0 7 15-3 20 8 20 3 0 3-3zm419 197q-2-8-6.5-11.5t-13-5-14.5-5.5q-5-3-9.5-8t-7-8-5.5-6.5-4-4-4 1.5q-14 16 7 43.5t39 31.5q9 1 14.5-8t3.5-20zm-178-213q0-11-5-19.5t-11-12.5-9-3q-6 0-8 2t0 4 5 3q14 4 18 31 0 3 8-2 2-2 2-3zm54-233q0-2-2.5-5t-9-7-9.5-6q-15-15-24-15-9 1-11.5 7.5t-1 13-.5 12.5q-1 4-6 10.5t-6 9 3 8.5q4 3 8 0t11-9 15-9q1-1 9-1t15-2 9-7zm565 1341q20 12 31 24.5t12 24-2.5 22.5-15.5 22-23.5 19.5-30 18.5-31.5 16.5-32 15.5-27 13q-38 19-85.5 56t-75.5 64q-17 16-68 19.5t-89-14.5q-18-9-29.5-23.5t-16.5-25.5-22-19.5-47-9.5q-44-1-130-1-19 0-57 1.5t-58 2.5q-44 1-79.5 15t-53.5 30-43.5 28.5-53.5 11.5q-29-1-111-31t-146-43q-19-4-51-9.5t-50-9-39.5-9.5-33.5-14.5-17-19.5q-10-23 7-66.5t18-54.5q1-16-4-40t-10-42.5-4.5-36.5 10.5-27q14-12 57-14t60-12q30-18 42-35t12-51q21 73-32 106-32 20-83 15-34-3-43 10-13 15 5 57 2 6 8 18t8.5 18 4.5 17 1 22q0 15-17 49t-14 48q3 17 37 26 20 6 84.5 18.5t99.5 20.5q24 6 74 22t82.5 23 55.5 4q43-6 64.5-28t23-48-7.5-58.5-19-52-20-36.5q-121-190-169-242-68-74-113-40-11 9-15-15-3-16-2-38 1-29 10-52t24-47 22-42q8-21 26.5-72t29.5-78 30-61 39-54q110-143 124-195-12-112-16-310-2-90 24-151.5t106-104.5q39-21 104-21 53-1 106 13.5t89 41.5q57 42 91.5 121.5t29.5 147.5q-5 95 30 214 34 113 133 218 55 59 99.5 163t59.5 191q8 49 5 84.5t-12 55.5-20 22q-10 2-23.5 19t-27 35.5-40.5 33.5-61 14q-18-1-31.5-5t-22.5-13.5-13.5-15.5-11.5-20.5-9-19.5q-22-37-41-30t-28 49 7 97q20 70 1 195-10 65 18 100.5t73 33 85-35.5q59-49 89.5-66.5t103.5-42.5q53-18 77-36.5t18.5-34.5-25-28.5-51.5-23.5q-33-11-49.5-48t-15-72.5 15.5-47.5q1 31 8 56.5t14.5 40.5 20.5 28.5 21 19 21.5 13 16.5 9.5z"/></svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

2
images/windows.svg Normal file
View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M746 1006v651l-682-94v-557h682zm0-743v659h-682v-565zm982 743v786l-907-125v-661h907zm0-878v794h-907v-669z"/></svg>

After

Width:  |  Height:  |  Size: 252 B

View File

@ -69,6 +69,16 @@
return accountManager;
};
function isTimeToUpgrade(text) {
var percentage = parseInt(text, 10);
if (isNaN(percentage)) {
return false;
}
var roll = _.random(1, 100);
return roll <= percentage;
}
storage.fetch();
storage.onready(function() {
@ -94,7 +104,6 @@
}
Whisper.WallClockListener.init(Whisper.events);
Whisper.RotateSignedPreKeyListener.init(Whisper.events);
Whisper.ExpiringMessagesListener.init(Whisper.events);
});
@ -103,6 +112,8 @@
};
Whisper.events.on('start-shutdown', function() {
Whisper.RotateSignedPreKeyListener.stop();
if (messageReceiver) {
messageReceiver.close().then(function() {
messageReceiver = null;
@ -115,62 +126,6 @@
function init(firstRun) {
window.removeEventListener('online', init);
if (!Whisper.Registration.isDone()) { return; }
if (Whisper.Migration.inProgress()) { return; }
if (messageReceiver) { messageReceiver.close(); }
var USERNAME = storage.get('number_id');
var PASSWORD = storage.get('password');
var mySignalingKey = storage.get('signaling_key');
// initialize the socket and start listening for messages
messageReceiver = new textsecure.MessageReceiver(
SERVER_URL, SERVER_PORTS, USERNAME, PASSWORD, mySignalingKey
);
messageReceiver.addEventListener('message', onMessageReceived);
messageReceiver.addEventListener('receipt', onDeliveryReceipt);
messageReceiver.addEventListener('contact', onContactReceived);
messageReceiver.addEventListener('group', onGroupReceived);
messageReceiver.addEventListener('sent', onSentMessage);
messageReceiver.addEventListener('read', onReadReceipt);
messageReceiver.addEventListener('verified', onVerified);
messageReceiver.addEventListener('error', onError);
messageReceiver.addEventListener('empty', onEmpty);
messageReceiver.addEventListener('progress', onProgress);
window.textsecure.messaging = new textsecure.MessageSender(
SERVER_URL, SERVER_PORTS, USERNAME, PASSWORD
);
// Because v0.43.2 introduced a bug that lost contact details, v0.43.4 introduces
// a one-time contact sync to restore all lost contact/group information. We
// disable this checking if a user is first registering.
var key = 'chrome-contact-sync-v0.43.4';
if (!storage.get(key)) {
storage.put(key, true);
if (!firstRun && textsecure.storage.user.getDeviceId() != '1') {
window.getSyncRequest();
}
}
if (firstRun === true && textsecure.storage.user.getDeviceId() != '1') {
if (!storage.get('theme-setting') && textsecure.storage.get('userAgent') === 'OWI') {
storage.put('theme-setting', 'ios');
}
var syncRequest = new textsecure.SyncRequest(textsecure.messaging, messageReceiver);
Whisper.events.trigger('contactsync:begin');
syncRequest.addEventListener('success', function() {
console.log('sync successful');
storage.put('synced_at', Date.now());
Whisper.events.trigger('contactsync');
});
syncRequest.addEventListener('timeout', function() {
console.log('sync timed out');
Whisper.events.trigger('contactsync');
});
}
}
function onEmpty() {

View File

@ -3,6 +3,22 @@
window.Whisper = window.Whisper || {};
function stringToBlob(string) {
if (string === null || string === undefined) {
console.log('stringToBlob: replacing null/undefined with empty string');
string = '';
}
if (string.type === 'ArrayBuffer' && string.encoding === 'base64') {
console.log('stringToBlob: Processing base64 attachment data');
string = dcodeIO.ByteBuffer.wrap(string.data, 'base64').toArrayBuffer();
}
if (typeof string !== 'string' && !(string instanceof ArrayBuffer)) {
// Not sure what this is, but perhaps we can make the right thing happen by sending
// it to a Uint8Array, which the wrap() method below handles just fine. Uint8Array
// can take an ArrayBuffer, so it will help if I'm right that the weird attachment
// data is an ArrayBuffer-like thing, while not being technically an instanceof.
console.log('stringToBlob: sending strange object to Uint8Array --', typeof string, JSON.stringify(string), string);
string = new Uint8Array(string);
}
var buffer = dcodeIO.ByteBuffer.wrap(string).toArrayBuffer();
return new Blob([buffer]);
}
@ -43,10 +59,8 @@
function createOutputStream(fileWriter) {
var wait = Promise.resolve();
var count = 0;
return {
write: function(string) {
var i = count++;
wait = wait.then(function() {
return new Promise(function(resolve, reject) {
fileWriter.onwriteend = resolve;
@ -60,9 +74,11 @@
};
}
function exportNonMessages(idb_db, parent) {
return createFileAndWriter(parent, 'db.json').then(function(writer) {
return exportToJsonFile(idb_db, writer);
function exportNonMessages(idb_db, parent, options) {
// We wouldn't want to overwrite another db file.
var exclusive = true;
return createFileAndWriter(parent, 'db.json', exclusive).then(function(writer) {
return exportToJsonFile(idb_db, writer, options);
});
}
@ -70,10 +86,27 @@
* Export all data from an IndexedDB database
* @param {IDBDatabase} idb_db
*/
function exportToJsonFile(idb_db, fileWriter) {
function exportToJsonFile(idb_db, fileWriter, options) {
options = options || {};
_.defaults(options, {excludeClientConfig: false});
return new Promise(function(resolve, reject) {
var storeNames = idb_db.objectStoreNames;
storeNames = _.without(storeNames, 'messages');
storeNames = _.without(storeNames, 'messages', 'debug');
if (options.excludeClientConfig) {
console.log('exportToJsonFile: excluding client config from export');
storeNames = _.without(
storeNames,
'items',
'signedPreKeys',
'preKeys',
'identityKeys',
'sessions',
'unprocessed' // since we won't be able to decrypt them anyway
);
}
var exportedStoreNames = [];
if (storeNames.length === 0) {
throw new Error('No stores to export');
@ -132,6 +165,12 @@
stream.write('}').then(function() {
console.log('Finished writing all stores to disk');
resolve();
}, function(error) {
console.log(
'Failed to write db.json to disk',
error && error.stack ? error.stack : error
);
reject(error);
});
}
}
@ -217,17 +256,19 @@
});
}
function createDirectory(parent, name) {
function createDirectory(parent, name, exclusive) {
var sanitized = sanitizeFileName(name);
console._log('-- about to create directory', sanitized);
return new Promise(function(resolve, reject) {
parent.getDirectory(sanitized, {create: true, exclusive: true}, resolve, reject);
parent.getDirectory(sanitized, {create: true, exclusive: exclusive}, resolve, reject);
});
}
function createFileAndWriter(parent, name) {
function createFileAndWriter(parent, name, exclusive) {
var sanitized = sanitizeFileName(name);
console._log('-- about to create file', sanitized);
return new Promise(function(resolve, reject) {
parent.getFile(sanitized, {create: true, exclusive: true}, function(file) {
parent.getFile(sanitized, {create: true, exclusive: exclusive}, function(file) {
return file.createWriter(function(writer) {
resolve(writer);
}, reject);
@ -312,16 +353,31 @@
});
}
var numAttachments = 0;
var numFailedAttachments = 0;
function writeAttachment(dir, attachment) {
numAttachments += 1;
var filename = getAttachmentFileName(attachment);
return createFileAndWriter(dir, filename).then(function(writer) {
// If attachments are in messages with the same received_at and the same name,
// then we'll let that overwrite happen. It should be very uncommon.
var exclusive = false;
return createFileAndWriter(dir, filename, exclusive).then(function(writer) {
var stream = createOutputStream(writer);
return stream.write(attachment.data);
}).catch(function(error) {
numFailedAttachments += 1;
console.log('writeAttachment error:', error && error.stack ? error.stack : error);
});
}
function writeAttachments(parentDir, name, messageId, attachments) {
return createDirectory(parentDir, messageId).then(function(dir) {
// We've had a lot of trouble with attachments, likely due to messages with the same
// received_at in the same conversation. So we sacrifice one of the attachments in
// this unusual case.
var exclusive = false;
return createDirectory(parentDir, messageId, exclusive).then(function(dir) {
return Promise.all(_.map(attachments, function(attachment) {
return writeAttachment(dir, attachment);
}));
@ -340,9 +396,54 @@
return filename.toString().replace(/[^a-z0-9.,+()'#\- ]/gi, '_');
}
var numConversations = 0;
var numFailedConversations = 0;
function delay(ms) {
console.log('Waiting', ms, 'milliseconds');
return new Promise(function(resolve) {
setTimeout(resolve, ms);
});
}
// When we write files to disk, those writes sometimes take a while to appear on disk,
// even if the Chrome file-writing APIs tell us that the write is complete. So, to
// ensure that the export is really complete, we repeatedly check the messages.json
// file for well-formed JSON, trying for up to five minutes.
var CHECK_MAX = 60;
function checkConversation(conversationId, dir, count) {
return delay(2000).then(function() {
console.log(
'Verifying messages.json produced for conversation',
conversationId,
'attempt number',
count
);
return readFileAsText(dir, 'messages.json');
}).then(function(contents) {
JSON.parse(contents);
}).catch(function(error) {
console.log(
'Export of conversation',
conversationId,
'may be malformed:',
error && error.stack ? error.stack : error
);
if (count >= CHECK_MAX) {
numFailedConversations += 1;
return;
}
return checkConversation(conversationId, dir, count + 1);
});
}
function exportConversation(idb_db, name, conversation, dir) {
console.log('exporting conversation', name);
return createFileAndWriter(dir, 'messages.json').then(function(writer) {
// We wouldn't want to overwrite the contents of a different conversation.
var exclusive = true;
return createFileAndWriter(dir, 'messages.json', exclusive).then(function(writer) {
return new Promise(function(resolve, reject) {
var transaction = idb_db.transaction('messages', "readwrite");
transaction.onerror = function(e) {
@ -381,23 +482,41 @@
request.onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
if (count !== 0) {
stream.write(',');
}
var message = cursor.value;
var messageId = message.received_at;
var attachments = message.attachments;
// skip message if it is disappearing, no matter the amount of time left
if (message.expireTimer) {
cursor.continue();
return;
}
if (count !== 0) {
stream.write(',');
}
// eliminate attachment data from the JSON, since it will go to disk
message.attachments = _.map(attachments, function(attachment) {
return _.omit(attachment, ['data']);
});
// completely drop any attachments in messages cached in error objects
message.errors = _.map(message.errors, function(error) {
if (error && error.args && error.args[0] && error.args[0].attachments) {
error.args[0].attachments = [];
}
return error;
});
var jsonString = JSON.stringify(stringify(message));
stream.write(jsonString);
if (attachments && attachments.length) {
var process = function() {
console._log('-- writing attachments for message', message.id);
if (!message.received_at) {
return Promise.reject(new Error('Message', message.id, 'had no received_at'));
}
return writeAttachments(dir, name, messageId, attachments);
};
promiseChain = promiseChain.then(process);
@ -407,7 +526,12 @@
cursor.continue();
} else {
var promise = stream.write(']}');
promiseChain = promiseChain.then(promise);
numConversations += 1;
promiseChain = promiseChain
.then(promise)
.then(checkConversation.bind(null, name, dir, 0));
return promiseChain.then(function() {
console.log('done exporting conversation', name);
@ -429,7 +553,7 @@
// Goals for directory names:
// 1. Human-readable, for easy use and verification by user (names not just ids)
// 2. Sorted just like the list of conversations in the left-pan (active_at)
// 2. Sorted just like the list of conversations in the left pane (active_at)
// 3. Disambiguated from other directories (active_at, truncated name, id)
function getConversationDirName(conversation) {
var name = conversation.active_at || 'never';
@ -487,7 +611,10 @@
var name = getConversationLoggingName(conversation);
var process = function() {
return createDirectory(parentDir, dir).then(function(dir) {
// If we have a conversation directory collision, the user will lose the
// contents of the first conversation. So we throw an error.
var exclusive = true;
return createDirectory(parentDir, dir, exclusive).then(function(dir) {
return exportConversation(idb_db, name, conversation, dir);
});
};
@ -650,27 +777,54 @@
return moment().format('YYYY MMM Do [at] h.mm.ss a');
}
function printAttachmentStats() {
console.log('Total attachments:', numAttachments);
console.log('Failed attachments:', numFailedAttachments);
}
function printConversationStats() {
console.log('Total conversations:', numConversations);
console.log('Possibly malformed conversations:', numFailedConversations);
}
Whisper.Backup = {
backupToDirectory: function() {
exportToDirectory: function(options) {
return getDirectory().then(function(directoryEntry) {
var idb;
var dir;
numConversations = 0;
numFailedConversations = 0;
numAttachments = 0;
numFailedAttachments = 0;
return openDatabase().then(function(idb_db) {
idb = idb_db;
var name = 'Signal Export ' + getTimestamp();
return createDirectory(directoryEntry, name);
// We don't want to overwrite another signal export, so we set exclusive = true
var exclusive = true;
return createDirectory(directoryEntry, name, exclusive);
}).then(function(directory) {
dir = directory;
return exportNonMessages(idb, dir);
return exportNonMessages(idb, dir, options);
}).then(function() {
return exportConversations(idb, dir);
}).then(function() {
return getDisplayPath(dir);
});
}).then(function(path) {
printAttachmentStats();
printConversationStats();
console.log('done backing up!');
if (numFailedAttachments) {
throw new Error('Export failed, one or more attachments failed');
}
if (numFailedConversations) {
throw new Error('Export failed, one or more conversations failed');
}
return path;
}, function(error) {
printAttachmentStats();
printConversationStats();
console.log(
'the backup went wrong:',
error && error.stack ? error.stack : error

View File

@ -172,9 +172,12 @@
extension.windows.open({
id: id,
url: url,
bounds: { width: 800, height: 666, },
minWidth: 800,
minHeight: 666
bounds: {
width: 580,
height: 440,
},
minWidth: 600,
minHeight: 360
});
}
};
@ -255,13 +258,15 @@
if (chrome.runtime.onInstalled) {
chrome.runtime.onInstalled.addListener(function(options) {
var version = chrome.runtime.getManifest().version;
if (options.reason === 'install') {
console.log('new install');
console.log('new install:', version);
extension.install();
} else if (options.reason === 'update') {
console.log('new update. previous version:', options.previousVersion);
console.log('new update:', version, '- previous version:', options.previousVersion);
} else {
console.log('onInstalled', options.reason);
console.log('onInstalled', options.reason, 'version:', version);
}
});
}

View File

@ -9,6 +9,70 @@
window.Whisper.Database.id = window.Whisper.Database.id || 'signal';
window.Whisper.Database.nolog = true;
window.Whisper.Database.cleanMessageAttachments = function(message) {
var attachments = message.attachments;
var changed = false;
if (!attachments || !attachments.length) {
return false;
}
for (var i = 0, max = attachments.length; i < max; i += 1) {
var attachment = attachments[i];
// catch missing and non-string filenames
if (typeof attachment.fileName !== 'string') {
if (attachment.fileName) {
delete attachment.fileName;
changed = true;
}
// if id is falsey, or neither number or string, we make our own id
if (!attachment.id || (typeof attachment.id !== 'number' && typeof attachment.id !== 'string')) {
attachment.id = _.uniqueId('attachment');
changed = true;
}
}
// if contentType is truthy, we ensure it's a string
if (attachment.contentType && typeof attachment.contentType !== 'string') {
delete attachment.contentType;
changed = true;
}
}
// elminate all attachments without data
var attachmentsWithData = _.filter(attachments, function(attachment) {
return attachment.data;
});
if (attachments.length !== attachmentsWithData.length) {
message.attachments = attachmentsWithData;
changed = true;
}
return changed;
};
window.Whisper.Database.dropZeroLengthAttachments = function(message) {
var attachments = message.attachments;
var changed = false;
if (!attachments || !attachments.length) {
return false;
}
var attachmentsWithData = _.filter(attachments, function(attachment) {
return attachment.data && (attachment.data.length || attachment.data.byteLength);
});
if (attachments.length !== attachmentsWithData.length) {
message.attachments = attachmentsWithData;
changed = true;
}
return changed;
};
Whisper.Database.migrations = [
{
version: "1.0",
@ -274,6 +338,92 @@
messages.createIndex('unique', ['source', 'sourceDevice', 'sent_at'], { unique: true });
next();
}
}
},
{
version: "16.0",
migrate: function(transaction, next) {
console.log('migration 16.0');
console.log('Cleaning up dirty attachment data');
var messages = transaction.objectStore('messages');
var queryRequest = messages.openCursor();
var promises = [];
queryRequest.onsuccess = function(event) {
var cursor = event.target.result;
if (!cursor) {
return Promise.all(promises).then(function() {
console.log('Fixed', promises.length, 'messages with unexpected attachment structure');
next();
});
}
var message = cursor.value;
var changed = window.Whisper.Database.cleanMessageAttachments(message);
if (!changed) {
return cursor.continue();
}
promises.push(new Promise(function(resolve, reject) {
var putRequest = messages.put(message, message.id);
putRequest.onsuccess = resolve;
putRequest.onerror = function(e) {
console.log(e);
reject(e);
};
}));
return cursor.continue();
};
queryRequest.onerror = function(event) {
console.log(event);
};
}
},
{
version: "17.0",
migrate: function(transaction, next) {
console.log('migration 17.0');
console.log('Removing attachments with zero-length data');
var messages = transaction.objectStore('messages');
var queryRequest = messages.openCursor();
var promises = [];
queryRequest.onsuccess = function(event) {
var cursor = event.target.result;
if (!cursor) {
return Promise.all(promises).then(function() {
console.log('Fixed', promises.length, 'messages with unexpected attachment structure');
next();
});
}
var message = cursor.value;
var changed = window.Whisper.Database.dropZeroLengthAttachments(message);
if (!changed) {
return cursor.continue();
}
promises.push(new Promise(function(resolve, reject) {
var putRequest = messages.put(message, message.id);
putRequest.onsuccess = resolve;
putRequest.onerror = function(e) {
console.log(e);
reject(e);
};
}));
return cursor.continue();
};
queryRequest.onerror = function(event) {
console.log(event);
};
}
},
];
}());

View File

@ -42,6 +42,7 @@
var MAX_MESSAGES = 2000;
var PHONE_REGEX = /\+\d{7,12}(\d{3})/g;
var DEBUGLOGS_BASE_URL = 'https://debuglogs.org';
var log = new DebugLog();
if (window.console) {
console._log = console.log;
@ -60,18 +61,56 @@
if (log === undefined) {
log = console.get();
}
return new Promise(function(resolve) {
$.post('https://api.github.com/gists', textsecure.utils.jsonThing({
"files": { "debugLog.txt": { "content": log } }
})).then(function(response) {
console._log('Posted debug log to ', response.html_url);
resolve(response.html_url);
}).fail(resolve);
return new Promise(function(resolve, reject) {
$.get(DEBUGLOGS_BASE_URL).then(function (signedForm) {
var url = signedForm.url;
var fields = signedForm.fields;
var formData = new FormData();
// NOTE: Service expects `key` to come first:
formData.append('key', fields.key);
formData.append('Content-Type', 'text/plain');
for (var key in fields) {
if (key === 'key') {
continue;
}
var value = fields[key];
formData.append(key, value);
}
var contentBlob = new Blob([log], { type: 'text/plain' });
formData.append('file', contentBlob);
var publishedLogURL = DEBUGLOGS_BASE_URL + '/' + fields.key;
var request = new XMLHttpRequest();
request.open('POST', url);
request.onreadystatechange = function (event) {
if (request.readyState !== XMLHttpRequest.DONE) {
return;
}
if (request.status !== 204) {
return reject(
new Error('Failed to publish debug log. Status: ' +
request.statusText + ' (' + request.status + ')'
)
);
}
return resolve(publishedLogURL);
};
request.send(formData);
}).fail(function () {
reject(new Error('Failed to publish logs'));
});
});
};
window.onerror = function(message, script, line, col, error) {
console.log(error.stack);
console.log(error && error.stack ? error.stack : error);
};
}
})();

View File

@ -618,11 +618,16 @@
updateExpirationTimer: function(expireTimer, source, received_at) {
if (!expireTimer) { expireTimer = null; }
source = source || textsecure.storage.user.getNumber();
var timestamp = received_at || Date.now();
// When we add a disappearing messages notification to the conversation, we want it
// to be above the message that initiated that change, hence the subtraction.
var timestamp = (received_at || Date.now()) - 1;
this.save({ expireTimer: expireTimer });
var message = this.messageCollection.add({
// Even though this isn't reflected to the user, we want to place the last seen
// indicator above it. We set it to 'unread' to trigger that placement.
unread : 1,
conversationId : this.id,
type : received_at ? 'incoming' : 'outgoing',
// No type; 'incoming' messages are specially treated by conversation.markRead()
sent_at : timestamp,
received_at : timestamp,
flags : textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE,
@ -635,16 +640,21 @@
message.set({destination: this.id});
}
message.save();
if (message.isOutgoing()) { // outgoing update, send it to the number/group
var sendFunc;
if (this.get('type') == 'private') {
sendFunc = textsecure.messaging.sendExpirationTimerUpdateToNumber;
}
else {
sendFunc = textsecure.messaging.sendExpirationTimerUpdateToGroup;
}
message.send(sendFunc(this.get('id'), this.get('expireTimer'), message.get('sent_at')));
// if change was made remotely, don't send it to the number/group
if (received_at) {
return message;
}
var sendFunc;
if (this.get('type') == 'private') {
sendFunc = textsecure.messaging.sendExpirationTimerUpdateToNumber;
}
else {
sendFunc = textsecure.messaging.sendExpirationTimerUpdateToGroup;
}
message.send(sendFunc(this.get('id'), this.get('expireTimer'), message.get('sent_at')));
return message;
},
@ -770,6 +780,10 @@
},
getProfile: function(id) {
if (!textsecure.messaging) {
return Promise.resolve();
}
return textsecure.messaging.getProfile(id).then(function(profile) {
var identityKey = dcodeIO.ByteBuffer.wrap(profile.identityKey, 'base64').toArrayBuffer();

View File

@ -45,8 +45,15 @@
timeout = setTimeout(runWhenOnline, waitTime);
}
var started = false;
Whisper.RotateSignedPreKeyListener = {
init: function(events) {
if (started) {
console.log('Already started signed prekey listener');
return;
}
started = true;
if (Whisper.Registration.isDone()) {
setTimeoutForNextRun();
}
@ -59,6 +66,15 @@
setTimeoutForNextRun();
}
});
},
stop: function() {
console.log('Stopping signed prekey rotation');
clearTimeout(timeout);
timeout = null;
},
start: function() {
console.log('Starting signed prekey rotation');
setTimeoutForNextRun();
}
};
}());

View File

@ -44,8 +44,12 @@
var item = items.get("" + key);
if (item) {
items.remove(item);
item.destroy();
return new Promise(function(resolve, reject) {
item.destroy().then(resolve, reject);
});
}
return Promise.resolve();
},
onready: function(callback) {

View File

@ -1,4 +1,4 @@
/*
/*
* vim: ts=4:sw=4:expandtab
*/
(function () {
@ -50,6 +50,9 @@
this.cancel();
}
},
focusOk: function() {
this.$('.ok').focus();
},
focusCancel: function() {
this.$('.cancel').focus();
}

View File

@ -25,9 +25,12 @@
this.listenTo(this.model.messageCollection, 'add remove', updateLastMessage);
this.listenTo(this.model, 'newmessage', updateLastMessage);
extension.windows.onClosed(this.stopListening.bind(this));
this.timeStampView = new Whisper.TimestampView({brief: true});
this.model.updateLastMessage();
if (extension.windows) {
extension.windows.onClosed(this.stopListening.bind(this));
}
},
markSelected: function() {

View File

@ -20,6 +20,11 @@
return { toastMessage: i18n('youLeftTheGroup') };
}
});
Whisper.ReadOnlyToast = Whisper.ToastView.extend({
render_attributes: function() {
return { toastMessage: i18n('readOnlyMode') };
}
});
var MenuView = Whisper.View.extend({
toggleMenu: function() {
@ -869,34 +874,10 @@
checkUnverifiedSendMessage: function(e, options) {
e.preventDefault();
this.sendStart = Date.now();
this.$messageField.prop('disabled', true);
options = options || {};
_.defaults(options, {force: false});
// This will go to the trust store for the latest identity key information,
// and may result in the display of a new banner for this conversation.
this.model.updateVerified().then(function() {
var contacts = this.model.getUnverified();
if (!contacts.length) {
return this.checkUntrustedSendMessage(e, options);
}
if (options.force) {
return this.markAllAsVerifiedDefault(contacts).then(function() {
this.checkUnverifiedSendMessage(e, options);
}.bind(this));
}
this.showSendConfirmationDialog(e, contacts);
}.bind(this)).catch(function(error) {
this.focusMessageField();
console.log(
'checkUnverifiedSendMessage error:',
error && error.stack ? error.stack : error
);
}.bind(this));
var toast = new Whisper.ReadOnlyToast();
toast.$el.insertAfter(this.$el);
toast.render();
},
checkUntrustedSendMessage: function(e, options) {

View File

@ -98,13 +98,6 @@
model: { window: options.window }
});
if (!options.initialLoadComplete) {
this.appLoadingScreen = new Whisper.AppLoadingScreen();
this.appLoadingScreen.render();
this.appLoadingScreen.$el.prependTo(this.el);
this.startConnectionListener();
}
var inboxCollection = getInboxCollection();
inboxCollection.on('messageError', function() {
@ -141,23 +134,13 @@
this.networkStatusView = new Whisper.NetworkStatusView();
this.$el.find('.network-status-container').append(this.networkStatusView.render().el);
extension.windows.onClosed(function() {
this.inboxListView.stopListening();
}.bind(this));
this.showUpgradeScreen();
this.showUpgradeBanner();
if (extension.expired()) {
var banner = new Whisper.ExpiredAlertBanner().render();
banner.$el.prependTo(this.$el);
this.$el.addClass('expired');
} else if (Whisper.Migration.inProgress()) {
if (this.appLoadingScreen) {
this.appLoadingScreen.remove();
this.appLoadingScreen = null;
}
this.showMigrationScreen();
} else if (storage.get('migrationEnabled')) {
var migrationBanner = new Whisper.MigrationAlertBanner().render();
migrationBanner.$el.prependTo(this.$el);
if (extension.windows) {
extension.windows.onClosed(function() {
this.inboxListView.stopListening();
}.bind(this));
}
},
render_attributes: {
@ -165,6 +148,7 @@
selectAContact : i18n('selectAContact'),
searchForPeopleOrGroups : i18n('searchForPeopleOrGroups'),
submitDebugLog : i18n('submitDebugLog'),
migrate : i18n('migrateToStandalone'),
settings : i18n('settings'),
restartSignal : i18n('restartSignal'),
},
@ -179,12 +163,32 @@
'input input.search': 'filterContacts',
'click .restart-signal': 'reloadBackgroundPage',
'show .lightbox': 'showLightbox',
'click .migrate': 'confirmMigration'
'click .migrate': 'showUpgradeScreen',
'click .banner-close': 'removeUpgradeBanner'
},
confirmMigration: function() {
this.confirm(i18n('confirmMigration'), i18n('migrate')).then(this.showMigrationScreen.bind(this));
showUpgradeBanner: function() {
this.removeUpgradeBanner();
this.upgradeBanner = new Whisper.UpgradeBanner().render();
this.upgradeBanner.$el.prependTo(this.$el);
// This class makes AppView have a height of 100% - 62px, so it doesn't push
// our banner off-screen.
this.$el.addClass('expired');
},
showMigrationScreen: function() {
removeUpgradeBanner: function() {
if (this.upgradeBanner) {
this.upgradeBanner.remove();
this.upgradeBanner = null;
this.$el.removeClass('expired');
}
},
showUpgradeScreen: function() {
if (this.migrationScreen) {
this.migrationScreen.remove();
this.migrationScreen = null;
}
this.migrationScreen = new Whisper.MigrationView();
this.migrationScreen.render();
this.migrationScreen.$el.prependTo(this.el);
@ -307,13 +311,25 @@
}
});
Whisper.MigrationAlertBanner = Whisper.View.extend({
templateName: 'migration_alert',
className: 'expiredAlert clearfix',
Whisper.UpgradeBanner = Whisper.View.extend({
templateName: 'upgrade_banner',
className: 'expiredAlert upgrade-banner clearfix',
initializer: function() {
var HOUR = 1000 * 60 * 60;
this.interval = setInterval(this.render.bind(this), HOUR);
},
remove: function() {
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
}
Backbone.View.prototype.remove.call(this);
},
render_attributes: function() {
return {
migrationWarning: i18n('migrationWarning'),
migrate: i18n('migrate'),
upgradeMessage: i18n('upgradeBannerExpired'),
upgradeNow: i18n('upgradeNow'),
};
}
});

View File

@ -7,138 +7,20 @@
Whisper.InstallView = Whisper.View.extend({
templateName: 'install_flow_template',
id: 'install',
className: 'main',
render_attributes: function() {
var playStoreHref = 'https://play.google.com/store/apps/details?id=org.thoughtcrime.securesms';
var appStoreHref = 'https://itunes.apple.com/us/app/signal-private-messenger/id874139669';
var twitterHref = 'https://twitter.com/whispersystems';
return {
installWelcome: i18n('installWelcome'),
installTagline: i18n('installTagline'),
installGetStartedButton: i18n('installGetStartedButton'),
installSignalLink: this.i18n_with_links('installSignalLinks', playStoreHref, appStoreHref),
installIHaveSignalButton: i18n('installGotIt'),
installFollowUs: this.i18n_with_links('installFollowUs', twitterHref),
installAndroidInstructions: i18n('installAndroidInstructions'),
installLinkingWithNumber: i18n('installLinkingWithNumber'),
installComputerName: i18n('installComputerName'),
installFinalButton: i18n('installFinalButton'),
installTooManyDevices: i18n('installTooManyDevices'),
ok: i18n('ok'),
preLinkExpiredHeader: i18n('preLinkExpiredHeader'),
startExportIntroParagraph1: i18n('startExportIntroParagraph1'),
getNewVersion: i18n('getNewVersion'),
};
},
initialize: function(options) {
this.counter = 0;
console.log('initialize!', this);
this.render();
var deviceName = textsecure.storage.user.getDeviceName();
if (!deviceName) {
deviceName = 'Chrome';
if (navigator.userAgent.match('Mac OS')) {
deviceName += ' on Mac';
} else if (navigator.userAgent.match('Linux')) {
deviceName += ' on Linux';
} else if (navigator.userAgent.match('Windows')) {
deviceName += ' on Windows';
}
}
this.$('#device-name').val(deviceName);
this.$('#step1').show();
this.connect();
this.on('disconnected', this.reconnect);
},
connect: function() {
this.clearQR();
var accountManager = getAccountManager();
accountManager.registerSecondDevice(
this.setProvisioningUrl.bind(this),
this.confirmNumber.bind(this),
this.incrementCounter.bind(this)
).catch(this.handleDisconnect.bind(this));
},
handleDisconnect: function(e) {
if (e.message === 'websocket closed') {
this.showConnectionError();
this.trigger('disconnected');
} else if (e.name === 'HTTPError' && e.code == 411) {
this.showTooManyDevices();
} else {
throw e;
}
},
reconnect: function() {
setTimeout(this.connect.bind(this), 10000);
},
close: function() {
this.remove();
},
events: function() {
return {
'click .error-dialog .ok': 'connect',
'click .step1': this.selectStep.bind(this, 1),
'click .step2': this.selectStep.bind(this, 2),
'click .step3': this.selectStep.bind(this, 3)
};
},
clearQR: function() {
this.$('#qr').text(i18n("installConnecting"));
},
setProvisioningUrl: function(url) {
this.$('#qr').html('');
new QRCode(this.$('#qr')[0]).makeCode(url);
},
confirmNumber: function(number) {
var parsed = libphonenumber.parse(number);
if (!libphonenumber.isValidNumber(parsed)) {
throw new Error('Invalid number ' + number);
}
this.$('#step4 .number').text(libphonenumber.format(
parsed,
libphonenumber.PhoneNumberFormat.INTERNATIONAL
));
this.selectStep(4);
this.$('#device-name').focus();
return new Promise(function(resolve, reject) {
this.$('#step4 .cancel').click(function(e) {
reject();
});
this.$('#step4').submit(function(e) {
e.stopPropagation();
e.preventDefault();
var name = this.$('#device-name').val();
name = name.replace(/\0/g,''); // strip unicode null
if (name.trim().length === 0) {
this.$('#device-name').focus();
return;
}
this.$('.progress-dialog .status').text(i18n('installGeneratingKeys'));
this.selectStep(5);
resolve(name);
}.bind(this));
}.bind(this));
},
incrementCounter: function() {
this.$('.progress-dialog .bar').css('width', (++this.counter * 100 / 100) + '%');
},
selectStep: function(step) {
this.$('.step').hide();
this.$('#step' + step).show();
},
showSync: function() {
this.$('.progress-dialog .status').text(i18n('installSyncingGroupsAndContacts'));
this.$('.progress-dialog .bar').addClass('progress-bar-striped active');
},
showTooManyDevices: function() {
this.selectStep('TooManyDevices');
},
showConnectionError: function() {
this.$('#qr').text(i18n("installConnectionFailed"));
},
hideDots: function() {
this.$('#step3 .nav .dot').hide();
}
});
})();

View File

@ -78,6 +78,7 @@
this.conversation = this.model.getExpirationTimerUpdateSource();
this.listenTo(this.conversation, 'change', this.render);
this.listenTo(this.model, 'unload', this.remove);
this.listenTo(this.model, 'change', this.onChange);
},
render_attributes: function() {
var seconds = this.model.get('expirationTimerUpdate').expireTimer;
@ -91,6 +92,13 @@
Whisper.ExpirationTimerOptions.getName(seconds)]);
}
return { content: timerMessage };
},
onChange: function() {
this.addId();
},
addId: function() {
// This is important to enable the lastSeenIndicator when it's just been added.
this.$el.attr('id', this.id());
}
});

View File

@ -2,12 +2,86 @@
'use strict';
window.Whisper = window.Whisper || {};
var LinuxInstructionsView = Whisper.ConfirmationDialogView.extend({
className: 'linux-install-instructions',
templateName: 'linux-install-instructions',
_super: Whisper.ConfirmationDialogView.prototype,
initialize: function() {
this._super.initialize.call(this, {
okText: i18n('close'),
});
},
events: {
'keyup': 'onKeyup',
'click .ok': 'ok',
'click .modal': 'ok',
},
render_attributes: function() {
var attributes = this._super.render_attributes.call(this);
attributes.header = i18n('linuxInstallInstructions');
return attributes;
},
ok: function(event) {
// We have an event on .modal, which is the background div, darkening the screen.
// This ensures that a click on the dialog will not fire that event, by checking
// the actual thing clicked against the target.
if (event.target !== event.currentTarget) {
return;
}
this._super.ok.call(this);
}
});
var State = {
DISCONNECTING: 1,
EXPORTING: 2,
COMPLETE: 3
COMPLETE: 3,
};
var STEPS = {
INTRODUCTION: 1,
CHOOSE: 2,
EXPORTING: 3,
COMPLETE: 4,
UNINSTALL: 5,
};
var GET_YAML_PATH = /^path: (.+)$/m;
var BASE_PATH = 'https://updates.signal.org/desktop/';
function getCacheBuster() {
return Math.random().toString(36).substring(7);
}
function getLink(file) {
return new Promise(function(resolve, reject) {
$.get(BASE_PATH + file + '?b=' + getCacheBuster())
.done(function(data) {
var match = GET_YAML_PATH.exec(data);
if (match && match[1]) {
return resolve(BASE_PATH + match[1]);
}
return reject(new Error('Link not found in YAML from ' + file + ': ' + data));
})
.fail(function(xhr) {
return reject(new Error(
'Problem pulling ' + file + '; Request Status: ' + xhr.status +
' Status Text: ' + xhr.statusText + ' ' + xhr.responseText)
);
});
});
}
function getMacLink() {
return getLink('latest-mac-import.yml');
}
function getWindowsLink() {
return getLink('latest-import.yml');
}
Whisper.Migration = {
isComplete: function() {
return storage.get('migrationState') === State.COMPLETE;
@ -23,16 +97,35 @@
}
},
cancel: function() {
storage.remove('migrationState');
return Promise.all([
storage.remove('migrationState'),
storage.remove('migrationEverCompleted'),
storage.remove('migrationStorageLocation')
]);
},
beginExport: function() {
storage.put('migrationState', State.EXPORTING);
return Whisper.Backup.backupToDirectory();
var everAttempted = this.everAttempted();
storage.put('migrationEverAttempted', true);
// If this is the second time the user is attempting to export, we'll exclude
// client-specific encryption configuration. Yes, this will fire if the user
// initially attempts to save to a read-only directory or something like that, but
// it will prevent the horrible encryption errors which result from import to the
// same client config more than once. They can import the same message history
// more than once, so we preserve that.
return Whisper.Backup.exportToDirectory({
excludeClientConfig: everAttempted,
});
},
init: function() {
storage.put('migrationState', State.DISCONNECTING);
Whisper.events.trigger('start-shutdown');
},
everAttempted: function() {
return Boolean(storage.get('migrationEverAttempted'));
},
everComplete: function() {
return Boolean(storage.get('migrationEverCompleted'));
},
@ -42,15 +135,39 @@
};
Whisper.MigrationView = Whisper.View.extend({
templateName: 'app-migration-screen',
className: 'app-loading-screen',
templateName: 'migration-flow-template',
className: 'migration-flow',
events: {
'click .install': 'onClickInstall',
'click .export': 'onClickExport',
'click .debug-log': 'onClickDebugLog',
'click .start': 'onClickStart',
'click .choose': 'onClickChoose',
'click .submit-debug-log': 'onClickDebugLog',
'click .cancel': 'onClickCancel',
'click .get-new-version': 'onGetNewVersion',
},
initialize: function() {
this.step = STEPS.INTRODUCTION;
// init() tells MessageReceiver to disconnect and drain its queue, will fire
// 'shutdown-complete' event when that is done. Might result in a synchronous
// event, so call it after we register our callback.
Whisper.events.once('shutdown-complete', function() {
this.shutdownComplete = true;
}.bind(this));
Whisper.Migration.init();
Promise.all([getMacLink(), getWindowsLink()]).then(function(results) {
this.macLink = results[0];
this.windowsLink = results[1];
this.render();
}.bind(this), function(error) {
console.log(
'MigrationView: Ran into problem pulling Mac/Windows install links:',
error.stack
);
});
if (!Whisper.Migration.inProgress()) {
this.render();
return;
}
@ -59,65 +176,133 @@
if (Whisper.Migration.everComplete()) {
// If the user has ever successfully exported before, we'll show the 'finished'
// screen with the 'Export again' button.
this.step = STEPS.UNINSTALL;
Whisper.Migration.markComplete();
} else if (!Whisper.Migration.isComplete()) {
// This takes the user back to the very beginning of the process.
Whisper.Migration.cancel();
}
this.render();
},
render_attributes: function() {
var message;
var exportButton;
var hideProgress = Whisper.Migration.isComplete();
var debugLogButton = i18n('submitDebugLog');
var installButton = i18n('installNewSignal');
if (this.error) {
return {
message: i18n('exportError'),
hideProgress: true,
exportButton: i18n('exportAgain'),
isError: true,
errorHeader: i18n('exportErrorHeader'),
error: i18n('exportError'),
tryAgain: i18n('chooseFolderAndTryAgain'),
debugLogButton: i18n('submitDebugLog'),
};
}
switch (storage.get('migrationState')) {
case State.COMPLETE:
var location = Whisper.Migration.getExportLocation() || i18n('selectedLocation');
message = i18n('exportComplete', location);
exportButton = i18n('exportAgain');
debugLogButton = null;
break;
case State.EXPORTING:
message = i18n('exporting');
break;
case State.DISCONNECTING:
message = i18n('migrationDisconnecting');
installButton = null;
break;
default:
hideProgress = true;
message = i18n('exportInstructions');
exportButton = i18n('export');
debugLogButton = null;
installButton = null;
var location = Whisper.Migration.getExportLocation() || i18n('selectedLocation');
var userAgent = navigator.userAgent.toLowerCase();
var downloadLocation = '#';
if (userAgent.indexOf('windows') !== -1) {
downloadLocation = this.windowsLink || 'https://signal.org/download';
} else if (userAgent.indexOf('macintosh') !== -1) {
downloadLocation = this.macLink || 'https://signal.org/download';
} else {
downloadLocation = 'https://signal.org/download';
}
return {
hideProgress: hideProgress,
message: message,
exportButton: exportButton,
debugLogButton: debugLogButton,
installButton: installButton,
cancelButton: i18n('upgradeLater'),
debugLogButton: i18n('submitDebugLog'),
isStep1: this.step === 1,
startHeader: i18n('startExportHeader'),
startParagraph1: i18n('startExportIntroParagraph1'),
startParagraph2: i18n('startExportIntroParagraph2'),
startParagraph3: i18n('startExportIntroParagraph3'),
moreInformation: i18n('moreInformation'),
startButton: i18n('imReady'),
isStep2: this.step === 2,
chooseHeader: i18n('saveHeader'),
choose: i18n('saveDataPrompt'),
chooseButton: i18n('chooseFolder'),
isStep3: this.step === 3,
exportHeader: i18n('savingData'),
isStep4: this.step === 4,
completeHeader: i18n('completeHeader'),
completeIntro: i18n('completeIntro'),
completeLocation: location,
completeNextSteps: i18n('completeNextSteps'),
downloadLocation: downloadLocation,
installButton: i18n('getNewVersion'),
isStep5: this.step === 5,
uninstallHeader: i18n('uninstallHeader'),
uninstallStep1: i18n('uninstallStep1'),
uninstallStep2: i18n('uninstallStep2'),
uninstallStep3: i18n('uninstallStep3'),
uninstallStep4: i18n('uninstallStep4'),
uninstallStep5: i18n('uninstallStep5'),
uninstallStep6: userAgent.indexOf('macintosh') !== -1 ? i18n('uninstallStep6') : null,
};
},
onClickInstall: function() {
var url = 'https://support.whispersystems.org/hc/en-us/articles/214507138';
window.open(url, '_blank');
onGetNewVersion: function(e) {
var userAgent = navigator.userAgent.toLowerCase();
if (userAgent.indexOf('linux') !== -1) {
e.preventDefault();
var dialog = this.linuxInstructionsView = new LinuxInstructionsView({});
this.$el.prepend(dialog.el);
dialog.focusOk();
}
},
onClickStart: function() {
this.selectStep(STEPS.CHOOSE);
},
onClickChoose: function() {
this.error = null;
if (!this.shutdownComplete) {
console.log("Preventing export start; we haven't disconnected from the server");
this.error = true;
this.render();
return;
}
if (!Whisper.Migration.everComplete()) {
return this.beginMigration();
}
// Different behavior for the user's second time through
this.selectStep(STEPS.EXPORTING);
Whisper.Migration.beginExport()
.then(this.completeMigration.bind(this))
.catch(function(error) {
if (!error || error.name !== 'ChooseError') {
this.error = error || new Error('in case we reject() null!');
}
// Even if we run into an error, we call this complete because the user has
// completed the process once before.
Whisper.Migration.markComplete();
this.selectStep(STEPS.COMPLETE);
}.bind(this));
this.render();
},
onClickCancel: function() {
this.cancel();
},
onClickDebugLog: function() {
this.openDebugLog();
},
cancel: function() {
Whisper.Migration.cancel().then(function() {
console.log('Removing migration view');
this.remove();
}.bind(this));
},
selectStep: function(step) {
this.step = step;
this.render();
},
openDebugLog: function() {
this.closeDebugLog();
this.debugLogView = new Whisper.DebugLogView();
@ -129,62 +314,33 @@
this.debugLogView = null;
}
},
onClickExport: function() {
this.error = null;
if (!Whisper.Migration.everComplete()) {
return this.beginMigration();
}
beginMigration: function() {
this.selectStep(STEPS.EXPORTING);
// Different behavior for the user's second time through
Whisper.Migration.beginExport()
.then(this.completeMigration.bind(this))
.catch(function(error) {
if (error.name !== 'ChooseError') {
this.error = error.message;
// We ensure that a restart of the app acts as if the user never tried
// to export.
Whisper.Migration.cancel();
// We special-case the error we get when the user cancels out of the
// filesystem choice dialog: it's not shown an error.
if (!error || error.name !== 'ChooseError') {
this.error = error || new Error('in case we reject() null!');
}
// Even if we run into an error, we call this complete because the user has
// completed the process once before.
Whisper.Migration.markComplete();
this.render();
// Because this is our first time attempting to export, we go back to
// the choice step. Compare this to the end of onClickChoose().
this.selectStep(STEPS.CHOOSE);
}.bind(this));
this.render();
},
beginMigration: function() {
Whisper.events.once('shutdown-complete', function() {
Whisper.Migration.beginExport()
.then(this.completeMigration.bind(this))
.catch(this.onError.bind(this));
// Rendering because we're now in the 'exporting' state
this.render();
}.bind(this));
// tells MessageReceiver to disconnect and drain its queue, will fire
// 'shutdown-complete' event when that is done. Might result in a synchronous
// event, so call it after we register our callback.
Whisper.Migration.init();
// Rendering because we're now in the 'disconnected' state
this.render();
},
completeMigration: function(target) {
// This will prevent connection to the server on future app launches
Whisper.Migration.markComplete(target);
this.render();
this.selectStep(STEPS.COMPLETE);
},
onError: function(error) {
if (error.name === 'ChooseError') {
this.cancelMigration();
} else {
Whisper.Migration.cancel();
this.error = error.message;
this.render();
}
},
cancelMigration: function() {
Whisper.Migration.cancel();
this.render();
}
});
}());

View File

@ -11,6 +11,10 @@
initialize: function() {
this.startTime = Date.now();
this.interval = setInterval(this.updateTime.bind(this), 1000);
this.onSwitchAwayBound = this.onSwitchAway.bind(this);
$(window).on('blur', this.onSwitchAwayBound);
this.start();
},
events: {
@ -18,6 +22,9 @@
'click .finish': 'finish',
'close': 'close'
},
onSwitchAway: function() {
this.close();
},
updateTime: function() {
var duration = moment.duration(Date.now() - this.startTime, 'ms');
var minutes = '' + Math.trunc(duration.asMinutes());
@ -44,17 +51,23 @@
}
this.remove();
this.trigger('closed');
$(window).off('blur', this.onSwitchAwayBound);
},
finish: function() {
this.clickedFinish = true;
this.recorder.finishRecording();
this.close();
},
handleBlob: function(recorder, blob) {
if (blob) {
if (blob && this.clickedFinish) {
this.trigger('send', blob);
} else {
this.close();
}
},
start: function() {
this.clickedFinish = false;
this.context = new AudioContext();
this.input = this.context.createGain();
this.recorder = new WebAudioRecorder(this.input, {
@ -62,7 +75,7 @@
workerDir: 'js/' // must end with slash
});
this.recorder.onComplete = this.handleBlob.bind(this);
this.recorder.onError = this.onError;
this.recorder.onError = this.onError.bind(this);
navigator.webkitGetUserMedia({ audio: true }, function(stream) {
this.source = this.context.createMediaStreamSource(stream);
this.source.connect(this.input);

View File

@ -4,7 +4,7 @@
"name": "Signal Private Messenger",
"short_name": "Signal",
"description": "__MSG_installTagline__",
"version": "0.43.4",
"version": "0.48.1",
"offline_enabled": false,
"minimum_chrome_version": "37",
"default_locale": "en",

View File

@ -7,7 +7,7 @@
<link href='/images/icon_128.png' rel='shortcut icon'>
</head>
<body>
<div id='install' class='main'>
<div id='install' class='migration-flow'>
</div>
<script type="text/javascript" src="js/components.js"></script>

View File

@ -218,7 +218,7 @@ button.hamburger {
}
.dropoff {
outline: solid 1px #2090ea;
outline: solid 1px $blue;
}
$avatar-size: 44px;
@ -542,6 +542,51 @@ input[type=text], input[type=search], textarea {
}
}
.upgrade-banner {
background: linear-gradient(
to bottom,
rgb(213,213,213) 0%, // (1 - 0.41) * 255 + 0.41 * 213
rgb(249,249,249) 35%, // (1 - 0.19) * 255 + 0.19 * 191
rgb(255,255,255) 50%,
rgb(249,249,249) 65%, // (1 - 0.19) * 255 + 0.19 * 222
rgb(213,213,213) 100% // (1 - 0.27) * 255 + 0.27 * 98
);
padding: 10px;
font-family: roboto-light;
font-size: 14pt;
button {
float: right;
border: none;
color: white;
font-family: roboto-light;
font-size: 14pt;
border-radius: 0;
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
line-height: 36px;
padding: 0 2em;
background: $blue;
margin-left: 20px;
}
.message {
padding: 10px 0;
}
.highlight {
font-weight: bold;
font-family: roboto;
}
.x {
float: right;
margin-left: 0.5em;
margin-top: 0.5em;
background-color: $grey_l2;
}
}
.inbox {
position: relative;
}
@ -602,6 +647,289 @@ input[type=text], input[type=search], textarea {
}
}
.migration-flow {
z-index: 1000;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
font-family: roboto-light;
color: black;
background: linear-gradient(
to bottom,
rgb(238,238,238) 0%, // (1 - 0.41) * 255 + 0.41 * 213
rgb(243,243,243) 12%, // (1 - 0.19) * 255 + 0.19 * 191
rgb(255,255,255) 27%,
rgb(255,255,255) 60%,
rgb(249,249,249) 85%, // (1 - 0.19) * 255 + 0.19 * 222
rgb(213,213,213) 100% // (1 - 0.27) * 255 + 0.27 * 98
);
display: flex;
align-items: center;
text-align: center;
font-size: 10pt;
@media (min-height: 750px) and (min-width: 700px) {
font-size: 14pt;
}
.banner-image {
margin: 1em;
display: none;
@media (min-height: 550px) {
display: inline-block;
height: 10em;
width: 10em;
}
}
.banner-icon {
margin: 1em;
display: none;
// 640px by 338px is the smallest the window can go
@media (min-height: 550px) {
display: inline-block;
height: 10em;
width: 10em;
}
&.folder-outline {
@include color-svg('../images/folder-outline.svg', #DEDEDE);
}
&.export {
@include color-svg('../images/export.svg', #DEDEDE);
}
&.check-circle-outline {
@include color-svg('../images/check-circle-outline.svg', #DEDEDE);
}
&.alert-outline {
@include color-svg('../images/alert-outline.svg', #DEDEDE);
}
}
.os-header: {
margin-top: 2em;
}
.header {
font-weight: normal;
margin-bottom: 1.5em;
font-size: 20pt;
@media (min-height: 750px) and (min-width: 700px) {
font-size: 28pt;
}
}
.export-location {
text-align: center;
font-family: roboto;
border: solid 1px $blue;
color: $blue;
margin-bottom: 1em;
padding: 0.5em;
-webkit-user-select: text;
cursor: text;
}
.center {
text-align: center;
}
.body-text {
margin-bottom: 1em;
max-width: 22em;
text-align: left;
margin-left: auto;
margin-right: auto;
}
.body-text-wide {
margin-bottom: 1em;
max-width: 30em;
text-align: left;
margin-left: auto;
margin-right: auto;
&.red-text {
color: red;
a {
color: red;
}
}
}
.step {
height: 100%;
width: 100%;
padding: 70px 0 50px;
}
.step-body {
margin-left: auto;
margin-right: auto;
max-width: 35em;
}
.uninstall-steps {
margin-bottom: 4em;
li {
margin: 0.5em;
}
}
.url {
font-weight: bold;
}
.linux-install-instructions .content {
max-width: 1300px;
.header {
color: black;
font-size: 120%;
}
pre {
&::-webkit-scrollbar {
width: 5px;
height: 5px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: rgba(255,255,255,0.40);
border-radius: 2;
}
cursor: text;
-webkit-user-select: text;
background-color: black;
color: white;
text-align: left;
padding: 0.5em;
overflow-x: scroll;
}
}
.inner {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
height: 100%;
}
.button {
cursor: pointer;
display: inline-block;
text-decoration: none;
border: none;
min-width: 300px;
padding: 0.75em;
margin-top: 1em;
margin-bottom: 1em;
color: white;
background: $blue;
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
font-size: 12pt;
@media (min-height: 750px) and (min-width: 700px) {
font-size: 20pt;
}
}
a.link {
display: block;
cursor: pointer;
text-decoration: underline;
margin: 0.5em;
color: $blue;
}
.progress {
text-align: center;
padding: 1em;
width: 80%;
margin: auto;
.bar-container {
height: 1em;
margin: 1em;
background-color: $grey_l;
}
.bar {
width: 100%;
height: 100%;
background-color: $blue_l;
transition: width 0.25s;
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}
}
.install-container {
@media (min-height: 550px) {
margin-top: 4em;
}
text-align: center;
}
.install {
cursor: pointer;
background-color: white;
padding: 0.5em;
margin: 0.5em;
display: inline-block;
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
margin-left: 1em;
margin-right: 1em;
a {
color: black;
text-decoration: none;
}
}
.install-icon {
height: 7em;
width: 7em;
vertical-align: text-bottom;
display: inline-block;
margin: 1em;
@media (min-width: 800px) {
margin-left: 2em;
margin-right: 2em;
}
&.apple {
@include color-svg('/images/apple.svg', $blue);
}
&.windows {
@include color-svg('/images/windows.svg', $blue);
}
&.linux {
@include color-svg('/images/linux.svg', $blue);
}
}
.nav {
width: 100%;
bottom: 50px;
margin-top: auto;
padding-bottom: 1em;
padding-left: 20px;
padding-right: 20px;
}
.installed {
visibility: hidden;
}
}
//yellow border fix
.inbox:focus {
outline: none;

View File

@ -5,7 +5,7 @@
.expired {
.conversation-stack, .gutter {
height: calc(100% - 56px);
height: calc(100% - 62px);
}
}

View File

@ -40,6 +40,23 @@ $text-dark_l2: darken($text-dark, 30%);
background-color: darken($button-dark, 8%);
}
}
.upgrade-banner {
button {
float: right;
border: none;
color: white;
font-family: roboto-light;
font-size: 14pt;
border-radius: 0;
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
line-height: 36px;
padding: 0 2em;
background: $blue;
margin-left: 20px;
}
}
.message-detail, .message-container, .conversation,
.discussion-container {
background-color: $grey-dark_l3;

View File

@ -363,7 +363,7 @@ button.hamburger {
vertical-align: middle;
display: inline-block;
margin: 0 0 0 8px;
width: calc(100% - 44px - 8px - 0.28571em);
width: calc(100% - 44px - 8px - 0.2857142857em);
text-align: left;
cursor: pointer; }
.contact-details p {
@ -378,7 +378,7 @@ button.hamburger {
text-align: left; }
.contact-details .number {
color: #616161;
font-size: 0.92857em; }
font-size: 0.9285714286em; }
.contact-details.not-clickable {
cursor: default; }
.contact-details .verified-icon {
@ -485,6 +485,34 @@ input[type=text]:active, input[type=text]:focus, input[type=search]:active, inpu
.expiredAlert .message {
padding: 10px 0; }
.upgrade-banner {
background: linear-gradient(to bottom, #d5d5d5 0%, #f9f9f9 35%, white 50%, #f9f9f9 65%, #d5d5d5 100%);
padding: 10px;
font-family: roboto-light;
font-size: 14pt; }
.upgrade-banner button {
float: right;
border: none;
color: white;
font-family: roboto-light;
font-size: 14pt;
border-radius: 0;
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
line-height: 36px;
padding: 0 2em;
background: #2090ea;
margin-left: 20px; }
.upgrade-banner .message {
padding: 10px 0; }
.upgrade-banner .highlight {
font-weight: bold;
font-family: roboto; }
.upgrade-banner .x {
float: right;
margin-left: 0.5em;
margin-top: 0.5em;
background-color: #d9d9d9; }
.inbox {
position: relative; }
@ -530,6 +558,217 @@ input[type=text]:active, input[type=text]:focus, input[type=search]:active, inpu
.app-loading-screen .dot:nth-child(3) {
animation: loading 1500ms ease infinite 666ms; }
.migration-flow {
z-index: 1000;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
font-family: roboto-light;
color: black;
background: linear-gradient(to bottom, #eeeeee 0%, #f3f3f3 12%, white 27%, white 60%, #f9f9f9 85%, #d5d5d5 100%);
display: flex;
align-items: center;
text-align: center;
font-size: 10pt;
.os-header-margin-top: 2em; }
@media (min-height: 750px) and (min-width: 700px) {
.migration-flow {
font-size: 14pt; } }
.migration-flow .banner-image {
margin: 1em;
display: none; }
@media (min-height: 550px) {
.migration-flow .banner-image {
display: inline-block;
height: 10em;
width: 10em; } }
.migration-flow .banner-icon {
margin: 1em;
display: none; }
@media (min-height: 550px) {
.migration-flow .banner-icon {
display: inline-block;
height: 10em;
width: 10em; } }
.migration-flow .banner-icon.folder-outline {
-webkit-mask: url("../images/folder-outline.svg") no-repeat center;
-webkit-mask-size: 100%;
background-color: #DEDEDE; }
.migration-flow .banner-icon.export {
-webkit-mask: url("../images/export.svg") no-repeat center;
-webkit-mask-size: 100%;
background-color: #DEDEDE; }
.migration-flow .banner-icon.check-circle-outline {
-webkit-mask: url("../images/check-circle-outline.svg") no-repeat center;
-webkit-mask-size: 100%;
background-color: #DEDEDE; }
.migration-flow .banner-icon.alert-outline {
-webkit-mask: url("../images/alert-outline.svg") no-repeat center;
-webkit-mask-size: 100%;
background-color: #DEDEDE; }
.migration-flow .header {
font-weight: normal;
margin-bottom: 1.5em;
font-size: 20pt; }
@media (min-height: 750px) and (min-width: 700px) {
.migration-flow .header {
font-size: 28pt; } }
.migration-flow .export-location {
text-align: center;
font-family: roboto;
border: solid 1px #2090ea;
color: #2090ea;
margin-bottom: 1em;
padding: 0.5em;
-webkit-user-select: text;
cursor: text; }
.migration-flow .center {
text-align: center; }
.migration-flow .body-text {
margin-bottom: 1em;
max-width: 22em;
text-align: left;
margin-left: auto;
margin-right: auto; }
.migration-flow .body-text-wide {
margin-bottom: 1em;
max-width: 30em;
text-align: left;
margin-left: auto;
margin-right: auto; }
.migration-flow .body-text-wide.red-text {
color: red; }
.migration-flow .body-text-wide.red-text a {
color: red; }
.migration-flow .step {
height: 100%;
width: 100%;
padding: 70px 0 50px; }
.migration-flow .step-body {
margin-left: auto;
margin-right: auto;
max-width: 35em; }
.migration-flow .uninstall-steps {
margin-bottom: 4em; }
.migration-flow .uninstall-steps li {
margin: 0.5em; }
.migration-flow .url {
font-weight: bold; }
.migration-flow .linux-install-instructions .content {
max-width: 1300px; }
.migration-flow .linux-install-instructions .content .header {
color: black;
font-size: 120%; }
.migration-flow .linux-install-instructions .content pre {
cursor: text;
-webkit-user-select: text;
background-color: black;
color: white;
text-align: left;
padding: 0.5em;
overflow-x: scroll; }
.migration-flow .linux-install-instructions .content pre::-webkit-scrollbar {
width: 5px;
height: 5px; }
.migration-flow .linux-install-instructions .content pre::-webkit-scrollbar-track {
background: transparent; }
.migration-flow .linux-install-instructions .content pre::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.4);
border-radius: 2; }
.migration-flow .inner {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
height: 100%; }
.migration-flow .button {
cursor: pointer;
display: inline-block;
text-decoration: none;
border: none;
min-width: 300px;
padding: 0.75em;
margin-top: 1em;
margin-bottom: 1em;
color: white;
background: #2090ea;
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
font-size: 12pt; }
@media (min-height: 750px) and (min-width: 700px) {
.migration-flow .button {
font-size: 20pt; } }
.migration-flow a.link {
display: block;
cursor: pointer;
text-decoration: underline;
margin: 0.5em;
color: #2090ea; }
.migration-flow .progress {
text-align: center;
padding: 1em;
width: 80%;
margin: auto; }
.migration-flow .progress .bar-container {
height: 1em;
margin: 1em;
background-color: #f3f3f3; }
.migration-flow .progress .bar {
width: 100%;
height: 100%;
background-color: #a2d2f4;
transition: width 0.25s;
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); }
.migration-flow .install-container {
text-align: center; }
@media (min-height: 550px) {
.migration-flow .install-container {
margin-top: 4em; } }
.migration-flow .install {
cursor: pointer;
background-color: white;
padding: 0.5em;
margin: 0.5em;
display: inline-block;
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
margin-left: 1em;
margin-right: 1em; }
.migration-flow .install a {
color: black;
text-decoration: none; }
.migration-flow .install-icon {
height: 7em;
width: 7em;
vertical-align: text-bottom;
display: inline-block;
margin: 1em; }
@media (min-width: 800px) {
.migration-flow .install-icon {
margin-left: 2em;
margin-right: 2em; } }
.migration-flow .install-icon.apple {
-webkit-mask: url("/images/apple.svg") no-repeat center;
-webkit-mask-size: 100%;
background-color: #2090ea; }
.migration-flow .install-icon.windows {
-webkit-mask: url("/images/windows.svg") no-repeat center;
-webkit-mask-size: 100%;
background-color: #2090ea; }
.migration-flow .install-icon.linux {
-webkit-mask: url("/images/linux.svg") no-repeat center;
-webkit-mask-size: 100%;
background-color: #2090ea; }
.migration-flow .nav {
width: 100%;
bottom: 50px;
margin-top: auto;
padding-bottom: 1em;
padding-left: 20px;
padding-right: 20px; }
.migration-flow .installed {
visibility: hidden; }
.inbox:focus {
outline: none; }
@ -841,11 +1080,11 @@ img.emoji.jumbo {
padding: 0 20px;
margin: 0 0 20px 20px; }
.settings .syncSettings .synced_at {
font-size: 0.92857em;
font-size: 0.9285714286em;
color: #616161; }
.settings .syncSettings .sync_failed {
display: none;
font-size: 0.92857em;
font-size: 0.9285714286em;
color: red; }
.conversation-stack,
@ -853,7 +1092,7 @@ img.emoji.jumbo {
height: 100%; }
.expired .conversation-stack, .expired .gutter {
height: calc(100% - 56px); }
height: calc(100% - 62px); }
.scrollable {
height: 100%;
@ -994,7 +1233,7 @@ input.search {
padding: 0.5em; }
.index .last-message {
margin: 6px 0 0;
font-size: 0.92857em;
font-size: 0.9285714286em;
font-weight: 300; }
.index .gutter .timestamp {
position: absolute;
@ -1166,7 +1405,7 @@ input.search {
.key-verification label {
display: block;
margin: 10px 0;
font-size: 0.92857em; }
font-size: 0.9285714286em; }
.key-verification .icon {
height: 1.25em;
width: 1.25em;
@ -1272,7 +1511,7 @@ input.search {
background-color: white; }
.message-detail .contacts .contact-detail .error-message {
margin: 6px 0 0;
font-size: 0.92857em;
font-size: 0.9285714286em;
font-weight: bold;
color: red; }
.message-detail h3 {
@ -1654,7 +1893,7 @@ li.entry .error-icon-container {
color: white;
box-shadow: 0 0 5px 0 black;
border-radius: 5px;
font-size: 0.92857em;
font-size: 0.9285714286em;
z-index: 100; }
.confirmation-dialog .content {
@ -2137,13 +2376,25 @@ li.entry .error-icon-container {
border: 1px solid #292929; }
.android-dark button:hover, .android-dark .confirmation-dialog .content .buttons button:hover {
background-color: #b8b8b8; }
.android-dark .upgrade-banner button {
float: right;
border: none;
color: white;
font-family: roboto-light;
font-size: 14pt;
border-radius: 0;
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
line-height: 36px;
padding: 0 2em;
background: #2090ea;
margin-left: 20px; }
.android-dark .message-detail, .android-dark .message-container, .android-dark .conversation,
.android-dark .discussion-container {
background-color: #171717; }
.android-dark .modal .content {
background-color: #333333; }
.android-dark .lightbox .content {
background-color: transparent; }
background-color: rgba(0, 0, 0, 0); }
.android-dark .key-verification .key {
background-color: #030303;
border-color: #292929; }

File diff suppressed because it is too large Load Diff

View File

@ -1,366 +1,7 @@
@import 'variables';
@import 'mixins';
@import 'global';
@import 'intlTelInput';
@import 'progress';
.iti-flag {
// override intlTelInput's flags image location
background: url("/images/flags.png");
}
* {
box-sizing: border-box;
}
html,body {
height: 100%;
}
body {
margin: 0;
font-family: $roboto;
position: relative;
background: #2090ea;
color: white;
text-align: center;
font-size: 16px;
overflow: auto;
}
.clearfix:before,
.clearfix:after {
display: table;
content: " ";
}
.clearfix:after {
clear: both;
}
input, button, select, textarea {
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
#install {
display: none;
height: 100%;
// 666px is the minimum window height in chromium.js
@media screen and (max-height: 665px) {
height: 666px;
}
}
.main {
padding: 70px 0 50px;
}
.step {
display: none;
height: 100%;
}
.inner {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
height: 100%;
.step-body {
margin-top: auto;
width: 100%;
max-width: 600px;
}
}
#signal-computer,
#signal-phone {
max-width: 50%;
max-height: 250px;
}
p {
max-width: 35em;
margin: 1em auto;
line-height: 1.5em;
font-size: 1.2em;
font-weight: bold;
}
a {
cursor: pointer;
&, &:visited, &:hover {
text-decoration: none;
}
}
.button {
display: inline-block;
text-transform: uppercase;
border: none;
font-weight: bold;
min-width: 300px;
padding: 0.5em;
margin: 0.5em 0;
background: white;
color: $blue;
}
.nav {
width: 100%;
bottom: 50px;
margin-top: auto;
padding: 0 20px;
.button {
margin-bottom: 3em;
}
.dot {
display: inline-block;
cursor: pointer;
margin: 10px;
width: 20px;
height: 20px;
border-radius: 10px;
background: white;
border: solid 5px $blue;
&.selected {
background: $blue_l;
}
}
}
.link {
&:hover, &:focus {
background: rgba(255,255,255,0.3);
outline: none;
}
&, &:visited, &:hover {
padding: 0 3px;
color: white;
font-weight: bold;
border-bottom: dashed 2px white;
text-decoration: none;
}
}
.container {
min-width: 650px;
}
h1 {
font-size: 30pt;
font-weight: normal;
padding-bottom: 10px;
}
h3.step {
margin-top: 0;
font-weight: bold;
}
.help {
border-top: 2px solid $grey_l;
padding: 1.5em 0.1em;
}
.install {
display: inline-block;
margin-top: 90px;
}
#qr {
display: inline-block;
min-height: 266px;
img {
border: 5px solid white;
}
canvas {
display: none;
}
}
#device-name {
border: none;
border-bottom: 1px solid white;
padding: 8px;
background: transparent;
color: white;
font-weight: bold;
text-align: center;
&::selection, a::selection {
color: $grey_d;
background: white;
}
&::-moz-selection, a::-moz-selection {
color: $grey_d;
background: white;
}
&:focus {
outline: none;
}
&:hover, &:focus {
background: rgba(255,255,255,0.1);
}
}
#verifyCode,
#code,
#number {
box-sizing: border-box;
width: 100%;
display: block;
margin-bottom: 0.5em;
text-align: center;
}
#request-voice,
#request-sms {
box-sizing: border-box;
}
#request-sms {
width: 57%;
float: right;
}
#request-voice {
width: 40%;
float: left;
}
.number-container {
position: relative;
margin-bottom: 0.5em;
}
.number-container .intl-tel-input,
.number-container .number {
width: 100%;
}
.number-container::after {
visibility: hidden;
content: ' ';
display: inline-block;
border-radius: 1.5em;
width: 1.5em;
height: 1.5em;
line-height: 1.5em;
color: #ffffff;
position: absolute;
top: 0;
left: 100%;
margin: 3px 8px;
text-align: center;
}
.number-container.valid::after {
visibility: visible;
content: '';
background-color: #0f9d58;
color: #ffffff;
}
.number-container.invalid::after {
visibility: visible;
content: '!';
background-color: #f44336;
color: #ffffff;
}
#error {
color: white;
font-weight: bold;
padding: 0.5em;
text-align: center;
}
#error { background-color: #f44336; }
#error:before {
content: '\26a0';
padding-right: 0.5em;
}
.narrow {
margin: auto;
box-sizing: border-box;
width: 275px;
max-width: 100%;
}
ul.country-list {
min-width: 197px !important;
}
.confirmation-dialog, .progress-dialog, .error-dialog {
padding: 1em;
text-align: left;
}
.number { text-align: center; }
.confirmation-dialog, .error-dialog {
button {
float: right;
margin-left: 10px;
}
}
.progress-dialog {
text-align: center;
padding: 1em;
width: 100%;
max-width: 600px;
margin: auto;
.status { padding: 1em; }
.bar-container {
height: 1em;
background-color: $grey_l;
border: solid 1px white;
}
.bar {
width: 0;
height: 100%;
background-color: $blue_l;
transition: width 0.25s;
&.active {
}
}
}
.error-dialog {
display: none;
}
.modal-container {
display: none;
position: absolute;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.1);
top: 0;
padding-top: 10em;
text-align: center;
.modal-main {
display: inline-block;
width: 80%;
max-width: 500px;
border: solid 2px $blue;
background: white;
margin: 10% auto;
box-shadow: 0 0 5px 3px rgba(darken($blue, 30%), 0.2);
h4 {
background-color: $blue;
color: white;
padding: 1em;
margin: 0;
text-align: left;
}
}
}
.intl-tel-input .country-list {
text-align: left;
}
.intl-tel-input .country-list .country .country-name {
color: #000;
}

166
test/database_test.js Normal file
View File

@ -0,0 +1,166 @@
'use strict';
describe('Database', function() {
describe('cleanMessageAttachments', function() {
it('does not modify a message with no attachments field', function() {
const message = {};
const actual = window.Whisper.Database.cleanMessageAttachments(message);
assert.strictEqual(actual, false);
});
it('does not modify a message with no attachments', function() {
const message = {
attachments: [],
};
const actual = window.Whisper.Database.cleanMessageAttachments(message);
assert.strictEqual(actual, false);
});
it('does not modify a message with an attachment with a string fileName', function() {
const message = {
attachments: [{
fileName: 'blah.jpg',
data: 'something',
}],
};
const actual = window.Whisper.Database.cleanMessageAttachments(message);
assert.strictEqual(actual, false);
});
it('does not modify a message with an attachment with no fileName and a number id', function() {
const message = {
attachments: [{
id: 4,
data: 'something',
}],
};
const actual = window.Whisper.Database.cleanMessageAttachments(message);
console.log(message);
assert.strictEqual(actual, false);
});
it('does not modify a message with an attachment with no fileName and a string id', function() {
const message = {
attachments: [{
id: '4',
data: 'something',
}],
};
const actual = window.Whisper.Database.cleanMessageAttachments(message);
console.log(message);
assert.strictEqual(actual, false);
});
it('eliminates non-string fileName', function() {
const message = {
attachments: [{
fileName: 4,
data: 'something',
}],
};
const actual = window.Whisper.Database.cleanMessageAttachments(message);
assert.isUndefined(message.attachments[0].fileName);
assert.strictEqual(actual, true);
});
it('eliminates object id', function() {
const message = {
attachments: [{
id: {
info: 'yes',
},
data: 'something',
}],
};
const actual = window.Whisper.Database.cleanMessageAttachments(message);
assert.strictEqual(typeof message.attachments[0].id, 'string');
assert.strictEqual(actual, true);
});
it('eliminates non-string contentType', function() {
const message = {
attachments: [{
contentType: 4,
data: 'something',
}],
};
const actual = window.Whisper.Database.cleanMessageAttachments(message);
assert.isUndefined(message.attachments[0].contentType);
assert.strictEqual(actual, true);
});
it('drops an attachment with no data attribute', function() {
const message = {
attachments: [{
id: 1,
data: null,
}, {
id: 2,
data: 'something'
}],
};
const actual = window.Whisper.Database.cleanMessageAttachments(message);
assert.strictEqual(message.attachments.length, 1);
assert.strictEqual(message.attachments[0].id, 2);
assert.strictEqual(actual, true);
});
});
describe('dropZeroLengthAttachments', function() {
it('does not modify a message with no attachments field', function() {
const message = {};
const actual = window.Whisper.Database.dropZeroLengthAttachments(message);
assert.strictEqual(actual, false);
});
it('does not modify a message with no attachments', function() {
const message = {
attachments: [],
};
const actual = window.Whisper.Database.dropZeroLengthAttachments(message);
assert.strictEqual(actual, false);
});
it('does not modify a message with an attachment with a non-zero data field', function() {
const message = {
attachments: [{
fileName: 'blah.jpg',
data: 'something',
}],
};
const actual = window.Whisper.Database.dropZeroLengthAttachments(message);
assert.strictEqual(actual, false);
});
it('drops an attachment with null or zero-length data field', function() {
const message = {
attachments: [{
id: 1,
data: null,
}, {
id: 3,
data: {
byteLength: 1,
},
}],
};
const actual = window.Whisper.Database.dropZeroLengthAttachments(message);
assert.strictEqual(message.attachments.length, 1);
assert.strictEqual(message.attachments[0].id, 3);
assert.strictEqual(actual, true);
});
});
});

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +0,0 @@
'use strict';
describe("Fixtures", function() {
before(function(done) {
// NetworkStatusView checks this method every five seconds while showing
window.getSocketStatus = function() { return WebSocket.OPEN; };
Whisper.Fixtures.saveAll().then(function() {
done();
});
});
it('renders', function(done) {
ConversationController.loadPromise().then(function() {
var view = new Whisper.InboxView({window: window});
view.onEmpty();
view.$el.prependTo($('#render-android'));
var view = new Whisper.InboxView({window: window});
view.$el.removeClass('android').addClass('ios');
view.onEmpty();
view.$el.prependTo($('#render-ios'));
var view = new Whisper.InboxView({window: window});
view.$el.removeClass('android').addClass('android-dark');
view.onEmpty();
view.$el.prependTo($('#render-android-dark'));
}).then(done, done);
});
});

View File

@ -650,9 +650,7 @@
<script type="text/javascript" src="emoji_util_test.js"></script>
<script type="text/javascript" src="reliable_trigger_test.js"></script>
<script type="text/javascript" src="backup_test.js"></script>
<script type="text/javascript" src="fixtures.js"></script>
<script type="text/javascript" src="fixtures_test.js"></script>
<script type="text/javascript" src="database_test.js"></script>
<!-- Comment out to turn off code coverage. Useful for getting real callstacks. -->
<!-- <script type="text/javascript" src="blanket_mocha.js"></script> -->