fixing display of avatars by URL
This commit is contained in:
+2
-2
@@ -45,12 +45,12 @@ export const MEDIA_BREAKPOINT = 640;
|
||||
// Size of css 'rem' unit in pixels. Default 1rem = 10pt = 13px.
|
||||
export const REM_SIZE = 13;
|
||||
|
||||
// Size of the avatar image: when an image is uploaded, it's resized to
|
||||
// Size of the avatar image: when image dimensions are greater or the image is not square, it's resized to
|
||||
// a square of this size or less.
|
||||
export const AVATAR_SIZE = 384;
|
||||
|
||||
// Maximum size of an avatar in bytes for sending in-band. Bigger avatars will be sent out of band (as uploads).
|
||||
export const MAX_AVATAR_BYTES = 8192;
|
||||
export const MAX_AVATAR_BYTES = 4096;
|
||||
|
||||
// Size of the broken_image shown in MessagesView
|
||||
export const BROKEN_IMAGE_SIZE = 32;
|
||||
|
||||
@@ -6,8 +6,8 @@ import { bytesToHumanSize } from './strformat.js'
|
||||
export const SUPPORTED_IMAGE_FORMATS = ['image/jpeg', 'image/gif', 'image/png', 'image/svg', 'image/svg+xml'];
|
||||
export const MIME_EXTENSIONS = ['jpg', 'gif', 'png', 'svg', 'svg'];
|
||||
|
||||
// Make a data: URL from public.photo
|
||||
export function makeImageDataUrl(photo) {
|
||||
// Get an URL from a theCard photo: either create a data: URL or return reference URL.
|
||||
export function makeImageUrl(photo) {
|
||||
if (photo) {
|
||||
if (photo.data && photo.type) {
|
||||
const mime = photo.type.startsWith('image/') ? photo.type : ('image/' + photo.type);
|
||||
|
||||
+5
-5
@@ -38,10 +38,12 @@ export function theCard(fn, imageUrl, imageMimeType) {
|
||||
if (matches) {
|
||||
mimeType = matches[1];
|
||||
card.photo = {
|
||||
data: imageUrl.substring(imageUrl.indexOf(',') + 1)
|
||||
data: imageUrl.substring(imageUrl.indexOf(',') + 1),
|
||||
ref: Tinode.DEL_CHAR
|
||||
};
|
||||
} else {
|
||||
card.photo = {
|
||||
data: Tinode.DEL_CHAR,
|
||||
ref: imageUrl
|
||||
};
|
||||
}
|
||||
@@ -107,10 +109,9 @@ export function isUrlRelative(url) {
|
||||
|
||||
// Ensure URL does not present an XSS risk. Optional allowedSchemes may contain an array of
|
||||
// strings with permitted URL schemes, such as ['ftp', 'ftps']; otherwise accept http and https only.
|
||||
// If apikey and/or token is defined, it's appended to relative URL.
|
||||
export function sanitizeUrl(url, allowedSchemes) {
|
||||
if (!url) {
|
||||
return null;
|
||||
if (typeof url != 'string') {
|
||||
return url;
|
||||
}
|
||||
|
||||
// Strip control characters and whitespace. They are not valid URL characters anyway.
|
||||
@@ -139,7 +140,6 @@ export function sanitizeUrl(url, allowedSchemes) {
|
||||
|
||||
// Ensure URL is suitable for <img src="url"> field: the URL must be a relative URL or
|
||||
// have http:, https:, blob: or data: scheme.
|
||||
// The relative URL is appended with the API key and/or auth token.
|
||||
// In case of data: scheme, the URL must start with a 'data:image/XXXX;base64,'.
|
||||
export function sanitizeImageUrl(url) {
|
||||
if (!url) {
|
||||
|
||||
@@ -12,7 +12,7 @@ import TagManager from '../widgets/tag-manager.jsx';
|
||||
|
||||
import { AVATAR_SIZE, MAX_AVATAR_BYTES, MAX_EXTERN_ATTACHMENT_SIZE,
|
||||
MAX_INBAND_ATTACHMENT_SIZE, MAX_TITLE_LENGTH } from '../config.js';
|
||||
import { imageScaled, blobToBase64, makeImageDataUrl } from '../lib/blob-helpers.js';
|
||||
import { imageScaled, blobToBase64, makeImageUrl } from '../lib/blob-helpers.js';
|
||||
import { arrayEqual, asEmail, asPhone, theCard } from '../lib/utils.js';
|
||||
|
||||
export default class AccGeneralView extends React.Component {
|
||||
@@ -22,7 +22,7 @@ export default class AccGeneralView extends React.Component {
|
||||
const me = this.props.tinode.getMeTopic();
|
||||
this.state = {
|
||||
fullName: me.public ? me.public.fn : undefined,
|
||||
avatar: makeImageDataUrl(me.public ? me.public.photo : null),
|
||||
avatar: makeImageUrl(me.public ? me.public.photo : null),
|
||||
tags: me.tags(),
|
||||
credentials: me.getCredentials() || [],
|
||||
addCredActive: false,
|
||||
@@ -100,10 +100,6 @@ export default class AccGeneralView extends React.Component {
|
||||
if (blob.size > MAX_AVATAR_BYTES) {
|
||||
// Too large to send inband - uploading out of band and sending as a link.
|
||||
const uploader = this.props.tinode.getLargeFileHelper();
|
||||
if (!uploader) {
|
||||
this.props.onError(this.props.intl.formatMessage(messages.cannot_initiate_upload));
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({uploading: true});
|
||||
uploader.upload(blob)
|
||||
@@ -117,17 +113,19 @@ export default class AccGeneralView extends React.Component {
|
||||
this.setState({uploading: false});
|
||||
});
|
||||
} else {
|
||||
this.setState({uploading: true});
|
||||
// Convert blob to base64-encoded bits.
|
||||
blobToBase64(blob, (unused, base64bits) => {
|
||||
const du = makeImageDataUrl({data: base64bits, type: mime});
|
||||
const du = makeImageUrl({data: base64bits, type: mime});
|
||||
this.setState({source: du});
|
||||
this.props.onUpdateAccount(undefined, theCard(null, du));
|
||||
this.setState({uploading: false});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (width > AVATAR_SIZE || height > AVATAR_SIZE) {
|
||||
// Avatar is too large even after scaling. Shring it and make square.
|
||||
if (width > AVATAR_SIZE || height > AVATAR_SIZE || width != height) {
|
||||
// Avatar is not square or too large even after cropping. Shrink it and make square.
|
||||
imageScaled(blob, AVATAR_SIZE, AVATAR_SIZE, MAX_EXTERN_ATTACHMENT_SIZE, true,
|
||||
readyToUpload,
|
||||
(err) => {
|
||||
|
||||
@@ -55,9 +55,13 @@ export default class AvatarCropView extends React.PureComponent {
|
||||
onChange={this.handleChange} />
|
||||
</div>
|
||||
<div className="dialog-buttons">
|
||||
{this.props.onCancel ?
|
||||
<button className="secondary" onClick={this.props.onCancel}>
|
||||
<FormattedMessage id="button_cancel" defaultMessage="Cancel" description="Button [Cancel]" />
|
||||
</button>
|
||||
: null}
|
||||
<button className="primary" onClick={this.handleSubmit}>
|
||||
<FormattedMessage id="button_ok"
|
||||
defaultMessage="OK" description="Button [OK]" />
|
||||
<FormattedMessage id="button_ok" defaultMessage="OK" description="Button [OK]" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { FormattedMessage } from 'react-intl';
|
||||
import AvatarUpload from '../widgets/avatar-upload.jsx';
|
||||
import BadgeList from '../widgets/badge-list.jsx';
|
||||
|
||||
import { makeImageDataUrl } from '../lib/blob-helpers.js';
|
||||
import { makeImageUrl } from '../lib/blob-helpers.js';
|
||||
|
||||
export default class EditAccountView extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -14,7 +14,7 @@ export default class EditAccountView extends React.Component {
|
||||
const me = this.props.tinode.getMeTopic();
|
||||
this.state = {
|
||||
fullName: me.public ? me.public.fn : undefined,
|
||||
avatar: makeImageDataUrl(me.public ? me.public.photo : null)
|
||||
avatar: makeImageUrl(me.public ? me.public.photo : null)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import TagManager from '../widgets/tag-manager.jsx';
|
||||
|
||||
import { MAX_TITLE_LENGTH, NO_ACCESS_MODE } from '../config.js';
|
||||
|
||||
import { makeImageDataUrl } from '../lib/blob-helpers.js';
|
||||
import { makeImageUrl } from '../lib/blob-helpers.js';
|
||||
import { arrayEqual, theCard } from '../lib/utils.js';
|
||||
|
||||
const messages = defineMessages({
|
||||
@@ -237,7 +237,7 @@ class InfoView extends React.Component {
|
||||
muted: acs && acs.isMuted(),
|
||||
|
||||
fullName: topic.public ? topic.public.fn : undefined,
|
||||
avatar: makeImageDataUrl(topic.public ? topic.public.photo : null),
|
||||
avatar: makeImageUrl(topic.public ? topic.public.photo : null),
|
||||
trustedBadges: badges,
|
||||
private: topic.private ? topic.private.comment : null,
|
||||
address: topic.name,
|
||||
|
||||
@@ -19,7 +19,7 @@ import SendMessage from '../widgets/send-message.jsx';
|
||||
import { DEFAULT_P2P_ACCESS_MODE, IMAGE_PREVIEW_DIM, KEYPRESS_DELAY, MESSAGES_PAGE,
|
||||
MAX_EXTERN_ATTACHMENT_SIZE, MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE, READ_DELAY } from '../config.js';
|
||||
import { SUPPORTED_IMAGE_FORMATS, blobToBase64, filePasted, fileToBase64,
|
||||
imageScaled, makeImageDataUrl } from '../lib/blob-helpers.js';
|
||||
imageScaled, makeImageUrl } from '../lib/blob-helpers.js';
|
||||
import HashNavigation from '../lib/navigation.js';
|
||||
import { bytesToHumanSize, shortDateFormat } from '../lib/strformat.js';
|
||||
|
||||
@@ -265,7 +265,7 @@ class MessagesView extends React.Component {
|
||||
if (topic.public) {
|
||||
Object.assign(nextState, {
|
||||
title: topic.public.fn,
|
||||
avatar: makeImageDataUrl(topic.public.photo)
|
||||
avatar: makeImageUrl(topic.public.photo)
|
||||
});
|
||||
} else {
|
||||
Object.assign(nextState, {
|
||||
@@ -395,7 +395,7 @@ class MessagesView extends React.Component {
|
||||
if (desc.public) {
|
||||
this.setState({
|
||||
title: desc.public.fn,
|
||||
avatar: makeImageDataUrl(desc.public.photo)
|
||||
avatar: makeImageUrl(desc.public.photo)
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
@@ -645,10 +645,6 @@ class MessagesView extends React.Component {
|
||||
if (file.size > maxInbandAttachmentSize) {
|
||||
// Too large to send inband - uploading out of band and sending as a link.
|
||||
const uploader = this.props.tinode.getLargeFileHelper();
|
||||
if (!uploader) {
|
||||
this.props.onError(this.props.intl.formatMessage(messages.cannot_initiate_upload));
|
||||
return;
|
||||
}
|
||||
const uploadCompletionPromise = uploader.upload(file);
|
||||
const msg = Drafty.attachFile(null, {
|
||||
mime: file.type,
|
||||
@@ -873,7 +869,7 @@ class MessagesView extends React.Component {
|
||||
const user = topic.userDesc(thisFrom);
|
||||
if (user && user.public) {
|
||||
userName = user.public.fn;
|
||||
userAvatar = makeImageDataUrl(user.public.photo);
|
||||
userAvatar = makeImageUrl(user.public.photo);
|
||||
}
|
||||
userFrom = thisFrom;
|
||||
chatBoxClass='chat-box group';
|
||||
|
||||
@@ -118,6 +118,8 @@ class SidepanelView extends React.Component {
|
||||
badges = null;
|
||||
}
|
||||
|
||||
console.log("avatar URL=", avatar);
|
||||
|
||||
let onCancel;
|
||||
if (['login', 'contacts'].indexOf(view) == -1) {
|
||||
onCancel = this.props.onCancel;
|
||||
@@ -129,6 +131,7 @@ class SidepanelView extends React.Component {
|
||||
state={view}
|
||||
title={title}
|
||||
avatar={avatar}
|
||||
tinode={this.props.tinode}
|
||||
trustedBadges={badges}
|
||||
myUserId={this.props.myUserId}
|
||||
onSignUp={this.props.onSignUp}
|
||||
|
||||
+10
-12
@@ -18,7 +18,7 @@ import SidepanelView from './sidepanel-view.jsx';
|
||||
import { API_KEY, APP_NAME, DEFAULT_P2P_ACCESS_MODE, LOGGING_ENABLED,
|
||||
MEDIA_BREAKPOINT, RECEIVED_DELAY } from '../config.js';
|
||||
import { PACKAGE_VERSION } from '../version.js';
|
||||
import { base64ReEncode, makeImageDataUrl } from '../lib/blob-helpers.js';
|
||||
import { base64ReEncode, makeImageUrl } from '../lib/blob-helpers.js';
|
||||
import { detectServerAddress, isLocalHost, isSecureConnection } from '../lib/host-name.js';
|
||||
import LocalStorageUtil from '../lib/local-storage.js';
|
||||
import HashNavigation from '../lib/navigation.js';
|
||||
@@ -308,7 +308,7 @@ class TinodeWeb extends React.Component {
|
||||
initFCMessaging() {
|
||||
const {formatMessage, locale} = this.props.intl;
|
||||
const onError = (msg, err) => {
|
||||
console.log(msg, err);
|
||||
console.error(msg, err);
|
||||
this.handleError(formatMessage(messages.push_init_failed), 'err');
|
||||
this.setState({desktopAlertsEnabled: false});
|
||||
}
|
||||
@@ -384,7 +384,7 @@ class TinodeWeb extends React.Component {
|
||||
'cred','reset','newtpk','archive','blocked','contacts',''].includes(hash.path[0])) {
|
||||
this.setState({sidePanelSelected: hash.path[0]});
|
||||
} else {
|
||||
console.log("Unknown sidepanel view", hash.path[0]);
|
||||
console.info("Unknown sidepanel view", hash.path[0]);
|
||||
}
|
||||
|
||||
// Topic for MessagesView selector.
|
||||
@@ -464,13 +464,11 @@ class TinodeWeb extends React.Component {
|
||||
handlePersistenceChange(persist) {
|
||||
if (persist) {
|
||||
this.tinode.initStorage().then(() => {
|
||||
console.log("storage initialized");
|
||||
LocalStorageUtil.setObject('keep-logged-in', true);
|
||||
this.setState({persist: true});
|
||||
});
|
||||
} else {
|
||||
this.tinode.clearStorage().then(() => {
|
||||
console.log("storage cleared");
|
||||
LocalStorageUtil.setObject('keep-logged-in', false);
|
||||
this.setState({persist: false});
|
||||
});
|
||||
@@ -653,7 +651,7 @@ class TinodeWeb extends React.Component {
|
||||
if (desc.public) {
|
||||
this.setState({
|
||||
sidePanelTitle: desc.public.fn,
|
||||
sidePanelAvatar: makeImageDataUrl(desc.public.photo)
|
||||
sidePanelAvatar: makeImageUrl(desc.public.photo)
|
||||
});
|
||||
}
|
||||
if (desc.trusted) {
|
||||
@@ -721,7 +719,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.log("Unsupported (yet) presence update:" + what + " in: " + cont.topic);
|
||||
console.info("Unsupported (yet) presence update:" + what + " in: " + cont.topic);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -945,7 +943,7 @@ class TinodeWeb extends React.Component {
|
||||
});
|
||||
break;
|
||||
default:
|
||||
console.log("Unknown invitation action", '"' + action + '""');
|
||||
console.info("Unknown invitation action", '"' + action + '""');
|
||||
}
|
||||
|
||||
if (response != null) {
|
||||
@@ -1077,7 +1075,7 @@ class TinodeWeb extends React.Component {
|
||||
}).then(() => {
|
||||
this.requestPushToken();
|
||||
}).catch((err) => {
|
||||
console.log("Failed to get notification permission.", err);
|
||||
console.error("Failed to get notification permission.", err);
|
||||
this.handleError(err.message, 'err');
|
||||
this.setState({desktopAlerts: false, firebaseToken: null});
|
||||
LocalStorageUtil.updateObject('settings', {desktopAlerts: false});
|
||||
@@ -1090,7 +1088,7 @@ class TinodeWeb extends React.Component {
|
||||
}
|
||||
} else if (this.state.firebaseToken && this.fbPush) {
|
||||
this.fbPush.deleteToken(this.state.firebaseToken).catch((err) => {
|
||||
console.log("Unable to delete token.", err);
|
||||
console.error("Unable to delete token.", err);
|
||||
}).finally(() => {
|
||||
LocalStorageUtil.updateObject('settings', {desktopAlerts: false});
|
||||
localStorage.removeItem('firebase-token');
|
||||
@@ -1119,7 +1117,7 @@ class TinodeWeb extends React.Component {
|
||||
}
|
||||
}).catch((err) => {
|
||||
this.handleError(err.message, 'err');
|
||||
console.log("Failed to retrieve firebase token", err);
|
||||
console.error("Failed to retrieve firebase token", err);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1154,7 +1152,7 @@ class TinodeWeb extends React.Component {
|
||||
let path = '';
|
||||
if (['security','support','general','notif'].includes(parsed.path[0])) {
|
||||
path = 'edit';
|
||||
} else if ('crop') {
|
||||
} else if ('crop' == parsed.path[0]) {
|
||||
path = 'general';
|
||||
} else if ('blocked' == parsed.path[0]) {
|
||||
path = 'security';
|
||||
|
||||
@@ -3,6 +3,8 @@ import React from 'react';
|
||||
import LetterTile from './letter-tile.jsx';
|
||||
import LoadSpinner from './load-spinner.jsx';
|
||||
|
||||
import { sanitizeImageUrl } from '../lib/utils.js';
|
||||
|
||||
import { AVATAR_SIZE } from '../config.js';
|
||||
|
||||
export default class AvatarUpload extends React.Component {
|
||||
@@ -42,7 +44,7 @@ export default class AvatarUpload extends React.Component {
|
||||
<i className="material-icons">clear</i>
|
||||
</a>}
|
||||
{this.state.source ?
|
||||
<img src={this.state.source} className="preview" /> :
|
||||
<img src={this.props.tinode.authorizeURL(sanitizeImageUrl(this.state.source))} className="preview" /> :
|
||||
this.props.readOnly && this.props.uid ?
|
||||
<div className="avatar-box">
|
||||
<LetterTile
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
|
||||
import Chip from './chip.jsx';
|
||||
|
||||
import { makeImageDataUrl } from '../lib/blob-helpers.js';
|
||||
import { makeImageUrl } from '../lib/blob-helpers.js';
|
||||
|
||||
/* BEGIN ChipInput: group membership widget */
|
||||
export default class ChipInput extends React.Component {
|
||||
@@ -119,7 +119,7 @@ export default class ChipInput extends React.Component {
|
||||
chips.push(
|
||||
<Chip
|
||||
onCancel={this.handleChipCancel}
|
||||
avatar={makeImageDataUrl(item.public ? item.public.photo : null)}
|
||||
avatar={makeImageUrl(item.public ? item.public.photo : null)}
|
||||
title={item.public ? item.public.fn : undefined}
|
||||
noAvatar={this.props.avatarDisabled}
|
||||
topic={item.user}
|
||||
|
||||
@@ -9,7 +9,7 @@ import Tinode from 'tinode-sdk';
|
||||
import Contact from './contact.jsx';
|
||||
import ContactAction from './contact-action.jsx';
|
||||
|
||||
import { makeImageDataUrl } from '../lib/blob-helpers.js';
|
||||
import { makeImageUrl } from '../lib/blob-helpers.js';
|
||||
|
||||
import { MESSAGE_PREVIEW_LENGTH } from '../config.js';
|
||||
|
||||
@@ -90,7 +90,7 @@ class ContactList extends React.Component {
|
||||
contactNodes.push(
|
||||
<Contact
|
||||
title={c.public ? c.public.fn : null}
|
||||
avatar={makeImageDataUrl(c.public ? c.public.photo : null)}
|
||||
avatar={makeImageUrl(c.public ? c.public.photo : null)}
|
||||
comment={comment}
|
||||
preview={preview}
|
||||
received={deliveryStatus}
|
||||
|
||||
@@ -129,10 +129,12 @@ export default class Cropper extends React.Component {
|
||||
});
|
||||
|
||||
// Initial zoom level fills the bounding box at the smallest image dimension and overflows the largest, i.e. "fill" not "fit".
|
||||
let zoom = Math.max((this.bBoxRect.width / imgRect.width), (this.bBoxRect.height / imgRect.height));
|
||||
const zoom = Math.max(this.bBoxRect.width / imgRect.width, this.bBoxRect.height / imgRect.height);
|
||||
// Converting from viewport coordinates to container, then panning.
|
||||
let panX = (this.cutoutRect.left - this.bBoxRect.left) - ((imgRect.width - this.cutoutRect.width) / 2);
|
||||
let panY = (this.cutoutRect.top - this.bBoxRect.top) - ((imgRect.height - this.cutoutRect.height) / 2);
|
||||
const panX = this.cutoutRect.left - this.bBoxRect.left -
|
||||
(imgRect.width - this.cutoutRect.width) / 2;
|
||||
const panY = this.cutoutRect.top - this.bBoxRect.top -
|
||||
(imgRect.height - this.cutoutRect.height) / 2;
|
||||
|
||||
this.positionAll(panX, panY, zoom);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { FormattedMessage } from 'react-intl';
|
||||
import { MAX_ONLINE_IN_TOPIC } from '../config.js'
|
||||
|
||||
import LetterTile from './letter-tile.jsx';
|
||||
import { makeImageDataUrl } from '../lib/blob-helpers.js';
|
||||
import { makeImageUrl } from '../lib/blob-helpers.js';
|
||||
|
||||
export default class GroupSubs extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -23,7 +23,7 @@ export default class GroupSubs extends React.Component {
|
||||
<div className="avatar-box" key={sub.user}>
|
||||
<LetterTile
|
||||
topic={sub.user}
|
||||
avatar={makeImageDataUrl(sub.public ? sub.public.photo : null) || true}
|
||||
avatar={makeImageUrl(sub.public ? sub.public.photo : null) || true}
|
||||
title={sub.public ? sub.public.fn : null} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -23,7 +23,7 @@ export default class LetterTile extends React.PureComponent {
|
||||
} else if (this.props.avatar) {
|
||||
// If avatar image is invalid, show a placeholder.
|
||||
avatar = <img className="avatar" alt="avatar" src={this.props.avatar}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||
import CheckBox from './checkbox.jsx';
|
||||
import Contact from './contact.jsx';
|
||||
|
||||
import { makeImageDataUrl } from '../lib/blob-helpers.js';
|
||||
import { makeImageUrl } from '../lib/blob-helpers.js';
|
||||
|
||||
// Translatable permission names.
|
||||
const messages = defineMessages({
|
||||
@@ -135,7 +135,7 @@ class PermissionsEditor extends React.Component {
|
||||
<ul className="contact-box"><Contact
|
||||
item={this.props.item}
|
||||
title={this.props.userTitle}
|
||||
avatar={makeImageDataUrl(this.props.userAvatar ? this.props.userAvatar : null)} /></ul> : null}
|
||||
avatar={makeImageUrl(this.props.userAvatar ? this.props.userAvatar : null)} /></ul> : null}
|
||||
<label className="small"><FormattedMessage id="title_permissions"
|
||||
defaultMessage="Permissions" description="Section title"/></label>
|
||||
<table className="permission-editor">
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import React from 'react';
|
||||
|
||||
|
||||
import LetterTile from './letter-tile.jsx';
|
||||
import ButtonBack from './button-back.jsx';
|
||||
import ContactBadges from './contact-badges.jsx';
|
||||
import MenuContacts from './menu-contacts.jsx';
|
||||
import MenuStart from './menu-start.jsx';
|
||||
|
||||
import { sanitizeImageUrl } from '../lib/utils.js';
|
||||
|
||||
export default class SideNavbar extends React.PureComponent {
|
||||
render() {
|
||||
const icon_badges = [];
|
||||
@@ -15,13 +16,17 @@ export default class SideNavbar extends React.PureComponent {
|
||||
icon_badges.push({icon: b, color: 'badge-inv'});
|
||||
});
|
||||
}
|
||||
let avatar = null;
|
||||
if (this.props.tinode) {
|
||||
avatar = this.props.tinode.authorizeURL(sanitizeImageUrl(this.props.avatar));
|
||||
}
|
||||
return (
|
||||
<div id="side-caption-panel" className="caption-panel">
|
||||
{this.props.onCancel ? <ButtonBack onBack={this.props.onCancel} /> : null}
|
||||
{this.props.avatar ?
|
||||
{avatar ?
|
||||
<div id="self-avatar" className="avatar-box">
|
||||
<LetterTile
|
||||
avatar={this.props.avatar}
|
||||
avatar={avatar}
|
||||
topic={this.props.myUserId}
|
||||
title={this.props.title} />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user