mirror of
https://github.com/oxen-io/session-desktop.git
synced 2023-12-14 02:12:57 +01:00
Merge branch 'brand-redesign' into brand-redesign
This commit is contained in:
commit
9cd27abf31
23 changed files with 1612 additions and 154 deletions
|
@ -2289,5 +2289,77 @@
|
|||
"example": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"createAccount": {
|
||||
"message": "Create Account"
|
||||
},
|
||||
"signIn": {
|
||||
"message": "Sign In"
|
||||
},
|
||||
"yourUniqueSessionID": {
|
||||
"message": "Your Unique Session ID"
|
||||
},
|
||||
"allUsersAreRandomly...": {
|
||||
"message":
|
||||
"All users are randomly generated a set of numbers that act as their unique Session ID. Share your Session ID in order to chat with your friends!"
|
||||
},
|
||||
"getStarted": {
|
||||
"message": "Get started"
|
||||
},
|
||||
"generateSessionID": {
|
||||
"message": "Generate Session ID"
|
||||
},
|
||||
"mnemonicSeed": {
|
||||
"message": "Mnemonic Seed"
|
||||
},
|
||||
"enterSeed": {
|
||||
"message": "Enter Seed"
|
||||
},
|
||||
"displayName": {
|
||||
"message": "Display Name"
|
||||
},
|
||||
"enterDisplayName": {
|
||||
"message": "Enter Display Name / Alias"
|
||||
},
|
||||
"optionalPassword": {
|
||||
"message": "Optional Password"
|
||||
},
|
||||
"enterOptionalPassword": {
|
||||
"message": "Enter Optional Password"
|
||||
},
|
||||
"verifyPassword": {
|
||||
"message": "Verify Password"
|
||||
},
|
||||
"devicePairingHeader": {
|
||||
"message":
|
||||
"Open the Loki Messenger App on your primary device and select Device Pairing from the main menu. Then, enter your Session ID below to sign in."
|
||||
},
|
||||
"enterSessionIDHere": {
|
||||
"message": "Enter your Session ID here"
|
||||
},
|
||||
"continueYourSession": {
|
||||
"message": "Continue Your Session"
|
||||
},
|
||||
"restoreUsingSeed": {
|
||||
"message": "Restore Using Seed"
|
||||
},
|
||||
"linkDeviceToExistingAccount": {
|
||||
"message": "Link Device To Existing Account"
|
||||
},
|
||||
"or": {
|
||||
"message": "or"
|
||||
},
|
||||
"ByUsingThiService...": {
|
||||
"message":
|
||||
"By using this service, you agree to our <a>Terms and Conditions</a> and <a>Privacy Statement</a>"
|
||||
},
|
||||
"beginYourSession": {
|
||||
"message": "Begin<br />your<br />Session."
|
||||
},
|
||||
"welcomeToYourSession": {
|
||||
"message": "Welcome to your Session!"
|
||||
},
|
||||
"completeSignUp": {
|
||||
"message": "Complete Sign Up"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -667,93 +667,6 @@
|
|||
{{/isError}}
|
||||
</script>
|
||||
|
||||
<script type='text/x-tmpl-mustache' id='standalone'>
|
||||
<div class='step standalone'>
|
||||
<div class='inner'>
|
||||
<div class='step-body'>
|
||||
<div class='header'>Create your Loki Messenger Account</div>
|
||||
|
||||
<!-- Registration -->
|
||||
<div class='page'>
|
||||
<!-- New account -->
|
||||
<h4 class='section-toggle section-toggle-visible'>Register a new account</h4>
|
||||
<div class='standalone-register section-content'>
|
||||
<div class='standalone-register-warning'>
|
||||
Please save the seed below in a safe location.
|
||||
</br>
|
||||
They can be used to restore your account if you lose access or migrate to a new device.
|
||||
</div>
|
||||
<div class='standalone-register-language'>
|
||||
<span>Language:</span>
|
||||
<div class='select-container'>
|
||||
<select id='mnemonic-display-language'></select>
|
||||
</div>
|
||||
</div>
|
||||
<div id='mnemonic-display' />
|
||||
<div class='standalone-register-buttons'>
|
||||
<a class='button grey' id='generate-mnemonic'>Generate Seed</a>
|
||||
<a class='button grey' id='copy-mnemonic'>Copy Seed</a>
|
||||
<a class='button' id='register' data-loading-text='Please wait...'>Register</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Restore using seed -->
|
||||
<h4 class='section-toggle'>Restore using seed</h4>
|
||||
<div class='standalone-mnemonic section-content'>
|
||||
<div class='standalone-mnemonic-inputs'>
|
||||
<input class='form-control' type='text' id='mnemonic' placeholder='Mnemonic Seed' autocomplete='off' spellcheck='false' />
|
||||
</div>
|
||||
<div id='error' class='collapse'></div>
|
||||
<div class='restore standalone-register-language'>
|
||||
<span>Language:</span>
|
||||
<div class='select-container'>
|
||||
<select id='mnemonic-language'></select>
|
||||
</div>
|
||||
</div>
|
||||
<a class='button' id='register-mnemonic'>Restore</a>
|
||||
<div id=status></div>
|
||||
</div>
|
||||
<!-- Link device to an existing account -->
|
||||
<h4 class='section-toggle'>Link device to an existing account</h4>
|
||||
<div class='standalone-secondary-device section-content'>
|
||||
<p class='standalone-register-warning'>
|
||||
You will need your Primary Device handy during this step.
|
||||
</br>
|
||||
Open the Loki Messenger App on your Primary Device
|
||||
</br>
|
||||
and select "Device Pairing" in the main menu.
|
||||
</p>
|
||||
<div class='standalone-secondary-device-inputs'>
|
||||
<input class='form-control' type='text' id='primary-pubkey' placeholder='Primary Account Public Key' autocomplete='off' spellcheck='false' />
|
||||
</div>
|
||||
<div id='pubkey' class='collapse'></div>
|
||||
<div id='error' class='collapse'></div>
|
||||
<button type='button' class='button' id='register-secondary-device'>Link</button>
|
||||
<button type='button' class='button' id='cancel-secondary-device' style="display: none;">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Profile -->
|
||||
<div class='page'>
|
||||
<div class='display-name-input'>
|
||||
<div class='input-header'>Enter your public display name</div>
|
||||
<input class='form-control' type='text' id='display-name' placeholder='Display Name' autocomplete='off' spellcheck='false' maxlength='25'>
|
||||
</div>
|
||||
<div class='password-inputs'>
|
||||
<div class='input-header'>Type an optional password for added security</div>
|
||||
<input class='form-control' type='password' id='password' placeholder='Password (optional)' autocomplete='off' spellcheck='false'>
|
||||
<input class='form-control' type='password' id='password-confirmation' placeholder='Retype your password' autocomplete='off' spellcheck='false'>
|
||||
<div class='error'></div>
|
||||
</div>
|
||||
<div class='buttons'>
|
||||
<button class='button grey' id='back-button' tabindex='2'>Back</button>
|
||||
<button class='button' id='save-button' tabindex='1'>Save</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type='text/javascript' src='js/components.js'></script>
|
||||
<script type='text/javascript' src='js/reliable_trigger.js'></script>
|
||||
|
@ -814,7 +727,7 @@
|
|||
<script type='text/javascript' src='js/views/install_view.js'></script>
|
||||
<script type='text/javascript' src='js/views/banner_view.js'></script>
|
||||
<script type="text/javascript" src="js/views/phone-input-view.js"></script>
|
||||
<script type='text/javascript' src='js/views/standalone_registration_view.js'></script>
|
||||
<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>
|
||||
|
@ -825,7 +738,7 @@
|
|||
<script type='text/javascript' src='js/views/edit_profile_dialog_view.js'></script>
|
||||
<script type='text/javascript' src='js/views/invite_friends_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>
|
||||
|
|
31
images/session/brand.svg
Normal file
31
images/session/brand.svg
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 404.08533 448.40668"
|
||||
height="448.40668"
|
||||
width="404.08533"
|
||||
xml:space="preserve"
|
||||
id="svg2"
|
||||
version="1.1"><metadata
|
||||
id="metadata8"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs6"><clipPath
|
||||
id="clipPath18"
|
||||
clipPathUnits="userSpaceOnUse"><path
|
||||
id="path16"
|
||||
d="M 0,336.305 H 303.064 V 0 H 0 Z" /></clipPath></defs><g
|
||||
transform="matrix(1.3333333,0,0,-1.3333333,0,448.40667)"
|
||||
id="g10"><g
|
||||
id="g12"><g
|
||||
clip-path="url(#clipPath18)"
|
||||
id="g14"><g
|
||||
transform="translate(216.4556,21.0229)"
|
||||
id="g20"><path
|
||||
id="path22"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 0,0 h -147.251 c -25.182,0 -46.881,19.311 -48.123,44.461 -1.328,26.885 20.162,49.182 46.765,49.182 h 84.804 c 5.189,0 9.395,4.206 9.395,9.394 v 69.229 L 29.025,126.032 C 51.087,113.807 64.975,90.781 65.565,65.646 66.415,29.551 36.104,0 0,0 m -158.872,168.227 c -22.062,12.225 -35.95,35.251 -36.541,60.386 -0.849,36.095 29.462,65.646 65.565,65.646 H 17.403 c 25.182,0 46.881,-19.311 48.124,-44.462 1.328,-26.884 -20.162,-49.181 -46.764,-49.181 0,0 -60.987,-0.01 -84.808,-0.014 -5.186,-0.001 -9.374,-4.206 -9.376,-9.392 l -0.016,-69.217 z M 39.213,144.42 -24.265,179.593 h 43.028 c 37.408,0 67.845,30.434 67.845,67.843 0,37.408 -30.437,67.846 -67.845,67.846 h -150.224 c -46.864,0 -84.995,-38.131 -84.995,-84.995 0,-33.425 18.162,-64.248 47.396,-80.449 l 63.477,-35.172 h -43.026 c -37.41,0 -67.847,-30.435 -67.847,-67.843 0,-37.408 30.437,-67.846 67.847,-67.846 H 1.613 c 46.866,0 84.995,38.13 84.995,84.995 0,33.425 -18.162,64.248 -47.395,80.448" /></g></g></g></g></svg>
|
After Width: | Height: | Size: 2.1 KiB |
|
@ -321,7 +321,7 @@
|
|||
window.Events = {
|
||||
getDeviceName: () => textsecure.storage.user.getDeviceName(),
|
||||
|
||||
getThemeSetting: () => storage.get('theme-setting', 'light'),
|
||||
getThemeSetting: () => storage.get('theme-setting', 'dark'),
|
||||
setThemeSetting: value => {
|
||||
storage.put('theme-setting', value);
|
||||
onChangeTheme();
|
||||
|
@ -953,6 +953,12 @@
|
|||
}
|
||||
});
|
||||
|
||||
Whisper.events.on('openInbox', () => {
|
||||
appView.openInbox({
|
||||
initialLoadComplete,
|
||||
});
|
||||
});
|
||||
|
||||
Whisper.events.on('onEditProfile', async () => {
|
||||
const ourNumber = window.storage.get('primaryDevicePubKey');
|
||||
const conversation = await ConversationController.getOrCreateAndWait(
|
||||
|
|
|
@ -51,6 +51,9 @@ const {
|
|||
const { EditProfileDialog } = require('../../ts/components/EditProfileDialog');
|
||||
const { UserDetailsDialog } = require('../../ts/components/UserDetailsDialog');
|
||||
const { SessionToast } = require('../../ts/components/session/SessionToast');
|
||||
const {
|
||||
SessionRegistrationView,
|
||||
} = require('../../ts/components/session/SessionRegistrationView');
|
||||
|
||||
const {
|
||||
UpdateGroupDialog,
|
||||
|
@ -240,6 +243,7 @@ exports.setup = (options = {}) => {
|
|||
CreateGroupDialog,
|
||||
EditProfileDialog,
|
||||
UserDetailsDialog,
|
||||
SessionRegistrationView,
|
||||
ConfirmDialog,
|
||||
UpdateGroupDialog,
|
||||
InviteFriendsDialog,
|
||||
|
|
|
@ -105,7 +105,7 @@
|
|||
openStandalone() {
|
||||
window.addSetupMenuItems();
|
||||
this.resetViews();
|
||||
this.standaloneView = new Whisper.StandaloneRegistrationView();
|
||||
this.standaloneView = new Whisper.SessionRegistrationView();
|
||||
this.openView(this.standaloneView);
|
||||
},
|
||||
closeStandalone() {
|
||||
|
|
133
js/views/session_registration_view.js
Normal file
133
js/views/session_registration_view.js
Normal file
|
@ -0,0 +1,133 @@
|
|||
/* eslint-disable no-plusplus */
|
||||
/* global
|
||||
Whisper,
|
||||
*/
|
||||
|
||||
/* eslint-disable more/no-then */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
window.Whisper = window.Whisper || {};
|
||||
|
||||
Whisper.SessionRegistrationView = Whisper.View.extend({
|
||||
className: 'session-fullscreen',
|
||||
initialize() {
|
||||
this.render();
|
||||
},
|
||||
render() {
|
||||
this.session_registration_view = new Whisper.ReactWrapperView({
|
||||
className: 'session-full-screen-flow session-fullscreen',
|
||||
Component: window.Signal.Components.SessionRegistrationView,
|
||||
props: {},
|
||||
});
|
||||
|
||||
this.$el.append(this.session_registration_view.el);
|
||||
return this;
|
||||
},
|
||||
|
||||
log(s) {
|
||||
window.log.info(s);
|
||||
this.$('#status').text(s);
|
||||
},
|
||||
displayError(error) {
|
||||
this.$('#error')
|
||||
.hide()
|
||||
.text(error)
|
||||
.addClass('in')
|
||||
.fadeIn();
|
||||
},
|
||||
|
||||
showToast(message) {
|
||||
const toast = new Whisper.MessageToastView({
|
||||
message,
|
||||
});
|
||||
toast.$el.appendTo(this.$el);
|
||||
toast.render();
|
||||
},
|
||||
});
|
||||
|
||||
class TextScramble {
|
||||
constructor(el) {
|
||||
this.el = el;
|
||||
this.chars = '0123456789qwertyuiopasdfghjklzxcvbnm';
|
||||
this.update = this.update.bind(this);
|
||||
}
|
||||
|
||||
setText(newText) {
|
||||
const oldText = this.el.innerText;
|
||||
const length = Math.max(oldText.length, newText.length);
|
||||
// eslint-disable-next-line no-return-assign
|
||||
const promise = new Promise(resolve => (this.resolve = resolve));
|
||||
this.queue = [];
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
const from = oldText[i] || '';
|
||||
const to = newText[i] || '';
|
||||
const start = Math.floor(Math.random() * 40);
|
||||
const end = start + Math.floor(Math.random() * 40);
|
||||
this.queue.push({
|
||||
from,
|
||||
to,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
}
|
||||
|
||||
cancelAnimationFrame(this.frameRequest);
|
||||
this.frame = 0;
|
||||
this.update();
|
||||
return promise;
|
||||
}
|
||||
|
||||
update() {
|
||||
let output = '';
|
||||
let complete = 0;
|
||||
|
||||
for (let i = 0, n = this.queue.length; i < n; i++) {
|
||||
const { from, to, start, end } = this.queue[i];
|
||||
let { char } = this.queue[i];
|
||||
|
||||
if (this.frame >= end) {
|
||||
complete++;
|
||||
output += to;
|
||||
} else if (this.frame >= start) {
|
||||
if (!char || Math.random() < 0.28) {
|
||||
char = this.randomChar();
|
||||
this.queue[i].char = char;
|
||||
}
|
||||
|
||||
output += `<span class="dud">${char}</span>`;
|
||||
} else {
|
||||
output += from;
|
||||
}
|
||||
}
|
||||
|
||||
this.el.innerHTML = output;
|
||||
|
||||
if (complete === this.queue.length) {
|
||||
this.resolve();
|
||||
} else {
|
||||
this.frameRequest = requestAnimationFrame(this.update);
|
||||
this.frame++;
|
||||
}
|
||||
}
|
||||
|
||||
randomChar() {
|
||||
return this.chars[Math.floor(Math.random() * this.chars.length)];
|
||||
}
|
||||
}
|
||||
window.Session = window.Session || {};
|
||||
|
||||
window.Session.setNewSessionID = sessionID => {
|
||||
const el = document.querySelector('.session-signin-enter-session-id');
|
||||
const fx = new TextScramble(el);
|
||||
el.innerHTML = sessionID;
|
||||
fx.setText(sessionID);
|
||||
};
|
||||
|
||||
window.Session.emptyContentEditableDivs = () => {
|
||||
window.$('div[contenteditable]').html('');
|
||||
};
|
||||
})();
|
|
@ -40,6 +40,7 @@
|
|||
"test-node-coverage-html": "nyc --reporter=lcov --reporter=html mocha --recursive test/app test/modules ts/test libloki/test/node",
|
||||
"eslint": "eslint .",
|
||||
"lint": "yarn format --list-different && yarn lint-windows",
|
||||
"dev-lint": "yarn format --list-different; yarn lint-windows",
|
||||
"lint-windows": "yarn eslint && yarn tslint",
|
||||
"lint-deps": "node ts/util/lint/linter.js",
|
||||
"tslint": "tslint --format stylish --project .",
|
||||
|
@ -54,6 +55,7 @@
|
|||
"dependencies": {
|
||||
"@journeyapps/sqlcipher": "https://github.com/scottnonnenberg-signal/node-sqlcipher.git#2e28733b61640556b0272a3bfc78b0357daf71e6",
|
||||
"@sindresorhus/is": "0.8.0",
|
||||
"@types/dompurify": "^2.0.0",
|
||||
"backbone": "1.3.3",
|
||||
"blob-util": "1.3.0",
|
||||
"blueimp-canvas-to-blob": "3.14.0",
|
||||
|
@ -63,6 +65,7 @@
|
|||
"classnames": "2.2.5",
|
||||
"color": "^3.1.2",
|
||||
"config": "1.28.1",
|
||||
"dompurify": "^2.0.7",
|
||||
"electron-context-menu": "^0.15.0",
|
||||
"electron-editor-context-menu": "1.1.1",
|
||||
"electron-is-dev": "0.3.0",
|
||||
|
|
|
@ -472,3 +472,9 @@ window.lokiFeatureFlags = {
|
|||
multiDeviceUnpairing: true,
|
||||
privateGroupChats: false,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-extend-native,func-names
|
||||
Promise.prototype.ignore = function() {
|
||||
// eslint-disable-next-line more/no-then
|
||||
this.then(() => {});
|
||||
};
|
||||
|
|
|
@ -21,13 +21,15 @@
|
|||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Wasa';
|
||||
src: url('../fonts/Wasa-Bold.otf') format('truetype');
|
||||
src: url('../fonts/Wasa-Bold.otf') format('opentype');
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
// Session Colors
|
||||
$session-font-family: 'Wasa';
|
||||
|
||||
$session-color-green: #00f782;
|
||||
$session-color-green-alt-1: #00f480;
|
||||
$session-color-green-alt-2: #00fd73;
|
||||
|
@ -56,6 +58,7 @@ $session-opaque-dark-2: rgba(0, 0, 0, 0.37);
|
|||
$session-opaque-dark-3: rgba(0, 0, 0, 0.5);
|
||||
|
||||
$session-color-white: #fff;
|
||||
$session-color-dark-grey: #353535;
|
||||
$session-color-black: #000;
|
||||
$session-color-danger: #ff4538;
|
||||
$session-color-primary: $session-shade-13;
|
||||
|
@ -64,7 +67,9 @@ $session-color-secondary: $session-shade-16;
|
|||
$session-color-info: $session-shade-11;
|
||||
$session-color-success: #35d388;
|
||||
$session-color-error: #edd422;
|
||||
$session-color-warning: #e54e45;
|
||||
$session-color-warning: $session-shade-17;
|
||||
|
||||
$session-color-light-grey: #a0a0a0;
|
||||
|
||||
$session-shadow-opacity: 0.15;
|
||||
$session-overlay-opacity: 0.3;
|
||||
|
@ -77,7 +82,6 @@ $session-margin-lg: 20px;
|
|||
color: rgba($color, 0.6);
|
||||
}
|
||||
|
||||
$session-font-family: 'Wasa';
|
||||
$session-transition-duration: 0.25s;
|
||||
|
||||
$session-icon-size-sm: 15px;
|
||||
|
@ -86,6 +90,18 @@ $session-icon-size-lg: 30px;
|
|||
|
||||
$session-conversation-header-height: 60px;
|
||||
|
||||
@mixin fontWasaBold {
|
||||
font-weight: 700;
|
||||
font-family: $session-font-family;
|
||||
}
|
||||
|
||||
a,
|
||||
div,
|
||||
span,
|
||||
label {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
$session-gradient-green: linear-gradient(
|
||||
270deg,
|
||||
rgba($session-color-green-alt-1, 1),
|
||||
|
@ -134,6 +150,7 @@ $session_message-container-border-radius: 5px;
|
|||
font-weight: 700;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
transition: $session-transition-duration;
|
||||
|
||||
&.default,
|
||||
&.square,
|
||||
|
@ -141,7 +158,11 @@ $session_message-container-border-radius: 5px;
|
|||
color: $session-color-white;
|
||||
|
||||
&.green {
|
||||
border: 2px solid $session-color-green;
|
||||
background-color: $session-color-green;
|
||||
&:hover {
|
||||
@include transparent-background($session-color-green);
|
||||
}
|
||||
}
|
||||
&.white {
|
||||
background-color: $session-color-white;
|
||||
|
@ -165,9 +186,16 @@ $session_message-container-border-radius: 5px;
|
|||
&.square-outline {
|
||||
&.green {
|
||||
@include transparent-background($session-color-green);
|
||||
|
||||
&:hover {
|
||||
@include transparent-background($session-color-white);
|
||||
}
|
||||
}
|
||||
&.white {
|
||||
@include transparent-background($session-color-white);
|
||||
&:hover {
|
||||
@include transparent-background($session-color-green);
|
||||
}
|
||||
}
|
||||
&.primary {
|
||||
@include transparent-background($session-color-primary);
|
||||
|
@ -187,7 +215,7 @@ $session_message-container-border-radius: 5px;
|
|||
min-width: 165px;
|
||||
height: 45px;
|
||||
line-height: 45px;
|
||||
padding: 0 35px 0 35px;
|
||||
padding: 0;
|
||||
font-size: 15px;
|
||||
font-family: $session-font-family;
|
||||
border-radius: 500px;
|
||||
|
@ -279,47 +307,107 @@ $session_message-container-border-radius: 5px;
|
|||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.module-message__container {
|
||||
.odule-message__container {
|
||||
border-radius: $session_message-container-border-radius;
|
||||
}
|
||||
|
||||
.module-message__attachment-container,
|
||||
.module-image--curved-bottom-right,
|
||||
.module-image--curved-bottom-left {
|
||||
border-top-left-radius: 0px;
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-left-radius: $session_message-container-border-radius;
|
||||
border-bottom-right-radius: $session_message-container-border-radius;
|
||||
}
|
||||
label {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.conversation-header .session-icon-button {
|
||||
@include standard-icon-button();
|
||||
}
|
||||
.module-message__attachment-container,
|
||||
.module-image--curved-bottom-right,
|
||||
.module-image--curved-bottom-left {
|
||||
border-top-left-radius: 0px;
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-left-radius: $session_message-container-border-radius;
|
||||
border-bottom-right-radius: $session_message-container-border-radius;
|
||||
}
|
||||
|
||||
.module-conversation-header,
|
||||
.message-selection-overlay {
|
||||
height: $session-conversation-header-height;
|
||||
}
|
||||
.conversation-header .session-icon-button {
|
||||
@include standard-icon-button();
|
||||
}
|
||||
|
||||
.message-selection-overlay {
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
right: 15px;
|
||||
display: none;
|
||||
.module-conversation-header,
|
||||
.message-selection-overlay {
|
||||
height: $session-conversation-header-height;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
float: left;
|
||||
margin-top: 17px;
|
||||
margin-left: 7px;
|
||||
.message-selection-overlay {
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
right: 15px;
|
||||
display: none;
|
||||
|
||||
.close-button {
|
||||
float: left;
|
||||
margin-top: 17px;
|
||||
margin-left: 7px;
|
||||
}
|
||||
}
|
||||
.message-selection-overlay div[role='button'] {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.message-selection-overlay .button-group {
|
||||
float: right;
|
||||
margin-top: 13.5px;
|
||||
}
|
||||
}
|
||||
.message-selection-overlay div[role='button'] {
|
||||
display: inline-block;
|
||||
|
||||
.hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.message-selection-overlay .button-group {
|
||||
float: right;
|
||||
margin-top: 13.5px;
|
||||
.input-with-label-container {
|
||||
height: 46.5px;
|
||||
width: 280px;
|
||||
color: $session-color-white;
|
||||
padding: 2px 0 2px 0;
|
||||
transition: opacity $session-transition-duration;
|
||||
opacity: 1;
|
||||
position: relative;
|
||||
|
||||
label {
|
||||
line-height: 14px;
|
||||
opacity: 0;
|
||||
color: #737373;
|
||||
font-size: 10px;
|
||||
line-height: 11px;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
&.filled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
input {
|
||||
border: none;
|
||||
outline: 0;
|
||||
height: 14px;
|
||||
width: 280px;
|
||||
background: transparent;
|
||||
color: $session-color-white;
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid $session-color-white;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
button {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
#session-toast-container {
|
||||
|
|
245
stylesheets/_session_signin.scss
Normal file
245
stylesheets/_session_signin.scss
Normal file
|
@ -0,0 +1,245 @@
|
|||
.session {
|
||||
&-fullscreen {
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #121212 100%, #171717 0%);
|
||||
}
|
||||
|
||||
&-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&-accent {
|
||||
flex-grow: 1;
|
||||
padding-left: 20px;
|
||||
|
||||
&-text {
|
||||
color: $session-color-white;
|
||||
font-family: $session-font-family;
|
||||
|
||||
.title {
|
||||
font-size: 100px;
|
||||
font-weight: 700;
|
||||
line-height: 120px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-registration {
|
||||
height: 45%;
|
||||
padding-right: 128px;
|
||||
}
|
||||
|
||||
&-close-button {
|
||||
position: absolute;
|
||||
top: 17px;
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
&-session-button {
|
||||
position: absolute;
|
||||
top: 17px;
|
||||
right: 20px;
|
||||
|
||||
img {
|
||||
width: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-registration {
|
||||
&-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 289px;
|
||||
}
|
||||
|
||||
&__content {
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
&__sections {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__tab-container {
|
||||
display: flex;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
width: 289px;
|
||||
height: 30px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
color: $session-color-white;
|
||||
}
|
||||
|
||||
&__tab {
|
||||
@include fontWasaBold();
|
||||
width: 100%;
|
||||
padding-bottom: 10px;
|
||||
background-color: transparent;
|
||||
text-align: center;
|
||||
color: $session-color-white;
|
||||
border-bottom: 2px solid $session-color-dark-grey;
|
||||
transition: border-color $session-transition-duration linear;
|
||||
line-height: 17px;
|
||||
font-size: 15px;
|
||||
|
||||
&--active {
|
||||
border-bottom: 4px solid $session-color-green;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin registration-label-mixin {
|
||||
color: $session-color-white;
|
||||
text-align: center;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
line-height: 17px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
&__or {
|
||||
@include registration-label-mixin;
|
||||
}
|
||||
|
||||
&__welcome-session {
|
||||
@include registration-label-mixin;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
line-height: 12px;
|
||||
padding-top: 2em;
|
||||
}
|
||||
|
||||
&__unique-session-id {
|
||||
@include registration-label-mixin;
|
||||
padding-top: 3em;
|
||||
}
|
||||
|
||||
&__entry-fields {
|
||||
margin: 0px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
&-input-floating-label-show-hide {
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
&-input-with-label-container {
|
||||
height: 46.5px;
|
||||
width: 280px;
|
||||
color: $session-color-white;
|
||||
padding: 2px 0 2px 0;
|
||||
transition: opacity $session-transition-duration;
|
||||
opacity: 1;
|
||||
position: relative;
|
||||
|
||||
label {
|
||||
line-height: 14px;
|
||||
opacity: 0;
|
||||
color: #737373;
|
||||
font-size: 10px;
|
||||
line-height: 11px;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
&.filled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
input {
|
||||
border: none;
|
||||
outline: 0;
|
||||
height: 14px;
|
||||
width: 280px;
|
||||
background: transparent;
|
||||
color: $session-color-white;
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid $session-color-light-grey;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
.session-icon-button {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
&-terms-conditions-agreement {
|
||||
padding-top: 10px;
|
||||
color: $session-color-light-grey;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
|
||||
a {
|
||||
white-space: nowrap;
|
||||
font-weight: bold;
|
||||
color: $session-color-light-grey;
|
||||
transition: $session-transition-duration;
|
||||
|
||||
&:visited &:link {
|
||||
color: $session-color-light-grey;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $session-color-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-signup-header,
|
||||
&-signin-device-pairing-header {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
color: $session-color-light-grey;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
&-signin-enter-session-id {
|
||||
height: 94px;
|
||||
width: 289px;
|
||||
border-radius: 8px;
|
||||
border: 2px solid $session-color-dark-grey;
|
||||
outline: 0;
|
||||
background: transparent;
|
||||
color: $session-color-white;
|
||||
font-size: 15px;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
overflow-wrap: break-word;
|
||||
padding: 20px 5px 20px 5px;
|
||||
display: inline-block;
|
||||
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
|
||||
}
|
||||
}
|
||||
|
||||
[contenteditable='true']:empty::before {
|
||||
content: attr(placeholder);
|
||||
color: $session-color-light-grey;
|
||||
}
|
|
@ -25,6 +25,7 @@
|
|||
// New CSS
|
||||
@import 'modules';
|
||||
@import 'session';
|
||||
@import 'session_signin';
|
||||
@import 'session_theme_dark';
|
||||
|
||||
// Installer
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Avatar } from './Avatar';
|
||||
|
||||
import { SessionToast, SessionToastType } from './session/SessionToast';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
displayNameRegex: any;
|
||||
|
@ -48,11 +46,6 @@ export class UserDetailsDialog extends React.Component<Props> {
|
|||
{cancelText}
|
||||
</button>
|
||||
|
||||
<SessionToast
|
||||
title="This is a notification!"
|
||||
type={SessionToastType.Error}
|
||||
/>
|
||||
|
||||
<button
|
||||
className="ok"
|
||||
tabIndex={0}
|
||||
|
|
|
@ -4,6 +4,7 @@ import LinkifyIt from 'linkify-it';
|
|||
|
||||
import { RenderTextCallbackType } from '../../types/Util';
|
||||
import { isLinkSneaky } from '../../../js/modules/link_previews';
|
||||
import { SessionHtmlRenderer } from '../session/SessionHTMLRenderer';
|
||||
|
||||
const linkify = LinkifyIt();
|
||||
|
||||
|
@ -28,19 +29,7 @@ export class Linkify extends React.Component<Props> {
|
|||
let count = 1;
|
||||
|
||||
if (isRss && text.indexOf('</') !== -1) {
|
||||
results.push(
|
||||
<div
|
||||
key={count++}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: text
|
||||
.replace(
|
||||
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
|
||||
''
|
||||
)
|
||||
.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, ''),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
results.push(<SessionHtmlRenderer key={count++} html={text} tag="div" />);
|
||||
// should already have links
|
||||
|
||||
return results;
|
||||
|
|
11
ts/components/session/AccentText.tsx
Normal file
11
ts/components/session/AccentText.tsx
Normal file
|
@ -0,0 +1,11 @@
|
|||
import React from 'react';
|
||||
|
||||
import { SessionHtmlRenderer } from './SessionHTMLRenderer';
|
||||
|
||||
export const AccentText: React.FC = () => (
|
||||
<div className="session-content-accent-text">
|
||||
<div className="session-content-accent-text title">
|
||||
<SessionHtmlRenderer html={window.i18n('beginYourSession')} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
763
ts/components/session/RegistrationTabs.tsx
Normal file
763
ts/components/session/RegistrationTabs.tsx
Normal file
|
@ -0,0 +1,763 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { SessionInput } from './SessionInput';
|
||||
import {
|
||||
SessionButton,
|
||||
SessionButtonColor,
|
||||
SessionButtonType,
|
||||
} from './SessionButton';
|
||||
import { trigger } from '../../shims/events';
|
||||
import { SessionHtmlRenderer } from './SessionHTMLRenderer';
|
||||
|
||||
enum SignInMode {
|
||||
Default,
|
||||
UsingSeed,
|
||||
LinkingDevice,
|
||||
}
|
||||
|
||||
enum SignUpMode {
|
||||
Default,
|
||||
SessionIDShown,
|
||||
EnterDetails,
|
||||
}
|
||||
|
||||
enum TabType {
|
||||
Create,
|
||||
SignIn,
|
||||
}
|
||||
|
||||
interface State {
|
||||
selectedTab: TabType;
|
||||
signInMode: SignInMode;
|
||||
signUpMode: SignUpMode;
|
||||
displayName: string;
|
||||
password: string;
|
||||
validatePassword: string;
|
||||
passwordErrorString: string;
|
||||
passwordFieldsMatch: boolean;
|
||||
mnemonicSeed: string;
|
||||
hexGeneratedPubKey: string;
|
||||
primaryDevicePubKey: string;
|
||||
}
|
||||
|
||||
const Tab = ({
|
||||
isSelected,
|
||||
label,
|
||||
onSelect,
|
||||
type,
|
||||
}: {
|
||||
isSelected: boolean;
|
||||
label: string;
|
||||
onSelect?: (event: TabType) => void;
|
||||
type: TabType;
|
||||
}) => {
|
||||
const handleClick = onSelect
|
||||
? () => {
|
||||
onSelect(type);
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'session-registration__tab',
|
||||
isSelected ? 'session-registration__tab--active' : null
|
||||
)}
|
||||
onClick={handleClick}
|
||||
role="tab"
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export class RegistrationTabs extends React.Component<{}, State> {
|
||||
private readonly accountManager: any;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
this.onSeedChanged = this.onSeedChanged.bind(this);
|
||||
this.onDisplayNameChanged = this.onDisplayNameChanged.bind(this);
|
||||
this.onPasswordChanged = this.onPasswordChanged.bind(this);
|
||||
this.onPasswordVerifyChanged = this.onPasswordVerifyChanged.bind(this);
|
||||
this.onSignUpGenerateSessionIDClick = this.onSignUpGenerateSessionIDClick.bind(
|
||||
this
|
||||
);
|
||||
this.onSignUpGetStartedClick = this.onSignUpGetStartedClick.bind(this);
|
||||
this.onSecondDeviceSessionIDChanged = this.onSecondDeviceSessionIDChanged.bind(
|
||||
this
|
||||
);
|
||||
this.onSecondaryDeviceRegistered = this.onSecondaryDeviceRegistered.bind(
|
||||
this
|
||||
);
|
||||
this.onCompleteSignUpClick = this.onCompleteSignUpClick.bind(this);
|
||||
this.handlePressEnter = this.handlePressEnter.bind(this);
|
||||
this.handleContinueYourSessionClick = this.handleContinueYourSessionClick.bind(
|
||||
this
|
||||
);
|
||||
|
||||
this.state = {
|
||||
selectedTab: TabType.Create,
|
||||
signInMode: SignInMode.Default,
|
||||
signUpMode: SignUpMode.Default,
|
||||
displayName: '',
|
||||
password: '',
|
||||
validatePassword: '',
|
||||
passwordErrorString: '',
|
||||
passwordFieldsMatch: false,
|
||||
mnemonicSeed: '',
|
||||
hexGeneratedPubKey: '',
|
||||
primaryDevicePubKey: '',
|
||||
};
|
||||
|
||||
this.accountManager = window.getAccountManager();
|
||||
// Clean status in case the app closed unexpectedly
|
||||
window.textsecure.storage.remove('secondaryDeviceStatus');
|
||||
}
|
||||
|
||||
public render() {
|
||||
this.generateMnemonicAndKeyPair().ignore();
|
||||
|
||||
return this.renderTabs();
|
||||
}
|
||||
|
||||
private async generateMnemonicAndKeyPair() {
|
||||
if (this.state.mnemonicSeed === '') {
|
||||
const language = 'english';
|
||||
const mnemonic = await this.accountManager.generateMnemonic(language);
|
||||
|
||||
let seedHex = window.mnemonic.mn_decode(mnemonic, language);
|
||||
// handle shorter than 32 bytes seeds
|
||||
const privKeyHexLength = 32 * 2;
|
||||
if (seedHex.length !== privKeyHexLength) {
|
||||
seedHex = seedHex.concat(seedHex);
|
||||
seedHex = seedHex.substring(0, privKeyHexLength);
|
||||
}
|
||||
const privKeyHex = window.mnemonic.sc_reduce32(seedHex);
|
||||
const privKey = window.dcodeIO.ByteBuffer.wrap(
|
||||
privKeyHex,
|
||||
'hex'
|
||||
).toArrayBuffer();
|
||||
const keyPair = await window.libsignal.Curve.async.createKeyPair(privKey);
|
||||
const hexGeneratedPubKey = Buffer.from(keyPair.pubKey).toString('hex');
|
||||
|
||||
this.setState({
|
||||
mnemonicSeed: mnemonic,
|
||||
hexGeneratedPubKey, // our 'frontend' sessionID
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private renderTabs() {
|
||||
const { selectedTab } = this.state;
|
||||
|
||||
const createAccount = window.i18n('createAccount');
|
||||
const signIn = window.i18n('signIn');
|
||||
const isCreateSelected = selectedTab === TabType.Create;
|
||||
const isSignInSelected = selectedTab === TabType.SignIn;
|
||||
|
||||
return (
|
||||
<div className="session-registration-container">
|
||||
<div className="session-registration__tab-container">
|
||||
<Tab
|
||||
label={createAccount}
|
||||
type={TabType.Create}
|
||||
isSelected={isCreateSelected}
|
||||
onSelect={this.handleTabSelect}
|
||||
/>
|
||||
<Tab
|
||||
label={signIn}
|
||||
type={TabType.SignIn}
|
||||
isSelected={isSignInSelected}
|
||||
onSelect={this.handleTabSelect}
|
||||
/>
|
||||
</div>
|
||||
{this.renderSections()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private readonly handleTabSelect = (tabType: TabType): void => {
|
||||
if (tabType !== TabType.SignIn) {
|
||||
this.cancelSecondaryDevice().ignore();
|
||||
}
|
||||
this.setState({
|
||||
selectedTab: tabType,
|
||||
signInMode: SignInMode.Default,
|
||||
signUpMode: SignUpMode.Default,
|
||||
displayName: '',
|
||||
password: '',
|
||||
validatePassword: '',
|
||||
passwordErrorString: '',
|
||||
passwordFieldsMatch: false,
|
||||
mnemonicSeed: '',
|
||||
hexGeneratedPubKey: '',
|
||||
primaryDevicePubKey: '',
|
||||
});
|
||||
};
|
||||
|
||||
private onSeedChanged(val: string) {
|
||||
this.setState({ mnemonicSeed: val });
|
||||
}
|
||||
|
||||
private onDisplayNameChanged(val: string) {
|
||||
const sanitizedName = this.sanitiseNameInput(val);
|
||||
this.setState({ displayName: sanitizedName });
|
||||
}
|
||||
|
||||
private onPasswordChanged(val: string) {
|
||||
this.setState({ password: val });
|
||||
this.onValidatePassword(); // FIXME add bubbles or something to help the user know what he did wrong
|
||||
}
|
||||
|
||||
private onPasswordVerifyChanged(val: string) {
|
||||
this.setState({ validatePassword: val });
|
||||
}
|
||||
|
||||
private renderSections() {
|
||||
const { selectedTab } = this.state;
|
||||
if (selectedTab === TabType.Create) {
|
||||
return this.renderSignUp();
|
||||
}
|
||||
|
||||
return this.renderSignIn();
|
||||
}
|
||||
|
||||
private renderSignUp() {
|
||||
const { signUpMode } = this.state;
|
||||
switch (signUpMode) {
|
||||
case SignUpMode.Default:
|
||||
return (
|
||||
<div className="session-registration__content">
|
||||
{this.renderSignUpHeader()}
|
||||
{this.renderSignUpButton()}
|
||||
</div>
|
||||
);
|
||||
case SignUpMode.SessionIDShown:
|
||||
return (
|
||||
<div className="session-registration__content">
|
||||
{this.renderSignUpHeader()}
|
||||
<div className="session-registration__unique-session-id">
|
||||
{window.i18n('yourUniqueSessionID')}
|
||||
</div>
|
||||
{this.renderEnterSessionID(false)}
|
||||
{this.renderSignUpButton()}
|
||||
{this.getRenderTermsConditionAgreement()}
|
||||
</div>
|
||||
);
|
||||
|
||||
default:
|
||||
return (
|
||||
<div className="session-registration__content">
|
||||
<div className="session-registration__welcome-session">
|
||||
{window.i18n('welcomeToYourSession')}
|
||||
</div>
|
||||
|
||||
{this.renderRegistrationContent()}
|
||||
<SessionButton
|
||||
onClick={() => {
|
||||
this.onCompleteSignUpClick();
|
||||
}}
|
||||
buttonType={SessionButtonType.Brand}
|
||||
buttonColor={SessionButtonColor.Green}
|
||||
text={window.i18n('completeSignUp')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private getRenderTermsConditionAgreement() {
|
||||
const { selectedTab, signInMode, signUpMode } = this.state;
|
||||
if (selectedTab === TabType.Create) {
|
||||
return signUpMode !== SignUpMode.Default
|
||||
? this.renderTermsConditionAgreement()
|
||||
: null;
|
||||
} else {
|
||||
return signInMode !== SignInMode.Default
|
||||
? this.renderTermsConditionAgreement()
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
private renderSignUpHeader() {
|
||||
const allUsersAreRandomly = window.i18n('allUsersAreRandomly...');
|
||||
|
||||
return <div className="session-signup-header">{allUsersAreRandomly}</div>;
|
||||
}
|
||||
|
||||
private renderSignUpButton() {
|
||||
const { signUpMode } = this.state;
|
||||
|
||||
let buttonType: SessionButtonType;
|
||||
let buttonColor: SessionButtonColor;
|
||||
let buttonText: string;
|
||||
if (signUpMode !== SignUpMode.Default) {
|
||||
buttonType = SessionButtonType.Brand;
|
||||
buttonColor = SessionButtonColor.Green;
|
||||
buttonText = window.i18n('getStarted');
|
||||
} else {
|
||||
buttonType = SessionButtonType.BrandOutline;
|
||||
buttonColor = SessionButtonColor.Green;
|
||||
buttonText = window.i18n('generateSessionID');
|
||||
}
|
||||
|
||||
return (
|
||||
<SessionButton
|
||||
onClick={() => {
|
||||
if (signUpMode === SignUpMode.Default) {
|
||||
this.onSignUpGenerateSessionIDClick().ignore();
|
||||
} else {
|
||||
this.onSignUpGetStartedClick();
|
||||
}
|
||||
}}
|
||||
buttonType={buttonType}
|
||||
buttonColor={buttonColor}
|
||||
text={buttonText}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private async onSignUpGenerateSessionIDClick() {
|
||||
this.setState(
|
||||
{
|
||||
signUpMode: SignUpMode.SessionIDShown,
|
||||
},
|
||||
() => {
|
||||
window.Session.setNewSessionID(this.state.hexGeneratedPubKey);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private onSignUpGetStartedClick() {
|
||||
this.setState({
|
||||
signUpMode: SignUpMode.EnterDetails,
|
||||
});
|
||||
}
|
||||
|
||||
private onCompleteSignUpClick() {
|
||||
this.register('english').ignore();
|
||||
}
|
||||
|
||||
private renderSignIn() {
|
||||
return (
|
||||
<div className="session-registration__content">
|
||||
{this.renderRegistrationContent()}
|
||||
|
||||
{this.renderSignInButtons()}
|
||||
{this.getRenderTermsConditionAgreement()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderRegistrationContent() {
|
||||
const { signInMode, signUpMode } = this.state;
|
||||
|
||||
if (signInMode === SignInMode.UsingSeed) {
|
||||
return (
|
||||
<div className={classNames('session-registration__entry-fields')}>
|
||||
<SessionInput
|
||||
label={window.i18n('mnemonicSeed')}
|
||||
type="password"
|
||||
placeholder={window.i18n('enterSeed')}
|
||||
enableShowHide={true}
|
||||
onValueChanged={(val: string) => {
|
||||
this.onSeedChanged(val);
|
||||
}}
|
||||
onEnterPressed={() => {
|
||||
this.handlePressEnter();
|
||||
}}
|
||||
/>
|
||||
{this.renderNamePasswordAndVerifyPasswordFields()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (signInMode === SignInMode.LinkingDevice) {
|
||||
return (
|
||||
<div className="">
|
||||
<div className="session-signin-device-pairing-header">
|
||||
{window.i18n('devicePairingHeader')}
|
||||
</div>
|
||||
{this.renderEnterSessionID(true)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (signUpMode === SignUpMode.EnterDetails) {
|
||||
return (
|
||||
<div className={classNames('session-registration__entry-fields')}>
|
||||
{this.renderNamePasswordAndVerifyPasswordFields()};
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private renderNamePasswordAndVerifyPasswordFields() {
|
||||
return (
|
||||
<div className="inputfields">
|
||||
<SessionInput
|
||||
label={window.i18n('displayName')}
|
||||
type="text"
|
||||
placeholder={window.i18n('enterDisplayName')}
|
||||
value={this.state.displayName}
|
||||
onValueChanged={(val: string) => {
|
||||
this.onDisplayNameChanged(val);
|
||||
}}
|
||||
onEnterPressed={() => {
|
||||
this.handlePressEnter();
|
||||
}}
|
||||
/>
|
||||
|
||||
<SessionInput
|
||||
label={window.i18n('optionalPassword')}
|
||||
type="password"
|
||||
placeholder={window.i18n('enterOptionalPassword')}
|
||||
onValueChanged={(val: string) => {
|
||||
this.onPasswordChanged(val);
|
||||
}}
|
||||
onEnterPressed={() => {
|
||||
this.handlePressEnter();
|
||||
}}
|
||||
/>
|
||||
|
||||
<SessionInput
|
||||
label={window.i18n('verifyPassword')}
|
||||
type="password"
|
||||
placeholder={window.i18n('optionalPassword')}
|
||||
onValueChanged={(val: string) => {
|
||||
this.onPasswordVerifyChanged(val);
|
||||
}}
|
||||
onEnterPressed={() => {
|
||||
this.handlePressEnter();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderEnterSessionID(contentEditable: boolean) {
|
||||
const enterSessionIDHere = window.i18n('enterSessionIDHere');
|
||||
|
||||
return (
|
||||
<div
|
||||
className="session-signin-enter-session-id"
|
||||
placeholder={enterSessionIDHere}
|
||||
contentEditable={contentEditable}
|
||||
onInput={(e: any) => {
|
||||
if (contentEditable) {
|
||||
this.onSecondDeviceSessionIDChanged(e);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private onSecondDeviceSessionIDChanged(e: any) {
|
||||
e.preventDefault();
|
||||
const hexEncodedPubKey = e.target.innerHTML;
|
||||
this.setState({
|
||||
primaryDevicePubKey: hexEncodedPubKey,
|
||||
});
|
||||
}
|
||||
|
||||
private renderSignInButtons() {
|
||||
const { signInMode } = this.state;
|
||||
|
||||
const or = window.i18n('or');
|
||||
|
||||
if (signInMode === SignInMode.Default) {
|
||||
return (
|
||||
<div>
|
||||
{this.renderRestoreUsingSeedButton(
|
||||
SessionButtonType.BrandOutline,
|
||||
SessionButtonColor.Green
|
||||
)}
|
||||
<div className="session-registration__or">{or}</div>
|
||||
{this.renderLinkDeviceToExistingAccountButton()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (signInMode === SignInMode.LinkingDevice) {
|
||||
return (
|
||||
<div>
|
||||
{this.renderContinueYourSessionButton()}
|
||||
<div className="session-registration__or">{or}</div>
|
||||
{this.renderRestoreUsingSeedButton(
|
||||
SessionButtonType.BrandOutline,
|
||||
SessionButtonColor.Green
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{this.renderContinueYourSessionButton()}
|
||||
<div className="session-registration__or">{or}</div>
|
||||
{this.renderLinkDeviceToExistingAccountButton()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderTermsConditionAgreement() {
|
||||
// FIXME link to our Terms and Conditions and privacy statement
|
||||
|
||||
return (
|
||||
<div className="session-terms-conditions-agreement">
|
||||
<SessionHtmlRenderer html={window.i18n('ByUsingThiService...')} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private handleContinueYourSessionClick() {
|
||||
if (this.state.signInMode === SignInMode.UsingSeed) {
|
||||
this.register('english').ignore();
|
||||
} else {
|
||||
this.registerSecondaryDevice().ignore();
|
||||
}
|
||||
}
|
||||
|
||||
private renderContinueYourSessionButton() {
|
||||
return (
|
||||
<SessionButton
|
||||
onClick={() => {
|
||||
this.handleContinueYourSessionClick();
|
||||
}}
|
||||
buttonType={SessionButtonType.Brand}
|
||||
buttonColor={SessionButtonColor.Green}
|
||||
text={window.i18n('continueYourSession')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderRestoreUsingSeedButton(
|
||||
buttonType: SessionButtonType,
|
||||
buttonColor: SessionButtonColor
|
||||
) {
|
||||
return (
|
||||
<SessionButton
|
||||
onClick={() => {
|
||||
this.cancelSecondaryDevice().ignore();
|
||||
this.setState({
|
||||
signInMode: SignInMode.UsingSeed,
|
||||
primaryDevicePubKey: '',
|
||||
mnemonicSeed: '',
|
||||
displayName: '',
|
||||
signUpMode: SignUpMode.Default,
|
||||
});
|
||||
//FIXME ugly hack to empty the content editable div used on enter session ID
|
||||
window.Session.emptyContentEditableDivs();
|
||||
}}
|
||||
buttonType={buttonType}
|
||||
buttonColor={buttonColor}
|
||||
text={window.i18n('restoreUsingSeed')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderLinkDeviceToExistingAccountButton() {
|
||||
return (
|
||||
<SessionButton
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
signInMode: SignInMode.LinkingDevice,
|
||||
mnemonicSeed: '',
|
||||
displayName: '',
|
||||
signUpMode: SignUpMode.Default,
|
||||
});
|
||||
}}
|
||||
buttonType={SessionButtonType.BrandOutline}
|
||||
buttonColor={SessionButtonColor.White}
|
||||
text={window.i18n('linkDeviceToExistingAccount')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private handlePressEnter() {
|
||||
const { signInMode, signUpMode } = this.state;
|
||||
if (signUpMode === SignUpMode.EnterDetails) {
|
||||
this.onCompleteSignUpClick();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (signInMode === SignInMode.UsingSeed) {
|
||||
this.handleContinueYourSessionClick();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private trim(value: string) {
|
||||
return value ? value.trim() : value;
|
||||
}
|
||||
|
||||
private validatePassword() {
|
||||
const input = this.trim(this.state.password);
|
||||
const confirmationInput = this.trim(this.state.validatePassword);
|
||||
|
||||
// If user hasn't set a value then skip
|
||||
if (!input && !confirmationInput) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const error = window.passwordUtil.validatePassword(input, window.i18n);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (input !== confirmationInput) {
|
||||
return "Password don't match";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private onValidatePassword() {
|
||||
const passwordValidation = this.validatePassword();
|
||||
if (passwordValidation) {
|
||||
this.setState({ passwordErrorString: passwordValidation });
|
||||
} else {
|
||||
// Show green box around inputs that match
|
||||
const input = this.trim(this.state.password);
|
||||
const confirmationInput = this.trim(this.state.validatePassword);
|
||||
const passwordFieldsMatch =
|
||||
input !== undefined && input === confirmationInput;
|
||||
|
||||
this.setState({
|
||||
passwordErrorString: '',
|
||||
passwordFieldsMatch,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private sanitiseNameInput(val: string) {
|
||||
return val.trim().replace(window.displayNameRegex, '');
|
||||
}
|
||||
|
||||
private async resetRegistration() {
|
||||
await window.Signal.Data.removeAllIdentityKeys();
|
||||
await window.Signal.Data.removeAllPrivateConversations();
|
||||
window.Whisper.Registration.remove();
|
||||
// Do not remove all items since they are only set
|
||||
// at startup.
|
||||
window.textsecure.storage.remove('identityKey');
|
||||
window.textsecure.storage.remove('secondaryDeviceStatus');
|
||||
window.ConversationController.reset();
|
||||
await window.ConversationController.load();
|
||||
window.Whisper.RotateSignedPreKeyListener.stop(window.Whisper.events);
|
||||
}
|
||||
|
||||
private async register(language: string) {
|
||||
const { password, mnemonicSeed, displayName } = this.state;
|
||||
// Make sure the password is valid
|
||||
if (this.validatePassword()) {
|
||||
//this.showToast(window.i18n('invalidPassword'));
|
||||
return;
|
||||
}
|
||||
if (!mnemonicSeed) {
|
||||
return;
|
||||
}
|
||||
if (!displayName) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure we clear the secondary device registration status
|
||||
window.textsecure.storage.remove('secondaryDeviceStatus');
|
||||
|
||||
try {
|
||||
await this.resetRegistration();
|
||||
|
||||
await window.setPassword(password);
|
||||
await this.accountManager.registerSingleDevice(
|
||||
mnemonicSeed,
|
||||
language,
|
||||
displayName
|
||||
);
|
||||
trigger('openInbox');
|
||||
} catch (e) {
|
||||
if (typeof e === 'string') {
|
||||
//this.showToast(e);
|
||||
}
|
||||
//this.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
private async cancelSecondaryDevice() {
|
||||
window.Whisper.events.off(
|
||||
'secondaryDeviceRegistration',
|
||||
this.onSecondaryDeviceRegistered
|
||||
);
|
||||
|
||||
await this.resetRegistration();
|
||||
}
|
||||
|
||||
private async registerSecondaryDevice() {
|
||||
// tslint:disable-next-line: no-backbone-get-set-outside-model
|
||||
if (window.textsecure.storage.get('secondaryDeviceStatus') === 'ongoing') {
|
||||
return;
|
||||
}
|
||||
await this.resetRegistration();
|
||||
window.textsecure.storage.put('secondaryDeviceStatus', 'ongoing');
|
||||
|
||||
const primaryPubKey = this.state.primaryDevicePubKey;
|
||||
|
||||
// Ensure only one listener
|
||||
window.Whisper.events.off(
|
||||
'secondaryDeviceRegistration',
|
||||
this.onSecondaryDeviceRegistered
|
||||
);
|
||||
window.Whisper.events.once(
|
||||
'secondaryDeviceRegistration',
|
||||
this.onSecondaryDeviceRegistered
|
||||
);
|
||||
|
||||
const onError = async (error: any) => {
|
||||
window.console.error(error);
|
||||
|
||||
await this.resetRegistration();
|
||||
};
|
||||
|
||||
const c = new window.Whisper.Conversation({
|
||||
id: primaryPubKey,
|
||||
type: 'private',
|
||||
});
|
||||
|
||||
const validationError = c.validateNumber();
|
||||
if (validationError) {
|
||||
onError('Invalid public key').ignore();
|
||||
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const fakeMnemonic = this.state.mnemonicSeed;
|
||||
|
||||
await this.accountManager.registerSingleDevice(
|
||||
fakeMnemonic,
|
||||
'english',
|
||||
null
|
||||
);
|
||||
|
||||
await this.accountManager.requestPairing(primaryPubKey);
|
||||
const pubkey = window.textsecure.storage.user.getNumber();
|
||||
const words = window.mnemonic.pubkey_to_secret_words(pubkey);
|
||||
window.console.log('pubkey_to_secret_words');
|
||||
window.console.log(`Here is your secret:\n${words}`);
|
||||
} catch (e) {
|
||||
window.console.log(e);
|
||||
//onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private async onSecondaryDeviceRegistered() {
|
||||
// Ensure the left menu is updated
|
||||
trigger('userChanged', { isSecondaryDevice: true });
|
||||
// will re-run the background initialisation
|
||||
trigger('registration_done');
|
||||
trigger('openInbox');
|
||||
}
|
||||
}
|
|
@ -42,13 +42,18 @@ export class SessionButton extends React.PureComponent<Props> {
|
|||
public render() {
|
||||
const { buttonType, buttonColor, text } = this.props;
|
||||
|
||||
const buttonTypes = [];
|
||||
|
||||
buttonTypes.push(buttonType);
|
||||
if (buttonType.includes('-outline')) {
|
||||
buttonTypes.push(buttonType.replace('-outline', ''));
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={e => {
|
||||
this.clickHandler(e);
|
||||
}}
|
||||
className={classNames('session-button', buttonType, buttonColor)}
|
||||
className={classNames('session-button', ...buttonTypes, buttonColor)}
|
||||
role="button"
|
||||
onClick={this.clickHandler}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
|
|
26
ts/components/session/SessionHTMLRenderer.tsx
Normal file
26
ts/components/session/SessionHTMLRenderer.tsx
Normal file
|
@ -0,0 +1,26 @@
|
|||
import React from 'react';
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
interface ReceivedProps {
|
||||
html: string;
|
||||
tag?: string;
|
||||
key?: any;
|
||||
}
|
||||
|
||||
type Props = ReceivedProps;
|
||||
|
||||
export const SessionHtmlRenderer: React.SFC<Props> = ({
|
||||
tag = 'div',
|
||||
key,
|
||||
html,
|
||||
}) => {
|
||||
const clean = DOMPurify.sanitize(html, {
|
||||
USE_PROFILES: { html: true },
|
||||
FORBID_ATTR: ['style', 'script'],
|
||||
});
|
||||
|
||||
return React.createElement(tag, {
|
||||
key,
|
||||
dangerouslySetInnerHTML: { __html: clean },
|
||||
});
|
||||
};
|
103
ts/components/session/SessionInput.tsx
Normal file
103
ts/components/session/SessionInput.tsx
Normal file
|
@ -0,0 +1,103 @@
|
|||
import React from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { SessionIconButton, SessionIconSize, SessionIconType } from './icon';
|
||||
|
||||
interface Props {
|
||||
label: string;
|
||||
type: string;
|
||||
value?: string;
|
||||
placeholder: string;
|
||||
enableShowHide?: boolean;
|
||||
onValueChanged?: any;
|
||||
onEnterPressed?: any;
|
||||
}
|
||||
|
||||
interface State {
|
||||
inputValue: string;
|
||||
forceShow: boolean;
|
||||
}
|
||||
|
||||
export class SessionInput extends React.PureComponent<Props, State> {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
this.updateInputValue = this.updateInputValue.bind(this);
|
||||
this.renderShowHideButton = this.renderShowHideButton.bind(this);
|
||||
|
||||
this.state = {
|
||||
inputValue: '',
|
||||
forceShow: false,
|
||||
};
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { placeholder, type, label, value, enableShowHide } = this.props;
|
||||
const { inputValue, forceShow } = this.state;
|
||||
|
||||
const correctType = forceShow ? 'text' : type;
|
||||
|
||||
return (
|
||||
<div className="session-input-with-label-container">
|
||||
<label
|
||||
htmlFor="session-input-floating-label"
|
||||
className={classNames(
|
||||
inputValue !== ''
|
||||
? 'session-input-with-label-container filled'
|
||||
: 'session-input-with-label-container'
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
<input
|
||||
id="session-input-floating-label"
|
||||
type={correctType}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={e => {
|
||||
this.updateInputValue(e);
|
||||
}}
|
||||
className={classNames(
|
||||
enableShowHide ? 'session-input-floating-label-show-hide' : ''
|
||||
)}
|
||||
onKeyPress={event => {
|
||||
event.persist();
|
||||
if (event.key === 'Enter' && this.props.onEnterPressed) {
|
||||
this.props.onEnterPressed();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{enableShowHide && this.renderShowHideButton()}
|
||||
|
||||
<hr />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderShowHideButton() {
|
||||
return (
|
||||
<SessionIconButton
|
||||
iconType={SessionIconType.Eye}
|
||||
iconSize={SessionIconSize.Medium}
|
||||
iconPadded={false}
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
forceShow: !this.state.forceShow,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private updateInputValue(e: any) {
|
||||
e.preventDefault();
|
||||
this.setState({
|
||||
inputValue: e.target.value,
|
||||
});
|
||||
|
||||
if (this.props.onValueChanged) {
|
||||
this.props.onValueChanged(e.target.value);
|
||||
}
|
||||
}
|
||||
}
|
30
ts/components/session/SessionRegistrationView.tsx
Normal file
30
ts/components/session/SessionRegistrationView.tsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
import React from 'react';
|
||||
import { AccentText } from './AccentText';
|
||||
|
||||
import { RegistrationTabs } from './RegistrationTabs';
|
||||
import { SessionIconButton, SessionIconSize, SessionIconType } from './icon';
|
||||
|
||||
export const SessionRegistrationView: React.FC = () => (
|
||||
<div className="session-content">
|
||||
<div id="error" className="collapse" />
|
||||
<div className="session-content-close-button">
|
||||
<SessionIconButton
|
||||
iconSize={SessionIconSize.Medium}
|
||||
iconType={SessionIconType.Exit}
|
||||
onClick={() => {
|
||||
window.close();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="session-content-accent">
|
||||
<AccentText />
|
||||
</div>
|
||||
<div className="session-content-registration">
|
||||
<RegistrationTabs />
|
||||
</div>
|
||||
<div className="session-content-session-button">
|
||||
<img alt="brand" src="./images/session/brand.svg" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
19
ts/global.d.ts
vendored
Normal file
19
ts/global.d.ts
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
interface Window {
|
||||
getAccountManager: any;
|
||||
mnemonic: any;
|
||||
passwordUtil: any;
|
||||
dcodeIO: any;
|
||||
libsignal: any;
|
||||
displayNameRegex: any;
|
||||
Signal: any;
|
||||
Whisper: any;
|
||||
ConversationController: any;
|
||||
setPassword: any;
|
||||
textsecure: any;
|
||||
Session: any;
|
||||
i18n: any;
|
||||
}
|
||||
|
||||
interface Promise<T> {
|
||||
ignore(): void;
|
||||
}
|
|
@ -141,9 +141,9 @@
|
|||
"react-no-dangerous-html": [
|
||||
true,
|
||||
{
|
||||
"file": "ts/components/conversation/Linkify.tsx",
|
||||
"method": "render",
|
||||
"comment": "Usage has been approved by Ryan Tharp on 2019-07-22"
|
||||
"file": "ts/components/session/SessionHTMLRenderer.tsx",
|
||||
"method": "<unknown>",
|
||||
"comment": "Usage has been approved by Maxim on 13 Dec 2019"
|
||||
}
|
||||
],
|
||||
// Reasonable functions can exceed the default of 100 lines
|
||||
|
|
17
yarn.lock
17
yarn.lock
|
@ -138,6 +138,13 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd"
|
||||
integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==
|
||||
|
||||
"@types/dompurify@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.0.0.tgz#9616caa5bf2569aea2e4889d4f929d968c081b40"
|
||||
integrity sha512-g/ilp+Bo6Ljy60i5LnjkGw00X7EIoFjoPGlxqZhV8TJ9fWEzXheioU1O+U/UzCzUA7pUDy/JNMytTQDJctpUHg==
|
||||
dependencies:
|
||||
"@types/trusted-types" "*"
|
||||
|
||||
"@types/events@*":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
|
||||
|
@ -324,6 +331,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.5.tgz#9da44ed75571999b65c37b60c9b2b88db54c585d"
|
||||
integrity sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==
|
||||
|
||||
"@types/trusted-types@*":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-1.0.4.tgz#922d092c84a776a59acb0bd6785fd82b59b9bad5"
|
||||
integrity sha512-6jtHrHpmiXOXoJ31Cg9R+iEVwuEKPf0XHwFUI93eEPXx492/J2JHyafkleKE2EYzZprayk9FSjTyK1GDqcwDng==
|
||||
|
||||
"@types/uuid@3.4.4":
|
||||
version "3.4.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.4.tgz#7af69360fa65ef0decb41fd150bf4ca5c0cefdf5"
|
||||
|
@ -2581,6 +2593,11 @@ domain-browser@^1.1.1:
|
|||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
|
||||
|
||||
dompurify@^2.0.7:
|
||||
version "2.0.7"
|
||||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.0.7.tgz#f8266ad38fe1602fb5b3222f31eedbf5c16c4fd5"
|
||||
integrity sha512-S3O0lk6rFJtO01ZTzMollCOGg+WAtCwS3U5E2WSDY/x/sy7q70RjEC4Dmrih5/UqzLLB9XoKJ8KqwBxaNvBu4A==
|
||||
|
||||
dot-prop@^4.1.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.1.1.tgz#a8493f0b7b5eeec82525b5c7587fa7de7ca859c1"
|
||||
|
|
Loading…
Reference in a new issue