merge next
This commit is contained in:
@@ -46,12 +46,21 @@ export default class AccountSettingsView extends React.Component {
|
||||
val={this.state.credEdit.val}
|
||||
done={this.state.credEdit.done}
|
||||
onShowCountrySelector={this.props.onShowCountrySelector}
|
||||
onSubmit={this.props.onCredAdd}
|
||||
onCredAdd={this.props.onCredAdd}
|
||||
onCredConfirm={this.props.onCredConfirm}
|
||||
onCancel={_ => this.setState({credEdit: undefined})}
|
||||
onError={this.props.onError} />
|
||||
);
|
||||
}
|
||||
|
||||
// Count how many times each method is validated.
|
||||
const validated = {};
|
||||
this.state.credentials.forEach(cred => {
|
||||
if (cred.done) {
|
||||
validated[cred.meth] = (validated[cred.meth] || 0) + 1;
|
||||
}
|
||||
});
|
||||
|
||||
const credentials = [];
|
||||
this.state.credentials.forEach((cred, idx) => {
|
||||
if (!['email', 'tel'].includes(cred.meth)) {
|
||||
@@ -68,15 +77,14 @@ export default class AccountSettingsView extends React.Component {
|
||||
credentials.push(
|
||||
<div className="group quoted" key={idx}>
|
||||
<tt className="clickable" onClick={e => {e.preventDefault(); this.setState({credEdit: cred});}}>{val}</tt>
|
||||
<span> {
|
||||
cred.done ? null :
|
||||
<>
|
||||
<i className="material-icons">pending</i>
|
||||
<span> {cred.done ? null : <i className="material-icons">pending</i>}
|
||||
{!cred.done || validated[cred.meth] > 1 || this.props.reqCredMethod != cred.meth ?
|
||||
<a href="#" onClick={e => {e.preventDefault(); this.props.onCredDelete(cred.meth, cred.val);}}>
|
||||
<i className="material-icons">delete</i>
|
||||
</a>
|
||||
</>
|
||||
}</span>
|
||||
:
|
||||
null}
|
||||
</span>
|
||||
</div>);
|
||||
});
|
||||
if (credentials.length > 0) {
|
||||
|
||||
@@ -186,16 +186,23 @@ class MessagesView extends React.Component {
|
||||
|
||||
getVisibleMessageRange(holderRect) {
|
||||
let min = Number.MAX_SAFE_INTEGER, max = -1;
|
||||
this.chatMessageRefs.forEach((ref, seq) => {
|
||||
let visibilityStatus = false;
|
||||
this.chatMessageRefs.every((ref, seq) => {
|
||||
if (ref.current) {
|
||||
const { top, bottom, height } = ref.current.getBoundingClientRect();
|
||||
const visible = top <= holderRect.top ? holderRect.top - top <= height : bottom - holderRect.bottom <= height;
|
||||
if (visible) {
|
||||
visibilityStatus = true;
|
||||
min = Math.min(min, seq);
|
||||
max = Math.max(max, seq);
|
||||
} else if (visibilityStatus) {
|
||||
// The remaining elements are no longer visible, no need to iterate them.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return max >= min ? {min: min, max: max} : {min: 0, max: 0};
|
||||
}
|
||||
|
||||
@@ -274,12 +281,12 @@ class MessagesView extends React.Component {
|
||||
}
|
||||
|
||||
if (topic) {
|
||||
if ((this.state.topic != prevState.topic) || !prevProps.ready) {
|
||||
if ((this.state.topic != prevState.topic) || (this.props.myUserId && !prevProps.myUserId)) {
|
||||
// Don't immediately subscribe to a new p2p topic, wait for the first message.
|
||||
const newTopic = (this.props.newTopicParams && this.props.newTopicParams._topicName == this.props.topic);
|
||||
if (topic.isP2PType() && newTopic && !IMMEDIATE_P2P_SUBSCRIPTION) {
|
||||
topic.getMeta(topic.startMetaQuery().withDesc().build());
|
||||
} else {
|
||||
} else if (this.props.myUserId) {
|
||||
this.subscribe(topic);
|
||||
}
|
||||
} else if (topic.isSubscribed() && this.state.isReader && !prevState.isReader) {
|
||||
@@ -321,7 +328,8 @@ class MessagesView extends React.Component {
|
||||
showGoToLastButton: false,
|
||||
dragging: false,
|
||||
pins: [],
|
||||
selectedPin: 0
|
||||
selectedPin: 0,
|
||||
subsVersion: 0
|
||||
};
|
||||
} else if (nextProps.topic != prevState.topic) {
|
||||
const topic = nextProps.tinode.getTopic(nextProps.topic);
|
||||
@@ -478,7 +486,6 @@ class MessagesView extends React.Component {
|
||||
|
||||
// Is this a new topic?
|
||||
const newTopic = (this.props.newTopicParams && this.props.newTopicParams._topicName == this.props.topic);
|
||||
|
||||
// Don't request the tags. They are useless unless the user
|
||||
// is the owner and is editing the topic.
|
||||
let getQuery = topic.startMetaQuery().withLaterDesc().withLaterSub().withAux();
|
||||
@@ -503,6 +510,9 @@ class MessagesView extends React.Component {
|
||||
if (this.state.topic != ctrl.topic) {
|
||||
this.setState({topic: ctrl.topic});
|
||||
}
|
||||
if (this.state.deleted) {
|
||||
this.setState({deleted: false});
|
||||
}
|
||||
this.props.onNewTopicCreated(this.props.topic, ctrl.topic);
|
||||
// If there are unsent messages (except hard-failed and video call messages),
|
||||
// try sending them now. Hard-failed and unsent video call messages will be dropped.
|
||||
@@ -576,13 +586,14 @@ class MessagesView extends React.Component {
|
||||
showGoToLastButton: (pos > SHOW_GO_TO_LAST_DIST) && (pos < this.state.scrollPosition),
|
||||
});
|
||||
|
||||
if (this.state.fetchingMessages) {
|
||||
if (this.state.fetchingMessages || this.processingScrollEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.target.scrollTop <= FETCH_PAGE_TRIGGER) {
|
||||
const topic = this.props.tinode.getTopic(this.state.topic);
|
||||
if (topic && topic.isSubscribed()) {
|
||||
this.processingScrollEvent = true;
|
||||
const {min, max} = this.getVisibleMessageRange(event.target.getBoundingClientRect());
|
||||
const gaps = topic.msgHasMoreMessages(min, max, false);
|
||||
if (gaps.length > 0) {
|
||||
@@ -594,6 +605,7 @@ class MessagesView extends React.Component {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.processingScrollEvent = false;
|
||||
}
|
||||
|
||||
/* Mount drag and drop events */
|
||||
@@ -705,12 +717,12 @@ class MessagesView extends React.Component {
|
||||
if (this.state.topic) {
|
||||
const subs = [];
|
||||
const topic = this.props.tinode.getTopic(this.state.topic);
|
||||
topic.subscribers((sub) => {
|
||||
topic.subscribers(sub => {
|
||||
if (sub.online && sub.user != this.props.myUserId) {
|
||||
subs.push(sub);
|
||||
}
|
||||
});
|
||||
const newState = {onlineSubs: subs};
|
||||
const newState = {onlineSubs: subs, subsVersion: this.state.subsVersion + 1};
|
||||
const peer = topic.p2pPeerDesc();
|
||||
if (peer) {
|
||||
Object.assign(newState, {
|
||||
|
||||
@@ -48,7 +48,23 @@ class PasswordResetView extends React.PureComponent {
|
||||
|
||||
componentDidMount() {
|
||||
const parsed = HashNavigation.parseUrlHash(window.location.hash);
|
||||
this.setState({token: parsed.params.token, scheme: parsed.params.scheme});
|
||||
const newState = {
|
||||
token: parsed.params.token,
|
||||
scheme: parsed.params.scheme,
|
||||
code: parsed.params.code || ''
|
||||
}
|
||||
|
||||
this.savedCred = parsed.params.cred;
|
||||
if (this.props.reqCredMethod && parsed.params.cred) {
|
||||
newState[this.props.reqCredMethod] = this.savedCred;
|
||||
}
|
||||
this.setState(newState);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevProps.reqCredMethod != this.props.reqCredMethod && this.props.reqCredMethod) {
|
||||
this.setState({[this.props.reqCredMethod]: this.savedCred || ''});
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit(e) {
|
||||
@@ -57,12 +73,12 @@ class PasswordResetView extends React.PureComponent {
|
||||
// Reset using token.
|
||||
this.props.onReset(this.state.password.trim(), {
|
||||
scheme: 'token',
|
||||
token: this.state.token
|
||||
secret: this.state.token
|
||||
});
|
||||
} else if (this.state.code && this.props.reqCredMethod) {
|
||||
// Reset using security code.
|
||||
// The secret is structured as <code>:<cred_method>:<cred_value>, "123456:email:alice@example.com".
|
||||
const cred = this.state.email.trim() || this.state.tel.trim();
|
||||
const cred = this.props.reqCredMethod == 'email' ? this.state.email.trim() : this.state.tel.trim();
|
||||
this.props.onReset(this.state.password.trim(), {
|
||||
scheme: 'code',
|
||||
secret: btoa(`${this.state.code}:${this.props.reqCredMethod}:${cred}`)
|
||||
@@ -102,7 +118,7 @@ class PasswordResetView extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const showCredentialInput = !(this.state.token && this.state.scheme);
|
||||
const showCredentialInput = !((this.state.token || (this.state.code && this.props.reqCredMethod)) && this.state.scheme);
|
||||
const showPasswordInput = !showCredentialInput || this.state.haveCode || this.state.sent;
|
||||
|
||||
const passwordInput = (<>
|
||||
@@ -193,7 +209,7 @@ class PasswordResetView extends React.PureComponent {
|
||||
{this.state.haveCode || this.state.sent ? codeInput : null}
|
||||
{showPasswordInput ? passwordInput : null}
|
||||
<div className="dialog-buttons">
|
||||
{this.state.haveCode || this.state.sent ? null :
|
||||
{this.state.haveCode || this.state.sent || this.state.token || this.state.code ? null :
|
||||
<a href="#" onClick={this.handleShowCodeField} style={{marginRight: 'auto'}}>
|
||||
<FormattedMessage id="password_i_have_code" defaultMessage="I have code"
|
||||
description="Call to open field to enter password reset code" />
|
||||
|
||||
@@ -181,6 +181,7 @@ class SidepanelView extends React.PureComponent {
|
||||
tinode={this.props.tinode}
|
||||
myUserId={this.props.myUserId}
|
||||
trustedBadges={this.props.trustedBadges}
|
||||
reqCredMethod={this.props.reqCredMethod}
|
||||
onShowCountrySelector={this.props.onShowCountrySelector}
|
||||
onNavigate={this.props.onNavigate}
|
||||
onCredAdd={this.props.onCredAdd}
|
||||
|
||||
@@ -83,7 +83,12 @@ const messages = defineMessages({
|
||||
cred_confirmed_successfully: {
|
||||
id: 'cred_confirmed_successfully',
|
||||
defaultMessage: 'Confirmed successfully',
|
||||
description: 'Message explaining that the credential was successfully validated.'
|
||||
description: 'Notification message that the credential was successfully validated.'
|
||||
},
|
||||
password_reset_success: {
|
||||
id: 'password_reset_success',
|
||||
defaultMessage: 'Password reset successfully',
|
||||
description: 'Notification message that the password was successfully reset.'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -763,6 +768,7 @@ class TinodeWeb extends React.Component {
|
||||
parsed.path[0] = 'cred';
|
||||
parsed.params['method'] = params.cred[0];
|
||||
parsed.params['token'] = params.token;
|
||||
parsed.params['code'] = params.code;
|
||||
HashNavigation.navigateTo(HashNavigation.composeUrlHash(parsed.path, parsed.params));
|
||||
}
|
||||
|
||||
@@ -848,7 +854,7 @@ class TinodeWeb extends React.Component {
|
||||
}
|
||||
} else if (what == 'read') {
|
||||
this.resetContactList();
|
||||
} else if (what == 'msg') {
|
||||
} else if (what == 'msg' && cont) {
|
||||
// Check if the topic is archived. If so, don't play a sound.
|
||||
const topic = this.tinode.getTopic(cont.topic);
|
||||
const archived = topic && topic.isArchived();
|
||||
@@ -887,7 +893,7 @@ class TinodeWeb extends React.Component {
|
||||
} else {
|
||||
// TODO(gene): handle other types of notifications:
|
||||
// * ua -- user agent changes (maybe display a pictogram for mobile/desktop).
|
||||
console.info("Unsupported (yet) presence update:", what, "in", cont.topic);
|
||||
console.info("Unsupported (yet) presence update:", what, "in", (cont || {}).topic);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1019,6 +1025,7 @@ class TinodeWeb extends React.Component {
|
||||
} else {
|
||||
// Currently selected contact deleted
|
||||
this.setState({
|
||||
topicSelected: null,
|
||||
errorText: '',
|
||||
errorLevel: null,
|
||||
mobilePanel: 'sidepanel',
|
||||
@@ -1287,7 +1294,7 @@ class TinodeWeb extends React.Component {
|
||||
}
|
||||
|
||||
handleCredConfirm(method, response) {
|
||||
TinodeWeb.navigateToCredentialsView({cred: [method]});
|
||||
TinodeWeb.navigateToCredentialsView({cred: [method], code: response});
|
||||
}
|
||||
|
||||
// User clicked Cancel button in Setting or Sign Up panel.
|
||||
@@ -1709,6 +1716,7 @@ class TinodeWeb extends React.Component {
|
||||
if (this.tinode.isAuthenticated()) {
|
||||
// Adding new email or phone number in account setting.
|
||||
this.tinode.getMeTopic().setMeta({cred: {meth: cred, resp: code}})
|
||||
.then(_ => HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash, 'contacts')))
|
||||
.catch(err => this.handleError(err.message, 'err'));
|
||||
} else {
|
||||
// Credential validation on signup.
|
||||
@@ -1734,9 +1742,11 @@ class TinodeWeb extends React.Component {
|
||||
} else {
|
||||
this.tinode.connect()
|
||||
.then(_ => this.tinode.updateAccountBasic(null, null, newPassword, {scheme: tempAuth.scheme, secret: secret}))
|
||||
.then(_ => HashNavigation.navigateTo(''))
|
||||
.then(_ => {
|
||||
this.handleError(this.props.intl.formatMessage(messages.password_reset_success), 'info');
|
||||
HashNavigation.navigateTo('');
|
||||
})
|
||||
.catch(err => {
|
||||
// Socket error
|
||||
this.handleError(err.message, 'err');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ class ValidationView extends React.PureComponent {
|
||||
(numbers_only) => <input type="text" id="enter-confirmation-code"
|
||||
placeholder={numbers_only}
|
||||
value={this.state.code} onChange={this.handleCodeChange}
|
||||
onKeyPress={this.handleKeyPress} required />
|
||||
onKeyDown={this.handleKeyPress} required />
|
||||
}</FormattedMessage>
|
||||
</div>
|
||||
<div className="dialog-buttons">
|
||||
|
||||
Reference in New Issue
Block a user