refactoring for better topic and account editing
This commit is contained in:
+32
-18
@@ -20,6 +20,7 @@ html, body {
|
||||
|
||||
tt {
|
||||
font-family: 'Roboto Mono', Courier, monospace;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -422,8 +423,8 @@ div.trusted-badge {
|
||||
}
|
||||
|
||||
.float-right {
|
||||
margin-left: auto;
|
||||
float: right;
|
||||
position: absolute;
|
||||
right: 0.5rem;
|
||||
}
|
||||
|
||||
.hr {
|
||||
@@ -440,6 +441,12 @@ div.trusted-badge {
|
||||
height: 0.015rem;
|
||||
}
|
||||
|
||||
.ellipsized {
|
||||
overflow-x: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Element which can take focus but shows no outline */
|
||||
.group-focus {
|
||||
outline: none;
|
||||
@@ -617,7 +624,7 @@ div.trusted-badge {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
/* END of InPlaceEdit styles */
|
||||
|
||||
@@ -842,7 +849,7 @@ i.search {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.flex-column.narrow {
|
||||
@@ -1021,12 +1028,14 @@ i.search {
|
||||
/* Various forms inside a panel (signup form, new topic form) */
|
||||
.panel-form {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.panel-form-row {
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0.75rem;
|
||||
line-height: 1.5;
|
||||
@@ -1036,6 +1045,7 @@ i.search {
|
||||
|
||||
.panel-form-column {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
flex-grow: 0;
|
||||
padding: 0.5rem 0.75rem;
|
||||
@@ -1054,15 +1064,19 @@ i.search {
|
||||
padding: 0!important;
|
||||
}
|
||||
|
||||
.panel-form-column > .group {
|
||||
margin-top: 0.25rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
/* Styling of avatar upload control */
|
||||
.avatar-upload {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
width: 8rem;
|
||||
height: 8.5rem;
|
||||
margin-left: 0.5rem;
|
||||
width: 10rem;
|
||||
height: 10.5rem;
|
||||
font-size: 4.5rem;
|
||||
padding: 0;
|
||||
}
|
||||
@@ -1086,14 +1100,14 @@ i.search {
|
||||
|
||||
.avatar-upload .avatar-box {
|
||||
position: relative;
|
||||
width: 8rem;
|
||||
height: 8rem;
|
||||
width: 10rem;
|
||||
height: 10rem;
|
||||
}
|
||||
|
||||
.avatar-upload .avatar-box > .material-icons {
|
||||
position: relative;
|
||||
font-size: 4rem;
|
||||
line-height: 8rem;
|
||||
line-height: 10rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@@ -1112,17 +1126,17 @@ i.search {
|
||||
border: 1px dashed #999;
|
||||
font-size: 1rem;
|
||||
flex-shrink: 0;
|
||||
width: 8rem;
|
||||
height: 8rem;
|
||||
width: 10rem;
|
||||
height: 10rem;
|
||||
overflow: hidden;
|
||||
color: #999;
|
||||
line-height: 8rem;
|
||||
line-height: 10rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.avatar-upload img.preview {
|
||||
width: 8rem;
|
||||
height: 8rem;
|
||||
width: 10rem;
|
||||
height: 10rem;
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
}
|
||||
@@ -1136,8 +1150,8 @@ i.search {
|
||||
color: #666;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 8rem;
|
||||
height: 8rem;
|
||||
width: 10rem;
|
||||
height: 10rem;
|
||||
justify-content: center;
|
||||
background: center no-repeat url("../img/broken_image.png");
|
||||
border-radius: 50%;
|
||||
@@ -1654,7 +1668,7 @@ img.inline-image::before {
|
||||
|
||||
img.image-preview::before {
|
||||
content: " ";
|
||||
background-size: 8rem 8rem;
|
||||
background-size: 10rem 10rem;
|
||||
}
|
||||
|
||||
/* Attachment styles */
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+8
-1
@@ -39,7 +39,7 @@
|
||||
"defaultMessage": "More",
|
||||
"description": "Action for showing more content",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
"obsolete": true
|
||||
},
|
||||
"action_report_chat": {
|
||||
"translation": "Unterhaltung melden",
|
||||
@@ -1284,5 +1284,12 @@
|
||||
"description": "Label for editing topic description",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"button_security": {
|
||||
"translation": "Sicherheit",
|
||||
"defaultMessage": "Security",
|
||||
"description": "Navigaton button for security panel.",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
+8
-1
@@ -39,7 +39,7 @@
|
||||
"defaultMessage": "More",
|
||||
"description": "Action for showing more content",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
"obsolete": true
|
||||
},
|
||||
"action_report_chat": {
|
||||
"translation": "Report Conversation",
|
||||
@@ -1284,5 +1284,12 @@
|
||||
"description": "Label for editing topic description",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"button_security": {
|
||||
"translation": "Security",
|
||||
"defaultMessage": "Security",
|
||||
"description": "Navigaton button for security panel.",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
+8
-1
@@ -39,7 +39,7 @@
|
||||
"defaultMessage": "More",
|
||||
"description": "Action for showing more content",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
"obsolete": true
|
||||
},
|
||||
"action_report_chat": {
|
||||
"translation": "Reportar conversación",
|
||||
@@ -1284,5 +1284,12 @@
|
||||
"description": "Label for editing topic description",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"button_security": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Security",
|
||||
"description": "Navigaton button for security panel.",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,6 @@
|
||||
"defaultMessage": "Leave Conversation",
|
||||
"description": "Flat button [Leave Conversation]"
|
||||
},
|
||||
"action_more": {
|
||||
"defaultMessage": "More",
|
||||
"description": "Action for showing more content"
|
||||
},
|
||||
"action_report_chat": {
|
||||
"defaultMessage": "Report Conversation",
|
||||
"description": "Flat button [Report Group]"
|
||||
@@ -99,6 +95,10 @@
|
||||
"defaultMessage": "Reset",
|
||||
"description": "Button [Reset]"
|
||||
},
|
||||
"button_security": {
|
||||
"defaultMessage": "Security",
|
||||
"description": "Navigaton button for security panel."
|
||||
},
|
||||
"button_send_request": {
|
||||
"defaultMessage": "Send request",
|
||||
"description": "Button [Send request]"
|
||||
|
||||
+8
-1
@@ -39,7 +39,7 @@
|
||||
"defaultMessage": "More",
|
||||
"description": "Action for showing more content",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
"obsolete": true
|
||||
},
|
||||
"action_report_chat": {
|
||||
"translation": "대화기록",
|
||||
@@ -1294,5 +1294,12 @@
|
||||
"description": "Label for editing topic description",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"button_security": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Security",
|
||||
"description": "Navigaton button for security panel.",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
+8
-1
@@ -39,7 +39,7 @@
|
||||
"defaultMessage": "More",
|
||||
"description": "Action for showing more content",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
"obsolete": true
|
||||
},
|
||||
"action_report_chat": {
|
||||
"translation": "Raportează conversația",
|
||||
@@ -1284,5 +1284,12 @@
|
||||
"description": "Label for editing topic description",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"button_security": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Security",
|
||||
"description": "Navigaton button for security panel.",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
+8
-1
@@ -39,7 +39,7 @@
|
||||
"defaultMessage": "More",
|
||||
"description": "Action for showing more content",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
"obsolete": true
|
||||
},
|
||||
"action_report_chat": {
|
||||
"translation": "Сообщить о нарушении",
|
||||
@@ -1284,5 +1284,12 @@
|
||||
"description": "Label for editing topic description",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"button_security": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Security",
|
||||
"description": "Navigaton button for security panel.",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
+8
-1
@@ -39,7 +39,7 @@
|
||||
"defaultMessage": "More",
|
||||
"description": "Action for showing more content",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
"obsolete": true
|
||||
},
|
||||
"action_report_chat": {
|
||||
"translation": "檢舉對話",
|
||||
@@ -1283,5 +1283,12 @@
|
||||
"description": "Label for editing topic description",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"button_security": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Security",
|
||||
"description": "Navigaton button for security panel.",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
+8
-1
@@ -39,7 +39,7 @@
|
||||
"defaultMessage": "More",
|
||||
"description": "Action for showing more content",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
"obsolete": true
|
||||
},
|
||||
"action_report_chat": {
|
||||
"translation": "检举垃圾邮件",
|
||||
@@ -1284,5 +1284,12 @@
|
||||
"description": "Label for editing topic description",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"button_security": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Security",
|
||||
"description": "Navigaton button for security panel.",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export default class HashNavigation {
|
||||
}
|
||||
if (parts[1]) {
|
||||
parts[1].split('&').forEach(function(part) {
|
||||
let item = part.split('=');
|
||||
const item = part.split('=');
|
||||
if (item[0]) {
|
||||
params[decodeURIComponent(item[0])] = decodeURIComponent(item[1]);
|
||||
}
|
||||
@@ -58,6 +58,12 @@ export default class HashNavigation {
|
||||
return this.composeUrlHash(parsed.path, parsed.params);
|
||||
}
|
||||
|
||||
static setUrlInfoPanel(hash, infopanel) {
|
||||
const parsed = this.parseUrlHash(hash);
|
||||
parsed.params.info = infopanel;
|
||||
return this.composeUrlHash(parsed.path, parsed.params);
|
||||
}
|
||||
|
||||
static setUrlTopic(hash, topic) {
|
||||
const parsed = this.parseUrlHash(hash);
|
||||
parsed.path[1] = topic;
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -6,6 +6,11 @@ import AvatarUpload from '../widgets/avatar-upload.jsx';
|
||||
import BadgeList from '../widgets/badge-list.jsx';
|
||||
|
||||
import { makeImageUrl } from '../lib/blob-helpers.js';
|
||||
import { MAX_TITLE_LENGTH, MAX_TOPIC_DESCRIPTION_LENGTH } from '../config.js';
|
||||
|
||||
function _clip(str, length) {
|
||||
return str && str.substring(0, length);
|
||||
}
|
||||
|
||||
export default class AccountSettingsView extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -13,7 +18,8 @@ export default class AccountSettingsView extends React.Component {
|
||||
|
||||
const me = this.props.tinode.getMeTopic();
|
||||
this.state = {
|
||||
fullName: me.public ? me.public.fn : undefined,
|
||||
fullName: _clip(me.public ? me.public.fn : undefined, MAX_TITLE_LENGTH),
|
||||
description: _clip(me.public ? me.public.note : undefined, MAX_TOPIC_DESCRIPTION_LENGTH),
|
||||
avatar: makeImageUrl(me.public ? me.public.photo : null)
|
||||
};
|
||||
}
|
||||
@@ -21,44 +27,54 @@ export default class AccountSettingsView extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="scrollable-panel">
|
||||
<div className="panel-form-row">
|
||||
<div className="panel-form-column">
|
||||
<div className="panel-form-column">
|
||||
<a href="#" className="flat-button float-right" onClick={(e) => {e.preventDefault(); this.props.onNavigate('general');}}>
|
||||
<i className="material-icons">edit</i>
|
||||
<FormattedMessage id="button_edit" defaultMessage="Edit" description="Call to action [Edit]" />
|
||||
</a>
|
||||
<center>
|
||||
<AvatarUpload
|
||||
tinode={this.props.tinode}
|
||||
avatar={this.state.avatar}
|
||||
readOnly={true}
|
||||
uid={this.props.myUserId}
|
||||
title={this.state.fullName} />
|
||||
</center>
|
||||
<div className="group">
|
||||
<label className="small">
|
||||
<FormattedMessage id="label_your_name" defaultMessage="Your name"
|
||||
description="Label for full name editing" />
|
||||
</label>
|
||||
<div className="large">{this.state.fullName}</div>
|
||||
<div>
|
||||
<label className="small"><FormattedMessage id="label_user_id" defaultMessage="ID:"
|
||||
description="Label for user address (ID)" /></label>
|
||||
<tt style={{whiteSpace: 'nowrap'}}>{this.props.myUserId}</tt>
|
||||
</div>
|
||||
<div className="large ellipsized">{this.state.fullName}</div>
|
||||
</div>
|
||||
<div className="group">
|
||||
<label className="small"><FormattedMessage id="label_user_id" defaultMessage="ID:"
|
||||
description="Label for user address (ID)" /></label>
|
||||
<tt>{this.props.myUserId}</tt>
|
||||
</div>
|
||||
<div className="group">
|
||||
<BadgeList trustedBadges={this.props.trustedBadges} />
|
||||
</div>
|
||||
<AvatarUpload
|
||||
tinode={this.props.tinode}
|
||||
avatar={this.state.avatar}
|
||||
readOnly={true}
|
||||
uid={this.props.myUserId}
|
||||
title={this.state.fullName} />
|
||||
</div>
|
||||
<div className="panel-form-row">
|
||||
<a href="#" className="flat-button" onClick={(e) => {e.preventDefault(); this.props.onBasicNavigate('general');}}>
|
||||
<i className="material-icons">edit</i>
|
||||
<FormattedMessage id="button_edit" defaultMessage="Edit" description="Call to action [Edit]" />
|
||||
</a>
|
||||
{this.state.description ?
|
||||
<div className="group">
|
||||
<label className="small">
|
||||
<FormattedMessage id="label_description" defaultMessage="Description"
|
||||
description="Label for editing topic description" />
|
||||
</label>
|
||||
<div>{this.state.description}</div>
|
||||
</div> : null}
|
||||
</div>
|
||||
<div className="hr" />
|
||||
<div className="panel-form-column">
|
||||
<a href="#" className="flat-button" onClick={(e) => {e.preventDefault(); this.props.onBasicNavigate('notif');}}>
|
||||
<a href="#" className="flat-button" onClick={(e) => {e.preventDefault(); this.props.onNavigate('notif');}}>
|
||||
<i className="material-icons">notifications</i> <FormattedMessage id="sidepanel_title_acc_notifications"
|
||||
defaultMessage="Notifications" description="Sidepanel title for AccNotificationsView." />
|
||||
</a>
|
||||
<a href="#" className="flat-button" onClick={(e) => {e.preventDefault(); this.props.onBasicNavigate('security');}}>
|
||||
<i className="material-icons">security</i> <FormattedMessage id="sidepanel_title_acc_security"
|
||||
defaultMessage="Security" description="Sidepanel title for AccSecurityView." />
|
||||
<a href="#" className="flat-button" onClick={(e) => {e.preventDefault(); this.props.onNavigate('security');}}>
|
||||
<i className="material-icons">security</i> <FormattedMessage id="button_security"
|
||||
defaultMessage="Security" description="Navigaton button for security panel." />
|
||||
</a>
|
||||
<a href="#" className="flat-button" onClick={(e) => {e.preventDefault(); this.props.onBasicNavigate('support');}}>
|
||||
<a href="#" className="flat-button" onClick={(e) => {e.preventDefault(); this.props.onNavigate('support');}}>
|
||||
<i className="material-icons">contact_support</i> <FormattedMessage id="sidepanel_title_acc_support"
|
||||
defaultMessage="Support" description="Sidepanel title for AccSupportView." />
|
||||
</a>
|
||||
|
||||
+58
-181
@@ -4,6 +4,8 @@ import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
import Tinode from 'tinode-sdk';
|
||||
|
||||
import TopicSecurityView from './topic-security-view.jsx';
|
||||
|
||||
import AvatarUpload from '../widgets/avatar-upload.jsx';
|
||||
import BadgeList from '../widgets/badge-list.jsx';
|
||||
import CheckBox from '../widgets/checkbox.jsx';
|
||||
@@ -16,7 +18,7 @@ import MoreButton from '../widgets/more-button.jsx';
|
||||
import PermissionsEditor from '../widgets/permissions-editor.jsx';
|
||||
import TagManager from '../widgets/tag-manager.jsx';
|
||||
|
||||
import { MAX_TITLE_LENGTH, NO_ACCESS_MODE } from '../config.js';
|
||||
import { MAX_TITLE_LENGTH, MAX_TOPIC_DESCRIPTION_LENGTH, NO_ACCESS_MODE } from '../config.js';
|
||||
|
||||
import { makeImageUrl } from '../lib/blob-helpers.js';
|
||||
import { arrayEqual, theCard } from '../lib/utils.js';
|
||||
@@ -42,58 +44,12 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Other',
|
||||
description: 'Label for the other user when the user is unnamed'
|
||||
},
|
||||
clear_messages: {
|
||||
id: 'action_clear_messages',
|
||||
defaultMessage: 'Clear Messages',
|
||||
description: 'Flat button [Clear Messages] (soft-delete messages)'
|
||||
},
|
||||
clear_messages_warning: {
|
||||
id: 'clear_messages_warning',
|
||||
defaultMessage: 'Are you sure you want to clear all messages? It cannot be undone.',
|
||||
description: 'Alert dialog warning when deleting all messages.'
|
||||
},
|
||||
delete_messages: {
|
||||
id: 'action_delete_messages',
|
||||
defaultMessage: 'Clear Messages for All',
|
||||
description: 'Flat button [Clear for All] (hard-delete all messages)'
|
||||
},
|
||||
delete_messages_warning: {
|
||||
id: 'delete_messages_warning',
|
||||
defaultMessage: 'Are you sure you want to delete all messages for everyone? It cannot be undone.',
|
||||
description: 'Alert dialog warning when hard-deleting all messages.'
|
||||
},
|
||||
leave_chat: {
|
||||
id: 'action_leave_chat',
|
||||
defaultMessage: 'Leave Conversation',
|
||||
description: 'Flat button [Leave Conversation]'
|
||||
},
|
||||
leave_chat_warning: {
|
||||
id: 'leave_chat_warning',
|
||||
defaultMessage: 'Are you sure you want to leave this conversation?',
|
||||
description: 'Alert dialog warning when unsubscribing from a chat.'
|
||||
},
|
||||
block_contact: {
|
||||
id: 'action_block_contact',
|
||||
defaultMessage: "Block Contact",
|
||||
description: "Flat button [Block Contact]"
|
||||
},
|
||||
block_contact_warning: {
|
||||
id: 'block_contact_warning',
|
||||
defaultMessage: 'Are you sure you want to block this contact?',
|
||||
description: 'Alert dialog warning when blocking a contact.'
|
||||
},
|
||||
report_chat: {
|
||||
id: 'action_report_chat',
|
||||
defaultMessage: 'Report Conversation',
|
||||
description: 'Flat button [Report Group]'
|
||||
},
|
||||
report_chat_warning: {
|
||||
id: 'report_chat_warning',
|
||||
defaultMessage: 'Are you sure you want to block and report this conversation?',
|
||||
description: 'Alert dialog warning when reporting a conversation for abuse'
|
||||
},
|
||||
});
|
||||
|
||||
function _clip(str, length) {
|
||||
return str && str.substring(0, length);
|
||||
}
|
||||
|
||||
class InfoView extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -109,6 +65,7 @@ class InfoView extends React.Component {
|
||||
groupTopic: undefined,
|
||||
channel: undefined,
|
||||
fullName: undefined,
|
||||
description: undefined,
|
||||
avatar: null,
|
||||
private: null,
|
||||
selectedContact: null,
|
||||
@@ -124,7 +81,6 @@ class InfoView extends React.Component {
|
||||
trustedBadges: [],
|
||||
showMemberPanel: false,
|
||||
showPermissionEditorFor: undefined,
|
||||
moreInfoExpanded: false,
|
||||
previousMetaDesc: undefined,
|
||||
previousSubsUpdated: undefined,
|
||||
previousTagsUpdated: undefined
|
||||
@@ -145,12 +101,7 @@ class InfoView extends React.Component {
|
||||
this.handleShowAddMembers = this.handleShowAddMembers.bind(this);
|
||||
this.handleHideAddMembers = this.handleHideAddMembers.bind(this);
|
||||
this.handleMemberUpdateRequest = this.handleMemberUpdateRequest.bind(this);
|
||||
this.handleDeleteMessages = this.handleDeleteMessages.bind(this);
|
||||
this.handleLeave = this.handleLeave.bind(this);
|
||||
this.handleBlock = this.handleBlock.bind(this);
|
||||
this.handleReport = this.handleReport.bind(this);
|
||||
this.handleMemberSelected = this.handleMemberSelected.bind(this);
|
||||
this.handleMoreInfo = this.handleMoreInfo.bind(this);
|
||||
this.handleTagsUpdated = this.handleTagsUpdated.bind(this);
|
||||
this.handleContextMenu = this.handleContextMenu.bind(this);
|
||||
}
|
||||
@@ -236,10 +187,11 @@ class InfoView extends React.Component {
|
||||
deleter: acs && acs.isDeleter(),
|
||||
muted: acs && acs.isMuted(),
|
||||
|
||||
fullName: topic.public ? topic.public.fn : undefined,
|
||||
fullName: _clip(topic.public ? topic.public.fn : undefined, MAX_TITLE_LENGTH),
|
||||
description: _clip(topic.public ? topic.public.note : undefined, MAX_TOPIC_DESCRIPTION_LENGTH),
|
||||
avatar: makeImageUrl(topic.public ? topic.public.photo : null),
|
||||
trustedBadges: badges,
|
||||
private: topic.private ? topic.private.comment : null,
|
||||
private: _clip(topic.private ? topic.private.comment : null, MAX_TITLE_LENGTH),
|
||||
address: topic.name,
|
||||
groupTopic: topic.isGroupType(),
|
||||
channel: topic.isChannelType(),
|
||||
@@ -433,66 +385,10 @@ class InfoView extends React.Component {
|
||||
this.setState({showMemberPanel: false});
|
||||
}
|
||||
|
||||
handleDeleteMessages(e) {
|
||||
e.preventDefault();
|
||||
const {formatMessage} = this.props.intl;
|
||||
this.props.onShowAlert(
|
||||
formatMessage(this.state.deleter ? messages.delete_messages : messages.clear_messages), // title
|
||||
formatMessage(this.state.deleter ? messages.delete_messages_warning : messages.clear_messages_warning), // content
|
||||
(() => { this.props.onDeleteMessages(this.props.topic); }), // onConfirm
|
||||
null, // "OK"
|
||||
true, // Show Reject button
|
||||
null // "Cancel"
|
||||
);
|
||||
}
|
||||
|
||||
handleLeave(e) {
|
||||
e.preventDefault();
|
||||
const {formatMessage} = this.props.intl;
|
||||
this.props.onShowAlert(
|
||||
formatMessage(messages.leave_chat), // title
|
||||
formatMessage(messages.leave_chat_warning), // content
|
||||
(() => { this.props.onLeaveTopic(this.props.topic); }), // onConfirm
|
||||
null, // "OK"
|
||||
true, // Show Reject button
|
||||
null // "Cancel"
|
||||
);
|
||||
}
|
||||
|
||||
handleBlock(e) {
|
||||
e.preventDefault();
|
||||
const {formatMessage} = this.props.intl;
|
||||
this.props.onShowAlert(
|
||||
formatMessage(messages.block_contact), // title
|
||||
formatMessage(messages.block_contact_warning), // content
|
||||
(() => { this.props.onBlockTopic(this.props.topic); }), // onConfirm
|
||||
null, // "OK"
|
||||
true, // Show Reject button
|
||||
null // "Cancel"
|
||||
);
|
||||
}
|
||||
|
||||
handleReport(e) {
|
||||
e.preventDefault();
|
||||
const {formatMessage} = this.props.intl;
|
||||
this.props.onShowAlert(
|
||||
formatMessage(messages.report_chat), // title
|
||||
formatMessage(messages.report_chat_warning), // content
|
||||
(() => { this.props.onReportTopic(this.props.topic); }), // onConfirm
|
||||
null, // "OK"
|
||||
true, // Show Reject button
|
||||
null // "Cancel"
|
||||
);
|
||||
}
|
||||
|
||||
handleMemberSelected(uid) {
|
||||
this.setState({selectedContact: uid});
|
||||
}
|
||||
|
||||
handleMoreInfo(open) {
|
||||
this.setState({moreInfoExpanded: open});
|
||||
}
|
||||
|
||||
handleTagsUpdated(tags) {
|
||||
if (!arrayEqual(this.state.tags.slice(0), tags.slice(0))) {
|
||||
this.props.onTopicTagsUpdate(this.props.topic, tags);
|
||||
@@ -569,42 +465,52 @@ class InfoView extends React.Component {
|
||||
/>
|
||||
:
|
||||
<div id="info-view-content" className="scrollable-panel">
|
||||
<div className="panel-form-row">
|
||||
<div className="panel-form-column">
|
||||
<div className="panel-form-column">
|
||||
<a href="#" className="flat-button float-right" onClick={(e) => {e.preventDefault(); this.props.onNavigate('general');}}>
|
||||
<i className="material-icons">edit</i>
|
||||
<FormattedMessage id="button_edit" defaultMessage="Edit" description="Call to action [Edit]" />
|
||||
</a>
|
||||
<center>
|
||||
<AvatarUpload
|
||||
tinode={this.props.tinode}
|
||||
avatar={this.state.avatar}
|
||||
readOnly={true}
|
||||
uid={this.props.topic}
|
||||
title={this.state.fullName} />
|
||||
</center>
|
||||
<div className="group">
|
||||
<div><label className="small">
|
||||
<FormattedMessage id="label_topic_name" defaultMessage="Name"
|
||||
description="Label for editing topic name" />
|
||||
</label></div>
|
||||
<div className="large">{this.state.fullName}</div>
|
||||
{this.state.private ?
|
||||
<>
|
||||
<div><label className="small">
|
||||
<FormattedMessage id="label_private" defaultMessage="Private comment"
|
||||
description="Label for editing 'private'" />
|
||||
</label></div>
|
||||
<div className="large">{this.state.private}</div>
|
||||
</>
|
||||
: null
|
||||
}
|
||||
<div>
|
||||
<label className="small"><FormattedMessage id="label_user_id" defaultMessage="ID:"
|
||||
description="Label for user address (ID)" /></label>
|
||||
<tt>{this.props.myUserId}</tt>
|
||||
<div className="large ellipsized">{this.state.fullName}</div>
|
||||
</div>
|
||||
{this.state.private ?
|
||||
<div className="group">
|
||||
<div><label className="small">
|
||||
<FormattedMessage id="label_private" defaultMessage="Private comment"
|
||||
description="Label for editing 'private'" />
|
||||
</label></div>
|
||||
<div className="large ellipsized">{this.state.private}</div>
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
<div className="group">
|
||||
<label className="small"><FormattedMessage id="label_user_id" defaultMessage="ID:"
|
||||
description="Label for user address (ID)" /></label>
|
||||
<tt>{this.state.address}</tt>
|
||||
</div>
|
||||
<div className="group">
|
||||
<BadgeList trustedBadges={this.state.trustedBadges} />
|
||||
</div>
|
||||
<AvatarUpload
|
||||
tinode={this.props.tinode}
|
||||
avatar={this.state.avatar}
|
||||
readOnly={true}
|
||||
uid={this.props.topic}
|
||||
title={this.state.fullName} />
|
||||
</div>
|
||||
<div className="panel-form-row">
|
||||
<a href="#" className="flat-button" onClick={(e) => {e.preventDefault(); this.props.onBasicNavigate('general');}}>
|
||||
<i className="material-icons">edit</i>
|
||||
<FormattedMessage id="button_edit" defaultMessage="Edit" description="Call to action [Edit]" />
|
||||
</a>
|
||||
{this.state.description ?
|
||||
<div className="group">
|
||||
<label className="small">
|
||||
<FormattedMessage id="label_description" defaultMessage="Description"
|
||||
description="Label for editing topic description" />
|
||||
</label>
|
||||
<div>{this.state.description}</div>
|
||||
</div> : null}
|
||||
</div>
|
||||
<div className="hr" />
|
||||
<div className="panel-form-column">
|
||||
@@ -613,16 +519,15 @@ class InfoView extends React.Component {
|
||||
<FormattedMessage id="label_muting_topic" defaultMessage="Muted:"
|
||||
description="Label for Muting/unmuting the topic" />
|
||||
</label>
|
||||
<CheckBox name="P" checked={this.state.muted}
|
||||
onChange={this.handleMuted} />
|
||||
<CheckBox name="P" checked={this.state.muted} onChange={this.handleMuted} />
|
||||
</div>
|
||||
<FormattedMessage id="action_more" defaultMessage="More"
|
||||
description="Action for showing more content">{
|
||||
(more) => <MoreButton
|
||||
title={more}
|
||||
open={this.state.moreInfoExpanded}
|
||||
onToggle={this.handleMoreInfo} />
|
||||
}</FormattedMessage>
|
||||
</div>
|
||||
<div className="hr" />
|
||||
<div className="panel-form-column">
|
||||
<a href="#" className="flat-button" onClick={(e) => {e.preventDefault(); this.props.onNavigate('security');}}>
|
||||
<i className="material-icons">security</i> <FormattedMessage id="button_security"
|
||||
defaultMessage="Security" description="Navigaton button for security panel." />
|
||||
</a>
|
||||
{this.state.moreInfoExpanded ?
|
||||
<div className="panel-form-column">
|
||||
{this.state.groupTopic ?
|
||||
@@ -713,34 +618,6 @@ class InfoView extends React.Component {
|
||||
:
|
||||
null
|
||||
}
|
||||
<div className="panel-form-column">
|
||||
{!this.state.channel ?
|
||||
<a href="#" className="flat-button" onClick={this.handleDeleteMessages}>
|
||||
<i className="material-icons">delete_outline</i> {
|
||||
formatMessage(this.state.deleter ? messages.delete_messages : messages.clear_messages)
|
||||
}
|
||||
</a>
|
||||
:
|
||||
null
|
||||
}
|
||||
<a href="#" className="danger flat-button" onClick={this.handleLeave}>
|
||||
<i className="material-icons">exit_to_app</i> {formatMessage(messages.leave_chat)}
|
||||
</a>
|
||||
{!this.state.groupTopic ?
|
||||
<a href="#" className="danger flat-button" onClick={this.handleBlock}>
|
||||
<i className="material-icons">block</i> {formatMessage(messages.block_contact)}
|
||||
</a>
|
||||
:
|
||||
null
|
||||
}
|
||||
{!this.state.owner ?
|
||||
<a href="#" className="danger flat-button" onClick={this.handleReport}>
|
||||
<i className="material-icons">report</i> {formatMessage(messages.report_chat)}
|
||||
</a>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
{this.state.groupTopic && this.state.sharer ?
|
||||
<>
|
||||
<div className="hr" />
|
||||
|
||||
@@ -100,7 +100,7 @@ class SidepanelView extends React.Component {
|
||||
}
|
||||
|
||||
handleNewTopic() {
|
||||
this.props.onBasicNavigate('newtpk');
|
||||
this.props.onNavigate('newtpk');
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -173,14 +173,13 @@ class SidepanelView extends React.Component {
|
||||
tinode={this.props.tinode}
|
||||
myUserId={this.props.myUserId}
|
||||
trustedBadges={this.props.trustedBadges}
|
||||
onBasicNavigate={this.props.onBasicNavigate} /> :
|
||||
onNavigate={this.props.onNavigate} /> :
|
||||
|
||||
(view === 'general' || view === 'crop') ?
|
||||
<TopicCommonView
|
||||
topic="me"
|
||||
tinode={this.props.tinode}
|
||||
myUserId={this.props.myUserId}
|
||||
onBasicNavigate={this.props.onBasicNavigate}
|
||||
onUpdateTopicDesc={this.props.onUpdateAccountDesc}
|
||||
onUpdateTags={this.props.onUpdateAccountTags}
|
||||
onCredAdd={this.props.onCredAdd}
|
||||
|
||||
+18
-11
@@ -187,7 +187,7 @@ class TinodeWeb extends React.Component {
|
||||
newTopicParams: null,
|
||||
loginDisabled: false,
|
||||
displayMobile: (window.innerWidth <= MEDIA_BREAKPOINT),
|
||||
showInfoPanel: false,
|
||||
infoPanel: undefined,
|
||||
mobilePanel: 'sidepanel',
|
||||
|
||||
contextMenuVisible: false,
|
||||
@@ -425,7 +425,7 @@ class TinodeWeb extends React.Component {
|
||||
|
||||
// Additional parameters of panels.
|
||||
this.setState({
|
||||
showInfoPanel: hash.params.info,
|
||||
infoPanel: hash.params.info,
|
||||
newTopicTabSelected: hash.params.tab
|
||||
});
|
||||
}
|
||||
@@ -862,7 +862,7 @@ class TinodeWeb extends React.Component {
|
||||
errorText: '',
|
||||
errorLevel: null,
|
||||
mobilePanel: 'topic-view',
|
||||
showInfoPanel: false
|
||||
infoPanel: undefined
|
||||
});
|
||||
// Different contact selected.
|
||||
if (this.state.topicSelected != topicName) {
|
||||
@@ -880,7 +880,7 @@ class TinodeWeb extends React.Component {
|
||||
mobilePanel: 'sidepanel',
|
||||
topicSelectedOnline: false,
|
||||
topicSelectedAcs: null,
|
||||
showInfoPanel: false
|
||||
infoPanel: undefined
|
||||
});
|
||||
|
||||
HashNavigation.navigateTo(HashNavigation.setUrlTopic('', null));
|
||||
@@ -1164,11 +1164,16 @@ class TinodeWeb extends React.Component {
|
||||
this.setState({errorText: '', errorLevel: null});
|
||||
}
|
||||
|
||||
// Basic nagigator by hash value. No need to bind to this.
|
||||
// Sidepanel navigator. No need to bind to 'this'.
|
||||
basicNavigator(hash) {
|
||||
HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash, hash));
|
||||
}
|
||||
|
||||
// Topic info navigator. No need to bind to 'this'.
|
||||
infoNavigator(hash) {
|
||||
HashNavigation.navigateTo(HashNavigation.setUrlInfoPanel(window.location.hash, hash));
|
||||
}
|
||||
|
||||
// Request to start a topic, new or selected from search results, or "by ID".
|
||||
handleStartTopicRequest(topicName, pub, priv, tags, isChannel) {
|
||||
// Check if topic is indeed new. If not, launch it.
|
||||
@@ -1435,13 +1440,13 @@ class TinodeWeb extends React.Component {
|
||||
}
|
||||
|
||||
handleShowInfoView() {
|
||||
HashNavigation.navigateTo(HashNavigation.addUrlParam(window.location.hash, 'info', true));
|
||||
this.setState({showInfoPanel: true});
|
||||
HashNavigation.navigateTo(HashNavigation.addUrlParam(window.location.hash, 'info', 'info'));
|
||||
this.setState({infoPanel: 'info'});
|
||||
}
|
||||
|
||||
handleHideInfoView() {
|
||||
HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'info'));
|
||||
this.setState({showInfoPanel: false});
|
||||
this.setState({infoPanel: undefined});
|
||||
}
|
||||
|
||||
handleMemberUpdateRequest(topicName, added, removed) {
|
||||
@@ -1582,7 +1587,7 @@ class TinodeWeb extends React.Component {
|
||||
onGlobalSettings={this.handleGlobalSettings}
|
||||
onSignUp={this.handleNewAccount}
|
||||
onSettings={this.handleSettings}
|
||||
onBasicNavigate={this.basicNavigator}
|
||||
onNavigate={this.basicNavigator}
|
||||
onLoginRequest={this.handleLoginRequest}
|
||||
onPersistenceChange={this.handlePersistenceChange}
|
||||
onCreateAccount={this.handleNewAccountRequest}
|
||||
@@ -1624,7 +1629,7 @@ class TinodeWeb extends React.Component {
|
||||
viewportWidth={this.state.viewportWidth}
|
||||
viewportHeight={this.state.viewportHeight}
|
||||
hideSelf={this.state.displayMobile &&
|
||||
(this.state.mobilePanel !== 'topic-view' || this.state.showInfoPanel)}
|
||||
(this.state.mobilePanel !== 'topic-view' || this.state.infoPanel)}
|
||||
topic={this.state.topicSelected}
|
||||
myUserId={this.state.myUserId}
|
||||
serverVersion={this.state.serverVersion}
|
||||
@@ -1647,7 +1652,7 @@ class TinodeWeb extends React.Component {
|
||||
onNewChat={this.handleNewChatInvitation}
|
||||
sendMessage={this.handleSendMessage} />
|
||||
|
||||
{this.state.showInfoPanel ?
|
||||
{this.state.infoPanel ?
|
||||
<InfoView
|
||||
tinode={this.tinode}
|
||||
connected={this.state.connected}
|
||||
@@ -1655,12 +1660,14 @@ class TinodeWeb extends React.Component {
|
||||
topic={this.state.topicSelected}
|
||||
searchableContacts={this.state.searchableContacts}
|
||||
myUserId={this.state.myUserId}
|
||||
panel={this.state.infoPanel}
|
||||
|
||||
errorText={this.state.errorText}
|
||||
errorLevel={this.state.errorLevel}
|
||||
errorAction={this.state.errorAction}
|
||||
errorActionText={this.state.errorActionText}
|
||||
|
||||
onNavigate={this.infoNavigator}
|
||||
onTopicDescUpdate={this.handleTopicUpdateRequest}
|
||||
onCancel={this.handleHideInfoView}
|
||||
onShowAlert={this.handleShowAlert}
|
||||
|
||||
@@ -64,7 +64,7 @@ export default class TopicCommonView extends React.Component {
|
||||
}
|
||||
|
||||
handleCredEntered(e) {
|
||||
let value = this.state.newCred.trim();
|
||||
const value = this.state.newCred.trim();
|
||||
if (!value) {
|
||||
this.setState({addCredActive: false, addCredInvalid: false});
|
||||
return;
|
||||
@@ -92,14 +92,17 @@ export default class TopicCommonView extends React.Component {
|
||||
const credentials = [];
|
||||
if (this.state.isMe) {
|
||||
this.state.credentials.map((cred) => {
|
||||
credentials.push(<div key={cred.meth + ":" + cred.val + ":" + cred.done}>{cred.meth}: <tt>{cred.val}</tt>
|
||||
<span > {!cred.done ?
|
||||
<a href="#" onClick={(e) => {e.preventDefault(); this.props.onCredConfirm(cred.meth, cred.val);}}>
|
||||
credentials.push(
|
||||
<div key={cred.meth + ":" + cred.val + ":" + cred.done}>{cred.meth}: <tt>{cred.val}</tt>
|
||||
<span> {!cred.done ?
|
||||
<a href="#" onClick={(e) => {e.preventDefault(); this.props.onCredConfirm(cred.meth, cred.val);}}>
|
||||
<FormattedMessage id="validate_credential_action" defaultMessage="confirm"
|
||||
description="Validate credentail call to action" />
|
||||
</a>
|
||||
: null} <a href="#" onClick={(e) => {e.preventDefault(); this.props.onCredDelete(cred.meth, cred.val);}}><i
|
||||
className="material-icons gray">delete_outline</i></a></span></div>);
|
||||
className="material-icons gray">delete_outline</i></a></span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import React from 'react';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
|
||||
import { REM_SIZE } from '../config.js';
|
||||
import HashNavigation from '../lib/navigation.js';
|
||||
|
||||
const messages = defineMessages({
|
||||
info: {
|
||||
|
||||
@@ -173,37 +173,57 @@ export default class TopicDescEdit extends React.Component {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="panel-form-column">
|
||||
<div className="panel-form-row">
|
||||
<div className="panel-form-column">
|
||||
{this.state.isMe ? <>
|
||||
<label className="small">
|
||||
<FormattedMessage id="label_your_name" defaultMessage="Your name"
|
||||
description="Label for full name editing" />
|
||||
</label>
|
||||
<div><FormattedMessage id="full_name_prompt" defaultMessage="Full name, e.g. John Doe"
|
||||
<div className="panel-form-column">
|
||||
<center>
|
||||
<AvatarUpload
|
||||
tinode={this.props.tinode}
|
||||
avatar={this.state.avatar}
|
||||
readOnly={!editable}
|
||||
uid={this.props.topic}
|
||||
title={this.state.fullName}
|
||||
onImageUpdated={this.handleImageUpdated}
|
||||
onError={this.props.onError} />
|
||||
</center>
|
||||
{this.state.isMe ?
|
||||
<div className="group">
|
||||
<label className="small">
|
||||
<FormattedMessage id="label_your_name" defaultMessage="Your name"
|
||||
description="Label for full name editing" />
|
||||
</label>
|
||||
<div>
|
||||
<FormattedMessage id="full_name_prompt" defaultMessage="Full name, e.g. John Doe"
|
||||
description="Input placeholder for person's full name">{
|
||||
(full_name_placeholder) => <InPlaceEdit
|
||||
placeholder={full_name_placeholder}
|
||||
value={this.state.fullName}
|
||||
required={true}
|
||||
onFinished={this.handleFullNameUpdate} />
|
||||
}</FormattedMessage></div>
|
||||
</> : <>
|
||||
(full_name_placeholder) => <InPlaceEdit
|
||||
placeholder={full_name_placeholder}
|
||||
value={this.state.fullName}
|
||||
required={true}
|
||||
onFinished={this.handleFullNameUpdate} />
|
||||
}</FormattedMessage>
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
<>
|
||||
<div className="group">
|
||||
<div><label className="small">
|
||||
<FormattedMessage id="label_topic_name" defaultMessage="Name"
|
||||
description="Label for editing topic name" />
|
||||
</label></div>
|
||||
<div><InPlaceEdit
|
||||
<div>
|
||||
<InPlaceEdit
|
||||
placeholder={this.state.groupTopic ? "Group name" : <i>Unknown</i>}
|
||||
readOnly={!this.state.owner}
|
||||
value={this.state.fullName}
|
||||
required={true}
|
||||
onFinished={this.handleFullNameUpdate} /></div>
|
||||
<div><label className="small">
|
||||
<FormattedMessage id="label_private" defaultMessage="Private comment"
|
||||
description="Label for editing 'private'" />
|
||||
</label></div>
|
||||
onFinished={this.handleFullNameUpdate} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="group">
|
||||
<div>
|
||||
<label className="small">
|
||||
<FormattedMessage id="label_private" defaultMessage="Private comment"
|
||||
description="Label for editing 'private'" />
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<FormattedMessage id="private_editing_placeholder"
|
||||
defaultMessage="Visible to you only"
|
||||
@@ -214,18 +234,10 @@ export default class TopicDescEdit extends React.Component {
|
||||
onFinished={this.handlePrivateUpdate} />
|
||||
}</FormattedMessage>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
<AvatarUpload
|
||||
tinode={this.props.tinode}
|
||||
avatar={this.state.avatar}
|
||||
readOnly={!editable}
|
||||
uid={this.props.topic}
|
||||
title={this.state.fullName}
|
||||
onImageUpdated={this.handleImageUpdated}
|
||||
onError={this.props.onError} />
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
<div className="group">
|
||||
<div><label className="small">
|
||||
<FormattedMessage id="label_description" defaultMessage="Description"
|
||||
description="Label for editing topic description" />
|
||||
@@ -243,21 +255,22 @@ export default class TopicDescEdit extends React.Component {
|
||||
}</FormattedMessage>
|
||||
</div>
|
||||
</div>
|
||||
{editable ?
|
||||
<>
|
||||
<div className="hr" />
|
||||
<FormattedMessage id="title_tag_manager" defaultMessage="Tags (user discovery)"
|
||||
description="Section title for TagManager">{
|
||||
</div>
|
||||
{editable ?
|
||||
<>
|
||||
<div className="hr" />
|
||||
<FormattedMessage id="title_tag_manager" defaultMessage="Tags (user discovery)"
|
||||
description="Section title for TagManager">{
|
||||
(title_tag_manager) => <TagManager
|
||||
title={title_tag_manager}
|
||||
activated={false}
|
||||
tags={this.state.tags}
|
||||
tinode={this.props.tinode}
|
||||
onSubmit={this.handleTagsUpdated} />
|
||||
}</FormattedMessage>
|
||||
</>
|
||||
: null
|
||||
}
|
||||
}</FormattedMessage>
|
||||
</>
|
||||
: null
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
+464
-306
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user