Merge branch 'clearnet' into tls-fix

pull/759/head
Ryan Tharp committed by GitHub
commit 3964e9acf0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -9,15 +9,13 @@ Remember, you can preview this before saving it.
### First time contributor checklist:
* [ ] I have read the [README](https://github.com/signalapp/Signal-Desktop/blob/master/README.md) and [Contributor Guidelines](https://github.com/signalapp/Signal-Desktop/blob/master/CONTRIBUTING.md)
* [ ] I have signed the [Contributor Licence Agreement](https://signal.org/cla/)
* [ ] I have read the [README](https://github.com/loki-project/loki-messenger/blob/master/README.md) and [Contributor Guidelines](https://github.com/loki-project/loki-messenger/blob/master/CONTRIBUTING.md)
### Contributor checklist:
* [ ] My contribution is **not** related to translations. _Please submit translation changes via our [Signal Desktop Transifex project](https://www.transifex.com/signalapp/signal-desktop/)._
* [ ] My commits are in nice logical chunks with [good commit messages](http://chris.beams.io/posts/git-commit/)
* [ ] My changes are [rebased](https://medium.freecodecamp.org/git-rebase-and-the-golden-rule-explained-70715eccc372) on the latest [`development`](https://github.com/signalapp/Signal-Desktop/tree/development) branch
* [ ] A `yarn ready` run passes successfully ([more about tests here](https://github.com/signalapp/Signal-Desktop/blob/master/CONTRIBUTING.md#tests))
* [ ] My changes are [rebased](https://medium.freecodecamp.org/git-rebase-and-the-golden-rule-explained-70715eccc372) on the latest [`clearnet`](https://github.com/loki-project/loki-messenger/tree/development) branch
* [ ] A `yarn ready` run passes successfully ([more about tests here](https://github.com/loki-project/loki-messenger/blob/master/CONTRIBUTING.md#tests))
* [ ] My changes are ready to be shipped to users
### Description
@ -26,7 +24,7 @@ Remember, you can preview this before saving it.
Describe briefly what your pull request changes. Focus on the value provided to users.
Does it address any outstanding issues in this project?
https://github.com/signalapp/Signal-Desktop/issues?utf8=%E2%9C%93&q=is%3Aissue
https://github.com/loki-project/loki-messenger/issues?utf8=%E2%9C%93&q=is%3Aissue
Reference an issue with the hash symbol: "#222"
If you're fixing it, use something like "Fixes #222"

1
.gitignore vendored

@ -1,5 +1,6 @@
node_modules
.sass-cache
.eslintcache
coverage/*
build/curve25519_compiled.js
build/icons/*

@ -553,7 +553,7 @@
"message": "When including a non-image attachment, the limit is one attachment per message.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "You cannot mix non-image and image attachments in one message.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "الرسائل",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "مرحبا الى سيجنال Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "When including a non-image attachment, the limit is one attachment per message.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "You cannot mix non-image and image attachments in one message.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Съобщения",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Добре дошли в Сигнал",
"description": ""
},

@ -553,7 +553,7 @@
"message": "Quan inclogueu un adjunt que no sigui una imatge, el límit és un adjunt per missatge.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "No podeu barrejar adjunts d'imatge i de no imatge en un mateix missatge.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Missatges",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Benvingut al Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "When including a non-image attachment, the limit is one attachment per message.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "You cannot mix non-image and image attachments in one message.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Zprávy",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Vítejte v Signalu",
"description": ""
},

@ -553,7 +553,7 @@
"message": "Når der indgår en vedhæftet fil uden billed, er grænsen en vedhæftet fil pr. besked.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "Du kan ikke blande ikke-billed og billedvedhæftninger i en besked.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Beskeder",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Velkommen til Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "Mehrere Anhänge je Nachricht sind ausschließlich bei Bildern erlaubt.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "Du kannst Bilder nicht gemeinsam mit anderen Anhängen in einer Nachricht kombinieren.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Nachrichten",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Willkommen bei Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "Για συνημμένα αρχεία που δεν είναι εικόνες, το όριο είναι ένα αρχείο ανά μήνυμα.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "Δεν είναι δυνατό να συνάψετε εικόνες μαζί με αρχεία άλλου τύπου στο ίδιο μήνυμα.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Μηνύματα",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Καλώς ορίσατε στο Signal",
"description": ""
},

@ -2,7 +2,7 @@
"privacyPolicy": {
"message": "Terms & Privacy Policy",
"description":
"Shown in the about box for the link to https://signal.org/legal"
"Shown in the about box for the link to https://getsession.org/privacy-policy/"
},
"copyErrorAndQuit": {
"message": "Copy error and quit",
@ -603,7 +603,7 @@
"description": "Name for a voice message attachment"
},
"dangerousFileType": {
"message": "Attachment type not allowed for security reasons",
"message": "For security reasons, this file type cannot be sent",
"description":
"Shown in toast when user attempts to send .exe file, for example"
},
@ -646,25 +646,25 @@
},
"oneNonImageAtATimeToast": {
"message":
"When including a non-image attachment, the limit is one attachment per message.",
"Sorry, there is a limit of one non-image attachment per message",
"description":
"An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"message": "You cannot mix non-image and image attachments in one message.",
"cannotMixImageAndNonImageAttachments": {
"message": "Sorry, you cannot mix images with other file types in one message",
"description":
"An error popup when the user has attempted to add an attachment"
},
"maximumAttachments": {
"message": "You cannot add any more attachments to this message.",
"message": "Maximum number of attachments reached. Please send remaining attachments in a separate message.",
"description":
"An error popup when the user has attempted to add an attachment"
},
"fileSizeWarning": {
"message": "Sorry, the selected file exceeds message size restrictions."
"message": "Sorry, the selected attachment is too large"
},
"unableToLoadAttachment": {
"message": "Unable to load selected attachment."
"message": "Unable to load attachment."
},
"connect": {
"message": "Connect"
@ -784,7 +784,7 @@
"message": "Settings",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Welcome to Session"
},
"selectAContact": {
@ -933,7 +933,7 @@
},
"cannotUpdateDetail": {
"message":
"Signal Desktop failed to update, but there is a new version available. Please go to https://signal.org/download and install the new version manually, then either contact support or file a bug about this problem.",
"Signal Desktop failed to update, but there is a new version available. Please go to https://getsession.org/ and install the new version manually, then either contact support or file a bug about this problem.",
"description":
"Shown if a general error happened while trying to install update package"
},
@ -967,6 +967,15 @@
"continue": {
"message": "Continue"
},
"noThankyou": {
"message": "No, thank you"
},
"noMessagesTitle": {
"message": "You don't have any messages, yet."
},
"noMessagesSubtitle": {
"message": "Would you like to join Session's public chat?"
},
"pairNewDevice": {
"message": "Pair New Device"
},
@ -1052,7 +1061,7 @@
"Are you sure? Clicking 'delete' will permanently remove these messages from this device only."
},
"messageDeletionForbidden": {
"message": "You don't have permission to delete others' messages.",
"message": "You dont have permission to delete others messages",
"description":
"Toast message explaining that the user doens't have the rights to delete other people's messages."
},
@ -1110,12 +1119,12 @@
"message": "Show members"
},
"resetSession": {
"message": "Reset session",
"message": "Reset Session",
"description":
"This is a menu item for resetting the session, using the imperative case, as in a command."
},
"showSafetyNumber": {
"message": "View safety number"
"message": "View Safety Number"
},
"viewAllMedia": {
"message": "View all media",
@ -1367,6 +1376,13 @@
"Link previews supported for Imgur, Instagram, Pinterest, Reddit, and YouTube.",
"description": "Description shown for the Link Preview option "
},
"linkPreviewsConfirmTitle": {
"message": "Warning"
},
"linkPreviewsConfirmMessage": {
"message": "You will not have full metadata protection when sending or receiving link previews."
},
"mediaPermissionsTitle": {
"message": "Microphone and Camera"
},
@ -1429,7 +1445,7 @@
"description": "Title of the read receipts setting"
},
"typingIndicatorsSettingDescription": {
"message": "Enable the sending and receiving of typing indicators",
"message": "See and share when messages are being typed (applies to all sessions).",
"description": "Description of the typing indicators setting"
},
"typingIndicatorsSettingTitle": {
@ -1642,7 +1658,7 @@
"description": "Brief message shown when trying to message a blocked number"
},
"unblockGroupToSend": {
"message": "Unblock this group to send a message.",
"message": "This group is blocked. Unlock it if you would like to send a message.",
"description": "Brief message shown when trying to message a blocked group"
},
"youChangedTheTimer": {
@ -1747,7 +1763,7 @@
"description": "Conversation menu option to enable disappearing messages"
},
"changeNickname": {
"message": "Change nickname",
"message": "Change Nickname",
"description": "Conversation menu option to change user nickname"
},
"clearNickname": {
@ -2037,7 +2053,7 @@
"Shown in the conversation history when the user accepts a friend request"
},
"friendRequestDeclined": {
"message": "Friend request declined",
"message": "Session request declined",
"description":
"Shown in the conversation history when the user declines a friend request"
},
@ -2079,10 +2095,10 @@
}
},
"blockUser": {
"message": "Block user"
"message": "Block User"
},
"unblockUser": {
"message": "Unblock user"
"message": "Unblock User"
},
"settingsUnblockHeader": {
"message": "Blocked Users",
@ -2111,11 +2127,11 @@
"description": "Message shown when confirming user ban."
},
"userBanned": {
"message": "User successfully banned",
"message": "User banned successfully",
"description": "Toast on succesful user ban."
},
"userBanFailed": {
"message": "User ban failed!",
"message": "Ban failed!",
"description": "Toast on unsuccesful user ban."
},
"copyChatId": {
@ -2144,7 +2160,7 @@
"Title shown to the user to confirm they want to leave the group"
},
"copiedPublicKey": {
"message": "Copied public key",
"message": "Session ID copied",
"description": "A toast message telling the user that the key was copied"
},
"copiedChatId": {
@ -2161,7 +2177,7 @@
"description": "Button action that the user can click to select the message"
},
"copiedMessage": {
"message": "Copied message text",
"message": "Message text copied",
"description":
"A toast message telling the user that the message text was copied"
},
@ -2282,7 +2298,7 @@
"description": "The title shown when the user views their seeds"
},
"copiedMnemonic": {
"message": "Copied seed to clipboard",
"message": "Recovery phrase copied successfully",
"description":
"A toast message telling the user that the mnemonic seed was copied"
},
@ -2330,7 +2346,7 @@
"message": "Old password is invalid"
},
"invalidPassword": {
"message": "Invalid Password"
"message": "Invalid password"
},
"noGivenPassword": {
"message": "Please enter your password"
@ -2579,6 +2595,9 @@
"beginYourSession": {
"message": "Begin<br />your<br />Session."
},
"welcomeToSession": {
"message": "Welcome to Session"
},
"welcomeToYourSession": {
"message": "Welcome to your Session"
},
@ -2706,6 +2725,9 @@
"joinChannel": {
"message": "Join Open Group"
},
"joinPublicChat": {
"message": "Join Public Chat"
},
"next": {
"message": "Next"
},
@ -2722,9 +2744,9 @@
"message": "Pairing Device"
},
"gotPairingRequest": {
"message": "Got a pairing request"
"message": "Pairing request received"
},
"devicePairedSuccessfully": {
"message": "Device paired successfully"
"message": "Device linked successfully"
}
}

@ -553,7 +553,7 @@
"message": "Kiam oni enmetas nebildan kunsendaĵon, limo estas po unu kunsendaĵo mesaĝe.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "Vi ne povas kunmeti nebildajn kaj bildajn kunsendaĵojn en unu mesaĝo.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Mesaĝoj",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Bonvenon al Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "Al incluír un adjunto que no es una imagen, el límite es un adjunto por mensaje.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "No se pueden combinar adjuntos de otro tipo junto a imágenes.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Mensajes",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Bienvenida a Signal",
"description": ""
},

@ -565,7 +565,7 @@
"message": "Enter name or number",
"description": "Placeholder text in the search input"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Bienvenido a Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "Mittepildilisi manuseid sõnumile lisades kehtiv ühe manuse piirang.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "Piltmanuseid ja teisi manuseid ei saa koos ühte sõnumisse panna.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Sõnumid",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Tere tulemast Signalisse",
"description": ""
},

@ -553,7 +553,7 @@
"message": "When including a non-image attachment, the limit is one attachment per message.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "You cannot mix non-image and image attachments in one message.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "پیام ها",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "به Signal خوش‌آمدید",
"description": ""
},

@ -553,7 +553,7 @@
"message": "Muille kuin kuvatiedostoille raja on yksi liitetiedosto per viesti.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "Kuvatiedostoa ja muun tyyppistä liitetiedostoa ei voi lisätä samaan viestiin.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Viestit",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Tervetuloa Signaliin",
"description": ""
},

@ -553,7 +553,7 @@
"message": "Pour un fichier joint qui nest pas une image, la limite est un fichier joint par message.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "Vous ne pouvez pas joindre à la fois image et non image dans le même message.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Messages",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Bienvenue sur Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "בעת הכללת צרופת אי־תמונה, המגבלה היא צרופה אחת להודעה.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "אינך יכול לערבב צרופות של אי־תמונה ותמונה בהודעה אחת.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "הודעות",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "ברוכים הבאים לסיגנל",
"description": ""
},

@ -553,7 +553,7 @@
"message": "When including a non-image attachment, the limit is one attachment per message.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "You cannot mix non-image and image attachments in one message.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "संदेश",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "सिग्‍नल में आपका स्वागत है ",
"description": ""
},

@ -553,7 +553,7 @@
"message": "When including a non-image attachment, the limit is one attachment per message.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "You cannot mix non-image and image attachments in one message.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Poruke",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Dobrodošli u Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "Nem-kép csatolmány esetén üzenetenként egy melléklet csatolható.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "Nem keverheted a nem-kép és kép típusú csatolmányokat üzeneten belül.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Üzenetek",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Üdvözöl a Signal!",
"description": ""
},

@ -553,7 +553,7 @@
"message": "Ketika menyertakan lampiran bukan gambar, batasnya adalah satu lampiran per pesan.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "Anda tidak bisa mencampur lampiran gambar dan bukan gambar dalam satu pesan.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Pesan",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Selamat datang di Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "Quando includi un allegato che non è un'immagine, il limite è un solo allegato per messaggio.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "Non puoi allegare insieme immagini ad altri file in un messaggio.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Messaggi",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Benvenuto in Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "When including a non-image attachment, the limit is one attachment per message.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "You cannot mix non-image and image attachments in one message.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "メッセージ",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Signalにようこそ",
"description": ""
},

@ -553,7 +553,7 @@
"message": "ពេលបញ្ចូលឯកសារភ្ជាប់មិន-រូបភាពមួយ ការកំណត់គឺឯកសារភ្ជាប់មួយ សម្រាប់សារមួយ។",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "អ្នកមិនអាចលាយឡំឯកសារភ្ជាប់ មិន-រូបភាព និងរូបភាព ក្នុងសារតែមួយទេ។",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "សារ",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "ស្វាគមន៍មកកាន់ Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "When including a non-image attachment, the limit is one attachment per message.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "You cannot mix non-image and image attachments in one message.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Messages",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "ಸಿಗ್ನಲ್‌ಗೆ ಸ್ವಾಗತ",
"description": ""
},

@ -553,7 +553,7 @@
"message": "When including a non-image attachment, the limit is one attachment per message.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "You cannot mix non-image and image attachments in one message.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Messages",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Welcome to Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "Įtraukiant ne paveikslų priedus, vienoje žinutėje galimas tik vienas priedas.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "Negalite vienoje žinutėje maišyti paveikslų ir ne paveikslų priedus.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Žinutės",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Sveiki atvykę į Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "When including a non-image attachment, the limit is one attachment per message.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "You cannot mix non-image and image attachments in one message.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Messages",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Добредојдовте во Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "Når du inkluderer et vedlegg som ikke er et bilde, er begrensningen ett vedlegg per melding.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "Du kan ikke blande vedlegg som er bilder med vedlegg som ikke er bilder i samme melding.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Meldinger",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Velkommen til Signal!",
"description": ""
},

@ -553,7 +553,7 @@
"message": "Wanneer je een bijlage insluit die geen afbeelding is, is de limiet één bijlage per bericht.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "Je kunt geen combinatie van afbeeldingen en niet-afbeeldingen insluiten als bijlagen van één bericht.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Berichten",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Welkom bij Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "Når du inkluderer eit vedlegg som ikkje er eit bilde, er grensa eitt vedlegg per melding.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "Du kan ikkje blanda vedlegg som er bilde med vedlegg som ikkje er bilde i same melding.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Meldingar",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Velkommen til Signal!",
"description": ""
},

@ -553,7 +553,7 @@
"message": "Når du inkluderer et vedlegg som ikke er et bilde, er begrensningen ett vedlegg per melding.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "Du kan ikke blande vedlegg som er bilder med vedlegg som ikke er bilder i samme melding.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Messages",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Velkommen til Signal!",
"description": ""
},

@ -553,7 +553,7 @@
"message": "W przypadku dołączenia załącznika innego niż obraz, limit wynosi jeden załącznik na wiadomość.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "Nie można łączyć obrazów z innymi załącznikami w jednej wiadomości.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Wiadomości",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Witamy w Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "O limite para inclusão de anexos que não sejam imagens é de um por mensagem.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "Você não pode combinar imagens e anexos que não sejam imagens na mesma mensagem.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Mensagens",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Boas-vindas ao Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "Se o anexo não for uma imagem, apenas pode enviar um anexo por mensagem.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "Não pode misturar imagens com outro tipo de anexos numa só mensagem.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Mensagens",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Bem-vindo(a) ao Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "When including a non-image attachment, the limit is one attachment per message.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "You cannot mix non-image and image attachments in one message.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Mesaje",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Bun venit la Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "При включении в сообщение вложений, не относящихся к изображениям, ограничение составляет по одному вложению на сообщение.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "Нельзя смешивать вложения, не относящиеся к изображениям, с изображениями в одном сообщении.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Сообщения",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Добро пожаловать в Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "When including a non-image attachment, the limit is one attachment per message.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "V jednej správe nemôže byť súčasne príloha s obrázkom a príloha bez obrázku.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Správy",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Vitajte v aplikácii Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "Neslikovne datoteke so omejene na eno priponko na sporočilo.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "V istem sporočilu ne morete kombinirati neslikovnih in slikovnih priponk.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Sporočila",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Dobrodošli v aplikaciji Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "Kur përfshihet një bashkëngjitje që sështë figurë, kufiri është një bashkëngjitje për mesazh.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "Smund të përzieni brenda të njëjtit mesazh bashkëngjitje figurë dhe jo figurë.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Mesazhe",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Mirë se vini te Signal-i",
"description": ""
},

@ -553,7 +553,7 @@
"message": "When including a non-image attachment, the limit is one attachment per message.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "You cannot mix non-image and image attachments in one message.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Поруке",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Добродошли у Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "När man inkluderar bilagor som inte är bilder, är begränsningen en bilaga per meddelande.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "Du kan inte blanda bilagor som både är, och inte är, bilder i ett meddelande.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Meddelanden",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Välkommen till Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "When including a non-image attachment, the limit is one attachment per message.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "You cannot mix non-image and image attachments in one message.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "ข้อความ",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "ยินดีต้อนรับสู่ Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "Görüntü olmayan eklentiler içerildiğinde, ileti başı eklenti sınırı birdir.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "Görüntü ve görüntü olmayan eklentileri tek iletide birleştiremezsiniz.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "İletiler",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Signal'e Hoşgeldin",
"description": ""
},

@ -553,7 +553,7 @@
"message": "When including a non-image attachment, the limit is one attachment per message.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "You cannot mix non-image and image attachments in one message.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Повідомлення",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Ласкаво просимо до Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "When including a non-image attachment, the limit is one attachment per message.",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "You cannot mix non-image and image attachments in one message.",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "Messages",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "Chào mừng đến với Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "每条消息只能添加一个非图片的附件。",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "在一条消息中,可以同时添加一张图片和一个非图片附件。",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "信息",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "欢迎来到Signal",
"description": ""
},

@ -553,7 +553,7 @@
"message": "當包含的附檔不是圖片,每個訊息限制只能有一個附檔。",
"description": "An error popup when the user has attempted to add an attachment"
},
"cannotMixImageAdnNonImageAttachments": {
"cannotMixImageAndNonImageAttachments": {
"message": "你無法在一個訊息中,同時附上非圖片及圖片檔案。",
"description": "An error popup when the user has attempted to add an attachment"
},
@ -673,7 +673,7 @@
"message": "訊息",
"description": "Shown to separate the types of search results"
},
"welcomeToSignal": {
"welcomeToSession": {
"message": "歡迎來到 Signal",
"description": ""
},

@ -26,7 +26,7 @@
<!--
When making changes to these templates, be sure to update test/index.html as well
-->
<script type='text/x-tmpl-mustache' id='app-loading-screen'>
<div class='content'>
<img src='images/session/full-logo.svg' class='session-full-logo' />
@ -75,10 +75,11 @@
</script>
<script type='text/x-tmpl-mustache' id='expired_alert'>
<a target='_blank' href='https://signal.org/download/'>
<a target='_blank' href='https://getsession.org/'>
<button class='upgrade'>{{ upgrade }}</button>
</a>
{{ expiredWarning }}
<span>{{ expiredWarning }}</span>
<br clear="both">
</script>
<script type='text/x-tmpl-mustache' id='banner'>
@ -108,17 +109,17 @@
<div class='compose'>
<form class='send clearfix file-input'>
<div class='flex'>
<button class='emoji' {{#disable-inputs}} disabled="disabled" {{/disable-inputs}}></button>
<div class='send-message-container'>
<textarea maxlength='2000' class='send-message' placeholder='{{ send-message }}' rows='1' dir='auto' {{#disable-inputs}} disabled="disabled" {{/disable-inputs}}></textarea>
<div id='choose-file' class='choose-file'>
<button class='paperclip thumbnail' {{#disable-inputs}} disabled="disabled" {{/disable-inputs}}></button>
<input type='file' class='file-input' multiple='multiple'>
</div>
<div class='capture-audio'>
<button class='microphone' {{#disable-inputs}} disabled="disabled" {{/disable-inputs}}></button>
<button class='microphone' {{#disable-inputs}} disabled="disabled" {{/disable-inputs}}></button>
</div>
<div id='choose-file' class='choose-file'>
<button class='paperclip thumbnail' {{#disable-inputs}} disabled="disabled" {{/disable-inputs}}></button>
<input type='file' class='file-input' multiple='multiple'>
<div class='send-message-container'>
<textarea maxlength='2000' class='send-message' placeholder='{{ send-message }}' rows='1' dir='auto' {{#disable-inputs}} disabled="disabled" {{/disable-inputs}}></textarea>
</div>
<button class='emoji' {{#disable-inputs}} disabled="disabled" {{/disable-inputs}}></button>
</div>
</form>
</div>
@ -185,7 +186,7 @@
</div>
</div>
</script>
<script type='text/x-tmpl-mustache' id='device-pairing-words-dialog'>
<div class="content">
<h4>{{ title }}</h4>
@ -575,7 +576,6 @@
<script type='text/javascript' src='js/views/session_registration_view.js'></script>
<script type='text/javascript' src='js/views/app_view.js'></script>
<script type='text/javascript' src='js/views/import_view.js'></script>
<script type='text/javascript' src='js/views/clear_data_view.js'></script>
<script type='text/javascript' src='js/views/device_pairing_dialog_view.js'></script>
<script type='text/javascript' src='js/views/device_pairing_words_dialog_view.js'></script>
<script type='text/javascript' src='js/views/create_group_dialog_view.js'></script>
@ -585,7 +585,7 @@
<script type='text/javascript' src='js/views/moderators_add_dialog_view.js'></script>
<script type='text/javascript' src='js/views/moderators_remove_dialog_view.js'></script>
<script type='text/javascript' src='js/views/user_details_dialog_view.js'></script>
<script type='text/javascript' src='js/wall_clock_listener.js'></script>
<script type='text/javascript' src='js/rotate_signed_prekey_listener.js'></script>
<script type='text/javascript' src='js/keychange_listener.js'></script>

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="129px" height="89px" viewBox="0 0 129 89" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 62 (91390) - https://sketch.com -->
<title>Combined Shape</title>
<desc>Created with Sketch.</desc>
<g id="Desktop-(Dark)" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="OnboardingOpenGroupJoin" transform="translate(-168.000000, -235.000000)">
<g id="Combined-Shape" transform="translate(170.000000, 236.000000)">
<path d="M82.281886,20 C106.03013,20 125.281886,34.7746033 125.281886,53 C125.281886,59.5808228 122.771868,65.7117359 118.446468,70.8596473 C118.386871,73.4480518 120.309061,77.1248835 124.21251,81.8902344 C120.039982,81.5926483 116.036545,81.0288787 112.202199,80.1989258 C110.522391,79.8353273 109.249113,79.5109067 108.382366,79.2256639 C101.146682,83.4755303 92.0984313,86 82.281886,86 C58.5336418,86 39.281886,71.2253967 39.281886,53 C39.281886,34.7746033 58.5336418,20 82.281886,20 Z" stroke="#02D370" stroke-width="3" stroke-linejoin="round"></path>
<path d="M31.281886,1.13686838e-13 C48.4027133,1.13686838e-13 62.281886,10.9690236 62.281886,24.5 C62.281886,38.0309764 48.4027133,49 31.281886,49 C24.1228438,49 17.530598,47.0820895 12.2828822,43.860929 C11.6699441,44.0982914 10.6509168,44.3755915 9.22632153,44.6931419 C6.46202578,45.3093191 3.57582702,45.7278752 0.567725244,45.9488104 C3.76733392,41.9262539 5.12046225,38.9484139 4.62711023,37.0152905 C1.86698023,33.3528537 0.281886022,29.0727229 0.281886022,24.5 C0.281886022,10.9690236 14.1610588,1.13686838e-13 31.281886,1.13686838e-13 Z" stroke="#161616" stroke-width="2" fill="#FFFFFF" fill-rule="evenodd"></path>
<circle id="Oval" fill="#02D370" fill-rule="evenodd" cx="62" cy="53" r="4"></circle>
<circle id="Oval" fill="#02D370" fill-rule="evenodd" cx="82" cy="53" r="4"></circle>
<ellipse id="Oval" fill="#02D370" fill-rule="evenodd" cx="101.5" cy="53" rx="3.5" ry="4"></ellipse>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

@ -256,8 +256,7 @@
window.getDefaultFileServer()
);
}
// are there limits on tracking, is this unneeded?
// window.mixpanel.track("Desktop boot");
window.initialisedAPI = true;
if (storage.get('isSecondaryDevice')) {
@ -335,8 +334,9 @@
setTypingIndicatorsSetting: value =>
storage.put('typing-indicators-setting', value),
getLinkPreviewSetting: () => storage.get('linkPreviews', false),
setLinkPreviewSetting: value => storage.put('linkPreviews', value),
getLinkPreviewSetting: () => storage.get('link-preview-setting', false),
setLinkPreviewSetting: value =>
storage.put('link-preview-setting', value),
getNotificationSetting: () =>
storage.get('notification-setting', 'message'),
@ -629,7 +629,7 @@
// Disable link previews as default per Kee
storage.onready(async () => {
storage.put('linkPreviews', false);
storage.put('link-preview-setting', false);
});
// listeners
@ -920,6 +920,23 @@
}
};
// Set user's launch count.
const prevLaunchCount = window.getSettingValue('launch-count');
const launchCount = !prevLaunchCount ? 1 : prevLaunchCount + 1;
window.setSettingValue('launch-count', launchCount);
// On first launch
if (launchCount === 1) {
// Initialise default settings
window.setSettingValue('hide-menu-bar', true);
window.setSettingValue('link-preview-setting', false);
}
// Render onboarding message from LeftPaneMessageSection
// unless user turns it off during their session
window.setSettingValue('render-message-onboarding', true);
// Generates useful random ID for various purposes
window.generateID = () =>
Math.random()
.toString(36)
@ -1023,7 +1040,7 @@
window.toggleLinkPreview = () => {
const newValue = !window.getSettingValue('link-preview-setting');
window.Events.setLinkPreviewSetting(newValue);
window.setSettingValue('link-preview-setting', newValue);
};
window.toggleMediaPermissions = () => {
@ -1031,6 +1048,49 @@
window.setMediaPermissions(!mediaPermissions);
};
window.attemptConnection = async (serverURL, channelId) => {
let rawserverURL = serverURL
.replace(/^https?:\/\//i, '')
.replace(/[/\\]+$/i, '');
rawserverURL = rawserverURL.toLowerCase();
const sslServerURL = `https://${rawserverURL}`;
const conversationId = `publicChat:${channelId}@${rawserverURL}`;
const conversationExists = window.ConversationController.get(
conversationId
);
if (conversationExists) {
// We are already a member of this public chat
return new Promise((_resolve, reject) => {
reject(window.i18n('publicChatExists'));
});
}
const serverAPI = await window.lokiPublicChatAPI.findOrCreateServer(
sslServerURL
);
if (!serverAPI) {
// Url incorrect or server not compatible
return new Promise((_resolve, reject) => {
reject(window.i18n('connectToServerFail'));
});
}
const conversation = await window.ConversationController.getOrCreateAndWait(
conversationId,
'group'
);
await conversation.setPublicSource(sslServerURL, channelId);
await conversation.setFriendRequestStatus(
window.friends.friendRequestStatusEnum.friends
);
conversation.getPublicSendData(); // may want "await" if you want to use the API
return conversation;
};
window.sendGroupInvitations = (serverInfo, pubkeys) => {
pubkeys.forEach(async pubkey => {
const convo = await ConversationController.getOrCreateAndWait(
@ -1592,7 +1652,7 @@
}
if (linkPreviews === true || linkPreviews === false) {
storage.put('linkPreviews', linkPreviews);
storage.put('link-preview-setting', linkPreviews);
}
ev.confirm();
@ -2042,7 +2102,9 @@
// If we don't return early here, we can get into infinite error loops. So, no
// delivery receipts for sealed sender errors.
if (isError || !data.unidentifiedDeliveryReceived) {
// Note(LOKI): don't send receipt for FR as we don't have a session yet
if (isError || !data.unidentifiedDeliveryReceived || data.friendRequest) {
return message;
}

@ -1,22 +1,62 @@
/* global LokiAppDotNetServerAPI, LokiFileServerAPI, semver, log */
// eslint-disable-next-line func-names
(function() {
'use strict';
let BUILD_EXPIRATION = 0;
try {
BUILD_EXPIRATION = parseInt(window.getExpiration(), 10);
if (BUILD_EXPIRATION) {
window.log.info(
'Build expires: ',
new Date(BUILD_EXPIRATION).toISOString()
);
// hold last result
let expiredVersion = null;
window.tokenlessFileServerAdnAPI = new LokiAppDotNetServerAPI(
'', // no pubkey needed
window.getDefaultFileServer()
);
window.tokenlessFileServerAdnAPI.pubKey = window.Signal.Crypto.base64ToArrayBuffer(
LokiFileServerAPI.secureRpcPubKey
);
const checkForUpgrades = async () => {
const response = await window.tokenlessFileServerAdnAPI.serverRequest(
'loki/v1/version/client/desktop'
);
if (response && response.response) {
const latestVer = semver.clean(response.response.data[0][0]);
if (semver.valid(latestVer)) {
const ourVersion = window.getVersion();
if (latestVer === ourVersion) {
log.info('You have the latest version', latestVer);
// change the following to true ot test/see expiration banner
expiredVersion = false;
} else {
// expire if latest is newer than current
expiredVersion = semver.gt(latestVer, ourVersion);
if (expiredVersion) {
log.info('There is a newer version available', latestVer);
}
}
}
} else {
// give it a minute
log.warn('Could not check to see if newer version is available');
setTimeout(async () => {
await checkForUpgrades();
}, 60 * 1000); // wait a minute
}
} catch (e) {
// nothing
}
// no message logged means serverRequest never returned...
};
checkForUpgrades();
window.extension = window.extension || {};
window.extension.expired = () =>
BUILD_EXPIRATION && Date.now() > BUILD_EXPIRATION;
window.extension.expired = cb => {
if (expiredVersion === null) {
// just give it another second
log.info('Delaying expire banner determination for 1s');
setTimeout(() => {
window.extension.expired(cb);
}, 1000);
return;
}
// yes we know
cb(expiredVersion);
};
})();

@ -233,6 +233,12 @@
const lastMessageModel = messages.at(0);
if (lastMessageModel) {
lastMessageModel.acceptFriendRequest();
await this.markRead();
window.Whisper.events.trigger(
'showConversation',
this.id,
lastMessageModel.id
);
}
},
async declineFriendRequest() {

@ -813,7 +813,7 @@
},
getPropsForPreview() {
// Don't generate link previews if user has turned them off
if (!storage.get('linkPreviews', false)) {
if (!storage.get('link-preview-setting', false)) {
return null;
}

@ -1,4 +1,4 @@
/* global log, textsecure, libloki, Signal, Whisper, Headers, ConversationController,
/* global log, textsecure, libloki, Signal, Whisper, ConversationController,
clearTimeout, MessageController, libsignal, StringView, window, _,
dcodeIO, Buffer, lokiSnodeAPI, TextDecoder */
const nodeFetch = require('node-fetch');
@ -58,6 +58,12 @@ class LokiAppDotNetServerAPI {
channelId,
conversationId
);
log.info(
'LokiPublicChannelAPI started for',
channelId,
'on',
this.baseServerUrl
);
this.channels.push(thisChannel);
}
return thisChannel;
@ -225,9 +231,11 @@ class LokiAppDotNetServerAPI {
async refreshServerToken() {
// if currently not in progress
if (this.tokenPromise === null) {
// FIXME: add timeout
// a broken/stuck token endpoint can prevent you from removing channels
// set lock
this.tokenPromise = new Promise(async res => {
// request the oken
// request the token
const token = await this.requestToken();
if (!token) {
res(null);
@ -260,11 +268,13 @@ class LokiAppDotNetServerAPI {
};
url.search = new URLSearchParams(params);
res = await nodeFetch(url);
res = await this.proxyFetch(url);
} catch (e) {
log.error('requestToken request failed', e);
return null;
}
if (!res.ok) {
log.error('requestToken request failed');
return null;
}
const body = await res.json();
@ -286,7 +296,7 @@ class LokiAppDotNetServerAPI {
};
try {
const res = await nodeFetch(
const res = await this.proxyFetch(
`${this.baseServerUrl}/loki/v1/submit_challenge`,
options
);
@ -296,7 +306,32 @@ class LokiAppDotNetServerAPI {
}
}
async _sendToProxy(fetchOptions, method, headers, endpoint) {
async proxyFetch(urlObj, fetchOptions) {
if (
window.lokiFeatureFlags.useSnodeProxy &&
(this.baseServerUrl === 'https://file-dev.lokinet.org' ||
this.baseServerUrl === 'https://file.lokinet.org')
) {
const finalOptions = { ...fetchOptions };
if (!fetchOptions.method) {
finalOptions.method = 'GET';
}
const urlStr = urlObj.toString();
const endpoint = urlStr.replace(`${this.baseServerUrl}/`, '');
const { response, result } = await this._sendToProxy(
endpoint,
finalOptions
);
// emulate nodeFetch response...
return {
ok: result.status === 200,
json: () => response,
};
}
return nodeFetch(urlObj, fetchOptions);
}
async _sendToProxy(endpoint, fetchOptions) {
const randSnode = await lokiSnodeAPI.getRandomSnodeAddress();
const url = `https://${randSnode.ip}:${randSnode.port}/file_proxy`;
@ -304,8 +339,8 @@ class LokiAppDotNetServerAPI {
// I think this is a stream, we may need to collect it all?
body: fetchOptions.body, // might need to b64 if binary...
endpoint,
method,
headers,
method: fetchOptions.method,
headers: fetchOptions.headers,
};
// from https://github.com/sindresorhus/is-stream/blob/master/index.js
@ -424,7 +459,7 @@ class LokiAppDotNetServerAPI {
} else if (rawBody) {
fetchOptions.body = rawBody;
}
fetchOptions.headers = new Headers(headers);
fetchOptions.headers = headers;
// domain ends in .loki
if (url.match(/\.loki\//)) {
@ -448,15 +483,12 @@ class LokiAppDotNetServerAPI {
this.baseServerUrl === 'https://file.lokinet.org')
) {
mode = '_sendToProxy';
// have to send headers because fetchOptions.headers isn't readable
({ response, txtResponse, result } = await this._sendToProxy(
fetchOptions,
method,
headers,
endpoint
endpoint,
fetchOptions
));
} else {
result = await nodeFetch(url, fetchOptions || undefined);
result = await nodeFetch(url, fetchOptions);
txtResponse = await result.text();
response = JSON.parse(txtResponse);
}
@ -724,7 +756,7 @@ class LokiAppDotNetServerAPI {
options
);
if (statusCode !== 200) {
log.warn('Failed to upload data to fileserver');
log.warn('Failed to upload data to server', this.baseServerUrl);
return null;
}
@ -1676,7 +1708,6 @@ class LokiPublicChannelAPI {
objBody: payload,
});
if (!res.err && res.response) {
window.mixpanel.track('Public Message Sent');
return res.response.data.id;
}
if (res.err) {
@ -1691,7 +1722,6 @@ class LokiPublicChannelAPI {
}
// there's no retry on desktop
// this is supposed to be after retries
window.mixpanel.track('Failed to Send Public Message');
return false;
}
}

@ -56,13 +56,17 @@ class LokiFileServerInstance {
// FIXME: this is not file-server specific
// and is currently called by LokiAppDotNetAPI.
// LokiAppDotNetAPI (base) should not know about LokiFileServer.
async establishConnection(serverUrl) {
async establishConnection(serverUrl, options) {
// why don't we extend this?
this._server = new LokiAppDotNetAPI(this.ourKey, serverUrl);
// configure proxy
this._server.pubKey = this.pubKey;
if (options !== undefined && options.skipToken) {
return;
}
// get a token for multidevice
const gotToken = await this._server.getOrRefreshServerToken();
// TODO: Handle this failure gracefully
@ -307,11 +311,13 @@ class LokiFileServerFactoryAPI {
if (!thisServer) {
thisServer = new LokiFileServerInstance(this.ourKey);
log.info(`Registering FileServer ${serverUrl}`);
await thisServer.establishConnection(serverUrl);
await thisServer.establishConnection(serverUrl, { skipToken: true } );
this.servers.push(thisServer);
}
return thisServer;
}
}
// smuggle some data out of this joint (for expire.js/version upgrade check)
LokiFileServerFactoryAPI.secureRpcPubKey = LOKIFOUNDATION_FILESERVER_PUBKEY;
module.exports = LokiFileServerFactoryAPI;

@ -133,7 +133,6 @@ class LokiMessageAPI {
try {
// eslint-disable-next-line more/no-then
success = await firstTrue(promises);
window.mixpanel.track('Sent Message Using Swarm API');
} catch (e) {
if (e instanceof textsecure.WrongDifficultyError) {
// Force nonce recalculation
@ -147,7 +146,6 @@ class LokiMessageAPI {
throw e;
}
if (!success) {
window.mixpanel.track('Failed to Send Message Using Swarm API');
throw new window.textsecure.EmptySwarmError(
pubKey,
'Ran out of swarm nodes to query'
@ -221,7 +219,6 @@ class LokiMessageAPI {
} catch (e) {
log.warn('Loki send message:', e);
if (e instanceof textsecure.WrongSwarmError) {
window.mixpanel.track('Migrated Snode');
const { newSwarm } = e;
await lokiSnodeAPI.updateSwarmNodes(params.pubKey, newSwarm);
this.sendingData[params.timestamp].swarm = newSwarm;

@ -1,12 +0,0 @@
const Mixpanel = require('mixpanel');
class LokiMixpanelAPI {
constructor() {
this.mixpanel = Mixpanel.init('736cd9a854a157591153efacd1164e9a');
}
track(label) {
this.mixpanel.track(label);
}
}
module.exports = LokiMixpanelAPI;

@ -64,7 +64,6 @@ class LokiSnodeAPI {
}));
} catch (e) {
log.warn('initialiseRandomPool error', JSON.stringify(e));
window.mixpanel.track('Seed Node Failed');
if (seedNodes.length === 0) {
throw new window.textsecure.SeedNodeError(
'Failed to contact seed node'
@ -80,7 +79,6 @@ class LokiSnodeAPI {
const filteredNodes = swarmNodes.filter(
node => node.address !== nodeUrl && node.ip !== nodeUrl
);
window.mixpanel.track('Unreachable Snode');
await conversation.updateSwarmNodes(filteredNodes);
}

@ -130,8 +130,8 @@
// so its loading screen doesn't stick around forever.
// Two primary techniques at play for this situation:
// - background.js has two openInbox() calls, and passes initalLoadComplete
// directly via the options parameter.
// - background.js has X number of openInbox() calls,
// and passes initalLoadComplete directly via the options parameter.
// - in other situations openInbox() will be called with no options. So this
// view keeps track of whether onEmpty() has ever been called with
// this.initialLoadComplete. An example of this: on a phone-pairing setup.

@ -1934,7 +1934,7 @@
maybeGrabLinkPreview() {
// Don't generate link previews if user has turned them off
if (!storage.get('linkPreviews', false)) {
if (!storage.get('link-preview-setting', false)) {
return;
}
// Do nothing if we're offline
@ -2215,7 +2215,7 @@
getLinkPreview() {
// Don't generate link previews if user has turned them off
if (!storage.get('linkPreviews', false)) {
if (!storage.get('link-preview-setting', false)) {
return [];
}

@ -215,9 +215,9 @@
showCannotMixError() {
window.pushToast({
title: i18n('cannotMixImageAdnNonImageAttachments'),
title: i18n('cannotMixImageAndNonImageAttachments'),
type: 'error',
id: 'cannotMixImageAdnNonImageAttachments',
id: 'cannotMixImageAndNonImageAttachments',
});
},
@ -236,9 +236,8 @@
id: 'maximumAttachments',
});
},
// Housekeeping
addAttachment(attachment) {
if (attachment.isVoiceNote && this.attachments.length > 0) {
throw new Error('A voice note cannot be sent with other attachments');

@ -156,18 +156,20 @@
.find('.network-status-container')
.append(this.networkStatusView.render().el);
if (extension.expired()) {
const banner = new Whisper.ExpiredAlertBanner().render();
banner.$el.prependTo(this.$el);
this.$el.addClass('expired');
}
extension.expired((expired) => {
if (expired) {
const banner = new Whisper.ExpiredAlertBanner().render();
banner.$el.prependTo(this.$el);
this.$el.addClass('expired');
}
});
// FIXME: Fix this for new react views
this.updateInboxSectionUnread();
this.setupLeftPane();
},
render_attributes: {
welcomeToSignal: i18n('welcomeToSignal'),
welcomeToSession: i18n('welcomeToSession'),
selectAContact: i18n('selectAContact'),
},
events: {
@ -327,8 +329,6 @@
$target.toggleClass('section-toggle-visible');
},
async openConversation(id, messageId) {
const conversationExists = await ConversationController.get(id);
// If we call this to create a new conversation, it can only be private
// (group conversations are created elsewhere)
const conversation = await ConversationController.getOrCreateAndWait(
@ -341,19 +341,6 @@
}
if (conversation) {
if (conversation.isRss()) {
window.mixpanel.track('RSS Feed Opened');
}
if (conversation.isPublic()) {
window.mixpanel.track('Loki Public Chat Opened');
}
if (conversation.isPrivate()) {
if (conversation.isMe()) {
window.mixpanel.track('Note To Self Opened');
} else if (conversationExists) {
window.mixpanel.track('Conversation Opened');
}
}
conversation.updateProfileName();
}
@ -408,7 +395,7 @@
Whisper.ExpiredAlertBanner = Whisper.View.extend({
templateName: 'expired_alert',
className: 'expiredAlert clearfix',
className: 'expiredAlert',
render_attributes() {
return {
expiredWarning: i18n('expiredWarning'),

@ -25,9 +25,6 @@
(function() {
window.textsecure = window.textsecure || {};
// set up mixpanel
window.mixpanel = window.mixpanel || new window.LokiMixpanelAPI();
const ARCHIVE_AGE = 7 * 24 * 60 * 60 * 1000;
function AccountManager(username, password) {
@ -142,10 +139,8 @@
).toArrayBuffer();
return libsignal.Curve.async.createKeyPair(privKey);
};
window.mixpanel.track('Seed Restored');
} else {
generateKeypair = libsignal.KeyHelper.generateIdentityKeyPair;
window.mixpanel.track('Seed Created');
}
return this.queueTask(() =>
generateKeypair().then(async identityKeyPair =>

@ -654,6 +654,9 @@ MessageReceiver.prototype.extend({
},
async decrypt(envelope, ciphertext) {
let promise;
// We don't have source at this point yet (with sealed sender)
// This needs a massive cleanup!
const address = new libsignal.SignalProtocolAddress(
envelope.source,
envelope.sourceDevice
@ -668,33 +671,19 @@ MessageReceiver.prototype.extend({
options.messageKeysLimit = false;
}
// Will become obsolete
const sessionCipher = new libsignal.SessionCipher(
textsecure.storage.protocol,
address,
options
);
const secretSessionCipher = new window.Signal.Metadata.SecretSessionCipher(
textsecure.storage.protocol
);
const fallBackSessionCipher = new libloki.crypto.FallBackSessionCipher(
address
);
const me = {
number: ourNumber,
deviceId: parseInt(textsecure.storage.user.getDeviceId(), 10),
};
let conversation;
try {
conversation = await window.ConversationController.getOrCreateAndWait(
envelope.source,
'private'
);
} catch (e) {
window.log.info('Error getting conversation: ', envelope.source);
}
// Will become obsolete
const getCurrentSessionBaseKey = async () => {
const record = await sessionCipher.getRecord(address.toString());
if (!record) {
@ -707,71 +696,28 @@ MessageReceiver.prototype.extend({
const { baseKey } = openSession.indexInfo;
return baseKey;
};
// Will become obsolete
const captureActiveSession = async () => {
this.activeSessionBaseKey = await getCurrentSessionBaseKey(sessionCipher);
};
const restoreActiveSession = async () => {
const record = await sessionCipher.getRecord(address.toString());
if (!record) {
return;
}
record.archiveCurrentState();
const sessionToRestore = record.sessions[this.activeSessionBaseKey];
record.promoteState(sessionToRestore);
record.updateSessionState(sessionToRestore);
await textsecure.storage.protocol.storeSession(
address.toString(),
record.serialize()
);
};
const deleteAllSessionExcept = async sessionBaseKey => {
const record = await sessionCipher.getRecord(address.toString());
if (!record) {
return;
}
const sessionToKeep = record.sessions[sessionBaseKey];
record.sessions = {};
record.updateSessionState(sessionToKeep);
await textsecure.storage.protocol.storeSession(
address.toString(),
record.serialize()
);
};
let handleSessionReset;
if (conversation.isSessionResetOngoing()) {
handleSessionReset = async result => {
const currentSessionBaseKey = await getCurrentSessionBaseKey(
sessionCipher
);
if (
this.activeSessionBaseKey &&
currentSessionBaseKey !== this.activeSessionBaseKey
) {
if (conversation.isSessionResetReceived()) {
await restoreActiveSession();
} else {
await deleteAllSessionExcept(currentSessionBaseKey);
await conversation.onNewSessionAdopted();
}
} else if (conversation.isSessionResetReceived()) {
await deleteAllSessionExcept(this.activeSessionBaseKey);
await conversation.onNewSessionAdopted();
}
return result;
};
} else {
handleSessionReset = async result => result;
}
const handleSessionReset = async result => result;
switch (envelope.type) {
case textsecure.protobuf.Envelope.Type.CIPHERTEXT:
window.log.info('message from', this.getEnvelopeId(envelope));
promise = captureActiveSession()
.then(() => sessionCipher.decryptWhisperMessage(ciphertext))
.then(this.unpad)
.then(handleSessionReset);
.then(this.unpad);
break;
case textsecure.protobuf.Envelope.Type.FRIEND_REQUEST: {
window.log.info('friend-request message from ', envelope.source);
const fallBackSessionCipher = new libloki.crypto.FallBackSessionCipher(
address
);
promise = fallBackSessionCipher
.decrypt(ciphertext.toArrayBuffer())
.then(this.unpad);
@ -779,30 +725,33 @@ MessageReceiver.prototype.extend({
}
case textsecure.protobuf.Envelope.Type.PREKEY_BUNDLE:
window.log.info('prekey message from', this.getEnvelopeId(envelope));
promise = captureActiveSession(sessionCipher)
.then(async () => {
if (!this.activeSessionBaseKey) {
try {
const buffer = dcodeIO.ByteBuffer.wrap(ciphertext);
await window.libloki.storage.verifyFriendRequestAcceptPreKey(
envelope.source,
buffer
);
} catch (e) {
await this.removeFromCache(envelope);
throw e;
}
promise = captureActiveSession(sessionCipher).then(async () => {
if (!this.activeSessionBaseKey) {
try {
const buffer = dcodeIO.ByteBuffer.wrap(ciphertext);
await window.libloki.storage.verifyFriendRequestAcceptPreKey(
envelope.source,
buffer
);
} catch (e) {
await this.removeFromCache(envelope);
throw e;
}
return this.decryptPreKeyWhisperMessage(
ciphertext,
sessionCipher,
address
);
})
.then(handleSessionReset);
}
return this.decryptPreKeyWhisperMessage(
ciphertext,
sessionCipher,
address
);
});
break;
case textsecure.protobuf.Envelope.Type.UNIDENTIFIED_SENDER:
window.log.info('received unidentified sender message');
const secretSessionCipher = new window.Signal.Metadata.SecretSessionCipher(
textsecure.storage.protocol
);
promise = secretSessionCipher
.decrypt(ciphertext.toArrayBuffer(), me)
.then(
@ -872,8 +821,7 @@ MessageReceiver.prototype.extend({
throw error;
});
}
)
.then(handleSessionReset);
);
break;
default:
promise = Promise.reject(new Error('Unknown message type'));
@ -887,6 +835,75 @@ MessageReceiver.prototype.extend({
return null;
}
let conversation;
try {
conversation = await window.ConversationController.getOrCreateAndWait(
envelope.source,
'private'
);
} catch (e) {
window.log.info('Error getting conversation: ', envelope.source);
}
/// *** BEGIN: session reset ***
const address = new libsignal.SignalProtocolAddress(
envelope.source,
envelope.sourceDevice
);
const restoreActiveSession = async () => {
const record = await sessionCipher.getRecord(address.toString());
if (!record) {
return;
}
record.archiveCurrentState();
// NOTE: activeSessionBaseKey will be undefined here...
const sessionToRestore = record.sessions[this.activeSessionBaseKey];
record.promoteState(sessionToRestore);
record.updateSessionState(sessionToRestore);
await textsecure.storage.protocol.storeSession(
address.toString(),
record.serialize()
);
};
const deleteAllSessionExcept = async sessionBaseKey => {
const record = await sessionCipher.getRecord(address.toString());
if (!record) {
return;
}
const sessionToKeep = record.sessions[sessionBaseKey];
record.sessions = {};
record.updateSessionState(sessionToKeep);
await textsecure.storage.protocol.storeSession(
address.toString(),
record.serialize()
);
};
if (conversation.isSessionResetOngoing()) {
const currentSessionBaseKey = await getCurrentSessionBaseKey(
sessionCipher
);
if (
this.activeSessionBaseKey &&
currentSessionBaseKey !== this.activeSessionBaseKey
) {
if (conversation.isSessionResetReceived()) {
await restoreActiveSession();
} else {
await deleteAllSessionExcept(currentSessionBaseKey);
await conversation.onNewSessionAdopted();
}
} else if (conversation.isSessionResetReceived()) {
await deleteAllSessionExcept(this.activeSessionBaseKey);
await conversation.onNewSessionAdopted();
}
}
/// *** END ***
// Type here can actually be UNIDENTIFIED_SENDER even if
// the underlying message is FRIEND_REQUEST
if (

@ -266,9 +266,15 @@ OutgoingMessage.prototype = {
return this.convertMessageToText(messageBuffer);
},
async wrapInWebsocketMessage(outgoingObject) {
const source =
outgoingObject.type ===
textsecure.protobuf.Envelope.Type.UNIDENTIFIED_SENDER
? null
: outgoingObject.ourKey;
const messageEnvelope = new textsecure.protobuf.Envelope({
type: outgoingObject.type,
source: outgoingObject.ourKey,
source,
sourceDevice: outgoingObject.sourceDevice,
timestamp: this.timestamp,
content: outgoingObject.content,

@ -14,7 +14,7 @@ const packageJson = require('./package.json');
const GlobalErrors = require('./app/global_errors');
GlobalErrors.addHandler();
const { globalShortcut } = electron;
const electronLocalshortcut = require('electron-localshortcut');
const getRealPath = pify(fs.realpath);
const {
@ -286,10 +286,10 @@ function createWindow() {
// Disable system main menu
mainWindow.setMenu(null);
globalShortcut.register('f5', () => {
electronLocalshortcut.register(mainWindow, 'f5', () => {
mainWindow.reload();
});
globalShortcut.register('CommandOrControl+R', () => {
electronLocalshortcut.register(mainWindow, 'CommandOrControl+R', () => {
mainWindow.reload();
});

@ -78,6 +78,7 @@
"electron-context-menu": "^0.15.0",
"electron-editor-context-menu": "1.1.1",
"electron-is-dev": "0.3.0",
"electron-localshortcut": "^3.2.1",
"emoji-datasource": "4.0.0",
"emoji-datasource-apple": "4.0.0",
"emoji-js": "3.4.0",
@ -98,7 +99,6 @@
"libsodium-wrappers": "^0.7.4",
"linkify-it": "2.0.3",
"lodash": "4.17.11",
"mixpanel": "^0.10.2",
"mkdirp": "0.5.1",
"moment": "2.21.0",
"mustache": "2.3.0",

@ -11,6 +11,7 @@ const { app } = electron.remote;
const { clipboard } = electron;
window.PROTO_ROOT = 'protos';
const appConfig = require('./app/config');
const config = require('url').parse(window.location.toString(), true).query;
let title = config.name;
@ -26,6 +27,7 @@ window.Lodash = require('lodash');
// Regex to match all characters which are *not* supported in display names
window.displayNameRegex = /[^\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC _0-9]*/g;
window.semver = semver;
window.platform = process.platform;
window.getDefaultPoWDifficulty = () => config.defaultPoWDifficulty;
window.getTitle = () => title;
@ -62,6 +64,8 @@ window.CONSTANTS = {
MAX_LOGIN_TRIES: 3,
MAX_PASSWORD_LENGTH: 32,
MAX_USERNAME_LENGTH: 20,
DEFAULT_PUBLIC_CHAT_URL: appConfig.get('defaultPublicChatServer'),
MAX_CONNECTION_DURATION: 5000,
};
window.versionInfo = {
@ -364,14 +368,12 @@ window.LokiMessageAPI = require('./js/modules/loki_message_api');
window.LokiPublicChatAPI = require('./js/modules/loki_public_chat_api');
window.LokiAppDotNetServerAPI = require('./js/modules/loki_app_dot_net_api');
window.LokiFileServerAPI = require('./js/modules/loki_file_server_api');
window.LokiRssAPI = require('./js/modules/loki_rss_api');
const LokiMixpanelAPI = require('./js/modules/loki_mixpanel.js');
window.mixpanel = new LokiMixpanelAPI();
window.localServerPort = config.localServerPort;
window.mnemonic = require('./libloki/modules/mnemonic');

@ -86,7 +86,7 @@ button.emoji {
opacity: 0.5;
border: none;
background: transparent;
margin-left: 15px;
margin: 0px 10px 0px 15px;
&:before {
content: '';

@ -520,8 +520,20 @@
display: flex;
flex-direction: row;
align-items: center;
margin-top: 3px;
margin-top: 5px;
margin-bottom: -3px;
span {
opacity: 0.5;
transition: 0.25s;
&:not(.module-message__metadata__badge--separator):hover {
opacity: 1;
}
}
.module-message__metadata__badge--separator {
margin-top: -2px;
}
}
// With an image and no caption, this section needs to be on top of the image overlay

@ -30,7 +30,10 @@
font-family: 'SF Pro Text';
src: url('../fonts/SFProText-Regular.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Display';
src: url('../fonts/SFProDisplay-Regular.otf') format('opentype');
}
@keyframes fadein {
from {
opacity: 0;
@ -76,7 +79,7 @@ $session-color-dark-grey: #353535;
$session-color-black: #000;
$session-color-danger: #ff453a;
$session-color-primary: $session-shade-13;
$session-color-secondary: $session-shade-16;
$session-color-secondary: $session-shade-6;
$session-background-overlay: #212121;
$session-background: #121212;
@ -102,8 +105,15 @@ $session-font-md: 15px;
$session-font-lg: 18px;
$session-font-xl: 24px;
$session-font-h1: 30px;
$session-font-h2: 24px;
$session-font-h3: 20px;
$session-font-h4: 16px;
$session-search-input-height: 34px;
$main-view-header-height: 85px;
$session-left-pane-width: 300px;
$session-left-pane-sections-container-width: 80px;
div.spacer-sm {
height: $session-margin-sm;
@ -171,7 +181,7 @@ $session-modal-size-lg: 650px;
$session-conversation-header-height: 60px;
@mixin fontWasaBold {
font-weight: 700;
font-weight: bold;
font-family: $session-font-family;
}
@ -475,11 +485,13 @@ $session-element-border-green: 4px solid $session-color-green;
font-family: Wasa;
width: 100%;
display: flex;
font-size: $session-font-md;
&-text {
@include session-color-subtle($session-color-white);
font-family: 'SF Pro Text';
font-weight: 300;
font-size: $session-font-xs;
}
.module-contact-name {
@ -524,8 +536,18 @@ label {
border-bottom-right-radius: $session_message-container-border-radius;
}
.conversation-header .session-icon-button {
@include standard-icon-button();
.conversation-header {
.module-avatar img {
box-shadow: 0px 0px 5px 0px rgba(255, 255, 255, 0.2);
}
.search-icon {
margin-right: 10px;
}
.session-icon-button {
@include standard-icon-button();
}
}
.module-conversation-header {
@ -712,6 +734,11 @@ label {
.message {
text-align: center;
}
.session-id-editable {
width: 30vw;
max-width: 700px;
}
}
&__centered {
@ -807,7 +834,7 @@ label {
align-items: center;
height: 25px;
padding: 0px 10px;
padding: $session-margin-md $session-margin-sm;
background-color: $session-shade-4;
@ -1116,7 +1143,8 @@ label {
background-color: $session-shade-1;
padding: $session-margin-lg;
border-bottom: 2px solid $session-shade-5;
margin-bottom: 20px;
border-bottom: 1px solid $session-shade-5;
&.inline {
display: flex;
@ -1135,8 +1163,10 @@ label {
}
&__description {
font-family: 'SF Pro Text';
font-size: $session-font-sm;
font-weight: 100;
max-width: 700px;
@include session-color-subtle($session-color-white);
}
@ -1258,6 +1288,7 @@ label {
display: flex;
flex-grow: 1;
min-height: 60px;
margin-bottom: -5px;
}
textarea.send-message {
@ -1344,6 +1375,7 @@ label {
.session-id-editable textarea {
resize: none;
overflow: hidden;
user-select: all;
}
input {
@ -1566,3 +1598,81 @@ input {
}
}
}
.onboarding-message-section {
display: flex;
flex-grow: 1;
align-items: flex-start;
position: relative;
padding: $session-margin-lg 2 * $session-margin-lg;
background: linear-gradient(
180deg,
rgba(29, 28, 28, 1) 0%,
rgba(18, 18, 18, 1) 100%
);
&__exit {
position: absolute;
top: $session-margin-lg;
left: $session-margin-lg;
}
&__container {
display: flex;
flex-grow: 1;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding-top: 3 * $session-margin-lg;
height: 400px;
text-align: center;
font-family: 'SF Pro Text';
}
&__title h1 {
color: $session-color-white;
font-size: $session-font-h1;
font-weight: bold;
margin-bottom: 2 * $session-margin-lg;
}
&__icons img {
width: 180px;
margin-bottom: 2 * $session-margin-lg;
}
&__info {
font-family: 'Wasa';
margin-bottom: 2 * $session-margin-lg;
&--title {
font-size: $session-font-h3;
line-height: $session-font-h2;
margin-bottom: $session-margin-md;
}
&--subtitle {
font-family: 'SF Pro Text';
font-weight: 300;
line-height: $session-font-md;
opacity: 0.8;
}
}
&__spinner-container {
display: flex;
flex-grow: 1;
align-items: center;
justify-content: center;
}
&__buttons {
width: 100%;
.session-button:first-child {
margin-bottom: $session-margin-md;
}
}
}

@ -112,14 +112,15 @@ $session-compose-margin: 20px;
&__user__profile {
&-number,
&-name {
@include fontWasaBold();
font-size: 15px;
@at-root .light-theme #{&} {
color: $session-color-white;
}
@at-root .dark-theme #{&} {
color: $session-shade-17;
}
font-size: 15px;
}
}
}
@ -135,7 +136,7 @@ $session-compose-margin: 20px;
.module-left-pane {
border-right: none !important;
width: 300px;
width: $session-left-pane-width;
position: relative;
height: -webkit-fill-available;
@ -152,7 +153,7 @@ $session-compose-margin: 20px;
&__sections-container {
height: -webkit-fill-available;
width: 80px;
width: $session-left-pane-sections-container-width;
display: inline-flex;
flex-direction: column;
@ -189,6 +190,7 @@ $session-compose-margin: 20px;
.session-button {
margin-left: auto;
@include fontWasaBold();
}
&-buttons {
@ -245,37 +247,34 @@ $session-compose-margin: 20px;
h3 {
padding-top: 22px;
position: relative;
.green-border {
position: absolute;
color: $session-color-green;
background-color: $session-color-green;
height: 5px;
left: -10px;
right: -10px;
margin-top: 7px;
border: none;
z-index: 1;
}
}
h4 {
text-transform: uppercase;
}
&-border-container {
width: -webkit-fill-available;
.white-border {
width: $session-left-pane-width;
position: relative;
height: 1px;
opacity: 0.3;
margin-top: -10px;
margin-bottom: 50px;
.green {
position: absolute;
color: $session-color-green;
background-color: $session-color-green;
height: 6px;
width: 130px;
left: 50%;
margin-left: -65px;
top: 50%;
margin-top: 6px;
border: none;
}
.white {
position: absolute;
color: none;
height: 1px;
width: -webkit-fill-available;
opacity: 0.3;
}
}
.exit {
margin-top: 10px;
margin-left: 13px;
@ -292,23 +291,27 @@ $session-compose-margin: 20px;
}
.session-description-long {
font-size: 14px;
font-size: 13px;
margin: 0px 20px;
font-family: 'SF Pro Display';
}
.session-id-editable textarea::-webkit-inner-spin-button {
margin: 0px 20px;
width: -webkit-fill-available;
flex-shrink: 0;
user-select: all;
overflow: hidden;
resize: none;
.session-id-editable {
textarea::-webkit-inner-spin-button {
margin: 0px 20px;
width: -webkit-fill-available;
flex-shrink: 0;
}
}
.session-id-editable-disabled {
border: none;
}
.session-button {
width: fit-content;
margin-top: auto;
margin-bottom: 16px;
margin-top: 1rem;
margin-bottom: 3rem;
flex-shrink: 0;
}
}
@ -362,7 +365,8 @@ $session-compose-margin: 20px;
height: inherit;
border: none;
flex-grow: 1;
font-size: $session-font-md;
font-size: $session-font-sm;
font-family: 'SF Pro Text';
&:focus {
outline: none !important;
@ -413,7 +417,7 @@ $session-compose-margin: 20px;
border: 1px solid $session-shade-15;
}
@at-root .dark-theme #{&} {
border: 1px solid $session-shade-7;
border: 1px solid $session-shade-6;
}
}
}
@ -622,5 +626,7 @@ $session-compose-margin: 20px;
background-color: $session-background;
color: $session-color-light-grey;
border: 1px solid $session-color-dark-grey;
font-family: "SF Pro Text";
font-size: $session-font-sm;
}
}

@ -245,7 +245,7 @@
overflow-wrap: break-word;
padding: 20px 5px 20px 5px;
display: inline-block;
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
font-family: 'SpaceMono';
user-select: all;
}
}

@ -103,7 +103,7 @@
}
@mixin session-h-title {
font-weight: bold;
@include fontWasaBold();
}
h1 {

@ -1,5 +1,4 @@
// Don't forget to handle the background of the popup windows!
body.dark-theme {
background-color: $color-black;
color: $color-gray-05;
@ -189,7 +188,7 @@ body.dark-theme {
button.emoji {
&:before {
margin-top: 4px;
margin-top: 7px;
@include color-svg('../images/smile.svg', $color-dark-30);
}
}
@ -298,12 +297,24 @@ body.dark-theme {
}
}
/* why can't I access $session-color values here? */
.expiredAlert {
background: #f3f3a7;
background: #28f587;
color: black;
/* biggest we can make the font without wrapping the current text at minimum app width */
font-family: 'Wasa';
font-size: 20px;
height: 60px;
span {
line-height: 36px;
}
button {
font-size: 14px;
height: 36px;
color: white;
background: $blue;
background: #474646;
}
}

@ -550,7 +550,6 @@
<script type='text/javascript' src='../js/views/last_seen_indicator_view.js' data-cover></script>
<script type='text/javascript' src='../js/views/scroll_down_button_view.js' data-cover></script>
<script type='text/javascript' src='../js/views/banner_view.js' data-cover></script>
<script type='text/javascript' src='../js/views/clear_data_view.js'></script>
<script type='text/javascript' src='../js/views/conversation_loading_view.js'></script>
<script type='text/javascript' src='../js/views/create_group_dialog_view.js'></script>

@ -207,15 +207,17 @@ export class Avatar extends React.PureComponent<Props, State> {
: this.renderIdenticon();
}
private getBorderStyle(color?: string, width?: number) {
const borderWidth = typeof width === 'number' ? width : 3;
private getBorderStyle(_color?: string, _width?: number) {
//const borderWidth = typeof width === 'number' ? width : 3;
return color
// no border at all for now
return undefined;
/* return color
? {
borderColor: color,
borderStyle: 'solid',
borderWidth: borderWidth,
}
: undefined;
: undefined; */
}
}

@ -38,7 +38,7 @@ export class ContactListItem extends React.Component<Props> {
name={name}
phoneNumber={phoneNumber}
profileName={profileName}
size={48}
size={36}
/>
);
}

@ -254,7 +254,7 @@ We don't want Jumbomoji or links.
conversationType={'direct'}
lastUpdated={Date.now() - 5 * 60 * 1000}
lastMessage={{
text: 'Download at http://signal.org',
text: 'Download at http://getsession.org',
}}
onClick={result => console.log('onClick', result)}
i18n={util.i18n}

@ -85,7 +85,7 @@ export class ConversationListItem extends React.PureComponent<Props> {
if (!(isPendingFriendRequest && !hasSentFriendRequest)) {
borderColor = isOnline ? Colors.ONLINE : Colors.OFFLINE;
}
const iconSize = isPendingFriendRequest && !hasSentFriendRequest ? 28 : 48;
const iconSize = isPendingFriendRequest && !hasSentFriendRequest ? 28 : 36;
return (
<div className="module-conversation-list-item__avatar-container">

@ -113,7 +113,7 @@ export class MessageSearchResult extends React.PureComponent<Props> {
noteToSelf={isNoteToSelf}
phoneNumber={from.phoneNumber}
profileName={from.profileName}
size={48}
size={36}
/>
);
}

@ -191,7 +191,7 @@ export class ConversationHeader extends React.Component<Props> {
if (name) {
title = `${name}`;
} else {
title = phoneNumber;
title = `User ${window.shortenPubkey(phoneNumber)}`;
}
}
@ -232,7 +232,7 @@ export class ConversationHeader extends React.Component<Props> {
profileName={profileName}
size={28}
borderColor={borderColor}
borderWidth={2}
borderWidth={0}
onAvatarClick={() => {
this.onAvatarClickBound(phoneNumber);
}}
@ -286,7 +286,7 @@ export class ConversationHeader extends React.Component<Props> {
>
<SessionIconButton
iconType={SessionIconType.Ellipses}
iconSize={SessionIconSize.Large}
iconSize={SessionIconSize.Medium}
onClick={this.showMenuBound}
/>
</ContextMenuTrigger>

@ -47,7 +47,7 @@ export class EmbeddedContact extends React.Component<Props> {
role="button"
onClick={onClick}
>
{renderAvatar({ contact, i18n, size: 48, direction })}
{renderAvatar({ contact, i18n, size: 36, direction })}
<div className="module-embedded-contact__text-container">
{renderName({ contact, isIncoming, module })}
{renderContactShorthand({ contact, isIncoming, module })}

@ -224,17 +224,22 @@ export class Message extends React.PureComponent<Props, State> {
}
return (
<span
className={classNames(
'module-message__metadata__badge',
`module-message__metadata__badge--${direction}`,
`module-message__metadata__badge--${badgeText.toLowerCase()}`,
`module-message__metadata__badge--${badgeText.toLowerCase()}--${direction}`
)}
key={badgeText}
>
&nbsp;&nbsp;{badgeText}
</span>
<>
<span className="module-message__metadata__badge--separator">
&nbsp;&nbsp;
</span>
<span
className={classNames(
'module-message__metadata__badge',
`module-message__metadata__badge--${direction}`,
`module-message__metadata__badge--${badgeText.toLowerCase()}`,
`module-message__metadata__badge--${badgeText.toLowerCase()}--${direction}`
)}
key={badgeText}
>
{badgeText}
</span>
</>
);
})
.filter(i => !!i);

@ -48,7 +48,7 @@ export class MessageDetail extends React.Component<Props> {
name={name}
phoneNumber={phoneNumber}
profileName={profileName}
size={48}
size={36}
/>
);
}

@ -64,7 +64,6 @@ export class LeftPaneChannelSection extends React.Component<Props, State> {
this.handleToggleOverlay = this.handleToggleOverlay.bind(this);
this.updateSearchBound = this.updateSearch.bind(this);
this.debouncedSearch = debounce(this.search.bind(this), 20);
this.attemptConnection = this.attemptConnection.bind(this);
}
public componentWillUnmount() {
@ -328,104 +327,73 @@ export class LeftPaneChannelSection extends React.Component<Props, State> {
return false;
}
// TODO: Make this not hard coded
const channelId = 1;
this.setState({ loading: true });
const connectionResult = this.attemptConnection(
channelUrlPasted,
channelId
);
joinChannelStateManager(this, channelUrlPasted, this.handleToggleOverlay);
// Give 5s maximum for promise to revole. Else, throw error.
const maxConnectionDuration = 5000;
const connectionTimeout = setTimeout(() => {
if (!this.state.connectSuccess) {
this.setState({ loading: false });
window.pushToast({
title: window.i18n('connectToServerFail'),
type: 'error',
id: 'connectToServerFail',
});
return true;
}
}
return;
}
}, maxConnectionDuration);
connectionResult
.then(() => {
clearTimeout(connectionTimeout);
if (this.state.loading) {
this.setState({
connectSuccess: true,
loading: false,
});
window.pushToast({
title: window.i18n('connectToServerSuccess'),
id: 'connectToServerSuccess',
type: 'success',
});
this.handleToggleOverlay();
}
})
.catch((connectionError: string) => {
clearTimeout(connectionTimeout);
this.setState({
export function joinChannelStateManager(
thisRef: any,
serverURL: string,
onSuccess?: any
) {
// Any component that uses this function MUST have the keys [loading, connectSuccess]
// in their State
// TODO: Make this not hard coded
const channelId = 1;
thisRef.setState({ loading: true });
const connectionResult = window.attemptConnection(serverURL, channelId);
// Give 5s maximum for promise to revole. Else, throw error.
const connectionTimeout = setTimeout(() => {
if (!thisRef.state.connectSuccess) {
thisRef.setState({ loading: false });
window.pushToast({
title: window.i18n('connectToServerFail'),
type: 'error',
id: 'connectToServerFail',
});
return;
}
}, window.CONSTANTS.MAX_CONNECTION_DURATION);
connectionResult
.then(() => {
clearTimeout(connectionTimeout);
if (thisRef.state.loading) {
thisRef.setState({
connectSuccess: true,
loading: false,
});
window.pushToast({
title: connectionError,
id: 'connectToServerFail',
type: 'error',
title: window.i18n('connectToServerSuccess'),
id: 'connectToServerSuccess',
type: 'success',
});
return false;
});
return true;
}
private async attemptConnection(serverURL: string, channelId: number) {
let rawserverURL = serverURL
.replace(/^https?:\/\//i, '')
.replace(/[/\\]+$/i, '');
rawserverURL = rawserverURL.toLowerCase();
const sslServerURL = `https://${rawserverURL}`;
const conversationId = `publicChat:${channelId}@${rawserverURL}`;
const conversationExists = window.ConversationController.get(
conversationId
);
if (conversationExists) {
// We are already a member of this public chat
return new Promise((_resolve, reject) => {
reject(window.i18n('publicChatExists'));
if (onSuccess) {
onSuccess();
}
}
})
.catch((connectionError: string) => {
clearTimeout(connectionTimeout);
thisRef.setState({
connectSuccess: true,
loading: false,
});
}
const serverAPI = await window.lokiPublicChatAPI.findOrCreateServer(
sslServerURL
);
if (!serverAPI) {
// Url incorrect or server not compatible
return new Promise((_resolve, reject) => {
reject(window.i18n('connectToServerFail'));
window.pushToast({
title: connectionError,
id: 'connectToServerFail',
type: 'error',
});
}
const conversation = await window.ConversationController.getOrCreateAndWait(
conversationId,
'group'
);
await conversation.setPublicSource(sslServerURL, channelId);
await conversation.setFriendRequestStatus(
window.friends.friendRequestStatusEnum.friends
);
conversation.getPublicSendData(); // may want "await" if you want to use the API
return false;
});
return conversation;
}
return true;
}

@ -18,6 +18,14 @@ import { SearchOptions } from '../../types/Search';
import { validateNumber } from '../../types/PhoneNumber';
import { LeftPane, RowRendererParamsType } from '../LeftPane';
import { SessionClosableOverlay } from './SessionClosableOverlay';
import { SessionIconButton, SessionIconSize, SessionIconType } from './icon';
import {
SessionButton,
SessionButtonColor,
SessionButtonType,
} from './SessionButton';
import { SessionSpinner } from './SessionSpinner';
import { joinChannelStateManager } from './LeftPaneChannelSection';
export interface Props {
searchTerm: string;
@ -39,13 +47,37 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
public constructor(props: Props) {
super(props);
const conversations = this.getCurrentConversations();
const renderOnboardingSetting = window.getSettingValue(
'render-message-onboarding'
);
const realConversations: Array<ConversationListItemPropsType> = [];
if (conversations) {
conversations.forEach(conversation => {
const isRSS =
conversation.id &&
!!(conversation.id && conversation.id.match(/^rss:/));
return !isRSS && realConversations.push(conversation);
});
}
const length = realConversations.length;
this.state = {
showComposeView: false,
pubKeyPasted: '',
shouldRenderMessageOnboarding: length === 0 && renderOnboardingSetting,
connectSuccess: false,
loading: false,
};
this.updateSearchBound = this.updateSearch.bind(this);
this.handleToggleOverlay = this.handleToggleOverlay.bind(this);
this.handleCloseOnboarding = this.handleCloseOnboarding.bind(this);
this.handleJoinPublicChat = this.handleJoinPublicChat.bind(this);
this.handleOnPasteSessionID = this.handleOnPasteSessionID.bind(this);
this.handleMessageButtonClick = this.handleMessageButtonClick.bind(this);
this.debouncedSearch = debounce(this.search.bind(this), 20);
@ -178,16 +210,90 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
public renderConversations() {
return (
<div className="module-conversations-list-content">
<SessionSearchInput
searchString={this.props.searchTerm}
onChange={this.updateSearchBound}
placeholder={window.i18n('searchForAKeyPhrase')}
/>
{this.renderList()}
{this.state.shouldRenderMessageOnboarding ? (
<>{this.renderMessageOnboarding()}</>
) : (
<>
<SessionSearchInput
searchString={this.props.searchTerm}
onChange={this.updateSearchBound}
placeholder={window.i18n('searchForAKeyPhrase')}
/>
{this.renderList()}
</>
)}
</div>
);
}
public renderMessageOnboarding() {
return (
<div className="onboarding-message-section">
<div className="onboarding-message-section__exit">
<SessionIconButton
iconType={SessionIconType.Exit}
iconSize={SessionIconSize.Medium}
onClick={this.handleCloseOnboarding}
/>
</div>
<div className="onboarding-message-section__container">
<div className="onboarding-message-section__title">
<h1>{window.i18n('welcomeToSession')}</h1>
</div>
<div className="onboarding-message-section__icons">
<img
src="./images/session/chat-bubbles.svg"
alt=""
role="presentation"
/>
</div>
<div className="onboarding-message-section__info">
<div className="onboarding-message-section__info--title">
{window.i18n('noMessagesTitle')}
</div>
<div className="onboarding-message-section__info--subtitle">
{window.i18n('noMessagesSubtitle')}
</div>
</div>
<>
{this.state.loading ? (
<div className="onboarding-message-section__spinner-container">
<SessionSpinner />
</div>
) : (
<div className="onboarding-message-section__buttons">
<SessionButton
text={window.i18n('joinPublicChat')}
buttonType={SessionButtonType.BrandOutline}
buttonColor={SessionButtonColor.Green}
onClick={this.handleJoinPublicChat}
/>
<SessionButton
text={window.i18n('noThankyou')}
buttonType={SessionButtonType.Brand}
buttonColor={SessionButtonColor.Secondary}
onClick={this.handleCloseOnboarding}
/>
</div>
)}
</>
</div>
</div>
);
}
public handleCloseOnboarding() {
window.setSettingValue('render-message-onboarding', false);
this.setState({
shouldRenderMessageOnboarding: false,
});
}
public updateSearch(searchTerm: string) {
const { updateSearchTerm, clearSearch } = this.props;
@ -291,4 +397,9 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
});
}
}
private handleJoinPublicChat() {
const serverURL = window.CONSTANTS.DEFAULT_PUBLIC_CHAT_URL;
joinChannelStateManager(this, serverURL, this.handleCloseOnboarding);
}
}

@ -93,11 +93,11 @@ export class SessionClosableOverlay extends React.Component<Props> {
/>
</div>
<h2>{title}</h2>
<h3>{subtitle}</h3>
<div className="module-left-pane-overlay-border-container">
<hr className="white" />
<hr className="green" />
</div>
<h3>
{subtitle}
<hr className="green-border" />
</h3>
<hr className="white-border" />
<SessionIdEditable
ref={this.inputRef}
editable={true}

@ -1,4 +1,5 @@
import React from 'react';
import classNames from 'classnames';
interface Props {
placeholder?: string;
@ -28,7 +29,12 @@ export class SessionIdEditable extends React.PureComponent<Props> {
const { placeholder, editable, text } = this.props;
return (
<div className="session-id-editable">
<div
className={classNames(
'session-id-editable',
!editable && 'session-id-editable-disabled'
)}
>
<textarea
className="session-id-editable-textarea"
ref={this.inputRef}

@ -4,6 +4,16 @@ import classNames from 'classnames';
interface Props {
active: boolean;
onClick: any;
// If you require the toggle to be confirmed, use
// a confirmation dialog. The parameters needed in the
// setting item in SessionSettings.tsx are like such:
// confirmationDialogParams: {
// shouldShowConfirm: false,
// title: window.i18n('linkPreviewsConfirmTitle'),
// message: window.i18n('linkPreviewsConfirmMessage'),
// okTheme: 'danger',
// }
confirmationDialogParams?: any | undefined;
}
interface State {
@ -41,14 +51,33 @@ export class SessionToggle extends React.PureComponent<Props, State> {
);
}
private clickHandler(e: any) {
this.setState({
active: !this.state.active,
});
private clickHandler(event: any) {
const stateManager = (e: any) => {
this.setState({
active: !this.state.active,
});
if (this.props.onClick) {
e.stopPropagation();
this.props.onClick();
if (this.props.onClick) {
e.stopPropagation();
this.props.onClick();
}
};
if (
this.props.confirmationDialogParams &&
this.props.confirmationDialogParams.shouldShowConfirm()
) {
// If item needs a confirmation dialog to turn ON, render it
window.confirmationDialog({
resolve: () => {
stateManager(event);
},
...this.props.confirmationDialogParams,
});
return;
}
stateManager(event);
}
}

@ -17,6 +17,7 @@ interface Props {
onClick?: any;
onSliderChange?: any;
content: any;
confirmationDialogParams?: any;
}
interface State {
@ -65,6 +66,7 @@ export class SessionSettingListItem extends React.Component<Props, State> {
<SessionToggle
active={Boolean(value)}
onClick={this.handleClick}
confirmationDialogParams={this.props.confirmationDialogParams}
/>
</div>
)}

@ -47,6 +47,7 @@ interface LocalSettingType {
type: SessionSettingType | undefined;
setFn: any;
onClick: any;
confirmationDialogParams: any | undefined;
}
export class SettingsView extends React.Component<SettingsViewProps, State> {
@ -142,6 +143,9 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
onClick={onClickFn}
onSliderChange={sliderFn}
content={content}
confirmationDialogParams={
setting.confirmationDialogParams
}
/>
)}
</div>
@ -268,11 +272,13 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
public updateSetting(item: any) {
// If there's a custom afterClick function,
// execute it instead of automatically updating settings
if (item.setFn) {
item.setFn();
} else {
if (item.type === SessionSettingType.Toggle) {
// If no custom afterClick function given, alter values in storage here
// Switch to opposite state
const newValue = !window.getSettingValue(item.id);
window.setSettingValue(item.id, newValue);
@ -324,6 +330,7 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
setFn: window.toggleTheme,
content: undefined,
onClick: undefined,
confirmationDialogParams: undefined,
},
{
id: 'hide-menu-bar',
@ -336,6 +343,7 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
content: { defaultValue: true },
comparisonValue: undefined,
onClick: undefined,
confirmationDialogParams: undefined,
},
{
id: 'spell-check',
@ -348,6 +356,7 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
content: undefined,
comparisonValue: undefined,
onClick: undefined,
confirmationDialogParams: undefined,
},
{
id: 'link-preview-setting',
@ -360,6 +369,13 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
content: undefined,
comparisonValue: undefined,
onClick: undefined,
confirmationDialogParams: {
shouldShowConfirm: () =>
!window.getSettingValue('link-preview-setting'),
title: window.i18n('linkPreviewsConfirmTitle'),
message: window.i18n('linkPreviewsConfirmMessage'),
okTheme: 'danger',
},
},
{
id: 'notification-setting',
@ -398,6 +414,7 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
],
},
},
confirmationDialogParams: undefined,
},
{
id: 'media-permissions',
@ -410,6 +427,7 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
content: undefined,
comparisonValue: undefined,
onClick: undefined,
confirmationDialogParams: undefined,
},
{
id: 'message-ttl',
@ -424,6 +442,7 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
content: {
defaultValue: 24,
},
confirmationDialogParams: undefined,
},
{
id: 'read-receipt-setting',
@ -436,6 +455,7 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
comparisonValue: undefined,
onClick: undefined,
content: {},
confirmationDialogParams: undefined,
},
{
id: 'typing-indicators-setting',
@ -448,6 +468,7 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
comparisonValue: undefined,
onClick: undefined,
content: {},
confirmationDialogParams: undefined,
},
{
id: 'set-password',
@ -467,6 +488,7 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
action: 'set',
onSuccess: this.onPasswordUpdated,
}),
confirmationDialogParams: undefined,
},
{
id: 'change-password',
@ -486,6 +508,7 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
action: 'change',
onSuccess: this.onPasswordUpdated,
}),
confirmationDialogParams: undefined,
},
{
id: 'remove-password',
@ -505,6 +528,7 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
action: 'remove',
onSuccess: this.onPasswordUpdated,
}),
confirmationDialogParams: undefined,
},
];
}
@ -536,6 +560,7 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
},
hidden: undefined,
onClick: undefined,
confirmationDialogParams: undefined,
};
} else {
return {
@ -549,6 +574,7 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
setFn: undefined,
hidden: undefined,
onClick: undefined,
confirmationDialogParams: undefined,
};
}
});
@ -565,6 +591,7 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
onClick: undefined,
setFn: undefined,
hidden: undefined,
confirmationDialogParams: undefined,
},
];
}

1
ts/global.d.ts vendored

@ -8,6 +8,7 @@ interface Window {
getAccountManager: any;
mnemonic: any;
clipboard: any;
attemptConnection: any;
passwordUtil: any;
userConfig: any;

@ -261,7 +261,7 @@ function getGotOptions(): GotOptions<null> {
ca,
headers: {
'Cache-Control': 'no-cache',
'User-Agent': 'Signal Desktop (+https://signal.org/download)',
'User-Agent': 'Session Desktop (+https://getsession.org)',
},
useElectronNet: false,
};

@ -1976,7 +1976,7 @@ combined-stream@1.0.6:
dependencies:
delayed-stream "~1.0.0"
combined-stream@^1.0.6, combined-stream@~1.0.5, combined-stream@~1.0.6:
combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.5, combined-stream@~1.0.6:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
@ -2508,7 +2508,7 @@ debug@^3.0.0, debug@^3.1.0, debug@^3.1.1, debug@^3.2.6:
dependencies:
ms "^2.1.1"
debug@^4.1.0, debug@^4.1.1:
debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
@ -2971,6 +2971,11 @@ electron-icon-maker@0.0.3:
icon-gen "^1.0.7"
jimp "^0.2.27"
electron-is-accelerator@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/electron-is-accelerator/-/electron-is-accelerator-0.1.2.tgz#509e510c26a56b55e17f863a4b04e111846ab27b"
integrity sha1-UJ5RDCala1Xhf4Y6SwThEYRqsns=
electron-is-dev@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-0.3.0.tgz#14e6fda5c68e9e4ecbeff9ccf037cbd7c05c5afe"
@ -2981,6 +2986,16 @@ electron-is-dev@^1.0.1:
resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-1.1.0.tgz#b15a2a600bdc48a51a857d460e05f15b19a2522c"
integrity sha512-Z1qA/1oHNowGtSBIcWk0pcLEqYT/j+13xUw/MYOrBUOL4X7VN0i0KCTf5SqyvMPmW5pSPKbo28wkxMxzZ20YnQ==
electron-localshortcut@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/electron-localshortcut/-/electron-localshortcut-3.2.1.tgz#cfc83a3eff5e28faf98ddcc87f80a2ce4f623cd3"
integrity sha512-DWvhKv36GsdXKnaFFhEiK8kZZA+24/yFLgtTwJJHc7AFgDjNRIBJZ/jq62Y/dWv9E4ypYwrVWN2bVrCYw1uv7Q==
dependencies:
debug "^4.0.1"
electron-is-accelerator "^0.1.0"
keyboardevent-from-electron-accelerator "^2.0.0"
keyboardevents-areequal "^0.2.1"
electron-notarize@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/electron-notarize/-/electron-notarize-0.2.1.tgz#759e8006decae19134f82996ed910db26d9192cc"
@ -4763,14 +4778,6 @@ https-browserify@^1.0.0:
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
https-proxy-agent@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-3.0.0.tgz#0106efa5d63d6d6f3ab87c999fa4877a3fd1ff97"
integrity sha512-y4jAxNEihqvBI5F3SaO2rtsjIOnnNA8sEbuiP+UhJZJHeM2NRm6c09ax2tgqme+SgUUvjao2fJXF4h3D6Cb2HQ==
dependencies:
agent-base "^4.3.0"
debug "^3.1.0"
https-proxy-agent@^2.2.1:
version "2.2.4"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
@ -5729,6 +5736,16 @@ kew@^0.7.0:
resolved "https://registry.yarnpkg.com/kew/-/kew-0.7.0.tgz#79d93d2d33363d6fdd2970b335d9141ad591d79b"
integrity sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=
keyboardevent-from-electron-accelerator@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/keyboardevent-from-electron-accelerator/-/keyboardevent-from-electron-accelerator-2.0.0.tgz#ace21b1aa4e47148815d160057f9edb66567c50c"
integrity sha512-iQcmNA0M4ETMNi0kG/q0h/43wZk7rMeKYrXP7sqKIJbHkTU8Koowgzv+ieR/vWJbOwxx5nDC3UnudZ0aLSu4VA==
keyboardevents-areequal@^0.2.1:
version "0.2.2"
resolved "https://registry.yarnpkg.com/keyboardevents-areequal/-/keyboardevents-areequal-0.2.2.tgz#88191ec738ce9f7591c25e9056de928b40277194"
integrity sha512-Nv+Kr33T0mEjxR500q+I6IWisOQ0lK1GGOncV0kWE6n4KFmpcu7RUX5/2B0EUtX51Cb0HjZ9VJsSY3u4cBa0kw==
keyv@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.0.0.tgz#44923ba39e68b12a7cec7df6c3268c031f2ef373"
@ -6399,13 +6416,6 @@ mixin-deep@^1.2.0:
for-in "^1.0.2"
is-extendable "^1.0.1"
mixpanel@^0.10.2:
version "0.10.3"
resolved "https://registry.yarnpkg.com/mixpanel/-/mixpanel-0.10.3.tgz#2dff3bc0e17b57d6365547d315cbbf3ecfdb8a00"
integrity sha512-wIYr5o+1XSzJ80o3QED35K/yfPAKi5FigZXTSfcs4vltfeKbilIjNgwxdno7LrqzhjoSjmIyDWkI7D3lr7TwDw==
dependencies:
https-proxy-agent "3.0.0"
mkdirp@0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.0.tgz#1d73076a6df986cd9344e15e71fcc05a4c9abf12"

Loading…
Cancel
Save