Merge pull request #1948 from matrix-org/export_Group

Share Dialog
This commit is contained in:
David Baker 2018-06-15 10:52:27 +01:00 committed by GitHub
commit 6904c2bafe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
64 changed files with 459 additions and 72 deletions

View file

@ -80,6 +80,7 @@
"optimist": "^0.6.1",
"pako": "^1.0.5",
"prop-types": "^15.5.8",
"qrcode-react": "^0.1.16",
"querystring": "^0.2.0",
"react": "^15.6.0",
"react-addons-css-transition-group": "15.3.2",

View file

@ -42,6 +42,7 @@
@import "./views/dialogs/_SetEmailDialog.scss";
@import "./views/dialogs/_SetMxIdDialog.scss";
@import "./views/dialogs/_SetPasswordDialog.scss";
@import "./views/dialogs/_ShareDialog.scss";
@import "./views/dialogs/_UnknownDeviceDialog.scss";
@import "./views/directory/_NetworkDropdown.scss";
@import "./views/elements/_AccessibleButton.scss";

View file

@ -16,7 +16,7 @@ limitations under the License.
.mx_ContextualMenu_wrapper {
position: fixed;
z-index: 2000;
z-index: 5000;
}
.mx_ContextualMenu_background {
@ -26,7 +26,7 @@ limitations under the License.
width: 100%;
height: 100%;
opacity: 1.0;
z-index: 2000;
z-index: 5000;
}
.mx_ContextualMenu {
@ -37,7 +37,7 @@ limitations under the License.
position: absolute;
padding: 6px;
font-size: 14px;
z-index: 2001;
z-index: 5001;
}
.mx_ContextualMenu.mx_ContextualMenu_right {

View file

@ -0,0 +1,89 @@
/*
Copyright 2018 New Vector Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_ShareDialog {
// this is to center the content
padding-right: 58px;
}
.mx_ShareDialog hr {
margin-top: 25px;
margin-bottom: 25px;
border-color: $light-fg-color;
}
.mx_ShareDialog_content {
margin: 10px 0;
}
.mx_ShareDialog_matrixto {
display: flex;
justify-content: space-between;
border-radius: 5px;
border: solid 1px $light-fg-color;
margin-bottom: 10px;
margin-top: 30px;
padding: 10px;
}
.mx_ShareDialog_matrixto a {
text-decoration: none;
}
.mx_ShareDialog_matrixto_link {
flex-shrink: 1;
overflow: hidden;
text-overflow: ellipsis;
}
.mx_ShareDialog_matrixto_copy {
flex-shrink: 0;
cursor: pointer;
margin-left: 20px;
display: inherit;
}
.mx_ShareDialog_matrixto_copy > div {
background-image: url($copy-button-url);
margin-left: 5px;
width: 20px;
height: 20px;
}
.mx_ShareDialog_split {
display: flex;
flex-wrap: wrap;
}
.mx_ShareDialog_qrcode_container {
float: left;
background-color: #ffffff;
padding: 5px; // makes qr code more readable in dark theme
border-radius: 5px;
height: 256px;
width: 256px;
margin-right: 64px;
}
.mx_ShareDialog_social_container {
display: inline-block;
width: 299px;
}
.mx_ShareDialog_social_icon {
display: inline-grid;
margin-right: 10px;
margin-bottom: 10px;
}

6
res/img/icons-share.svg Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 481.6 481.6" style="enable-background:new 0 0 481.6 481.6;" xml:space="preserve" width="16px" height="16px">
<g>
<path stroke="#76CFA6" stroke-width="5" d="M381.6,309.4c-27.7,0-52.4,13.2-68.2,33.6l-132.3-73.9c3.1-8.9,4.8-18.5,4.8-28.4c0-10-1.7-19.5-4.9-28.5l132.2-73.8 c15.7,20.5,40.5,33.8,68.3,33.8c47.4,0,86.1-38.6,86.1-86.1S429,0,381.5,0s-86.1,38.6-86.1,86.1c0,10,1.7,19.6,4.9,28.5 l-132.1,73.8c-15.7-20.6-40.5-33.8-68.3-33.8c-47.4,0-86.1,38.6-86.1,86.1s38.7,86.1,86.2,86.1c27.8,0,52.6-13.3,68.4-33.9 l132.2,73.9c-3.2,9-5,18.7-5,28.7c0,47.4,38.6,86.1,86.1,86.1s86.1-38.6,86.1-86.1S429.1,309.4,381.6,309.4z M381.6,27.1 c32.6,0,59.1,26.5,59.1,59.1s-26.5,59.1-59.1,59.1s-59.1-26.5-59.1-59.1S349.1,27.1,381.6,27.1z M100,299.8 c-32.6,0-59.1-26.5-59.1-59.1s26.5-59.1,59.1-59.1s59.1,26.5,59.1,59.1S132.5,299.8,100,299.8z M381.6,454.5 c-32.6,0-59.1-26.5-59.1-59.1c0-32.6,26.5-59.1,59.1-59.1s59.1,26.5,59.1,59.1C440.7,428,414.2,454.5,381.6,454.5z" fill="#76cfa6"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

15
res/img/matrix-m.svg Normal file
View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 520 520" style="enable-background:new 0 0 520 520;" xml:space="preserve">
<rect width="100%" height="100%" fill="#FFFFFF"/>
<path d="M13.7,11.9v496.2h35.7V520H0V0h49.4v11.9H13.7z"/>
<path d="M166.3,169.2v25.1h0.7c6.7-9.6,14.8-17,24.2-22.2c9.4-5.3,20.3-7.9,32.5-7.9c11.7,0,22.4,2.3,32.1,6.8
c9.7,4.5,17,12.6,22.1,24c5.5-8.1,13-15.3,22.4-21.5c9.4-6.2,20.6-9.3,33.5-9.3c9.8,0,18.9,1.2,27.3,3.6c8.4,2.4,15.5,6.2,21.5,11.5
c6,5.3,10.6,12.1,14,20.6c3.3,8.5,5,18.7,5,30.7v124.1h-50.9V249.6c0-6.2-0.2-12.1-0.7-17.6c-0.5-5.5-1.8-10.3-3.9-14.3
c-2.2-4.1-5.3-7.3-9.5-9.7c-4.2-2.4-9.9-3.6-17-3.6c-7.2,0-13,1.4-17.4,4.1c-4.4,2.8-7.9,6.3-10.4,10.8c-2.5,4.4-4.2,9.4-5,15.1
c-0.8,5.6-1.3,11.3-1.3,17v103.3h-50.9v-104c0-5.5-0.1-10.9-0.4-16.3c-0.2-5.4-1.3-10.3-3.1-14.9c-1.8-4.5-4.8-8.2-9-10.9
c-4.2-2.7-10.3-4.1-18.5-4.1c-2.4,0-5.6,0.5-9.5,1.6c-3.9,1.1-7.8,3.1-11.5,6.1c-3.7,3-6.9,7.3-9.5,12.9c-2.6,5.6-3.9,13-3.9,22.1
v107.6h-50.9V169.2H166.3z"/>
<path d="M506.3,508.1V11.9h-35.7V0H520v520h-49.4v-11.9H506.3z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
res/img/social/email-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
res/img/social/facebook.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
res/img/social/linkedin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
res/img/social/reddit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -15,12 +16,10 @@ limitations under the License.
*/
'use strict';
const classNames = require('classnames');
const React = require('react');
const ReactDOM = require('react-dom');
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import classNames from 'classnames';
// Shamelessly ripped off Modal.js. There's probably a better way
// of doing reusable widgets like dialog boxes & menus where we go and
@ -61,7 +60,12 @@ export default class ContextualMenu extends React.Component {
// If true, insert an invisible screen-sized element behind the
// menu that when clicked will close it.
hasBackground: PropTypes.bool,
}
// The component to render as the context menu
elementClass: PropTypes.element.isRequired,
// on resize callback
windowResize: PropTypes.func
};
constructor() {
super();
@ -145,7 +149,7 @@ export default class ContextualMenu extends React.Component {
`;
}
const chevron = <div style={chevronOffset} className={"mx_ContextualMenu_chevron_" + chevronFace}></div>;
const chevron = <div style={chevronOffset} className={"mx_ContextualMenu_chevron_" + chevronFace} />;
const className = 'mx_ContextualMenu_wrapper';
const menuClasses = classNames({
@ -191,13 +195,13 @@ export default class ContextualMenu extends React.Component {
{ chevron }
<ElementClass {...props} onFinished={props.closeMenu} onResize={props.windowResize} />
</div>
{ props.hasBackground && <div className="mx_ContextualMenu_background" onClick={props.closeMenu}></div> }
{ props.hasBackground && <div className="mx_ContextualMenu_background" onClick={props.closeMenu} /> }
<style>{ chevronCSS }</style>
</div>;
}
}
export function createMenu(ElementClass, props) {
export function createMenu(ElementClass, props, hasBackground=true) {
const closeMenu = function(...args) {
ReactDOM.unmountComponentAtNode(getOrCreateContainer());
@ -208,8 +212,8 @@ export function createMenu(ElementClass, props) {
// We only reference closeMenu once per call to createMenu
const menu = <ContextualMenu
hasBackground={hasBackground}
{...props}
hasBackground={true}
elementClass={ElementClass}
closeMenu={closeMenu} // eslint-disable-line react/jsx-no-bind
windowResize={closeMenu} // eslint-disable-line react/jsx-no-bind

View file

@ -562,6 +562,13 @@ export default React.createClass({
});
},
_onShareClick: function() {
const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
Modal.createTrackedDialog('share community dialog', '', ShareDialog, {
target: this._matrixClient.getGroup(this.props.groupId),
});
},
_onCancelClick: function() {
this._closeSettings();
},
@ -1207,6 +1214,7 @@ export default React.createClass({
shortDescNode = <span onClick={onGroupHeaderItemClick}>{ summary.profile.short_description }</span>;
}
}
if (this.state.editing) {
rightButtons.push(
<AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
@ -1231,6 +1239,11 @@ export default React.createClass({
</AccessibleButton>,
);
}
rightButtons.push(
<AccessibleButton className="mx_GroupHeader_button" onClick={this._onShareClick} title={_t('Share Community')} key="_shareButton">
<TintableSvg src="img/icons-share.svg" width="16" height="16" />
</AccessibleButton>,
);
if (this.props.collapsedRhs) {
rightButtons.push(
<AccessibleButton className="mx_GroupHeader_button"

View file

@ -1108,6 +1108,14 @@ module.exports = React.createClass({
</div>;
},
onSelfShareClick: function() {
const cli = MatrixClientPeg.get();
const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
Modal.createTrackedDialog('share self dialog', '', ShareDialog, {
target: cli.getUser(this._me),
});
},
_showSpoiler: function(event) {
const target = event.target;
target.innerHTML = target.getAttribute('data-spoiler');
@ -1329,10 +1337,13 @@ module.exports = React.createClass({
<div className="mx_UserSettings_section">
<div className="mx_UserSettings_advanced">
{ _t("Logged in as:") } { this._me }
{ _t("Logged in as:") + ' ' }
<a onClick={this.onSelfShareClick} className="mx_UserSettings_link">
{ this._me }
</a>
</div>
<div className="mx_UserSettings_advanced">
{ _t('Access Token:') }
{ _t('Access Token:') + ' ' }
<span className="mx_UserSettings_advanced_spoiler"
onClick={this._showSpoiler}
data-spoiler={MatrixClientPeg.get().getAccessToken()}>

View file

@ -184,6 +184,15 @@ module.exports = React.createClass({
this.closeMenu();
},
onPermalinkClick: function(e: Event) {
e.preventDefault();
const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
Modal.createTrackedDialog('share room message dialog', '', ShareDialog, {
target: this.props.mxEvent,
});
this.closeMenu();
},
onReplyClick: function() {
dis.dispatch({
action: 'reply_to_event',
@ -290,7 +299,7 @@ module.exports = React.createClass({
const permalinkButton = (
<div className="mx_MessageContextMenu_field">
<a href={makeEventPermalink(this.props.mxEvent.getRoomId(), this.props.mxEvent.getId())}
target="_blank" rel="noopener" onClick={this.closeMenu}>{ _t('Permalink') }</a>
target="_blank" rel="noopener" onClick={this.onPermalinkClick}>{ _t('Share Message') }</a>
</div>
);

View file

@ -18,6 +18,7 @@ limitations under the License.
import React from 'react';
import FocusTrap from 'focus-trap-react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { MatrixClient } from 'matrix-js-sdk';
@ -64,7 +65,10 @@ export default React.createClass({
// Id of content element
// If provided, this is used to add a aria-describedby attribute
contentId: React.PropTypes.string,
contentId: PropTypes.string,
// optional additional class for the title element
titleClass: PropTypes.string,
},
getDefaultProps: function() {
@ -105,25 +109,28 @@ export default React.createClass({
render: function() {
const TintableSvg = sdk.getComponent("elements.TintableSvg");
let cancelButton;
if (this.props.hasCancel) {
cancelButton = <AccessibleButton onClick={this._onCancelClick} className="mx_Dialog_cancelButton">
<TintableSvg src="img/icons-close-button.svg" width="35" height="35" />
</AccessibleButton>;
}
return (
<FocusTrap onKeyDown={this._onKeyDown}
className={this.props.className}
role="dialog"
aria-labelledby='mx_BaseDialog_title'
// This should point to a node describing the dialog.
// If we were about to completelly follow this recommendation we'd need to
// If we were about to completely follow this recommendation we'd need to
// make all the components relying on BaseDialog to be aware of it.
// So instead we will use the whole content as the description.
// Description comes first and if the content contains more text,
// AT users can skip its presentation.
aria-describedby={this.props.contentId}
>
{ this.props.hasCancel ? <AccessibleButton onClick={this._onCancelClick}
className="mx_Dialog_cancelButton"
>
<TintableSvg src="img/icons-close-button.svg" width="35" height="35" />
</AccessibleButton> : null }
<div className={'mx_Dialog_title ' + this.props.titleClass} id='mx_BaseDialog_title'>
{ cancelButton }
<div className={classNames('mx_Dialog_title', this.props.titleClass)} id='mx_BaseDialog_title'>
{ this.props.title }
</div>
{ this.props.children }

View file

@ -187,7 +187,7 @@ export default class ChatCreateOrReuseDialog extends React.Component {
}
}
ChatCreateOrReuseDialog.propTyps = {
ChatCreateOrReuseDialog.propTypes = {
userId: PropTypes.string.isRequired,
// Called when clicking outside of the dialog
onFinished: PropTypes.func.isRequired,

View file

@ -0,0 +1,224 @@
/*
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import {Room, User, Group, RoomMember, MatrixEvent} from 'matrix-js-sdk';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import QRCode from 'qrcode-react';
import {makeEventPermalink, makeGroupPermalink, makeRoomPermalink, makeUserPermalink} from "../../../matrix-to";
import * as ContextualMenu from "../../structures/ContextualMenu";
const socials = [
{
name: 'Facebook',
img: 'img/social/facebook.png',
url: (url) => `https://www.facebook.com/sharer/sharer.php?u=${url}`,
}, {
name: 'Twitter',
img: 'img/social/twitter-2.png',
url: (url) => `https://twitter.com/home?status=${url}`,
}, /* // icon missing
name: 'Google Plus',
img: 'img/social/',
url: (url) => `https://plus.google.com/share?url=${url}`,
},*/ {
name: 'LinkedIn',
img: 'img/social/linkedin.png',
url: (url) => `https://www.linkedin.com/shareArticle?mini=true&url=${url}`,
}, {
name: 'Reddit',
img: 'img/social/reddit.png',
url: (url) => `http://www.reddit.com/submit?url=${url}`,
}, {
name: 'email',
img: 'img/social/email-1.png',
url: (url) => `mailto:?body=${url}`,
},
];
export default class ShareDialog extends React.Component {
static propTypes = {
onFinished: PropTypes.func.isRequired,
target: PropTypes.oneOfType([
PropTypes.instanceOf(Room),
PropTypes.instanceOf(User),
PropTypes.instanceOf(Group),
PropTypes.instanceOf(RoomMember),
PropTypes.instanceOf(MatrixEvent),
]).isRequired,
};
constructor(props) {
super(props);
this.onCopyClick = this.onCopyClick.bind(this);
this.onLinkSpecificEventCheckboxClick = this.onLinkSpecificEventCheckboxClick.bind(this);
this.state = {
// MatrixEvent defaults to share linkSpecificEvent
linkSpecificEvent: this.props.target instanceof MatrixEvent,
};
}
static _selectText(target) {
const range = document.createRange();
range.selectNodeContents(target);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
static onLinkClick(e) {
e.preventDefault();
const {target} = e;
ShareDialog._selectText(target);
}
onCopyClick(e) {
e.preventDefault();
ShareDialog._selectText(this.refs.link);
let successful;
try {
successful = document.execCommand('copy');
} catch (err) {
console.error('Failed to copy: ', err);
}
const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
const buttonRect = e.target.getBoundingClientRect();
// The window X and Y offsets are to adjust position when zoomed in to page
const x = buttonRect.right + window.pageXOffset;
const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19;
const {close} = ContextualMenu.createMenu(GenericTextContextMenu, {
chevronOffset: 10,
left: x,
top: y,
message: successful ? _t('Copied!') : _t('Failed to copy'),
}, false);
e.target.onmouseleave = close;
}
onLinkSpecificEventCheckboxClick() {
this.setState({
linkSpecificEvent: !this.state.linkSpecificEvent,
});
}
render() {
let title;
let matrixToUrl;
let checkbox;
if (this.props.target instanceof Room) {
title = _t('Share Room');
const events = this.props.target.getLiveTimeline().getEvents();
if (events.length > 0) {
checkbox = <div>
<input type="checkbox"
id="mx_ShareDialog_checkbox"
checked={this.state.linkSpecificEvent}
onClick={this.onLinkSpecificEventCheckboxClick} />
<label htmlFor="mx_ShareDialog_checkbox">
{ _t('Link to most recent message') }
</label>
</div>;
}
if (this.state.linkSpecificEvent) {
matrixToUrl = makeEventPermalink(this.props.target.roomId, events[events.length - 1].getId());
} else {
matrixToUrl = makeRoomPermalink(this.props.target.roomId);
}
} else if (this.props.target instanceof User || this.props.target instanceof RoomMember) {
title = _t('Share User');
matrixToUrl = makeUserPermalink(this.props.target.userId);
} else if (this.props.target instanceof Group) {
title = _t('Share Community');
matrixToUrl = makeGroupPermalink(this.props.target.groupId);
} else if (this.props.target instanceof MatrixEvent) {
title = _t('Share Room Message');
checkbox = <div>
<input type="checkbox"
id="mx_ShareDialog_checkbox"
checked={this.state.linkSpecificEvent}
onClick={this.onLinkSpecificEventCheckboxClick} />
<label htmlFor="mx_ShareDialog_checkbox">
{ _t('Link to selected message') }
</label>
</div>;
if (this.state.linkSpecificEvent) {
matrixToUrl = makeEventPermalink(this.props.target.getRoomId(), this.props.target.getId());
} else {
matrixToUrl = makeRoomPermalink(this.props.target.getRoomId());
}
}
const encodedUrl = encodeURIComponent(matrixToUrl);
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return <BaseDialog title={title}
className='mx_ShareDialog'
contentId='mx_Dialog_content'
onFinished={this.props.onFinished}
>
<div className="mx_ShareDialog_content">
<div className="mx_ShareDialog_matrixto">
<a ref="link"
href={matrixToUrl}
onClick={ShareDialog.onLinkClick}
className="mx_ShareDialog_matrixto_link"
>
{ matrixToUrl }
</a>
<a href={matrixToUrl} className="mx_ShareDialog_matrixto_copy" onClick={this.onCopyClick}>
{ _t('COPY') }
<div>&nbsp;</div>
</a>
</div>
{ checkbox }
<hr />
<div className="mx_ShareDialog_split">
<div className="mx_ShareDialog_qrcode_container">
<QRCode value={matrixToUrl} size={256} logoWidth={48} logo="img/matrix-m.svg" />
</div>
<div className="mx_ShareDialog_social_container">
{
socials.map((social) => <a rel="noopener"
target="_blank"
key={social.name}
name={social.name}
href={social.url(encodedUrl)}
className="mx_ShareDialog_social_icon"
>
<img src={social.img} alt={social.name} height={64} width={64} />
</a>)
}
</div>
</div>
</div>
</BaseDialog>;
}
}

View file

@ -336,8 +336,8 @@ module.exports = React.createClass({
left: x,
top: y,
message: successful ? _t('Copied!') : _t('Failed to copy'),
});
e.target.onmouseout = close;
}, false);
e.target.onmouseleave = close;
};
p.appendChild(button);
});

