sending messages to self works
This commit is contained in:
+5
-1
@@ -1,7 +1,7 @@
|
||||
import { PACKAGE_VERSION } from './version.js';
|
||||
|
||||
// Name of this application, used in the User-Agent.
|
||||
export const APP_NAME = 'TinodeWeb/' + (PACKAGE_VERSION || '0.21');
|
||||
export const APP_NAME = 'TinodeWeb/' + (PACKAGE_VERSION || '0.24');
|
||||
|
||||
// API key. Use https://github.com/tinode/chat/tree/master/keygen to generate your own
|
||||
export const API_KEY = 'AQEAAAABAAD_rAp4DJh05a1HAwFT3A6K';
|
||||
@@ -140,4 +140,8 @@ export const WAKE_UP_TIMEOUT = 80000;
|
||||
// Timer interval between checks if computer woke up from sleep (1 sec).
|
||||
export const WAKE_UP_TICK = 1000;
|
||||
|
||||
// Swipe distance to register as a gesture, in pixels.
|
||||
export const MIN_SWIPE_DISTANCE = REM_SIZE * 3;
|
||||
|
||||
// Self-topic avatar as an SVG icon encoded as data URI
|
||||
export const SELF_AVATAR_URI = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB2aWV3Qm94PSIwIDAgMTcgMTciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0zLjU2MiAxdjE1LjQ1OWw0LjY4Ni0zLjI3IDQuNzUyIDMuMjZ2LTE1LjQ0OWgtOS40Mzh6TTEyIDE0LjU1MWwtMy43NTYtMi41NzgtMy42ODEgMi41Njh2LTEyLjU0MWg3LjQzN3YxMi41NTF6IiBmaWxsPSIjMzMzMzMzIiAvPgo8L3N2Zz4=' // eslint-disable-line max-len
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1661,5 +1661,15 @@
|
||||
"description": "Title for scanning QR code",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"self_topic_comment": {
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"self_topic_name": {
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1661,5 +1661,15 @@
|
||||
"description": "Title for scanning QR code",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"self_topic_comment": {
|
||||
"translation": "Notes, messages, links, files saved for posterity",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"self_topic_name": {
|
||||
"translation": "Saved messages",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1661,5 +1661,15 @@
|
||||
"description": "Title for scanning QR code",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"self_topic_comment": {
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"self_topic_name": {
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -816,6 +816,10 @@
|
||||
"defaultMessage": "List like alice@example.com, +17025550003...",
|
||||
"description": "Placeholder in contacts search field"
|
||||
},
|
||||
"self_topic_comment": {
|
||||
},
|
||||
"self_topic_name": {
|
||||
},
|
||||
"sidepanel_title_acc_notifications": {
|
||||
"defaultMessage": "Notifications",
|
||||
"description": "Sidepanel title for AccNotificationsView."
|
||||
|
||||
@@ -1661,5 +1661,15 @@
|
||||
"description": "Title for scanning QR code",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"self_topic_comment": {
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"self_topic_name": {
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1661,5 +1661,15 @@
|
||||
"description": "Title for scanning QR code",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"self_topic_comment": {
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"self_topic_name": {
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1661,5 +1661,15 @@
|
||||
"description": "Title for scanning QR code",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"self_topic_comment": {
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"self_topic_name": {
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1661,5 +1661,15 @@
|
||||
"description": "Title for scanning QR code",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"self_topic_comment": {
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"self_topic_name": {
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1661,5 +1661,15 @@
|
||||
"description": "Menu item [Unpin] for un-pinning the message.",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"self_topic_comment": {
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"self_topic_name": {
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1661,5 +1661,15 @@
|
||||
"description": "Menu item [Unpin] for un-pinning the message.",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"self_topic_comment": {
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"self_topic_name": {
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1661,5 +1661,15 @@
|
||||
"description": "Title for scanning QR code",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"self_topic_comment": {
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"self_topic_name": {
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1661,5 +1661,15 @@
|
||||
"description": "Title for scanning QR code",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"self_topic_comment": {
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"self_topic_name": {
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -164,7 +164,7 @@ export function sanitizeUrlForMime(url, mimeMajor) {
|
||||
}
|
||||
|
||||
// Is this a data: URL of the appropriate mime type?
|
||||
const re = new RegExp(`data:${mimeMajor}\/[a-z0-9.-]+;base64,`, 'i');
|
||||
const re = new RegExp(`data:${mimeMajor}\/[-+.a-z0-9]+;base64,`, 'i');
|
||||
if (re.test(url.trim())) {
|
||||
return url;
|
||||
}
|
||||
|
||||
+60
-37
@@ -97,6 +97,7 @@ class InfoView extends React.Component {
|
||||
muted: false,
|
||||
address: null,
|
||||
groupTopic: undefined,
|
||||
isSelf: false,
|
||||
channel: undefined,
|
||||
fullName: undefined,
|
||||
description: undefined,
|
||||
@@ -203,6 +204,8 @@ class InfoView extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
const isSelf = topic.isSelfType();
|
||||
|
||||
this.setState({
|
||||
owner: acs && acs.isOwner(),
|
||||
admin: acs && acs.isAdmin(),
|
||||
@@ -210,14 +213,19 @@ class InfoView extends React.Component {
|
||||
deleter: acs && acs.isDeleter(),
|
||||
muted: acs && acs.isMuted(),
|
||||
|
||||
fullName: clipStr(topic.public && topic.public.fn, MAX_TITLE_LENGTH),
|
||||
description: clipStr(topic.public && topic.public.note, MAX_TOPIC_DESCRIPTION_LENGTH),
|
||||
fullName: isSelf ?
|
||||
props.intl.formatMessage({id: 'self_topic_name'}) :
|
||||
clipStr(topic.public && topic.public.fn, MAX_TITLE_LENGTH),
|
||||
description: isSelf ?
|
||||
props.intl.formatMessage({id: 'self_topic_comment'}) :
|
||||
clipStr(topic.public && topic.public.note, MAX_TOPIC_DESCRIPTION_LENGTH),
|
||||
avatar: makeImageUrl(topic.public ? topic.public.photo : null),
|
||||
trustedBadges: badges,
|
||||
private: clipStr(topic.private && topic.private.comment, MAX_TITLE_LENGTH),
|
||||
archived: topic.isArchived(),
|
||||
address: topic.name,
|
||||
groupTopic: topic.isGroupType(),
|
||||
isSelf: isSelf,
|
||||
channel: topic.isChannelType() || topic.chan,
|
||||
access: acs ? acs.getMode() : undefined,
|
||||
modeGiven: acs ? acs.getGiven() : undefined,
|
||||
@@ -539,10 +547,13 @@ class InfoView extends React.Component {
|
||||
:
|
||||
<div id="info-view-content" className="scrollable-panel">
|
||||
<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>
|
||||
{!this.state.isSelf ?
|
||||
<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>
|
||||
: null
|
||||
}
|
||||
<center>
|
||||
<AvatarUpload
|
||||
tinode={this.props.tinode}
|
||||
@@ -569,24 +580,30 @@ class InfoView extends React.Component {
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
<div className="panel-form-row">
|
||||
<div>
|
||||
<label className="small"><FormattedMessage id="label_user_id" defaultMessage="ID:"
|
||||
description="Label for user address (ID)" /></label>
|
||||
<tt>{this.state.address}</tt>
|
||||
{!this.state.isSelf ?
|
||||
<div className="panel-form-row">
|
||||
<div>
|
||||
<label className="small"><FormattedMessage id="label_user_id" defaultMessage="ID:"
|
||||
description="Label for user address (ID)" /></label>
|
||||
<tt>{this.state.address}</tt>
|
||||
</div>
|
||||
<div style={{marginLeft: 'auto'}}>
|
||||
<a href="#" onClick={this.handleCopyID}>
|
||||
<i className="material-icons">content_copy</i>
|
||||
</a>
|
||||
<a href="#" onClick={this.handleShowQRCode}>
|
||||
<i className="material-icons">qr_code</i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{marginLeft: 'auto'}}>
|
||||
<a href="#" onClick={this.handleCopyID}>
|
||||
<i className="material-icons">content_copy</i>
|
||||
</a>
|
||||
<a href="#" onClick={this.handleShowQRCode}>
|
||||
<i className="material-icons">qr_code</i>
|
||||
</a>
|
||||
: null
|
||||
}
|
||||
{this.state.trustedBadges.length > 0 ?
|
||||
<div className="group">
|
||||
<BadgeList trustedBadges={this.state.trustedBadges} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="group">
|
||||
<BadgeList trustedBadges={this.state.trustedBadges} />
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
{this.state.description ?
|
||||
<div className="group">
|
||||
<label className="small">
|
||||
@@ -596,14 +613,17 @@ class InfoView extends React.Component {
|
||||
<div>{this.state.description}</div>
|
||||
</div> : null}
|
||||
</div>
|
||||
<div className="hr" />
|
||||
<div className="panel-form-row">
|
||||
<label>
|
||||
<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} />
|
||||
</div>
|
||||
{!this.state.isSelf ?
|
||||
<><div className="hr" />
|
||||
<div className="panel-form-row">
|
||||
<label>
|
||||
<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} />
|
||||
</div></>
|
||||
: null
|
||||
}
|
||||
{this.state.archived ?
|
||||
<div className="panel-form-row">
|
||||
<label>
|
||||
@@ -615,13 +635,16 @@ class InfoView extends React.Component {
|
||||
:
|
||||
null
|
||||
}
|
||||
<div className="hr" />
|
||||
<div className="panel-form-row">
|
||||
<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>
|
||||
</div>
|
||||
{!this.state.isSelf ?
|
||||
<><div className="hr" />
|
||||
<div className="panel-form-row">
|
||||
<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>
|
||||
</div></>
|
||||
: null
|
||||
}
|
||||
{this.state.groupTopic && this.state.sharer ?
|
||||
<>
|
||||
<div className="hr" />
|
||||
|
||||
@@ -76,7 +76,7 @@ const messages = defineMessages({
|
||||
id: 'drag_file',
|
||||
defaultMessage: 'Drag file here',
|
||||
description: 'Prompt on the file drag-n-drop overlay banner'
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Checks if the access permissions are granted but not yet accepted.
|
||||
@@ -377,7 +377,14 @@ class MessagesView extends React.Component {
|
||||
onlineSubs: subs
|
||||
});
|
||||
|
||||
if (topic.public) {
|
||||
if (topic.isSelfType()) {
|
||||
Object.assign(nextState, {
|
||||
title: nextProps.intl.formatMessage({
|
||||
id: 'self_topic_name'
|
||||
}),
|
||||
avatar: true
|
||||
});
|
||||
} else if (topic.public) {
|
||||
Object.assign(nextState, {
|
||||
title: topic.public.fn,
|
||||
avatar: makeImageUrl(topic.public.photo)
|
||||
@@ -627,7 +634,14 @@ class MessagesView extends React.Component {
|
||||
}
|
||||
|
||||
handleDescChange(desc) {
|
||||
if (desc.public) {
|
||||
if (Tinode.isSelfTopicName(this.props.topic)) {
|
||||
this.setState({
|
||||
title: this.props.intl.formatMessage({
|
||||
id: 'self_topic_name'
|
||||
}),
|
||||
avatar: true
|
||||
});
|
||||
} else if (desc.public) {
|
||||
this.setState({
|
||||
title: desc.public.fn,
|
||||
avatar: makeImageUrl(desc.public.photo)
|
||||
@@ -1601,7 +1615,7 @@ class MessagesView extends React.Component {
|
||||
}
|
||||
}
|
||||
const avatar = this.state.avatar || true;
|
||||
const online = this.state.deleted ? null :
|
||||
const online = this.state.deleted || topic.isSelfType() ? null :
|
||||
this.props.online ? 'online' + (this.state.typingIndicator ? ' typing' : '') : 'offline';
|
||||
|
||||
const titleClass = 'panel-title' + (this.state.deleted ? ' deleted' : '');
|
||||
|
||||
@@ -125,6 +125,7 @@ class NewTopicView extends React.Component {
|
||||
contacts={this.props.searchResults}
|
||||
myUserId={this.props.myUserId}
|
||||
emptyListMessage={no_contacts_placeholder}
|
||||
showSelfTopic={true}
|
||||
showOnline={false}
|
||||
showUnread={false}
|
||||
showContextMenu={false}
|
||||
|
||||
@@ -949,7 +949,7 @@ class TinodeWeb extends React.Component {
|
||||
newState.ready = true;
|
||||
}
|
||||
|
||||
this.tinode.getMeTopic().contacts((c) => {
|
||||
this.tinode.getMeTopic().contacts(c => {
|
||||
if (!c.topic && !c.user) {
|
||||
// Contacts expect c.topic to be set.
|
||||
c.topic = c.name;
|
||||
@@ -1609,7 +1609,7 @@ class TinodeWeb extends React.Component {
|
||||
}
|
||||
|
||||
let muted = false, blocked = false, self_blocked = false, subscribed = false, deleter = false,
|
||||
archived = false, webrtc = false, writer = false;
|
||||
archived = false, webrtc = false, writer = false, p2p = false, self = false;
|
||||
if (topic) {
|
||||
subscribed = topic.isSubscribed();
|
||||
archived = topic.isArchived();
|
||||
@@ -1625,24 +1625,26 @@ class TinodeWeb extends React.Component {
|
||||
}
|
||||
|
||||
webrtc = writer && !!this.tinode.getServerParam('iceServers');
|
||||
p2p = Tinode.isP2PTopicName(topicName);
|
||||
self = Tinode.isSelfTopicName(topicName);
|
||||
|
||||
return [
|
||||
subscribed ? {
|
||||
title: this.props.intl.formatMessage(messages.menu_item_info),
|
||||
handler: this.handleShowInfoView
|
||||
} : null,
|
||||
subscribed && Tinode.isP2PTopicName(topicName) && webrtc ? {
|
||||
subscribed && p2p && webrtc ? {
|
||||
title: this.props.intl.formatMessage(messages.menu_item_audio_call),
|
||||
handler: this.handleStartAudioCall
|
||||
} : null,
|
||||
subscribed && Tinode.isP2PTopicName(topicName) && webrtc ? {
|
||||
subscribed && p2p && webrtc ? {
|
||||
title: this.props.intl.formatMessage(messages.menu_item_video_call),
|
||||
handler: this.handleStartVideoCall
|
||||
} : null,
|
||||
subscribed ? 'messages_clear' : null,
|
||||
subscribed && deleter ? 'messages_clear_hard' : null,
|
||||
muted ? (blocked ? null : 'topic_unmute') : 'topic_mute',
|
||||
self_blocked ? 'topic_unblock' : 'topic_block',
|
||||
subscribed && deleter && !self ? 'messages_clear_hard' : null,
|
||||
self ? null : (muted ? (blocked ? null : 'topic_unmute') : 'topic_mute'),
|
||||
self ? null : (self_blocked ? 'topic_unblock' : 'topic_block'),
|
||||
archived ? 'topic_restore' : 'topic_archive',
|
||||
'topic_delete'
|
||||
];
|
||||
|
||||
@@ -30,7 +30,28 @@ class ContactList extends React.Component {
|
||||
const { formatMessage } = this.props.intl;
|
||||
const showCheckmark = Array.isArray(this.props.topicSelected);
|
||||
const contactNodes = [];
|
||||
|
||||
// Count of contacts, excluding action items.
|
||||
let contactsCount = 0;
|
||||
if (this.props.showSelfTopic) {
|
||||
const selected = showCheckmark ?
|
||||
(this.props.topicSelected.indexOf('slf') > -1) :
|
||||
(this.props.topicSelected === 'slf');
|
||||
contactNodes.push(
|
||||
<Contact
|
||||
tinode={this.props.tinode}
|
||||
avatar={true}
|
||||
showMode={this.props.showMode}
|
||||
selected={selected}
|
||||
showCheckmark={showCheckmark}
|
||||
onSelected={this.props.onTopicSelected}
|
||||
showContextMenu={this.props.showContextMenu}
|
||||
index={contactNodes.length}
|
||||
item='slf'
|
||||
key='slf' />
|
||||
);
|
||||
contactsCount ++;
|
||||
}
|
||||
if (this.props.contacts && this.props.contacts.length > 0) {
|
||||
this.props.contacts.forEach(c => {
|
||||
if (c.action) {
|
||||
@@ -55,6 +76,7 @@ class ContactList extends React.Component {
|
||||
filterOn.push(('' + c.public.fn).toLowerCase());
|
||||
}
|
||||
if (!this.props.filterFunc(this.props.filter, filterOn)) {
|
||||
console.log('Filtered:', key);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
+28
-6
@@ -1,6 +1,8 @@
|
||||
// A single topic or user.
|
||||
import React from 'react';
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
import { Tinode } from 'tinode-sdk';
|
||||
|
||||
import ContactBadges from './contact-badges.jsx';
|
||||
import LetterTile from './letter-tile.jsx';
|
||||
@@ -11,6 +13,19 @@ import { Drafty } from 'tinode-sdk';
|
||||
import { previewFormatter } from '../lib/formatters.js';
|
||||
import { deliveryMarker } from '../lib/utils.js';
|
||||
|
||||
const messages = defineMessages({
|
||||
self_topic_name: {
|
||||
id: 'self_topic_name',
|
||||
defaultMessage: 'Saved messages',
|
||||
description: 'Name of self topic for UI'
|
||||
},
|
||||
self_topic_comment: {
|
||||
id: 'self_topic_comment',
|
||||
defaultMessage: 'Notes, messages, links, files saved for posterity',
|
||||
description: 'Comment for self topic for UI'
|
||||
}
|
||||
});
|
||||
|
||||
class Contact extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -36,9 +51,14 @@ class Contact extends React.Component {
|
||||
|
||||
render() {
|
||||
let title = this.props.title;
|
||||
const self = Tinode.isSelfTopicName(this.props.item);
|
||||
if (!title) {
|
||||
title = <i><FormattedMessage id="unnamed_topic" defaultMessage="Unnamed"
|
||||
description="Title shown when the topic has no name" /></i>;
|
||||
if (self) {
|
||||
title = this.props.intl.formatMessage(messages.self_topic_name);
|
||||
} else {
|
||||
title = <i><FormattedMessage id="unnamed_topic" defaultMessage="Unnamed"
|
||||
description="Title shown when the topic has no name" /></i>;
|
||||
}
|
||||
} else if (title.length > 30) {
|
||||
// FIXME: this is probably wrong for RTL languages.
|
||||
title = title.substring(0, 28) + '…';
|
||||
@@ -88,6 +108,8 @@ class Contact extends React.Component {
|
||||
(icon.color ? ' ' + icon.color : '')}>{icon.name}</i> : null;
|
||||
const titleClass = 'contact-title' + (this.props.deleted ? ' deleted' : '');
|
||||
|
||||
const comment = preview || this.props.comment ||
|
||||
(self ? this.props.intl.formatMessage(messages.self_topic_comment) : '\u00A0');
|
||||
return (
|
||||
<li className={!this.props.showCheckmark && this.props.selected ? 'selected' : null} onClick={this.handleClick}>
|
||||
<div className="avatar-box">
|
||||
@@ -98,7 +120,7 @@ class Contact extends React.Component {
|
||||
topic={this.props.item}
|
||||
deleted={this.props.deleted} />
|
||||
{this.props.deleted ? <i className="deleted material-icons">cancel</i> :
|
||||
this.props.showOnline ? <span className={online} /> :
|
||||
(!self && this.props.showOnline) ? <span className={online} /> :
|
||||
(this.props.showCheckmark && this.props.selected) ?
|
||||
<i className="checkmark material-icons">check_circle</i>
|
||||
: null}
|
||||
@@ -107,12 +129,12 @@ class Contact extends React.Component {
|
||||
<div><span className={titleClass}>{title}</span>
|
||||
{this.props.isGroup ? <i className="material-icons as-badge">group</i> : null}
|
||||
{this.props.isChannel ? <img src="/img/channel.png" className="channel" alt="channel" /> : null}
|
||||
<ContactBadges badges={icon_badges} />
|
||||
{!self ? <ContactBadges badges={icon_badges} /> : null}
|
||||
{!this.props.deleted ? <UnreadBadge count={this.props.unread} /> : null}
|
||||
</div>
|
||||
{this.props.showMode ?
|
||||
<span><ContactBadges badges={badges} /></span> :
|
||||
this.props.small ? null : <div className="contact-comment">{marker}<span>{preview || this.props.comment || '\u00A0'}</span></div>}
|
||||
this.props.small ? null : <div className="contact-comment">{marker}<span>{comment}</span></div>}
|
||||
</div>
|
||||
{this.props.showContextMenu ?
|
||||
<span className="menuTrigger">
|
||||
|
||||
@@ -75,8 +75,10 @@ export default class Cropper extends React.Component {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.overlay.current.removeEventListener('mousedown', this.mouseDown);
|
||||
this.overlay.current.removeEventListener('touchstart', this.mouseDown);
|
||||
if (this.overlay.current) {
|
||||
this.overlay.current.removeEventListener('mousedown', this.mouseDown);
|
||||
this.overlay.current.removeEventListener('touchstart', this.mouseDown);
|
||||
}
|
||||
}
|
||||
|
||||
// Position all elements.
|
||||
|
||||
@@ -5,26 +5,33 @@ import { Tinode } from 'tinode-sdk';
|
||||
import { idToColorClass } from '../lib/strformat.js';
|
||||
import { sanitizeUrlForMime } from '../lib/utils.js';
|
||||
|
||||
import { SELF_AVATAR_URI } from '../config.js';
|
||||
|
||||
export default class LetterTile extends React.PureComponent {
|
||||
render() {
|
||||
let avatar;
|
||||
if (this.props.avatar === true) {
|
||||
const isGroup = Tinode.isGroupTopicName(this.props.topic);
|
||||
const iconColor = idToColorClass(this.props.topic, isGroup);
|
||||
if (this.props.topic && this.props.title && this.props.title.trim()) {
|
||||
const letter = this.props.title.trim().charAt(0);
|
||||
const className = 'lettertile ' + iconColor + (this.props.deleted ? ' disabled' : '');
|
||||
avatar = (<div className={className}><div>{letter}</div></div>)
|
||||
if (Tinode.isSelfTopicName(this.props.topic)) {
|
||||
avatar = <img className="avatar self" alt="avatar" src={SELF_AVATAR_URI} />
|
||||
} else {
|
||||
const className = 'material-icons ' + iconColor + (this.props.deleted ? ' disabled' : '');
|
||||
avatar = isGroup ? <i className={className}>group</i> : <i className={className}>person</i>;
|
||||
const isGroup = Tinode.isGroupTopicName(this.props.topic);
|
||||
const iconColor = idToColorClass(this.props.topic, isGroup);
|
||||
|
||||
if (this.props.topic && this.props.title && this.props.title.trim()) {
|
||||
const letter = this.props.title.trim().charAt(0);
|
||||
const className = 'lettertile ' + iconColor + (this.props.deleted ? ' disabled' : '');
|
||||
avatar = (<div className={className}><div>{letter}</div></div>)
|
||||
} else {
|
||||
const className = 'material-icons ' + iconColor + (this.props.deleted ? ' disabled' : '');
|
||||
avatar = isGroup ? <i className={className}>group</i> : <i className={className}>person</i>;
|
||||
}
|
||||
}
|
||||
} else if (this.props.avatar) {
|
||||
const url = this.props.tinode.authorizeURL(sanitizeUrlForMime(this.props.avatar, 'image'));
|
||||
// If avatar image is invalid, show a placeholder.
|
||||
const className = 'avatar' + (this.props.deleted ? ' deleted' : '');
|
||||
avatar = <img className={className} alt="avatar" src={url}
|
||||
onError={(e)=>{e.target.onerror = null; e.target.src="../img/broken_image.png"}} />;
|
||||
onError={e=>{e.target.onerror = null; e.target.src="../img/broken_image.png"}} />;
|
||||
} else {
|
||||
avatar = null;
|
||||
}
|
||||
|
||||
@@ -26,15 +26,19 @@ class PinnedMessages extends React.PureComponent {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.touchSurface.current.addEventListener('touchstart', this.handleTouchEventStart, {passive: true});
|
||||
this.touchSurface.current.addEventListener('touchend', this.handleTouchEventEnd, {passive: true});
|
||||
this.touchSurface.current.addEventListener('touchcancel', this.handleTouchCancel, {passive: true});
|
||||
if (this.touchSurface.current) {
|
||||
this.touchSurface.current.addEventListener('touchstart', this.handleTouchEventStart, {passive: true});
|
||||
this.touchSurface.current.addEventListener('touchend', this.handleTouchEventEnd, {passive: true});
|
||||
this.touchSurface.current.addEventListener('touchcancel', this.handleTouchCancel, {passive: true});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.touchSurface.current.removeEventListener('touchstart', this.handleTouchEventStart);
|
||||
this.touchSurface.current.removeEventListener('touchend', this.handleTouchEventEnd);
|
||||
this.touchSurface.current.removeEventListener('touchcancel', this.handleTouchCancel);
|
||||
if (this.touchSurface.current) {
|
||||
this.touchSurface.current.removeEventListener('touchstart', this.handleTouchEventStart);
|
||||
this.touchSurface.current.removeEventListener('touchend', this.handleTouchEventEnd);
|
||||
this.touchSurface.current.removeEventListener('touchcancel', this.handleTouchCancel);
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedIndex() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Widget for editing topic description.
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
|
||||
import { Tinode } from 'tinode-sdk';
|
||||
|
||||
@@ -15,18 +15,22 @@ import { AVATAR_SIZE, MAX_AVATAR_BYTES, MAX_EXTERN_ATTACHMENT_SIZE, MAX_TITLE_LE
|
||||
import { imageScaled, blobToBase64, makeImageUrl } from '../lib/blob-helpers.js';
|
||||
import { arrayEqual, theCard } from '../lib/utils.js';
|
||||
|
||||
export default class TopicDescEdit extends React.Component {
|
||||
class TopicDescEdit extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const topic = this.props.tinode.getTopic(this.props.topic);
|
||||
const acs = topic.getAccessMode();
|
||||
const isSelf = Tinode.isSelfTopicName(this.props.topic);
|
||||
this.state = {
|
||||
isMe: Tinode.isMeTopicName(this.props.topic),
|
||||
isSelf: isSelf,
|
||||
owner: acs && acs.isOwner(),
|
||||
fullName: topic.public ? topic.public.fn : undefined,
|
||||
fullName: topic.public ? topic.public.fn :
|
||||
isSelf ? this.props.intl.formatMessage({id: 'self_topic_name'}) : null,
|
||||
private: topic.private ? topic.private.comment : null,
|
||||
description: topic.public ? topic.public.note : undefined,
|
||||
description: topic.public ? topic.public.note :
|
||||
isSelf ? this.props.intl.formatMessage({id: 'self_topic_comment'}) : null,
|
||||
avatar: makeImageUrl(topic.public ? topic.public.photo : null),
|
||||
tags: topic.tags() || [],
|
||||
newAvatar: null,
|
||||
@@ -161,7 +165,7 @@ export default class TopicDescEdit extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
const editable = this.state.isMe || this.state.owner;
|
||||
const editable = this.state.isMe || (this.state.owner && !this.state.isSelf);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -272,3 +276,5 @@ export default class TopicDescEdit extends React.Component {
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default injectIntl(TopicDescEdit);
|
||||
|
||||
Reference in New Issue
Block a user