merge next

This commit is contained in:
or-else
2023-06-10 12:14:59 -07:00
81 changed files with 28106 additions and 24984 deletions
+15 -7
View File
@@ -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>&nbsp;
<span> {cred.done ? null : <i className="material-icons">pending</i>}&nbsp;
{!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) {
+20 -8
View File
@@ -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, {
+21 -5
View File
@@ -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" />
+1
View File
@@ -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}
+16 -6
View File
@@ -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');
});
}
+1 -1
View File
@@ -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">