View file

@ -439,6 +439,17 @@ module.exports = withMatrixClient(React.createClass({
});
},
onPermalinkShareClicked: function(e) {
// These permalinks are like above, can be opened in new tab/window to matrix.to
// but otherwise fire the ShareDialog as it makes little sense to click permalink
// whilst it is in the current room
e.preventDefault();
const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
Modal.createTrackedDialog('share room event dialog', '', ShareDialog, {
target: this.props.mxEvent,
});
},
_renderE2EPadlock: function() {
const ev = this.props.mxEvent;
const props = {onClick: this.onCryptoClicked};
@ -669,7 +680,7 @@ module.exports = withMatrixClient(React.createClass({
{ avatar }
{ sender }
<div className="mx_EventTile_reply">
<a href={permalink} onClick={this.onPermalinkClicked}>
<a href={permalink} onClick={this.onPermalinkShareClicked}>
{ timestamp }
</a>
{ this._renderE2EPadlock() }
@ -696,7 +707,7 @@ module.exports = withMatrixClient(React.createClass({
{ avatar }
{ sender }
<div className="mx_EventTile_line">
<a href={permalink} onClick={this.onPermalinkClicked}>
<a href={permalink} onClick={this.onPermalinkShareClicked}>
{ timestamp }
</a>
{ this._renderE2EPadlock() }

View file

@ -632,6 +632,13 @@ module.exports = withMatrixClient(React.createClass({
);
},
onShareUserClick: function() {
const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
Modal.createTrackedDialog('share room member dialog', '', ShareDialog, {
target: this.props.member,
});
},
_renderUserOptions: function() {
const cli = this.props.matrixClient;
const member = this.props.member;
@ -705,13 +712,18 @@ module.exports = withMatrixClient(React.createClass({
}
}
if (!ignoreButton && !readReceiptButton && !insertPillButton && !inviteUserButton) return null;
const shareUserButton = (
<AccessibleButton onClick={this.onShareUserClick} className="mx_MemberInfo_field">
{ _t('Share Link to User') }
</AccessibleButton>
);
return (
<div>
<h3>{ _t("User Options") }</h3>
<div className="mx_MemberInfo_buttons">
{ readReceiptButton }
{ shareUserButton }
{ insertPillButton }
{ ignoreButton }
{ inviteUserButton }

View file

@ -149,6 +149,13 @@ module.exports = React.createClass({
dis.dispatch({ action: 'show_right_panel' });
},
onShareRoomClick: function(ev) {
const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
Modal.createTrackedDialog('share room dialog', '', ShareDialog, {
target: this.props.room,
});
},
_hasUnreadPins: function() {
const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", '');
if (!currentPinEvent) return false;
@ -379,6 +386,14 @@ module.exports = React.createClass({
</AccessibleButton>;
}
let shareRoomButton;
if (this.props.inRoom) {
shareRoomButton =
<AccessibleButton className="mx_RoomHeader_button" onClick={this.onShareRoomClick} title={_t('Share room')}>
<TintableSvg src="img/icons-share.svg" width="16" height="16" />
</AccessibleButton>;
}
let rightPanelButtons;
if (this.props.collapsedRhs) {
rightPanelButtons =
@ -400,6 +415,7 @@ module.exports = React.createClass({
<div className="mx_RoomHeader_rightRow">
{ settingsButton }
{ pinnedEventsButton }
{ shareRoomButton }
{ manageIntegsButton }
{ forgetButton }
{ searchButton }

View file

@ -31,7 +31,6 @@
"Noisy": "Шумна",
"Resend": "Паўторна",
"On": "Уключыць",
"Permalink": "Пастаянная спасылка",
"remove %(name)s from the directory.": "выдаліць %(name)s з каталога.",
"Off": "Выключыць",
"Delete the room alias %(alias)s and remove %(name)s from the directory?": "Выдаліць псеўданім пакоя %(alias)s і выдаліць %(name)s з каталога?",

View file

@ -1124,7 +1124,6 @@
"Set Password": "Задаване на парола",
"An error occurred whilst saving your email notification preferences.": "Възникна грешка при запазване на настройките за имейл известяване.",
"Enable audible notifications in web client": "Включване на звукови известия в уеб клиент",
"Permalink": "Permalink",
"Off": "Изключено",
"Riot does not know how to join a room on this network": "Riot не знае как да се присъедини към стая от тази мрежа",
"Mentions only": "Само при споменаване",

View file

@ -1003,7 +1003,6 @@
"Unable to fetch notification target list": "No s'ha pogut obtenir la llista d'objectius de les notificacions",
"Set Password": "Establiu una contrasenya",
"Enable audible notifications in web client": "Habilita les notificacions d'àudio al client web",
"Permalink": "Enllaç permanent",
"Off": "Apagat",
"Riot does not know how to join a room on this network": "El Riot no sap com unir-se a una sala en aquesta xarxa",
"Mentions only": "Només mencions",

View file

@ -1061,7 +1061,6 @@
"Set Password": "Nastavit heslo",
"An error occurred whilst saving your email notification preferences.": "Při ukládání nastavení e-mailových upozornění nastala chyba.",
"Enable audible notifications in web client": "Povolit zvuková upozornění ve webové aplikaci",
"Permalink": "Trvalý odkaz",
"Off": "Vypnout",
"#example": "#příklad",
"Mentions only": "Pouze zmínky",

View file

@ -370,7 +370,6 @@
"Unable to fetch notification target list": "Kan ikke hente meddelelsesmålliste",
"Set Password": "Indstil Password",
"Enable audible notifications in web client": "Aktivér hørbare underretninger i webklienten",
"Permalink": "Permanent link",
"Resend": "Send igen",
"Riot does not know how to join a room on this network": "Riot ved ikke, hvordan man kan deltage i et rum på dette netværk",
"Mentions only": "Kun nævninger",

View file

@ -1125,7 +1125,6 @@
"Unable to fetch notification target list": "Liste der Benachrichtigungsempfänger konnte nicht abgerufen werden",
"Set Password": "Passwort einrichten",
"Enable audible notifications in web client": "Audio-Benachrichtigungen im Web-Client aktivieren",
"Permalink": "Permanenter Link",
"Off": "Aus",
"Riot does not know how to join a room on this network": "Riot weiß nicht, wie es einem Raum auf diesem Netzwerk beitreten soll",
"Mentions only": "Nur, wenn du erwähnt wirst",

View file

@ -745,7 +745,6 @@
"What's New": "Τι νέο υπάρχει",
"Set Password": "Ορισμός κωδικού πρόσβασης",
"Enable audible notifications in web client": "Ενεργοποίηση ηχητικών ειδοποιήσεων",
"Permalink": "Μόνιμος σύνδεσμος",
"Off": "Ανενεργό",
"#example": "#παράδειγμα",
"Mentions only": "Μόνο αναφορές",

View file

@ -371,6 +371,7 @@
"Jump to read receipt": "Jump to read receipt",
"Mention": "Mention",
"Invite": "Invite",
"Share Link to User": "Share Link to User",
"User Options": "User Options",
"Direct chats": "Direct chats",
"Unmute": "Unmute",
@ -452,6 +453,7 @@
"Settings": "Settings",
"Forget room": "Forget room",
"Search": "Search",
"Share room": "Share room",
"Show panel": "Show panel",
"Drop here to favourite": "Drop here to favourite",
"Drop here to tag direct chat": "Drop here to tag direct chat",
@ -858,6 +860,13 @@
"(HTTP status %(httpStatus)s)": "(HTTP status %(httpStatus)s)",
"Please set a password!": "Please set a password!",
"This will allow you to return to your account after signing out, and sign in on other devices.": "This will allow you to return to your account after signing out, and sign in on other devices.",
"Share Room": "Share Room",
"Link to most recent message": "Link to most recent message",
"Share User": "Share User",
"Share Community": "Share Community",
"Share Room Message": "Share Room Message",
"Link to selected message": "Link to selected message",
"COPY": "COPY",
"You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.",
"We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.",
"Room contains unknown devices": "Room contains unknown devices",
@ -876,7 +885,7 @@
"View Source": "View Source",
"View Decrypted Source": "View Decrypted Source",
"Unhide Preview": "Unhide Preview",
"Permalink": "Permalink",
"Share Message": "Share Message",
"Quote": "Quote",
"Source URL": "Source URL",
"Collapse Reply Thread": "Collapse Reply Thread",

View file

@ -815,7 +815,6 @@
"Unable to fetch notification target list": "Unable to fetch notification target list",
"Set Password": "Set Password",
"Enable audible notifications in web client": "Enable audible notifications in web client",
"Permalink": "Permalink",
"Off": "Off",
"Riot does not know how to join a room on this network": "Riot does not know how to join a room on this network",
"Mentions only": "Mentions only",

View file

@ -1076,7 +1076,6 @@
"Unable to fetch notification target list": "Malsukcesis akiri la liston de celoj por sciigoj",
"Set Password": "Agordi pasvorton",
"Enable audible notifications in web client": "Ŝalti aŭdeblajn sciigojn en la retkliento",
"Permalink": "Konstanta ligilo",
"Off": "For",
"Riot does not know how to join a room on this network": "Riot ne scias aliĝi al ĉambroj en tiu ĉi reto",
"Mentions only": "Nur mencioj",

View file

@ -716,7 +716,6 @@
"Riot does not know how to join a room on this network": "Riot no sabe cómo unirse a una sala en esta red",
"Set Password": "Establecer contraseña",
"Enable audible notifications in web client": "Habilitar notificaciones audibles en el cliente web",
"Permalink": "Enlace permanente",
"Off": "Apagado",
"#example": "#ejemplo",
"Mentions only": "Sólo menciones",

View file

@ -1126,7 +1126,6 @@
"Unable to fetch notification target list": "Ezin izan da jakinarazpen helburuen zerrenda eskuratu",
"Set Password": "Ezarri pasahitza",
"Enable audible notifications in web client": "Gaitu jakinarazpen entzungarriak web bezeroan",
"Permalink": "Esteka iraunkorra",
"Off": "Ez",
"Riot does not know how to join a room on this network": "Riotek ez daki nola elkartu gela batetara sare honetan",
"Mentions only": "Aipamenak besterik ez",

View file

@ -124,7 +124,6 @@
"Set Password": "پسوردتان را انتخاب کنید",
"An error occurred whilst saving your email notification preferences.": "خطایی در حین ذخیره‌ی ترجیجات شما درباره‌ی رایانامه رخ داد.",
"Enable audible notifications in web client": "آگاه‌سازی صدادار را در کارگزار وب فعال کن",
"Permalink": "پایاپیوند",
"Off": "خاموش",
"Riot does not know how to join a room on this network": "رایوت از چگونگی ورود به یک گپ در این شبکه اطلاعی ندارد",
"Mentions only": "فقط نام‌بردن‌ها",

View file

@ -1049,7 +1049,6 @@
"Set Password": "Aseta salasana",
"An error occurred whilst saving your email notification preferences.": "Sähköposti-ilmoitusasetuksia tallettaessa tapahtui virhe.",
"Enable audible notifications in web client": "Ota käyttöön äänelliset ilmoitukset",
"Permalink": "Pysyvä linkki",
"remove %(name)s from the directory.": "poista %(name)s hakemistosta.",
"Off": "Pois päältä",
"Riot does not know how to join a room on this network": "Riot ei tiedä miten liittya huoneeseen tässä verkossa",

View file

@ -1120,7 +1120,6 @@
"Unable to fetch notification target list": "Impossible de récupérer la liste des appareils recevant les notifications",
"Set Password": "Définir un mot de passe",
"Enable audible notifications in web client": "Activer les notifications sonores pour le client web",
"Permalink": "Permalien",
"Off": "Désactivé",
"Riot does not know how to join a room on this network": "Riot ne peut pas joindre un salon sur ce réseau",
"Mentions only": "Seulement les mentions",

View file

@ -1126,7 +1126,6 @@
"Unable to fetch notification target list": "Non se puido procesar a lista de obxetivo de notificacións",
"Set Password": "Establecer contrasinal",
"Enable audible notifications in web client": "Habilitar notificacións audibles no cliente web",
"Permalink": "Ligazón permanente",
"Off": "Off",
"Riot does not know how to join a room on this network": "Riot non sabe cómo conectar con unha sala en esta rede",
"Mentions only": "Só mencións",

View file

@ -221,7 +221,6 @@
"Unable to fetch notification target list": "לא ניתן לאחזר רשימת יעדי התראה",
"Set Password": "הגדר סיסמא",
"Enable audible notifications in web client": "אפשר התראות קוליות בדפדפן",
"Permalink": "קישור קבוע",
"Off": "סגור",
"Riot does not know how to join a room on this network": "Riot אינו יודע כיצד להצטרף לחדר ברשת זו",
"Mentions only": "מאזכר בלבד",

View file

@ -1125,7 +1125,6 @@
"Unable to fetch notification target list": "Nem sikerült letölteni az értesítési célok listáját",
"Set Password": "Jelszó beállítása",
"Enable audible notifications in web client": "Hangértesítések engedélyezése a webkliensben",
"Permalink": "Állandó hivatkozás",
"Off": "Ki",
"Riot does not know how to join a room on this network": "A Riot nem tud csatlakozni szobához ezen a hálózaton",
"Mentions only": "Csak ha megemlítenek",

View file

@ -323,7 +323,6 @@
"Unable to fetch notification target list": "Tidak dapat mengambil daftar notifikasi target",
"Set Password": "Ubah Password",
"Enable audible notifications in web client": "Aktifkan notifikasi suara di klien web",
"Permalink": "Permalink",
"Off": "Mati",
"Riot does not know how to join a room on this network": "Riot tidak tau bagaimana gabung ruang di jaringan ini",
"Mentions only": "Hanya jika disinggung",

View file

@ -380,7 +380,6 @@
"View Source": "Skoða frumkóða",
"View Decrypted Source": "Skoða afkóðaða upprunaskrá",
"Unhide Preview": "Birta forskoðun",
"Permalink": "Varanlegur tengill",
"Quote": "Tilvitnun",
"Source URL": "Upprunaslóð",
"All messages (noisy)": "Öll skilaboð (hávært)",

View file

@ -1121,7 +1121,6 @@
"What's New": "Novità",
"Set Password": "Imposta Password",
"Enable audible notifications in web client": "Abilita notifiche audio nel client web",
"Permalink": "Link permanente",
"Off": "Spento",
"#example": "#esempio",
"Mentions only": "Solo le citazioni",

View file

@ -223,7 +223,6 @@
"Event Type": "イベントの形式",
"What's New": "新着",
"Enable audible notifications in web client": "ウェブクライアントで音による通知を有効化",
"Permalink": "パーマリンク",
"remove %(name)s from the directory.": "ディレクトリから %(name)s を消去する。",
"Riot does not know how to join a room on this network": "Riotはこのネットワークで部屋に参加する方法を知りません",
"You can now return to your account after signing out, and sign in on other devices.": "サインアウト後にあなたの\nアカウントに戻る、また、他の端末でサインインすることができます。",

View file

@ -753,7 +753,6 @@
"Riot does not know how to join a room on this network": "라이엇이 이 네트워크에서 방에 들어가는 법을 알 수 없어요",
"Set Password": "비밀번호 설정",
"Enable audible notifications in web client": "웹 클라이언트에서 알림 소리 켜기",
"Permalink": "고유주소",
"Off": "끄기",
"#example": "#예",
"Mentions only": "답만 하기",

View file

@ -161,7 +161,6 @@
"Set Password": "Nustatyti slaptažodį",
"An error occurred whilst saving your email notification preferences.": "Įrašant pranešimų el. paštu nuostatas, įvyko klaida.",
"Unable to join network": "Nepavyko prisijungti prie tinklo",
"Permalink": "Pastovioji nuoroda",
"Register": "Registruotis",
"Off": "Išjungta",
"Edit": "Koreguoti",

View file

@ -1114,7 +1114,6 @@
"Unable to fetch notification target list": "Neizdevās iegūt paziņojumu mērķu sarakstu",
"Set Password": "Iestatīt paroli",
"Enable audible notifications in web client": "Iespējot skaņus paziņojumus web klientā",
"Permalink": "Pastāvīgā saite",
"Off": "izslēgts",
"Riot does not know how to join a room on this network": "Riot nezin kā pievienoties šajā tīklā esošajai istabai",
"Mentions only": "Vienīgi atsauces",

View file

@ -137,7 +137,6 @@
"Unable to fetch notification target list": "നോട്ടിഫിക്കേഷന്‍ ടാര്‍ഗെറ്റ് ലിസ്റ്റ് നേടാനായില്ല",
"Set Password": "രഹസ്യവാക്ക് സജ്ജീകരിക്കുക",
"Enable audible notifications in web client": "വെബ് പതിപ്പിലെ അറിയിപ്പുകള്‍ കേള്‍ക്കാവുന്നതാക്കുക",
"Permalink": "പെര്‍മാലിങ്ക്",
"remove %(name)s from the directory.": "%(name)s ഡയറക്റ്ററിയില്‍ നിന്ന് നീക്കം ചെയ്യുക.",
"Off": "ഓഫ്",
"Riot does not know how to join a room on this network": "ഈ നെറ്റ്‍വര്‍ക്കിലെ ഒരു റൂമില്‍ എങ്ങനെ അംഗമാകാമെന്ന് റയട്ടിന് അറിയില്ല",

View file

@ -98,7 +98,6 @@
"Riot does not know how to join a room on this network": "Riot vet ikke hvordan man kan komme inn på et rom på dette nettverket",
"An error occurred whilst saving your email notification preferences.": "En feil oppsto i forbindelse med lagring av epost varsel innstillinger.",
"Enable audible notifications in web client": "Aktiver lyd-varsel i webklient",
"Permalink": "Permanent lenke",
"remove %(name)s from the directory.": "fjern %(name)s fra katalogen.",
"Off": "Av",
"#example": "#eksempel",

View file

@ -1119,7 +1119,6 @@
"Unable to fetch notification target list": "Het is mislukt om de lijst van notificatiedoelen op te halen",
"Set Password": "Wachtwoord instellen",
"Enable audible notifications in web client": "Geluidsmeldingen in de webclient aanzetten",
"Permalink": "Permanente link",
"Off": "Uit",
"Riot does not know how to join a room on this network": "Riot weet niet hoe het moet deelnemen in een ruimte op dit netwerk",
"Mentions only": "Alleen vermeldingen",

View file

@ -900,7 +900,6 @@
"Unable to fetch notification target list": "Nie można pobrać listy docelowej dla powiadomień",
"Set Password": "Ustaw hasło",
"Enable audible notifications in web client": "Włącz dźwiękowe powiadomienia w kliencie internetowym",
"Permalink": "Odnośnik bezpośredni",
"Off": "Wyłącz",
"Riot does not know how to join a room on this network": "Riot nie wie, jak dołączyć do pokoju w tej sieci",
"Mentions only": "Tylko, gdy wymienieni",

View file

@ -825,7 +825,6 @@
"Unable to fetch notification target list": "Não foi possível obter a lista de alvos de notificação",
"Set Password": "Definir palavra-passe",
"Enable audible notifications in web client": "Ativar notificações de áudio no cliente web",
"Permalink": "Link permanente",
"Off": "Desativado",
"Riot does not know how to join a room on this network": "O Riot não sabe como entrar numa sala nesta rede",
"Mentions only": "Apenas menções",

View file

@ -1100,7 +1100,6 @@
"Unable to fetch notification target list": "Não foi possível obter a lista de alvos de notificação",
"Set Password": "Definir senha",
"Enable audible notifications in web client": "Ativar notificações de áudio no cliente web",
"Permalink": "Link permanente",
"Off": "Desativado",
"Riot does not know how to join a room on this network": "O sistema não sabe como entrar na sala desta rede",
"Mentions only": "Apenas menções",

View file

@ -1124,7 +1124,6 @@
"Unable to fetch notification target list": "Не удалось получить список устройств для уведомлений",
"Set Password": "Задать пароль",
"Enable audible notifications in web client": "Включить звуковые уведомления в веб-клиенте",
"Permalink": "Постоянная ссылка",
"Off": "Выключить",
"Riot does not know how to join a room on this network": "Riot не знает, как присоединиться к комнате, принадлежащей к этой сети",
"Mentions only": "Только при упоминаниях",

View file

@ -1125,7 +1125,6 @@
"Set Password": "Nastaviť Heslo",
"An error occurred whilst saving your email notification preferences.": "Počas ukladania vašich nastavení oznamovania emailom sa vyskytla chyba.",
"Enable audible notifications in web client": "Povoliť zvukové oznámenia vo webovom klientovi",
"Permalink": "Trvalý odkaz",
"Off": "Zakázané",
"Riot does not know how to join a room on this network": "Riot nedokáže vstúpiť do miestnosti na tejto sieti",
"Mentions only": "Len zmienky",

View file

@ -269,7 +269,6 @@
"Set Password": "Caktoni Fjalëkalim",
"An error occurred whilst saving your email notification preferences.": "Ndodhi një gabim teksa ruheshin parapëlqimet tuaja për njoftime me email.",
"Enable audible notifications in web client": "Aktivizoni njoftime audio te klienti web",
"Permalink": "Permalidhje",
"Register": "Regjistrohuni",
"Off": "Off",
"Edit": "Përpunoni",

View file

@ -1100,7 +1100,6 @@
"Set Password": "Постави лозинку",
"An error occurred whilst saving your email notification preferences.": "Догодила се грешка при чувању ваших поставки мејл обавештења.",
"Enable audible notifications in web client": "Омогући звучна обавештења у веб клијенту",
"Permalink": "Трајна веза",
"Resend": "Поново пошаљи",
"Riot does not know how to join a room on this network": "Riot не зна како да приступи соби на овој мрежи",
"Mentions only": "Само спомињања",

View file

@ -546,7 +546,6 @@
"Unable to fetch notification target list": "Det gick inte att hämta aviseringsmållistan",
"Set Password": "Välj lösenord",
"Enable audible notifications in web client": "Sätt på högljudda aviseringar i webbklienten",
"Permalink": "Permanent länk",
"Off": "Av",
"Riot does not know how to join a room on this network": "Riot kan inte gå med i ett rum på det här nätverket",
"Mentions only": "Endast omnämnande",

View file

@ -78,7 +78,6 @@
"Off": "அமை",
"On": "மீது",
"Operation failed": "செயல்பாடு தோல்வியுற்றது",
"Permalink": "நிரந்தரத் தொடுப்பு",
"powered by Matrix": "Matrix-ஆல் ஆனது",
"Quote": "மேற்கோள்",
"Reject": "நிராகரி",

View file

@ -543,7 +543,6 @@
"Riot does not know how to join a room on this network": "Riot ไม่รู้วิธีเข้าร่วมห้องในเครือข่ายนี้",
"Set Password": "ตั้งรหัสผ่าน",
"Enable audible notifications in web client": "เปิดใช้งานเสียงแจ้งเตือนบนเว็บไคลเอนต์",
"Permalink": "ลิงก์ถาวร",
"Off": "ปิด",
"#example": "#example",
"Mentions only": "เมื่อถูกกล่าวถึงเท่านั้น",

View file

@ -740,7 +740,6 @@
"Unable to fetch notification target list": "Bildirim hedef listesi çekilemedi",
"An error occurred whilst saving your email notification preferences.": "E-posta bildirim tercihlerinizi kaydetme işlemi sırasında bir hata oluştu.",
"Enable audible notifications in web client": "Web istemcisinde sesli bildirimleri etkinleştir",
"Permalink": "Kalıcı Bağlantı(permalink)",
"Off": "Kapalı",
"Riot does not know how to join a room on this network": "Riot bu ağdaki bir odaya nasıl gireceğini bilmiyor",
"Mentions only": "Sadece Mention'lar",

View file

@ -232,7 +232,6 @@
"Unable to fetch notification target list": "Неможливо отримати перелік цілей сповіщення",
"Set Password": "Задати пароль",
"Enable audible notifications in web client": "Увімкнути звукові сповіщення у мережевому застосунку",
"Permalink": "Постійне посилання",
"Off": "Вимкнено",
"Riot does not know how to join a room on this network": "Riot не знає як приєднатись до кімнати у цій мережі",
"Mentions only": "Тільки згадки",

View file

@ -1098,7 +1098,6 @@
"Unable to fetch notification target list": "无法获取通知目标列表",
"Set Password": "设置密码",
"Enable audible notifications in web client": "在网页客户端启用音频通知",
"Permalink": "永久链接",
"Off": "关闭",
"Riot does not know how to join a room on this network": "Riot 不知道如何在此网络中加入聊天室",
"Mentions only": "只限提及",

View file

@ -1125,7 +1125,6 @@
"Riot does not know how to join a room on this network": "Riot 不知道如何在此網路中加入聊天室",
"Set Password": "設定密碼",
"Enable audible notifications in web client": "在網頁客戶端啟用音訊通知",
"Permalink": "永久連結",
"Off": "關閉",
"#example": "#範例",
"Mentions only": "僅提及",