mirror of
https://github.com/element-hq/element-web
synced 2024-11-26 03:05:51 +03:00
Merge branch 'develop' into travis/feature/wellknown2
This commit is contained in:
commit
3476be3327
41 changed files with 750 additions and 132 deletions
|
@ -557,4 +557,3 @@ textarea {
|
||||||
.mx_Username_color8 {
|
.mx_Username_color8 {
|
||||||
color: $username-variant8-color;
|
color: $username-variant8-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -120,10 +120,12 @@
|
||||||
@import "./views/messages/_ReactionDimension.scss";
|
@import "./views/messages/_ReactionDimension.scss";
|
||||||
@import "./views/messages/_ReactionsRow.scss";
|
@import "./views/messages/_ReactionsRow.scss";
|
||||||
@import "./views/messages/_ReactionsRowButton.scss";
|
@import "./views/messages/_ReactionsRowButton.scss";
|
||||||
|
@import "./views/messages/_ReactionsRowButtonTooltip.scss";
|
||||||
@import "./views/messages/_RoomAvatarEvent.scss";
|
@import "./views/messages/_RoomAvatarEvent.scss";
|
||||||
@import "./views/messages/_SenderProfile.scss";
|
@import "./views/messages/_SenderProfile.scss";
|
||||||
@import "./views/messages/_TextualEvent.scss";
|
@import "./views/messages/_TextualEvent.scss";
|
||||||
@import "./views/messages/_UnknownBody.scss";
|
@import "./views/messages/_UnknownBody.scss";
|
||||||
|
@import "./views/messages/_ViewSourceEvent.scss";
|
||||||
@import "./views/room_settings/_AliasSettings.scss";
|
@import "./views/room_settings/_AliasSettings.scss";
|
||||||
@import "./views/room_settings/_ColorSettings.scss";
|
@import "./views/room_settings/_ColorSettings.scss";
|
||||||
@import "./views/rooms/_AppsDrawer.scss";
|
@import "./views/rooms/_AppsDrawer.scss";
|
||||||
|
|
|
@ -82,8 +82,13 @@ limitations under the License.
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_DevTools_content .mx_Field_input + .mx_Field_input {
|
.mx_DevTools_eventTypeStateKeyGroup {
|
||||||
margin-left: 42px;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DevTools_content .mx_Field_input:first-of-type {
|
||||||
|
margin-right: 42px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_DevTools_tgl {
|
.mx_DevTools_tgl {
|
||||||
|
|
|
@ -16,17 +16,22 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_MessageEditor {
|
.mx_MessageEditor {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: $header-panel-bg-color;
|
padding: 3px;
|
||||||
padding: 11px 13px 7px 56px;
|
// this is to try not make the text move but still have some
|
||||||
|
// padding around and in the editor.
|
||||||
|
// Actual values from fiddling around in inspector
|
||||||
|
margin: -7px -10px -5px -10px;
|
||||||
|
|
||||||
.mx_MessageEditor_editor {
|
.mx_MessageEditor_editor {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: solid 1px #e9edf1;
|
border: solid 1px $primary-hairline-color;
|
||||||
background-color: #ffffff;
|
background-color: $primary-bg-color;
|
||||||
padding: 10px;
|
padding: 3px 6px;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-x: auto;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -48,8 +53,15 @@ limitations under the License.
|
||||||
.mx_MessageEditor_buttons {
|
.mx_MessageEditor_buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: end;
|
justify-content: flex-end;
|
||||||
padding: 5px 0;
|
padding: 5px;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
background: $header-panel-bg-color;
|
||||||
|
z-index: 100;
|
||||||
|
right: 0;
|
||||||
|
margin: 0 -110px 0 0;
|
||||||
|
padding-right: 104px;
|
||||||
|
|
||||||
.mx_AccessibleButton {
|
.mx_AccessibleButton {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
|
@ -62,3 +74,8 @@ limitations under the License.
|
||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_last .mx_MessageEditor_buttons {
|
||||||
|
position: static;
|
||||||
|
margin-right: -103px;
|
||||||
|
}
|
||||||
|
|
|
@ -74,3 +74,19 @@ limitations under the License.
|
||||||
animation: mx_fadeout 0.1s forwards;
|
animation: mx_fadeout 0.1s forwards;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_Tooltip_timeline {
|
||||||
|
box-shadow: none;
|
||||||
|
background-color: $tooltip-timeline-bg-color;
|
||||||
|
color: $tooltip-timeline-fg-color;
|
||||||
|
text-align: center;
|
||||||
|
border: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.2;
|
||||||
|
padding: 6px 8px;
|
||||||
|
|
||||||
|
.mx_Tooltip_chevron::after {
|
||||||
|
border-right-color: $tooltip-timeline-bg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
19
res/css/views/messages/_ReactionsRowButtonTooltip.scss
Normal file
19
res/css/views/messages/_ReactionsRowButtonTooltip.scss
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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_ReactionsRowButtonTooltip_reactedWith {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
50
res/css/views/messages/_ViewSourceEvent.scss
Normal file
50
res/css/views/messages/_ViewSourceEvent.scss
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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_EventTile_content.mx_ViewSourceEvent {
|
||||||
|
display: flex;
|
||||||
|
opacity: 0.6;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
pre, code {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
line-height: 1.2;
|
||||||
|
margin: 3.5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ViewSourceEvent_toggle {
|
||||||
|
width: 12px;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: 0 center;
|
||||||
|
mask-size: auto 12px;
|
||||||
|
visibility: hidden;
|
||||||
|
background-color: $accent-color;
|
||||||
|
mask-image: url('$(res)/img/feather-customised/widget/maximise.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_ViewSourceEvent_expanded .mx_ViewSourceEvent_toggle {
|
||||||
|
mask-position: 0 bottom;
|
||||||
|
margin-bottom: 7px;
|
||||||
|
mask-image: url('$(res)/img/feather-customised/widget/minimise.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .mx_ViewSourceEvent_toggle {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,6 +43,10 @@ limitations under the License.
|
||||||
padding-top: 0px ! important;
|
padding-top: 0px ! important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_isEditing {
|
||||||
|
background-color: $header-panel-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_EventTile .mx_SenderProfile {
|
.mx_EventTile .mx_SenderProfile {
|
||||||
color: $primary-fg-color;
|
color: $primary-fg-color;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
@ -72,6 +76,10 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_isEditing .mx_MessageTimestamp {
|
||||||
|
visibility: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_EventTile .mx_MessageTimestamp {
|
.mx_EventTile .mx_MessageTimestamp {
|
||||||
display: block;
|
display: block;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
|
@ -377,6 +385,14 @@ limitations under the License.
|
||||||
left: 41px;
|
left: 41px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_content .mx_EventTile_edited {
|
||||||
|
user-select: none;
|
||||||
|
font-size: 12px;
|
||||||
|
color: $roomtopic-color;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Various markdown overrides */
|
/* Various markdown overrides */
|
||||||
|
|
||||||
.mx_EventTile_content .markdown-body {
|
.mx_EventTile_content .markdown-body {
|
||||||
|
|
|
@ -157,6 +157,9 @@ $reaction-row-button-hover-border-color: $header-panel-text-primary-color;
|
||||||
$reaction-row-button-selected-bg-color: #1f6954;
|
$reaction-row-button-selected-bg-color: #1f6954;
|
||||||
$reaction-row-button-selected-border-color: $accent-color;
|
$reaction-row-button-selected-border-color: $accent-color;
|
||||||
|
|
||||||
|
$tooltip-timeline-bg-color: $tagpanel-bg-color;
|
||||||
|
$tooltip-timeline-fg-color: #ffffff;
|
||||||
|
|
||||||
// ***** Mixins! *****
|
// ***** Mixins! *****
|
||||||
|
|
||||||
@define-mixin mx_DialogButton {
|
@define-mixin mx_DialogButton {
|
||||||
|
|
|
@ -265,6 +265,9 @@ $reaction-row-button-hover-border-color: $focus-bg-color;
|
||||||
$reaction-row-button-selected-bg-color: #e9fff9;
|
$reaction-row-button-selected-bg-color: #e9fff9;
|
||||||
$reaction-row-button-selected-border-color: $accent-color;
|
$reaction-row-button-selected-border-color: $accent-color;
|
||||||
|
|
||||||
|
$tooltip-timeline-bg-color: $tagpanel-bg-color;
|
||||||
|
$tooltip-timeline-fg-color: #ffffff;
|
||||||
|
|
||||||
// ***** Mixins! *****
|
// ***** Mixins! *****
|
||||||
|
|
||||||
@define-mixin mx_DialogButton {
|
@define-mixin mx_DialogButton {
|
||||||
|
|
|
@ -361,7 +361,7 @@ async function _startCallApp(roomId, type) {
|
||||||
|
|
||||||
Modal.createTrackedDialog('Could not connect to the integration server', '', ErrorDialog, {
|
Modal.createTrackedDialog('Could not connect to the integration server', '', ErrorDialog, {
|
||||||
title: _t('Could not connect to the integration server'),
|
title: _t('Could not connect to the integration server'),
|
||||||
description: _t('A conference call could not be started because the intgrations server is not available'),
|
description: _t('A conference call could not be started because the integrations server is not available'),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ export function showGroupInviteDialog(groupId) {
|
||||||
Modal.createTrackedDialog('Group Invite', '', AddressPickerDialog, {
|
Modal.createTrackedDialog('Group Invite', '', AddressPickerDialog, {
|
||||||
title: _t("Invite new community members"),
|
title: _t("Invite new community members"),
|
||||||
description: description,
|
description: description,
|
||||||
placeholder: _t("Name or matrix ID"),
|
placeholder: _t("Name or Matrix ID"),
|
||||||
button: _t("Invite to Community"),
|
button: _t("Invite to Community"),
|
||||||
validAddressTypes: ['mx-user-id'],
|
validAddressTypes: ['mx-user-id'],
|
||||||
onFinished: (success, addrs) => {
|
onFinished: (success, addrs) => {
|
||||||
|
|
|
@ -107,6 +107,17 @@ function unicodeToImage(str, addAlt) {
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the shortcode for an emoji character.
|
||||||
|
*
|
||||||
|
* @param {String} char The emoji character
|
||||||
|
* @return {String} The shortcode (such as :thumbup:)
|
||||||
|
*/
|
||||||
|
export function unicodeToShort(char) {
|
||||||
|
const unicode = emojione.jsEscapeMap[char];
|
||||||
|
return emojione.mapUnicodeToShort()[unicode];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given one or more unicode characters (represented by unicode
|
* Given one or more unicode characters (represented by unicode
|
||||||
* character number), return an image node with the corresponding
|
* character number), return an image node with the corresponding
|
||||||
|
@ -530,8 +541,8 @@ export function bodyToHtml(content, highlights, opts={}) {
|
||||||
});
|
});
|
||||||
|
|
||||||
return isDisplayedWithHtml ?
|
return isDisplayedWithHtml ?
|
||||||
<span className={className} dangerouslySetInnerHTML={{ __html: safeBody }} dir="auto" /> :
|
<span key="body" className={className} dangerouslySetInnerHTML={{ __html: safeBody }} dir="auto" /> :
|
||||||
<span className={className} dir="auto">{ strippedBody }</span>;
|
<span key="body" className={className} dir="auto">{ strippedBody }</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function emojifyText(text, addAlt) {
|
export function emojifyText(text, addAlt) {
|
||||||
|
|
|
@ -45,7 +45,7 @@ export function showStartChatInviteDialog() {
|
||||||
Modal.createTrackedDialog('Start a chat', '', AddressPickerDialog, {
|
Modal.createTrackedDialog('Start a chat', '', AddressPickerDialog, {
|
||||||
title: _t('Start a chat'),
|
title: _t('Start a chat'),
|
||||||
description: _t("Who would you like to communicate with?"),
|
description: _t("Who would you like to communicate with?"),
|
||||||
placeholder: _t("Email, name or matrix ID"),
|
placeholder: _t("Email, name or Matrix ID"),
|
||||||
validAddressTypes: ['mx-user-id', 'email'],
|
validAddressTypes: ['mx-user-id', 'email'],
|
||||||
button: _t("Start Chat"),
|
button: _t("Start Chat"),
|
||||||
onFinished: _onStartChatFinished,
|
onFinished: _onStartChatFinished,
|
||||||
|
@ -58,7 +58,7 @@ export function showRoomInviteDialog(roomId) {
|
||||||
title: _t('Invite new room members'),
|
title: _t('Invite new room members'),
|
||||||
description: _t('Who would you like to add to this room?'),
|
description: _t('Who would you like to add to this room?'),
|
||||||
button: _t('Send Invites'),
|
button: _t('Send Invites'),
|
||||||
placeholder: _t("Email, name or matrix ID"),
|
placeholder: _t("Email, name or Matrix ID"),
|
||||||
onFinished: (shouldInvite, addrs) => {
|
onFinished: (shouldInvite, addrs) => {
|
||||||
_onRoomInviteFinished(roomId, shouldInvite, addrs);
|
_onRoomInviteFinished(roomId, shouldInvite, addrs);
|
||||||
},
|
},
|
||||||
|
|
|
@ -518,7 +518,7 @@ export const CommandMap = {
|
||||||
unban: new Command({
|
unban: new Command({
|
||||||
name: 'unban',
|
name: 'unban',
|
||||||
args: '<user-id>',
|
args: '<user-id>',
|
||||||
description: _td('Unbans user with given id'),
|
description: _td('Unbans user with given ID'),
|
||||||
runFn: function(roomId, args) {
|
runFn: function(roomId, args) {
|
||||||
if (args) {
|
if (args) {
|
||||||
const matches = args.match(/^(\S+)$/);
|
const matches = args.match(/^(\S+)$/);
|
||||||
|
|
|
@ -265,7 +265,7 @@ const RoleUserList = React.createClass({
|
||||||
Modal.createTrackedDialog('Add Users to Group Summary', '', AddressPickerDialog, {
|
Modal.createTrackedDialog('Add Users to Group Summary', '', AddressPickerDialog, {
|
||||||
title: _t('Add users to the community summary'),
|
title: _t('Add users to the community summary'),
|
||||||
description: _t("Who would you like to add to this summary?"),
|
description: _t("Who would you like to add to this summary?"),
|
||||||
placeholder: _t("Name or matrix ID"),
|
placeholder: _t("Name or Matrix ID"),
|
||||||
button: _t("Add to summary"),
|
button: _t("Add to summary"),
|
||||||
validAddressTypes: ['mx-user-id'],
|
validAddressTypes: ['mx-user-id'],
|
||||||
groupId: this.props.groupId,
|
groupId: this.props.groupId,
|
||||||
|
|
|
@ -24,6 +24,7 @@ import {wantsDateSeparator} from '../../DateUtils';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
|
|
||||||
import MatrixClientPeg from '../../MatrixClientPeg';
|
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||||
|
import SettingsStore from '../../settings/SettingsStore';
|
||||||
|
|
||||||
const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes
|
const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes
|
||||||
const continuedTypes = ['m.sticker', 'm.room.message'];
|
const continuedTypes = ['m.sticker', 'm.room.message'];
|
||||||
|
@ -248,6 +249,10 @@ module.exports = React.createClass({
|
||||||
return false; // ignored = no show (only happens if the ignore happens after an event was received)
|
return false; // ignored = no show (only happens if the ignore happens after an event was received)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SettingsStore.getValue("showHiddenEventsInTimeline")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
const EventTile = sdk.getComponent('rooms.EventTile');
|
const EventTile = sdk.getComponent('rooms.EventTile');
|
||||||
if (!EventTile.haveTileForEvent(mxEv)) {
|
if (!EventTile.haveTileForEvent(mxEv)) {
|
||||||
return false; // no tile = no show
|
return false; // no tile = no show
|
||||||
|
@ -450,14 +455,10 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
_getTilesForEvent: function(prevEvent, mxEv, last) {
|
_getTilesForEvent: function(prevEvent, mxEv, last) {
|
||||||
const EventTile = sdk.getComponent('rooms.EventTile');
|
const EventTile = sdk.getComponent('rooms.EventTile');
|
||||||
const MessageEditor = sdk.getComponent('elements.MessageEditor');
|
|
||||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||||
const ret = [];
|
const ret = [];
|
||||||
|
|
||||||
if (this.props.editEvent && this.props.editEvent.getId() === mxEv.getId()) {
|
const isEditing = this.props.editEvent && this.props.editEvent.getId() === mxEv.getId();
|
||||||
return [<MessageEditor key={mxEv.getId()} event={mxEv} />];
|
|
||||||
}
|
|
||||||
|
|
||||||
// is this a continuation of the previous message?
|
// is this a continuation of the previous message?
|
||||||
let continuation = false;
|
let continuation = false;
|
||||||
|
|
||||||
|
@ -527,6 +528,7 @@ module.exports = React.createClass({
|
||||||
continuation={continuation}
|
continuation={continuation}
|
||||||
isRedacted={mxEv.isRedacted()}
|
isRedacted={mxEv.isRedacted()}
|
||||||
replacingEventId={mxEv.replacingEventId()}
|
replacingEventId={mxEv.replacingEventId()}
|
||||||
|
isEditing={isEditing}
|
||||||
onHeightChanged={this._onHeightChanged}
|
onHeightChanged={this._onHeightChanged}
|
||||||
readReceipts={readReceipts}
|
readReceipts={readReceipts}
|
||||||
readReceiptMap={this._readReceiptMap}
|
readReceiptMap={this._readReceiptMap}
|
||||||
|
@ -714,7 +716,7 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
|
|
||||||
let whoIsTyping;
|
let whoIsTyping;
|
||||||
if (this.props.room) {
|
if (this.props.room && !this.props.tileShape) {
|
||||||
whoIsTyping = (<WhoIsTypingTile
|
whoIsTyping = (<WhoIsTypingTile
|
||||||
room={this.props.room}
|
room={this.props.room}
|
||||||
onShown={this._onTypingShown}
|
onShown={this._onTypingShown}
|
||||||
|
|
|
@ -128,8 +128,10 @@ class SendCustomEvent extends GenericEditor {
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
<div className="mx_DevTools_content">
|
<div className="mx_DevTools_content">
|
||||||
{ this.textInput('eventType', _t('Event Type')) }
|
<div className="mx_DevTools_eventTypeStateKeyGroup">
|
||||||
{ this.state.isStateEvent && this.textInput('stateKey', _t('State Key')) }
|
{ this.textInput('eventType', _t('Event Type')) }
|
||||||
|
{ this.state.isStateEvent && this.textInput('stateKey', _t('State Key')) }
|
||||||
|
</div>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
|
|
|
@ -114,7 +114,8 @@ export default class ShareDialog extends React.Component {
|
||||||
top: y,
|
top: y,
|
||||||
message: successful ? _t('Copied!') : _t('Failed to copy'),
|
message: successful ? _t('Copied!') : _t('Failed to copy'),
|
||||||
}, false);
|
}, false);
|
||||||
e.target.onmouseleave = close;
|
// Drop a reference to this close handler for componentWillUnmount
|
||||||
|
this.closeCopiedTooltip = e.target.onmouseleave = close;
|
||||||
}
|
}
|
||||||
|
|
||||||
onLinkSpecificEventCheckboxClick() {
|
onLinkSpecificEventCheckboxClick() {
|
||||||
|
@ -131,6 +132,12 @@ export default class ShareDialog extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
// if the Copied tooltip is open then get rid of it, there are ways to close the modal which wouldn't close
|
||||||
|
// the tooltip otherwise, such as pressing Escape or clicking X really quickly
|
||||||
|
if (this.closeCopiedTooltip) this.closeCopiedTooltip();
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let title;
|
let title;
|
||||||
let matrixToUrl;
|
let matrixToUrl;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 OpenMarket Ltd
|
Copyright 2016 OpenMarket Ltd
|
||||||
|
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -13,11 +14,13 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
const MemberAvatar = require('../avatars/MemberAvatar.js');
|
import MemberAvatar from '../avatars/MemberAvatar';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
import { formatCommaSeparatedList } from '../../../utils/FormattingUtils';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'MemberEventListSummary',
|
displayName: 'MemberEventListSummary',
|
||||||
|
@ -105,7 +108,7 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const desc = this._renderCommaSeparatedList(descs);
|
const desc = formatCommaSeparatedList(descs);
|
||||||
|
|
||||||
return _t('%(nameList)s %(transitionList)s', { nameList: nameList, transitionList: desc });
|
return _t('%(nameList)s %(transitionList)s', { nameList: nameList, transitionList: desc });
|
||||||
});
|
});
|
||||||
|
@ -132,7 +135,7 @@ module.exports = React.createClass({
|
||||||
* included before "and [n] others".
|
* included before "and [n] others".
|
||||||
*/
|
*/
|
||||||
_renderNameList: function(users) {
|
_renderNameList: function(users) {
|
||||||
return this._renderCommaSeparatedList(users, this.props.summaryLength);
|
return formatCommaSeparatedList(users, this.props.summaryLength);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -283,35 +286,6 @@ module.exports = React.createClass({
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a written English string representing `items`, with an optional limit on
|
|
||||||
* the number of items included in the result. If specified and if the length of
|
|
||||||
*`items` is greater than the limit, the string "and n others" will be appended onto
|
|
||||||
* the result.
|
|
||||||
* If `items` is empty, returns the empty string. If there is only one item, return
|
|
||||||
* it.
|
|
||||||
* @param {string[]} items the items to construct a string from.
|
|
||||||
* @param {number?} itemLimit the number by which to limit the list.
|
|
||||||
* @returns {string} a string constructed by joining `items` with a comma between each
|
|
||||||
* item, but with the last item appended as " and [lastItem]".
|
|
||||||
*/
|
|
||||||
_renderCommaSeparatedList(items, itemLimit) {
|
|
||||||
const remaining = itemLimit === undefined ? 0 : Math.max(
|
|
||||||
items.length - itemLimit, 0,
|
|
||||||
);
|
|
||||||
if (items.length === 0) {
|
|
||||||
return "";
|
|
||||||
} else if (items.length === 1) {
|
|
||||||
return items[0];
|
|
||||||
} else if (remaining > 0) {
|
|
||||||
items = items.slice(0, itemLimit);
|
|
||||||
return _t("%(items)s and %(count)s others", { items: items.join(', '), count: remaining } );
|
|
||||||
} else {
|
|
||||||
const lastItem = items.pop();
|
|
||||||
return _t("%(items)s and %(lastItem)s", { items: items.join(', '), lastItem: lastItem });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_renderAvatars: function(roomMembers) {
|
_renderAvatars: function(roomMembers) {
|
||||||
const avatars = roomMembers.slice(0, this.props.avatarsMaxLength).map((m) => {
|
const avatars = roomMembers.slice(0, this.props.avatarsMaxLength).map((m) => {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -77,35 +77,46 @@ export default class MessageEditor extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onKeyDown = (event) => {
|
_onKeyDown = (event) => {
|
||||||
|
// insert newline on Shift+Enter
|
||||||
|
if (event.shiftKey && event.key === "Enter") {
|
||||||
|
event.preventDefault(); // just in case the browser does support this
|
||||||
|
document.execCommand("insertHTML", undefined, "\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// autocomplete or enter to send below shouldn't have any modifier keys pressed.
|
||||||
if (event.metaKey || event.altKey || event.shiftKey) {
|
if (event.metaKey || event.altKey || event.shiftKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.model.autoComplete) {
|
if (this.model.autoComplete) {
|
||||||
return;
|
const autoComplete = this.model.autoComplete;
|
||||||
|
switch (event.key) {
|
||||||
|
case "Enter":
|
||||||
|
autoComplete.onEnter(event); break;
|
||||||
|
case "ArrowUp":
|
||||||
|
autoComplete.onUpArrow(event); break;
|
||||||
|
case "ArrowDown":
|
||||||
|
autoComplete.onDownArrow(event); break;
|
||||||
|
case "Tab":
|
||||||
|
autoComplete.onTab(event); break;
|
||||||
|
case "Escape":
|
||||||
|
autoComplete.onEscape(event); break;
|
||||||
|
default:
|
||||||
|
return; // don't preventDefault on anything else
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
} else if (event.key === "Enter") {
|
||||||
|
this._sendEdit();
|
||||||
|
event.preventDefault();
|
||||||
|
} else if (event.key === "Escape") {
|
||||||
|
this._cancelEdit();
|
||||||
}
|
}
|
||||||
const autoComplete = this.model.autoComplete;
|
|
||||||
switch (event.key) {
|
|
||||||
case "Enter":
|
|
||||||
autoComplete.onEnter(event); break;
|
|
||||||
case "ArrowUp":
|
|
||||||
autoComplete.onUpArrow(event); break;
|
|
||||||
case "ArrowDown":
|
|
||||||
autoComplete.onDownArrow(event); break;
|
|
||||||
case "Tab":
|
|
||||||
autoComplete.onTab(event); break;
|
|
||||||
case "Escape":
|
|
||||||
autoComplete.onEscape(event); break;
|
|
||||||
default:
|
|
||||||
return; // don't preventDefault on anything else
|
|
||||||
}
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onCancelClicked = () => {
|
_cancelEdit = () => {
|
||||||
dis.dispatch({action: "edit_event", event: null});
|
dis.dispatch({action: "edit_event", event: null});
|
||||||
}
|
}
|
||||||
|
|
||||||
_onSaveClicked = () => {
|
_sendEdit = () => {
|
||||||
const newContent = {
|
const newContent = {
|
||||||
"msgtype": "m.text",
|
"msgtype": "m.text",
|
||||||
"body": textSerialize(this.model),
|
"body": textSerialize(this.model),
|
||||||
|
@ -144,12 +155,7 @@ export default class MessageEditor extends React.Component {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this._updateEditorState();
|
this._updateEditorState();
|
||||||
const sel = document.getSelection();
|
setCaretPosition(this._editorRef, this.model, this.model.getPositionAtEnd());
|
||||||
const range = document.createRange();
|
|
||||||
range.selectNodeContents(this._editorRef);
|
|
||||||
range.collapse(false);
|
|
||||||
sel.removeAllRanges();
|
|
||||||
sel.addRange(range);
|
|
||||||
this._editorRef.focus();
|
this._editorRef.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,8 +187,8 @@ export default class MessageEditor extends React.Component {
|
||||||
ref={ref => this._editorRef = ref}
|
ref={ref => this._editorRef = ref}
|
||||||
></div>
|
></div>
|
||||||
<div className="mx_MessageEditor_buttons">
|
<div className="mx_MessageEditor_buttons">
|
||||||
<AccessibleButton kind="secondary" onClick={this._onCancelClicked}>{_t("Cancel")}</AccessibleButton>
|
<AccessibleButton kind="secondary" onClick={this._cancelEdit}>{_t("Cancel")}</AccessibleButton>
|
||||||
<AccessibleButton kind="primary" onClick={this._onSaveClicked}>{_t("Save")}</AccessibleButton>
|
<AccessibleButton kind="primary" onClick={this._sendEdit}>{_t("Save")}</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,6 +90,7 @@ module.exports = React.createClass({
|
||||||
tileShape={this.props.tileShape}
|
tileShape={this.props.tileShape}
|
||||||
maxImageHeight={this.props.maxImageHeight}
|
maxImageHeight={this.props.maxImageHeight}
|
||||||
replacingEventId={this.props.replacingEventId}
|
replacingEventId={this.props.replacingEventId}
|
||||||
|
isEditing={this.props.isEditing}
|
||||||
onHeightChanged={this.props.onHeightChanged} />;
|
onHeightChanged={this.props.onHeightChanged} />;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -168,6 +168,7 @@ export default class ReactionDimension extends React.PureComponent {
|
||||||
|
|
||||||
return <span className="mx_ReactionDimension"
|
return <span className="mx_ReactionDimension"
|
||||||
title={this.props.title}
|
title={this.props.title}
|
||||||
|
aria-hidden={true}
|
||||||
>
|
>
|
||||||
{items}
|
{items}
|
||||||
</span>;
|
</span>;
|
||||||
|
|
|
@ -116,8 +116,8 @@ export default class ReactionsRow extends React.PureComponent {
|
||||||
return <ReactionsRowButton
|
return <ReactionsRowButton
|
||||||
key={content}
|
key={content}
|
||||||
content={content}
|
content={content}
|
||||||
count={count}
|
|
||||||
mxEvent={mxEvent}
|
mxEvent={mxEvent}
|
||||||
|
reactionEvents={events}
|
||||||
myReactionEvent={myReactionEvent}
|
myReactionEvent={myReactionEvent}
|
||||||
/>;
|
/>;
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,17 +19,28 @@ import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
|
import sdk from '../../../index';
|
||||||
|
|
||||||
export default class ReactionsRowButton extends React.PureComponent {
|
export default class ReactionsRowButton extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
// The event we're displaying reactions for
|
// The event we're displaying reactions for
|
||||||
mxEvent: PropTypes.object.isRequired,
|
mxEvent: PropTypes.object.isRequired,
|
||||||
|
// The reaction content / key / emoji
|
||||||
content: PropTypes.string.isRequired,
|
content: PropTypes.string.isRequired,
|
||||||
count: PropTypes.number.isRequired,
|
// A Set of Martix reaction events for this key
|
||||||
|
reactionEvents: PropTypes.object.isRequired,
|
||||||
// A possible Matrix event if the current user has voted for this type
|
// A possible Matrix event if the current user has voted for this type
|
||||||
myReactionEvent: PropTypes.object,
|
myReactionEvent: PropTypes.object,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
tooltipVisible: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
onClick = (ev) => {
|
onClick = (ev) => {
|
||||||
const { mxEvent, myReactionEvent, content } = this.props;
|
const { mxEvent, myReactionEvent, content } = this.props;
|
||||||
if (myReactionEvent) {
|
if (myReactionEvent) {
|
||||||
|
@ -48,18 +59,53 @@ export default class ReactionsRowButton extends React.PureComponent {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onMouseOver = () => {
|
||||||
|
this.setState({
|
||||||
|
// To avoid littering the DOM with a tooltip for every reaction,
|
||||||
|
// only render it on first use.
|
||||||
|
tooltipRendered: true,
|
||||||
|
tooltipVisible: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseOut = () => {
|
||||||
|
this.setState({
|
||||||
|
tooltipVisible: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { content, count, myReactionEvent } = this.props;
|
const ReactionsRowButtonTooltip =
|
||||||
|
sdk.getComponent('messages.ReactionsRowButtonTooltip');
|
||||||
|
const { content, reactionEvents, myReactionEvent } = this.props;
|
||||||
|
|
||||||
|
const count = reactionEvents.size;
|
||||||
|
if (!count) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const classes = classNames({
|
const classes = classNames({
|
||||||
mx_ReactionsRowButton: true,
|
mx_ReactionsRowButton: true,
|
||||||
mx_ReactionsRowButton_selected: !!myReactionEvent,
|
mx_ReactionsRowButton_selected: !!myReactionEvent,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let tooltip;
|
||||||
|
if (this.state.tooltipRendered) {
|
||||||
|
tooltip = <ReactionsRowButtonTooltip
|
||||||
|
mxEvent={this.props.mxEvent}
|
||||||
|
content={content}
|
||||||
|
reactionEvents={reactionEvents}
|
||||||
|
visible={this.state.tooltipVisible}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
return <span className={classes}
|
return <span className={classes}
|
||||||
onClick={this.onClick}
|
onClick={this.onClick}
|
||||||
|
onMouseOver={this.onMouseOver}
|
||||||
|
onMouseOut={this.onMouseOut}
|
||||||
>
|
>
|
||||||
{content} {count}
|
{content} {count}
|
||||||
|
{tooltip}
|
||||||
</span>;
|
</span>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
81
src/components/views/messages/ReactionsRowButtonTooltip.js
Normal file
81
src/components/views/messages/ReactionsRowButtonTooltip.js
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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 MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
|
import sdk from '../../../index';
|
||||||
|
import { unicodeToShort } from '../../../HtmlUtils';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
import { formatCommaSeparatedList } from '../../../utils/FormattingUtils';
|
||||||
|
|
||||||
|
export default class ReactionsRowButtonTooltip extends React.PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
// The event we're displaying reactions for
|
||||||
|
mxEvent: PropTypes.object.isRequired,
|
||||||
|
// The reaction content / key / emoji
|
||||||
|
content: PropTypes.string.isRequired,
|
||||||
|
// A Set of Martix reaction events for this key
|
||||||
|
reactionEvents: PropTypes.object.isRequired,
|
||||||
|
visible: PropTypes.bool.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const Tooltip = sdk.getComponent('elements.Tooltip');
|
||||||
|
const { content, reactionEvents, mxEvent, visible } = this.props;
|
||||||
|
|
||||||
|
const room = MatrixClientPeg.get().getRoom(mxEvent.getRoomId());
|
||||||
|
let tooltipLabel;
|
||||||
|
if (room) {
|
||||||
|
const senders = [];
|
||||||
|
for (const reactionEvent of reactionEvents) {
|
||||||
|
const { name } = room.getMember(reactionEvent.getSender());
|
||||||
|
senders.push(name);
|
||||||
|
}
|
||||||
|
const shortName = unicodeToShort(content) || content;
|
||||||
|
tooltipLabel = <div>{_t(
|
||||||
|
"<reactors/><reactedWith>reacted with %(shortName)s</reactedWith>",
|
||||||
|
{
|
||||||
|
shortName,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reactors: () => {
|
||||||
|
return <div className="mx_ReactionsRowButtonTooltip_senders">
|
||||||
|
{formatCommaSeparatedList(senders, 6)}
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
reactedWith: (sub) => {
|
||||||
|
return <div className="mx_ReactionsRowButtonTooltip_reactedWith">
|
||||||
|
{sub}
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tooltip;
|
||||||
|
if (tooltipLabel) {
|
||||||
|
tooltip = <Tooltip
|
||||||
|
tooltipClassName="mx_Tooltip_timeline"
|
||||||
|
visible={visible}
|
||||||
|
label={tooltipLabel}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tooltip;
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ import ReactDOM from 'react-dom';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import highlight from 'highlight.js';
|
import highlight from 'highlight.js';
|
||||||
import * as HtmlUtils from '../../../HtmlUtils';
|
import * as HtmlUtils from '../../../HtmlUtils';
|
||||||
|
import {formatDate} from '../../../DateUtils';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import ScalarAuthClient from '../../../ScalarAuthClient';
|
import ScalarAuthClient from '../../../ScalarAuthClient';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
|
@ -88,7 +89,9 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
this._unmounted = false;
|
this._unmounted = false;
|
||||||
this._applyFormatting();
|
if (!this.props.isEditing) {
|
||||||
|
this._applyFormatting();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_applyFormatting() {
|
_applyFormatting() {
|
||||||
|
@ -127,11 +130,14 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidUpdate: function(prevProps) {
|
componentDidUpdate: function(prevProps) {
|
||||||
const messageWasEdited = prevProps.replacingEventId !== this.props.replacingEventId;
|
if (!this.props.isEditing) {
|
||||||
if (messageWasEdited) {
|
const stoppedEditing = prevProps.isEditing && !this.props.isEditing;
|
||||||
this._applyFormatting();
|
const messageWasEdited = prevProps.replacingEventId !== this.props.replacingEventId;
|
||||||
|
if (messageWasEdited || stoppedEditing) {
|
||||||
|
this._applyFormatting();
|
||||||
|
}
|
||||||
|
this.calculateUrlPreview();
|
||||||
}
|
}
|
||||||
this.calculateUrlPreview();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
|
@ -147,7 +153,9 @@ module.exports = React.createClass({
|
||||||
nextProps.replacingEventId !== this.props.replacingEventId ||
|
nextProps.replacingEventId !== this.props.replacingEventId ||
|
||||||
nextProps.highlightLink !== this.props.highlightLink ||
|
nextProps.highlightLink !== this.props.highlightLink ||
|
||||||
nextProps.showUrlPreview !== this.props.showUrlPreview ||
|
nextProps.showUrlPreview !== this.props.showUrlPreview ||
|
||||||
|
nextProps.isEditing !== this.props.isEditing ||
|
||||||
nextState.links !== this.state.links ||
|
nextState.links !== this.state.links ||
|
||||||
|
nextState.editedMarkerHovered !== this.state.editedMarkerHovered ||
|
||||||
nextState.widgetHidden !== this.state.widgetHidden);
|
nextState.widgetHidden !== this.state.widgetHidden);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -432,7 +440,39 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_onMouseEnterEditedMarker: function() {
|
||||||
|
this.setState({editedMarkerHovered: true});
|
||||||
|
},
|
||||||
|
|
||||||
|
_onMouseLeaveEditedMarker: function() {
|
||||||
|
this.setState({editedMarkerHovered: false});
|
||||||
|
},
|
||||||
|
|
||||||
|
_renderEditedMarker: function() {
|
||||||
|
let editedTooltip;
|
||||||
|
if (this.state.editedMarkerHovered) {
|
||||||
|
const Tooltip = sdk.getComponent('elements.Tooltip');
|
||||||
|
const editEvent = this.props.mxEvent.replacingEvent();
|
||||||
|
const date = editEvent && formatDate(editEvent.getDate());
|
||||||
|
editedTooltip = <Tooltip
|
||||||
|
tooltipClassName="mx_Tooltip_timeline"
|
||||||
|
label={_t("Edited at %(date)s", {date})}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key="editedMarker" className="mx_EventTile_edited"
|
||||||
|
onMouseEnter={this._onMouseEnterEditedMarker}
|
||||||
|
onMouseLeave={this._onMouseLeaveEditedMarker}
|
||||||
|
>{editedTooltip}<span>{`(${_t("edited")})`}</span></div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
if (this.props.isEditing) {
|
||||||
|
const MessageEditor = sdk.getComponent('elements.MessageEditor');
|
||||||
|
return <MessageEditor event={this.props.mxEvent} />;
|
||||||
|
}
|
||||||
const EmojiText = sdk.getComponent('elements.EmojiText');
|
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||||
const mxEvent = this.props.mxEvent;
|
const mxEvent = this.props.mxEvent;
|
||||||
const content = mxEvent.getContent();
|
const content = mxEvent.getContent();
|
||||||
|
@ -443,6 +483,9 @@ module.exports = React.createClass({
|
||||||
// Part of Replies fallback support
|
// Part of Replies fallback support
|
||||||
stripReplyFallback: stripReply,
|
stripReplyFallback: stripReply,
|
||||||
});
|
});
|
||||||
|
if (this.props.replacingEventId) {
|
||||||
|
body = [body, this._renderEditedMarker()];
|
||||||
|
}
|
||||||
|
|
||||||
if (this.props.highlightLink) {
|
if (this.props.highlightLink) {
|
||||||
body = <a href={this.props.highlightLink}>{ body }</a>;
|
body = <a href={this.props.highlightLink}>{ body }</a>;
|
||||||
|
|
67
src/components/views/messages/ViewSourceEvent.js
Normal file
67
src/components/views/messages/ViewSourceEvent.js
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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 classNames from 'classnames';
|
||||||
|
|
||||||
|
export default class ViewSourceEvent extends React.PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
/* the MatrixEvent to show */
|
||||||
|
mxEvent: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
expanded: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onToggle = (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
const { expanded } = this.state;
|
||||||
|
this.setState({
|
||||||
|
expanded: !expanded,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { mxEvent } = this.props;
|
||||||
|
const { expanded } = this.state;
|
||||||
|
|
||||||
|
let content;
|
||||||
|
if (expanded) {
|
||||||
|
content = <pre>{JSON.stringify(mxEvent, null, 4)}</pre>;
|
||||||
|
} else {
|
||||||
|
content = <code>{`{ "type": ${mxEvent.getType()} }`}</code>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const classes = classNames("mx_ViewSourceEvent mx_EventTile_content", {
|
||||||
|
mx_ViewSourceEvent_expanded: expanded,
|
||||||
|
});
|
||||||
|
|
||||||
|
return <span className={classes}>
|
||||||
|
{content}
|
||||||
|
<a
|
||||||
|
className="mx_ViewSourceEvent_toggle"
|
||||||
|
href="#"
|
||||||
|
onClick={this.onToggle}
|
||||||
|
/>
|
||||||
|
</span>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -520,7 +520,10 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
eventType !== 'm.room.message' && eventType !== 'm.sticker' && eventType != 'm.room.create'
|
eventType !== 'm.room.message' && eventType !== 'm.sticker' && eventType != 'm.room.create'
|
||||||
);
|
);
|
||||||
|
|
||||||
const tileHandler = getHandlerTile(this.props.mxEvent);
|
let tileHandler = getHandlerTile(this.props.mxEvent);
|
||||||
|
if (!tileHandler && SettingsStore.getValue("showHiddenEventsInTimeline")) {
|
||||||
|
tileHandler = "messages.ViewSourceEvent";
|
||||||
|
}
|
||||||
// This shouldn't happen: the caller should check we support this type
|
// This shouldn't happen: the caller should check we support this type
|
||||||
// before trying to instantiate us
|
// before trying to instantiate us
|
||||||
if (!tileHandler) {
|
if (!tileHandler) {
|
||||||
|
@ -540,6 +543,7 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
|
|
||||||
const classes = classNames({
|
const classes = classNames({
|
||||||
mx_EventTile: true,
|
mx_EventTile: true,
|
||||||
|
mx_EventTile_isEditing: this.props.isEditing,
|
||||||
mx_EventTile_info: isInfoMessage,
|
mx_EventTile_info: isInfoMessage,
|
||||||
mx_EventTile_12hr: this.props.isTwelveHour,
|
mx_EventTile_12hr: this.props.isTwelveHour,
|
||||||
mx_EventTile_encrypting: this.props.eventSendStatus === 'encrypting',
|
mx_EventTile_encrypting: this.props.eventSendStatus === 'encrypting',
|
||||||
|
@ -617,14 +621,14 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
const MessageActionBar = sdk.getComponent('messages.MessageActionBar');
|
const MessageActionBar = sdk.getComponent('messages.MessageActionBar');
|
||||||
const actionBar = <MessageActionBar
|
const actionBar = !this.props.isEditing ? <MessageActionBar
|
||||||
mxEvent={this.props.mxEvent}
|
mxEvent={this.props.mxEvent}
|
||||||
reactions={this.state.reactions}
|
reactions={this.state.reactions}
|
||||||
permalinkCreator={this.props.permalinkCreator}
|
permalinkCreator={this.props.permalinkCreator}
|
||||||
getTile={this.getTile}
|
getTile={this.getTile}
|
||||||
getReplyThread={this.getReplyThread}
|
getReplyThread={this.getReplyThread}
|
||||||
onFocusChange={this.onActionBarFocusChange}
|
onFocusChange={this.onActionBarFocusChange}
|
||||||
/>;
|
/> : undefined;
|
||||||
|
|
||||||
const timestamp = this.props.mxEvent.getTs() ?
|
const timestamp = this.props.mxEvent.getTs() ?
|
||||||
<MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.props.mxEvent.getTs()} /> : null;
|
<MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.props.mxEvent.getTs()} /> : null;
|
||||||
|
@ -780,6 +784,7 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
<EventTileType ref="tile"
|
<EventTileType ref="tile"
|
||||||
mxEvent={this.props.mxEvent}
|
mxEvent={this.props.mxEvent}
|
||||||
replacingEventId={this.props.replacingEventId}
|
replacingEventId={this.props.replacingEventId}
|
||||||
|
isEditing={this.props.isEditing}
|
||||||
highlights={this.props.highlights}
|
highlights={this.props.highlights}
|
||||||
highlightLink={this.props.highlightLink}
|
highlightLink={this.props.highlightLink}
|
||||||
showUrlPreview={this.props.showUrlPreview}
|
showUrlPreview={this.props.showUrlPreview}
|
||||||
|
|
|
@ -52,6 +52,7 @@ export default class LabsUserSettingsTab extends React.Component {
|
||||||
<div className="mx_SettingsTab_section">
|
<div className="mx_SettingsTab_section">
|
||||||
{flags}
|
{flags}
|
||||||
<SettingsFlag name={"enableWidgetScreenshots"} level={SettingLevel.ACCOUNT} />
|
<SettingsFlag name={"enableWidgetScreenshots"} level={SettingLevel.ACCOUNT} />
|
||||||
|
<SettingsFlag name={"showHiddenEventsInTimeline"} level={SettingLevel.DEVICE} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -61,6 +61,16 @@ export default class EditorModel {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPositionAtEnd() {
|
||||||
|
if (this._parts.length) {
|
||||||
|
const index = this._parts.length - 1;
|
||||||
|
const part = this._parts[index];
|
||||||
|
return new DocumentPosition(index, part.text.length);
|
||||||
|
} else {
|
||||||
|
return new DocumentPosition(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
serializeParts() {
|
serializeParts() {
|
||||||
return this._parts.map(({type, text}) => {return {type, text};});
|
return this._parts.map(({type, text}) => {return {type, text};});
|
||||||
}
|
}
|
||||||
|
@ -88,7 +98,8 @@ export default class EditorModel {
|
||||||
}
|
}
|
||||||
this._mergeAdjacentParts();
|
this._mergeAdjacentParts();
|
||||||
const caretOffset = diff.at - removedOffsetDecrease + addedLen;
|
const caretOffset = diff.at - removedOffsetDecrease + addedLen;
|
||||||
const newPosition = this._positionForOffset(caretOffset, true);
|
let newPosition = this._positionForOffset(caretOffset, true);
|
||||||
|
newPosition = newPosition.skipUneditableParts(this._parts);
|
||||||
this._setActivePart(newPosition);
|
this._setActivePart(newPosition);
|
||||||
this._updateCallback(newPosition);
|
this._updateCallback(newPosition);
|
||||||
}
|
}
|
||||||
|
@ -172,21 +183,26 @@ export default class EditorModel {
|
||||||
// part might be undefined here
|
// part might be undefined here
|
||||||
let part = this._parts[index];
|
let part = this._parts[index];
|
||||||
const amount = Math.min(len, part.text.length - offset);
|
const amount = Math.min(len, part.text.length - offset);
|
||||||
if (part.canEdit) {
|
// don't allow 0 amount deletions
|
||||||
const replaceWith = part.remove(offset, amount);
|
if (amount) {
|
||||||
if (typeof replaceWith === "string") {
|
if (part.canEdit) {
|
||||||
this._replacePart(index, this._partCreator.createDefaultPart(replaceWith));
|
const replaceWith = part.remove(offset, amount);
|
||||||
}
|
if (typeof replaceWith === "string") {
|
||||||
part = this._parts[index];
|
this._replacePart(index, this._partCreator.createDefaultPart(replaceWith));
|
||||||
// remove empty part
|
}
|
||||||
if (!part.text.length) {
|
part = this._parts[index];
|
||||||
this._removePart(index);
|
// remove empty part
|
||||||
|
if (!part.text.length) {
|
||||||
|
this._removePart(index);
|
||||||
|
} else {
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
index += 1;
|
removedOffsetDecrease += offset;
|
||||||
|
this._removePart(index);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
removedOffsetDecrease += offset;
|
index += 1;
|
||||||
this._removePart(index);
|
|
||||||
}
|
}
|
||||||
len -= amount;
|
len -= amount;
|
||||||
offset = 0;
|
offset = 0;
|
||||||
|
@ -261,4 +277,13 @@ class DocumentPosition {
|
||||||
get offset() {
|
get offset() {
|
||||||
return this._offset;
|
return this._offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
skipUneditableParts(parts) {
|
||||||
|
const part = parts[this.index];
|
||||||
|
if (part && !part.canEdit) {
|
||||||
|
return new DocumentPosition(this.index + 1, 0);
|
||||||
|
} else {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ class BasePart {
|
||||||
appendUntilRejected(str) {
|
appendUntilRejected(str) {
|
||||||
for (let i = 0; i < str.length; ++i) {
|
for (let i = 0; i < str.length; ++i) {
|
||||||
const chr = str.charAt(i);
|
const chr = str.charAt(i);
|
||||||
if (!this.acceptsInsertion(chr)) {
|
if (!this.acceptsInsertion(chr, i)) {
|
||||||
this._text = this._text + str.substr(0, i);
|
this._text = this._text + str.substr(0, i);
|
||||||
return str.substr(i);
|
return str.substr(i);
|
||||||
}
|
}
|
||||||
|
@ -180,8 +180,8 @@ class PillPart extends BasePart {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NewlinePart extends BasePart {
|
export class NewlinePart extends BasePart {
|
||||||
acceptsInsertion(chr) {
|
acceptsInsertion(chr, i) {
|
||||||
return this.text.length === 0 && chr === "\n";
|
return (this.text.length + i) === 0 && chr === "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
acceptsRemoval(position, chr) {
|
acceptsRemoval(position, chr) {
|
||||||
|
@ -205,6 +205,14 @@ export class NewlinePart extends BasePart {
|
||||||
get type() {
|
get type() {
|
||||||
return "newline";
|
return "newline";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this makes the cursor skip this part when it is inserted
|
||||||
|
// rather than trying to append to it, which is what we want.
|
||||||
|
// As a newline can also be only one character, it makes sense
|
||||||
|
// as it can only be one character long. This caused #9741.
|
||||||
|
get canEdit() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RoomPillPart extends PillPart {
|
export class RoomPillPart extends PillPart {
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
"You cannot place VoIP calls in this browser.": "You cannot place VoIP calls in this browser.",
|
"You cannot place VoIP calls in this browser.": "You cannot place VoIP calls in this browser.",
|
||||||
"You cannot place a call with yourself.": "You cannot place a call with yourself.",
|
"You cannot place a call with yourself.": "You cannot place a call with yourself.",
|
||||||
"Could not connect to the integration server": "Could not connect to the integration server",
|
"Could not connect to the integration server": "Could not connect to the integration server",
|
||||||
"A conference call could not be started because the intgrations server is not available": "A conference call could not be started because the intgrations server is not available",
|
"A conference call could not be started because the integrations server is not available": "A conference call could not be started because the integrations server is not available",
|
||||||
"Call in Progress": "Call in Progress",
|
"Call in Progress": "Call in Progress",
|
||||||
"A call is currently being placed!": "A call is currently being placed!",
|
"A call is currently being placed!": "A call is currently being placed!",
|
||||||
"A call is already in progress!": "A call is already in progress!",
|
"A call is already in progress!": "A call is already in progress!",
|
||||||
|
@ -80,7 +80,7 @@
|
||||||
"Who would you like to add to this community?": "Who would you like to add to this community?",
|
"Who would you like to add to this community?": "Who would you like to add to this community?",
|
||||||
"Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID",
|
"Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID",
|
||||||
"Invite new community members": "Invite new community members",
|
"Invite new community members": "Invite new community members",
|
||||||
"Name or matrix ID": "Name or matrix ID",
|
"Name or Matrix ID": "Name or Matrix ID",
|
||||||
"Invite to Community": "Invite to Community",
|
"Invite to Community": "Invite to Community",
|
||||||
"Which rooms would you like to add to this community?": "Which rooms would you like to add to this community?",
|
"Which rooms would you like to add to this community?": "Which rooms would you like to add to this community?",
|
||||||
"Show these rooms to non-members on the community page and room list?": "Show these rooms to non-members on the community page and room list?",
|
"Show these rooms to non-members on the community page and room list?": "Show these rooms to non-members on the community page and room list?",
|
||||||
|
@ -109,7 +109,7 @@
|
||||||
"Admin": "Admin",
|
"Admin": "Admin",
|
||||||
"Start a chat": "Start a chat",
|
"Start a chat": "Start a chat",
|
||||||
"Who would you like to communicate with?": "Who would you like to communicate with?",
|
"Who would you like to communicate with?": "Who would you like to communicate with?",
|
||||||
"Email, name or matrix ID": "Email, name or matrix ID",
|
"Email, name or Matrix ID": "Email, name or Matrix ID",
|
||||||
"Start Chat": "Start Chat",
|
"Start Chat": "Start Chat",
|
||||||
"Invite new room members": "Invite new room members",
|
"Invite new room members": "Invite new room members",
|
||||||
"Who would you like to add to this room?": "Who would you like to add to this room?",
|
"Who would you like to add to this room?": "Who would you like to add to this room?",
|
||||||
|
@ -157,7 +157,7 @@
|
||||||
"Unrecognised room alias:": "Unrecognised room alias:",
|
"Unrecognised room alias:": "Unrecognised room alias:",
|
||||||
"Kicks user with given id": "Kicks user with given id",
|
"Kicks user with given id": "Kicks user with given id",
|
||||||
"Bans user with given id": "Bans user with given id",
|
"Bans user with given id": "Bans user with given id",
|
||||||
"Unbans user with given id": "Unbans user with given id",
|
"Unbans user with given ID": "Unbans user with given ID",
|
||||||
"Ignores a user, hiding their messages from you": "Ignores a user, hiding their messages from you",
|
"Ignores a user, hiding their messages from you": "Ignores a user, hiding their messages from you",
|
||||||
"Ignored user": "Ignored user",
|
"Ignored user": "Ignored user",
|
||||||
"You are now ignoring %(userId)s": "You are now ignoring %(userId)s",
|
"You are now ignoring %(userId)s": "You are now ignoring %(userId)s",
|
||||||
|
@ -255,6 +255,9 @@
|
||||||
"This homeserver has exceeded one of its resource limits.": "This homeserver has exceeded one of its resource limits.",
|
"This homeserver has exceeded one of its resource limits.": "This homeserver has exceeded one of its resource limits.",
|
||||||
"Please <a>contact your service administrator</a> to continue using the service.": "Please <a>contact your service administrator</a> to continue using the service.",
|
"Please <a>contact your service administrator</a> to continue using the service.": "Please <a>contact your service administrator</a> to continue using the service.",
|
||||||
"Unable to connect to Homeserver. Retrying...": "Unable to connect to Homeserver. Retrying...",
|
"Unable to connect to Homeserver. Retrying...": "Unable to connect to Homeserver. Retrying...",
|
||||||
|
"%(items)s and %(count)s others|other": "%(items)s and %(count)s others",
|
||||||
|
"%(items)s and %(count)s others|one": "%(items)s and one other",
|
||||||
|
"%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s",
|
||||||
"Your browser does not support the required cryptography extensions": "Your browser does not support the required cryptography extensions",
|
"Your browser does not support the required cryptography extensions": "Your browser does not support the required cryptography extensions",
|
||||||
"Not a valid Riot keyfile": "Not a valid Riot keyfile",
|
"Not a valid Riot keyfile": "Not a valid Riot keyfile",
|
||||||
"Authentication check failed: incorrect password?": "Authentication check failed: incorrect password?",
|
"Authentication check failed: incorrect password?": "Authentication check failed: incorrect password?",
|
||||||
|
@ -335,6 +338,7 @@
|
||||||
"Prompt before sending invites to potentially invalid matrix IDs": "Prompt before sending invites to potentially invalid matrix IDs",
|
"Prompt before sending invites to potentially invalid matrix IDs": "Prompt before sending invites to potentially invalid matrix IDs",
|
||||||
"Show developer tools": "Show developer tools",
|
"Show developer tools": "Show developer tools",
|
||||||
"Order rooms in the room list by most important first instead of most recent": "Order rooms in the room list by most important first instead of most recent",
|
"Order rooms in the room list by most important first instead of most recent": "Order rooms in the room list by most important first instead of most recent",
|
||||||
|
"Show hidden events in timeline": "Show hidden events in timeline",
|
||||||
"Collecting app version information": "Collecting app version information",
|
"Collecting app version information": "Collecting app version information",
|
||||||
"Collecting logs": "Collecting logs",
|
"Collecting logs": "Collecting logs",
|
||||||
"Uploading report": "Uploading report",
|
"Uploading report": "Uploading report",
|
||||||
|
@ -909,6 +913,7 @@
|
||||||
"Invalid file%(extra)s": "Invalid file%(extra)s",
|
"Invalid file%(extra)s": "Invalid file%(extra)s",
|
||||||
"Error decrypting image": "Error decrypting image",
|
"Error decrypting image": "Error decrypting image",
|
||||||
"Error decrypting video": "Error decrypting video",
|
"Error decrypting video": "Error decrypting video",
|
||||||
|
"<reactors/><reactedWith>reacted with %(shortName)s</reactedWith>": "<reactors/><reactedWith>reacted with %(shortName)s</reactedWith>",
|
||||||
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s changed the avatar for %(roomName)s",
|
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s changed the avatar for %(roomName)s",
|
||||||
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removed the room avatar.",
|
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removed the room avatar.",
|
||||||
"%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s changed the room avatar to <img/>",
|
"%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s changed the room avatar to <img/>",
|
||||||
|
@ -918,6 +923,8 @@
|
||||||
"Failed to copy": "Failed to copy",
|
"Failed to copy": "Failed to copy",
|
||||||
"Add an Integration": "Add an Integration",
|
"Add an Integration": "Add an Integration",
|
||||||
"You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?",
|
"You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?",
|
||||||
|
"Edited at %(date)s": "Edited at %(date)s",
|
||||||
|
"edited": "edited",
|
||||||
"Removed or unknown message type": "Removed or unknown message type",
|
"Removed or unknown message type": "Removed or unknown message type",
|
||||||
"Message removed by %(userId)s": "Message removed by %(userId)s",
|
"Message removed by %(userId)s": "Message removed by %(userId)s",
|
||||||
"Message removed": "Message removed",
|
"Message removed": "Message removed",
|
||||||
|
@ -1044,9 +1051,6 @@
|
||||||
"%(severalUsers)schanged their avatar %(count)s times|one": "%(severalUsers)schanged their avatar",
|
"%(severalUsers)schanged their avatar %(count)s times|one": "%(severalUsers)schanged their avatar",
|
||||||
"%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)schanged their avatar %(count)s times",
|
"%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)schanged their avatar %(count)s times",
|
||||||
"%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)schanged their avatar",
|
"%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)schanged their avatar",
|
||||||
"%(items)s and %(count)s others|other": "%(items)s and %(count)s others",
|
|
||||||
"%(items)s and %(count)s others|one": "%(items)s and one other",
|
|
||||||
"%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s",
|
|
||||||
"collapse": "collapse",
|
"collapse": "collapse",
|
||||||
"expand": "expand",
|
"expand": "expand",
|
||||||
"Power level": "Power level",
|
"Power level": "Power level",
|
||||||
|
|
|
@ -887,5 +887,8 @@
|
||||||
"%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s changed their display name to %(displayName)s.",
|
"%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s changed their display name to %(displayName)s.",
|
||||||
"Spanner": "Wrench",
|
"Spanner": "Wrench",
|
||||||
"Aeroplane": "Airplane",
|
"Aeroplane": "Airplane",
|
||||||
"Cat": "Cat"
|
"Cat": "Cat",
|
||||||
|
"Sends the given message coloured as a rainbow": "Sends the given message colored as a rainbow",
|
||||||
|
"Sends the given emote coloured as a rainbow": "Sends the given emote colored as a rainbow",
|
||||||
|
"Unrecognised address": "Unrecognized address"
|
||||||
}
|
}
|
||||||
|
|
|
@ -426,7 +426,7 @@
|
||||||
"Start new chat": "Aloita uusi keskustelu",
|
"Start new chat": "Aloita uusi keskustelu",
|
||||||
"Failed to invite": "Kutsu epäonnistui",
|
"Failed to invite": "Kutsu epäonnistui",
|
||||||
"Failed to invite user": "Käyttäjän kutsuminen epäonnistui",
|
"Failed to invite user": "Käyttäjän kutsuminen epäonnistui",
|
||||||
"Failed to invite the following users to the %(roomName)s room:": "Seuraavian käyttäjien kutsuminen huoneeseen %(roomName)s epäonnistui:",
|
"Failed to invite the following users to the %(roomName)s room:": "Seuraavien käyttäjien kutsuminen huoneeseen %(roomName)s epäonnistui:",
|
||||||
"Confirm Removal": "Varmista poistaminen",
|
"Confirm Removal": "Varmista poistaminen",
|
||||||
"Unknown error": "Tuntematon virhe",
|
"Unknown error": "Tuntematon virhe",
|
||||||
"Incorrect password": "Virheellinen salasana",
|
"Incorrect password": "Virheellinen salasana",
|
||||||
|
@ -469,7 +469,7 @@
|
||||||
"Riot does not have permission to send you notifications - please check your browser settings": "Riotilla ei ole oikeuksia lähettää sinulle ilmoituksia. Ole hyvä ja tarkista selaimen asetukset",
|
"Riot does not have permission to send you notifications - please check your browser settings": "Riotilla ei ole oikeuksia lähettää sinulle ilmoituksia. Ole hyvä ja tarkista selaimen asetukset",
|
||||||
"Riot was not given permission to send notifications - please try again": "Riot ei saannut lupaa lähettää ilmoituksia. Ole hyvä ja yritä uudelleen",
|
"Riot was not given permission to send notifications - please try again": "Riot ei saannut lupaa lähettää ilmoituksia. Ole hyvä ja yritä uudelleen",
|
||||||
"Room %(roomId)s not visible": "Huone %(roomId)s ei ole näkyvissä",
|
"Room %(roomId)s not visible": "Huone %(roomId)s ei ole näkyvissä",
|
||||||
"%(roomName)s does not exist.": "%(roomName)s ei ole olemassa.",
|
"%(roomName)s does not exist.": "Huonetta %(roomName)s ei ole olemassa.",
|
||||||
"%(roomName)s is not accessible at this time.": "%(roomName)s ei ole saatavilla tällä hetkellä.",
|
"%(roomName)s is not accessible at this time.": "%(roomName)s ei ole saatavilla tällä hetkellä.",
|
||||||
"Seen by %(userName)s at %(dateTime)s": "Käyttäjän %(userName)s näkemä %(dateTime)s",
|
"Seen by %(userName)s at %(dateTime)s": "Käyttäjän %(userName)s näkemä %(dateTime)s",
|
||||||
"Send Reset Email": "Lähetä salasanan palautusviesti",
|
"Send Reset Email": "Lähetä salasanan palautusviesti",
|
||||||
|
@ -943,7 +943,7 @@
|
||||||
"Send Custom Event": "Lähetä mukautettu tapahtuma",
|
"Send Custom Event": "Lähetä mukautettu tapahtuma",
|
||||||
"Advanced notification settings": "Lisäasetukset ilmoituksille",
|
"Advanced notification settings": "Lisäasetukset ilmoituksille",
|
||||||
"delete the alias.": "poista alias.",
|
"delete the alias.": "poista alias.",
|
||||||
"To return to your account in future you need to <u>set a password</u>": "Voidaksesi tulevaisuudessa palata tilillesi sinun pitää <u>asettaa salasana</u>",
|
"To return to your account in future you need to <u>set a password</u>": "Jotta voit jatkossa palata tilillesi, sinun pitää <u>asettaa salasana</u>",
|
||||||
"Forget": "Unohda",
|
"Forget": "Unohda",
|
||||||
"#example": "#esimerkki",
|
"#example": "#esimerkki",
|
||||||
"Hide panel": "Piilota paneeli",
|
"Hide panel": "Piilota paneeli",
|
||||||
|
@ -1346,11 +1346,11 @@
|
||||||
"If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "Jos olet aikaisemmin käyttänyt uudempaa versiota Riotista, istuntosi voi olla epäyhteensopiva tämän version kanssa. Sulje tämä ikkuna ja yritä uudemman version kanssa.",
|
"If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "Jos olet aikaisemmin käyttänyt uudempaa versiota Riotista, istuntosi voi olla epäyhteensopiva tämän version kanssa. Sulje tämä ikkuna ja yritä uudemman version kanssa.",
|
||||||
"The platform you're on": "Alusta, jolla olet",
|
"The platform you're on": "Alusta, jolla olet",
|
||||||
"Whether or not you're logged in (we don't record your username)": "Riippumatta siitä oletko kirjautunut sisään (emme tallenna käyttäjätunnustasi)",
|
"Whether or not you're logged in (we don't record your username)": "Riippumatta siitä oletko kirjautunut sisään (emme tallenna käyttäjätunnustasi)",
|
||||||
"Whether or not you're using the Richtext mode of the Rich Text Editor": "Riippumatta siitä, että käytätkö muotoillun tekstin tilaa muotoilueditorissa",
|
"Whether or not you're using the Richtext mode of the Rich Text Editor": "Riippumatta siitä, käytätkö muotoillun tekstin tilaa muotoilueditorissa",
|
||||||
"Your User Agent": "Selaintunnisteesi",
|
"Your User Agent": "Selaintunnisteesi",
|
||||||
"The information being sent to us to help make Riot.im better includes:": "Tietoihin, jota lähetetään Riot.im:ään palvelun parantamiseksi, sisältyy:",
|
"The information being sent to us to help make Riot.im better includes:": "Tietoihin, jota lähetetään Riot.im:ään palvelun parantamiseksi, sisältyy:",
|
||||||
"Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Niissä kohdissa, missä tämä sivu sisältää yksilöivää tietoa, kuten huoneen, käyttäjän tai ryhmän ID:n, kyseinen tieto poistetaan ennen tiedon lähetystä palvelimelle.",
|
"Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Niissä kohdissa, missä tämä sivu sisältää yksilöivää tietoa, kuten huoneen, käyttäjän tai ryhmän ID:n, kyseinen tieto poistetaan ennen tiedon lähetystä palvelimelle.",
|
||||||
"A conference call could not be started because the intgrations server is not available": "Konferenssipuhelua ei pystytty aloittamaan, koska integraatiopalvelin ei ole käytettävissä",
|
"A conference call could not be started because the intgrations server is not available": "Konferenssipuhelua ei voitu aloittaa, koska integraatiopalvelin ei ole käytettävissä",
|
||||||
"A call is currently being placed!": "Puhelua ollaan aloittamassa!",
|
"A call is currently being placed!": "Puhelua ollaan aloittamassa!",
|
||||||
"A call is already in progress!": "Puhelu on jo meneillään!",
|
"A call is already in progress!": "Puhelu on jo meneillään!",
|
||||||
"Permission Required": "Lisäoikeuksia tarvitaan",
|
"Permission Required": "Lisäoikeuksia tarvitaan",
|
||||||
|
@ -1760,5 +1760,82 @@
|
||||||
"Recovery Method Removed": "Palautustapa poistettu",
|
"Recovery Method Removed": "Palautustapa poistettu",
|
||||||
"This device has detected that your recovery passphrase and key for Secure Messages have been removed.": "Tämä laite on huomannut, että palautuksen salalauseesi ja avaimesi salatuille viesteille on poistettu.",
|
"This device has detected that your recovery passphrase and key for Secure Messages have been removed.": "Tämä laite on huomannut, että palautuksen salalauseesi ja avaimesi salatuille viesteille on poistettu.",
|
||||||
"If you did this accidentally, you can setup Secure Messages on this device which will re-encrypt this device's message history with a new recovery method.": "Jos teit tämän vahingossa, voit ottaa käyttöön salatut viestit tälle laitteelle, joka uudelleensalaa tämän laitteen keskusteluhistorian uudella palautustavalla.",
|
"If you did this accidentally, you can setup Secure Messages on this device which will re-encrypt this device's message history with a new recovery method.": "Jos teit tämän vahingossa, voit ottaa käyttöön salatut viestit tälle laitteelle, joka uudelleensalaa tämän laitteen keskusteluhistorian uudella palautustavalla.",
|
||||||
"If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Jos et poistanut palautustapaa, hyökkääjä saattaa yrittää käyttää tiliäsi. Vaihda tilisi salasana ja aseta uusi palautustapa asetuksissa välittömästi."
|
"If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Jos et poistanut palautustapaa, hyökkääjä saattaa yrittää käyttää tiliäsi. Vaihda tilisi salasana ja aseta uusi palautustapa asetuksissa välittömästi.",
|
||||||
|
"Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Riippumatta siitä, käytätkö 'leivänmuruja' (kuvia huonelistan yläpuolella)",
|
||||||
|
"Replying With Files": "Tiedostoilla vastaaminen",
|
||||||
|
"At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "Tiedostolla vastaaminen ei onnistu tällä kertaa. Haluatko ladata tiedoston vastaamatta?",
|
||||||
|
"The file '%(fileName)s' failed to upload.": "Tiedoston '%(fileName)s' lataaminen ei onnistunut.",
|
||||||
|
"The server does not support the room version specified.": "Palvelin ei tue määritettyä huoneversiota.",
|
||||||
|
"Please confirm that you'd like to go forward with upgrading this room from <oldVersion /> to <newVersion />.": "Vahvista, että haluat päivittää huoneen versiosta <oldVersion /> versioon <newVersion />.",
|
||||||
|
"Changes your avatar in this current room only": "Vaihtaa kuvasi vain nykyisessä huoneessa",
|
||||||
|
"Sends the given message coloured as a rainbow": "Lähettää viestin sateenkaaren väreissä",
|
||||||
|
"Sends the given emote coloured as a rainbow": "Lähettää emoten sateenkaaren väreissä",
|
||||||
|
"The user's homeserver does not support the version of the room.": "Käyttäjän kotipalvelin ei tue huoneen versiota.",
|
||||||
|
"Show recent room avatars above the room list": "Näytä viimeaikaiset huoneen kuvat huoneluettelon yläpuolella",
|
||||||
|
"Edit messages after they have been sent (refresh to apply changes)": "Muokkaa viestejä niiden lähettämisen jälkeen (päivitä saattaaksesi muutokset voimaan)",
|
||||||
|
"React to messages with emoji (refresh to apply changes)": "Reagoi viesteihin emojeilla (päivitä saattaaksesi muutokset voimaan)",
|
||||||
|
"This device is <b>not backing up your keys</b>, but you do have an existing backup you can restore from and add to going forward.": "Tämä laite <b>ei varmuuskopioi avaimiasi</b>, mutta sinulla on olemassa varmuuskopio palauttamista ja lisäämistä varten.",
|
||||||
|
"Backup has an <validity>invalid</validity> signature from this device": "Varmuuskopiossa on <validity>epäkelpo</validity> allekirjoitus tältä laitteelta",
|
||||||
|
"this room": "tämä huone",
|
||||||
|
"View older messages in %(roomName)s.": "Näytä vanhemmat viestit huoneessa %(roomName)s.",
|
||||||
|
"Joining room …": "Liitytään huoneeseen …",
|
||||||
|
"Loading …": "Latataan …",
|
||||||
|
"Join the conversation with an account": "Liity keskusteluun tilin avulla",
|
||||||
|
"Sign Up": "Rekisteröidy",
|
||||||
|
"Sign In": "Kirjaudu",
|
||||||
|
"Reason: %(reason)s": "Syy: %(reason)s",
|
||||||
|
"Forget this room": "Unohda tämä huone",
|
||||||
|
"Re-join": "Liity uudelleen",
|
||||||
|
"You were banned from %(roomName)s by %(memberName)s": "%(memberName)s antoi sinulle porttikiellon huoneeseen %(roomName)s",
|
||||||
|
"Something went wrong with your invite to %(roomName)s": "Jotain meni vikaan kutsussasi huoneeseen %(roomName)s",
|
||||||
|
"%(errcode)s was returned while trying to valide your invite. You could try to pass this information on to a room admin.": "Kutsusi validointi palautti virhekoodin %(errcode)s. Voit koettaa välittää tiedon huoneen ylläpitäjälle.",
|
||||||
|
"You can only join it with a working invite.": "Voit liittyä siihen vain toimivalla kutsulla.",
|
||||||
|
"You can still join it because this is a public room.": "Voit silti liittyä siihen, koska huone on julkinen.",
|
||||||
|
"Join the discussion": "Liity keskusteluun",
|
||||||
|
"Try to join anyway": "Yritä silti liittyä",
|
||||||
|
"This invite to %(roomName)s wasn't sent to your account": "Kutsua huoneeseen %(roomName)s ei lähetetty tilillesi",
|
||||||
|
"Sign in with a different account, ask for another invite, or add the e-mail address %(email)s to this account.": "Kirjaudu eri tilillä, pyydä uutta kutsua tai lisää sähköpostiosoite %(email)s tähän tiliin.",
|
||||||
|
"Do you want to chat with %(user)s?": "Haluatko keskustella käyttäjän %(user)s kanssa?",
|
||||||
|
"Do you want to join %(roomName)s?": "Haluatko liittyä huoneeseen %(roomName)s?",
|
||||||
|
"<userName/> invited you": "<userName/> kutsui sinut",
|
||||||
|
"You're previewing %(roomName)s. Want to join it?": "Esikatselet huonetta %(roomName)s. Haluatko liittyä siihen?",
|
||||||
|
"%(roomName)s can't be previewed. Do you want to join it?": "Huonetta %(roomName)s ei voi esikatsella. Haluatko liittyä siihen?",
|
||||||
|
"This room doesn't exist. Are you sure you're at the right place?": "Tätä huonetta ei ole olemassa. Oletko varma, että olet oikeassa paikassa?",
|
||||||
|
"This room has already been upgraded.": "Tämä huone on jo päivitetty.",
|
||||||
|
"Rotate Left": "Kierrä vasempaan",
|
||||||
|
"Rotate counter-clockwise": "Kierrä vastapäivään",
|
||||||
|
"Rotate Right": "Kierrä oikeaan",
|
||||||
|
"Rotate clockwise": "Kierrä myötäpäivään",
|
||||||
|
"View Servers in Room": "Näytä huoneessa olevat palvelimet",
|
||||||
|
"Sign out and remove encryption keys?": "Kirjaudu ulos ja poista salausavaimet?",
|
||||||
|
"Missing session data": "Istunnon dataa puuttuu",
|
||||||
|
"Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.": "Istunnon dataa, mukaanlukien salausavaimia, puuttuu. Kirjaudu ulos ja sisään, jolloin avaimet palautetaan varmuuskopiosta.",
|
||||||
|
"Your browser likely removed this data when running low on disk space.": "Selaimesi luultavasti poisti tämän datan, kun levytila oli vähissä.",
|
||||||
|
"Upload files (%(current)s of %(total)s)": "Lataa tiedostot (%(current)s / %(total)s)",
|
||||||
|
"Upload files": "Lataa tiedostot",
|
||||||
|
"These files are <b>too large</b> to upload. The file size limit is %(limit)s.": "Tiedostot ovat <b>liian isoja</b> ladattaviksi. Tiedoston kokoraja on %(limit)s.",
|
||||||
|
"Some files are <b>too large</b> to be uploaded. The file size limit is %(limit)s.": "Osa tiedostoista on <b>liian isoja</b> ladattaviksi. Tiedoston kokoraja on %(limit)s.",
|
||||||
|
"Upload %(count)s other files|other": "Lataa %(count)s muuta tiedostoa",
|
||||||
|
"Upload %(count)s other files|one": "Lataa %(count)s muu tiedosto",
|
||||||
|
"Cancel All": "Peruuta kaikki",
|
||||||
|
"Upload Error": "Latausvirhe",
|
||||||
|
"Use an email address to recover your account": "Palauta tilisi sähköpostiosoitteen avulla",
|
||||||
|
"Enter email address (required on this homeserver)": "Syötä sähköpostiosoite (vaaditaan tällä kotipalvelimella)",
|
||||||
|
"Doesn't look like a valid email address": "Ei näytä kelvolliselta sähköpostiosoitteelta",
|
||||||
|
"Enter password": "Syötä salasana",
|
||||||
|
"Password is allowed, but unsafe": "Salasana on sallittu, mutta turvaton",
|
||||||
|
"Nice, strong password!": "Hyvä, vahva salasana!",
|
||||||
|
"Passwords don't match": "Salasanat eivät täsmää",
|
||||||
|
"Other users can invite you to rooms using your contact details": "Muut voivat kutsua sinut huoneisiin yhteystietojesi avulla",
|
||||||
|
"Enter phone number (required on this homeserver)": "Syötä puhelinnumero (vaaditaan tällä kotipalvelimella)",
|
||||||
|
"Doesn't look like a valid phone number": "Ei näytä kelvolliselta puhelinnumerolta",
|
||||||
|
"Use letters, numbers, dashes and underscores only": "Käytä vain kirjaimia, numeroita, viivoja ja alaviivoja",
|
||||||
|
"Enter username": "Syötä käyttäjänimi",
|
||||||
|
"Some characters not allowed": "Osaa merkeistä ei sallita",
|
||||||
|
"Use an email address to recover your account.": "Palauta tilisi sähköpostiosoitteen avulla.",
|
||||||
|
"Other users can invite you to rooms using your contact details.": "Muut käyttäjät voivat kutsua sinut huoneisiin yhteystietojesi avulla.",
|
||||||
|
"Error loading Riot": "Virhe Riotin lataamisessa",
|
||||||
|
"If this is unexpected, please contact your system administrator or technical support representative.": "Jos et odottanut tätä, ota yhteyttä järjestelmänvalvojaan tai tekniseen tukeen.",
|
||||||
|
"Homeserver URL does not appear to be a valid Matrix homeserver": "Kotipalvelimen osoite ei näytä olevan kelvollinen Matrix-kotipalvelin",
|
||||||
|
"Identity server URL does not appear to be a valid identity server": "Identiteettipalvelimen osoite ei näytä olevan kelvollinen identiteettipalvelin"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1892,5 +1892,75 @@
|
||||||
"Upload %(count)s other files|other": "Feltölt %(count)s másik fájlt",
|
"Upload %(count)s other files|other": "Feltölt %(count)s másik fájlt",
|
||||||
"Upload %(count)s other files|one": "Feltölt %(count)s másik fájlt",
|
"Upload %(count)s other files|one": "Feltölt %(count)s másik fájlt",
|
||||||
"Cancel All": "Mindent megszakít",
|
"Cancel All": "Mindent megszakít",
|
||||||
"Upload Error": "Feltöltési hiba"
|
"Upload Error": "Feltöltési hiba",
|
||||||
|
"The server does not support the room version specified.": "A szerver nem támogatja a megadott szoba verziót.",
|
||||||
|
"Please confirm that you'd like to go forward with upgrading this room from <oldVersion /> to <newVersion />.": "Kérlek erősítsd meg, hogy a szobát frissíted a <oldVersion /> verzióról <newVersion /> verzióra.",
|
||||||
|
"Changes your avatar in this current room only": "A profilképedet csak ebben a szobában változtatja meg",
|
||||||
|
"Sends the given message coloured as a rainbow": "A megadott üzenetet szivárvány színben küldi el",
|
||||||
|
"Sends the given emote coloured as a rainbow": "A megadott hangulatjelet szivárvány színben küldi el",
|
||||||
|
"The user's homeserver does not support the version of the room.": "A felhasználó matrix szervere nem támogatja a megadott szoba verziót.",
|
||||||
|
"Edit messages after they have been sent (refresh to apply changes)": "Üzenet szerkesztése küldés után (újratöltés szükséges)",
|
||||||
|
"React to messages with emoji (refresh to apply changes)": "Reagálj az üzenetre emoji-val (újratöltés szükséges)",
|
||||||
|
"When rooms are upgraded": "Ha a szobák frissültek",
|
||||||
|
"This device is <b>not backing up your keys</b>, but you do have an existing backup you can restore from and add to going forward.": "Ez az eszköz <b>nem menti el a kulcsaidat</b>, de létezik mentés amit visszaállíthatsz és folytathatod.",
|
||||||
|
"Connect this device to key backup before signing out to avoid losing any keys that may only be on this device.": "Csatlakozz ezzel az eszközzel a kulcs mentéshez kilépés előtt, hogy ne veszíts el kulcsot ami esetleg csak ezen az eszközön van meg.",
|
||||||
|
"Connect this device to Key Backup": "Csatlakozz ezzel az eszközzel a Kulcs Mentéshez",
|
||||||
|
"Backup has an <validity>invalid</validity> signature from this device": "A mentés <validity>érvénytelen</validity> aláírással rendelkezik erről az eszközről",
|
||||||
|
"this room": "ez a szoba",
|
||||||
|
"View older messages in %(roomName)s.": "Régebbi üzenetek megjelenítése itt: %(roomName)s.",
|
||||||
|
"Joining room …": "Szobához csatlakozás …",
|
||||||
|
"Loading …": "Betöltés …",
|
||||||
|
"Rejecting invite …": "Meghívó elutasítása …",
|
||||||
|
"Join the conversation with an account": "Beszélgetéshez csatlakozás felhasználói fiókkal",
|
||||||
|
"Sign Up": "Fiók készítés",
|
||||||
|
"Sign In": "Bejelentkezés",
|
||||||
|
"You were kicked from %(roomName)s by %(memberName)s": "Téged kirúgott %(memberName)s ebből a szobából: %(roomName)s",
|
||||||
|
"Reason: %(reason)s": "Ok: %(reason)s",
|
||||||
|
"Forget this room": "Szoba elfelejtése",
|
||||||
|
"Re-join": "Újra-csatlakozás",
|
||||||
|
"You were banned from %(roomName)s by %(memberName)s": "Téged kitiltott %(memberName)s ebből a szobából: %(roomName)s",
|
||||||
|
"Something went wrong with your invite to %(roomName)s": "A meghívóddal ebbe a szobába: %(roomName)s valami baj történt",
|
||||||
|
"%(errcode)s was returned while trying to valide your invite. You could try to pass this information on to a room admin.": "A meghívód ellenőrzése során az alábbi hibakódot kaptuk: %(errcode)s. Megpróbálhatod ezt az információt átadni a szoba adminisztrátorának.",
|
||||||
|
"You can only join it with a working invite.": "Csak érvényes meghívóval tudsz csatlakozni.",
|
||||||
|
"You can still join it because this is a public room.": "Mivel a szoba nyilvános megpróbálhatsz csatlakozni.",
|
||||||
|
"Join the discussion": "Beszélgetéshez csatlakozás",
|
||||||
|
"Try to join anyway": "Mindennek ellenére próbálj csatlakozni",
|
||||||
|
"This invite to %(roomName)s wasn't sent to your account": "Ezt a meghívót ide: %(roomName)s nem a te fiókodnak küldték",
|
||||||
|
"Sign in with a different account, ask for another invite, or add the e-mail address %(email)s to this account.": "Jelentkezz be más fiókkal, kérj másik meghívót vagy add hozzá a fiókodhoz ezt az e-mail címet: %(email)s.",
|
||||||
|
"Do you want to chat with %(user)s?": "%(user)s felhasználóval szeretnél beszélgetni?",
|
||||||
|
"Do you want to join %(roomName)s?": "%(roomName)s szobába szeretnél belépni?",
|
||||||
|
"<userName/> invited you": "<userName/> meghívott",
|
||||||
|
"You're previewing %(roomName)s. Want to join it?": "%(roomName)s szoba előnézetét látod. Belépsz?",
|
||||||
|
"%(roomName)s can't be previewed. Do you want to join it?": "%(roomName)s szobának nincs előnézete. Be szeretnél lépni?",
|
||||||
|
"This room doesn't exist. Are you sure you're at the right place?": "Ez a szoba nem létezik. Biztos, hogy jó helyen vagy?",
|
||||||
|
"Try again later, or ask a room admin to check if you have access.": "Próbálkozz később vagy kérd meg a szoba adminisztrátorát, hogy nézze meg van-e hozzáférésed.",
|
||||||
|
"%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please <issueLink>submit a bug report</issueLink>.": "Amikor a szobát próbáltuk elérni ezt a hibaüzenetet kaptuk: %(errcode)s. Ha úgy gondolod, hogy ez egy hiba légy szíves<issueLink>nyiss egy hibajegyet</issueLink>.",
|
||||||
|
"This room has already been upgraded.": "Ez a szoba már frissült.",
|
||||||
|
"Agree or Disagree": "Egyetért vagy Ellentmond",
|
||||||
|
"Like or Dislike": "Kedveli vagy Nem kedveli",
|
||||||
|
"Rotate Left": "Balra forgat",
|
||||||
|
"Rotate Right": "Jobbra forgat",
|
||||||
|
"View Servers in Room": "Szerverek megjelenítése a szobában",
|
||||||
|
"Use an email address to recover your account": "A felhasználói fiók visszaszerzése e-mail címmel",
|
||||||
|
"Enter email address (required on this homeserver)": "E-mail cím megadása (ezen a matrix szerveren kötelező)",
|
||||||
|
"Doesn't look like a valid email address": "Az e-mail cím nem tűnik érvényesnek",
|
||||||
|
"Enter password": "Jelszó megadása",
|
||||||
|
"Password is allowed, but unsafe": "A jelszó engedélyezett, de nem biztonságos",
|
||||||
|
"Nice, strong password!": "Szép, erős jelszó!",
|
||||||
|
"Passwords don't match": "A jelszavak nem egyeznek meg",
|
||||||
|
"Other users can invite you to rooms using your contact details": "Mások meghívhatnak a szobákba a kapcsolatoknál megadott adataiddal",
|
||||||
|
"Enter phone number (required on this homeserver)": "Telefonszám megadása (ennél a matrix szervernél kötelező)",
|
||||||
|
"Doesn't look like a valid phone number": "Ez a telefonszám nem tűnik érvényesnek",
|
||||||
|
"Use letters, numbers, dashes and underscores only": "Csak betűket, számokat, kötőjelet és aláhúzást használj",
|
||||||
|
"Enter username": "Felhasználói név megadása",
|
||||||
|
"Some characters not allowed": "Néhány karakter nem engedélyezett",
|
||||||
|
"Use an email address to recover your account.": "A felhasználói fiókod visszaszerzéséhez használd az e-mail címet.",
|
||||||
|
"Other users can invite you to rooms using your contact details.": "Mások meghívhatnak a szobákba a kapcsolatoknál megadott adataid alapján.",
|
||||||
|
"Error loading Riot": "A Riot betöltésénél hiba",
|
||||||
|
"If this is unexpected, please contact your system administrator or technical support representative.": "Ha ez váratlanul ért, kérlek vedd fel a kapcsolatot a rendszer adminisztrátorával vagy a technikai segítséggel.",
|
||||||
|
"Failed to get autodiscovery configuration from server": "A szerverről nem sikerült beszerezni az automatikus felderítés beállításait",
|
||||||
|
"Invalid base_url for m.homeserver": "Hibás base_url az m.homeserver -hez",
|
||||||
|
"Homeserver URL does not appear to be a valid Matrix homeserver": "A matrix URL nem tűnik érvényesnek",
|
||||||
|
"Invalid base_url for m.identity_server": "Érvénytelen base_url az m.identity_server -hez",
|
||||||
|
"Identity server URL does not appear to be a valid identity server": "Az Azonosító szerver URL nem tűnik érvényesnek"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,13 @@
|
||||||
{}
|
{
|
||||||
|
"This email address is already in use": "Ta e-poštni naslov je že v uporabi",
|
||||||
|
"This phone number is already in use": "Ta telefonska številka je že v uporabi",
|
||||||
|
"Failed to verify email address: make sure you clicked the link in the email": "E-poštnega naslova ni bilo mogoče preveriti: preverite, ali ste kliknili povezavo v e-poštnem sporočilu",
|
||||||
|
"The platform you're on": "Vaša platforma",
|
||||||
|
"The version of Riot.im": "Različica Riot.im",
|
||||||
|
"Dismiss": "Opusti",
|
||||||
|
"Chat with Riot Bot": "Klepetajte z Riot Botom",
|
||||||
|
"Sign In": "Prijava",
|
||||||
|
"powered by Matrix": "poganja Matrix",
|
||||||
|
"Custom Server Options": "Možnosti strežnika po meri",
|
||||||
|
"You can also set a custom identity server, but you won't be able to invite users by email address, or be invited by email address yourself.": "Nastavite lahko tudi strežnik za identiteto po meri, vendar ne boste mogli povabiti uporabnikov prek e-pošte, prav tako pa vas ne bodo mogli povabiti drugi."
|
||||||
|
}
|
||||||
|
|
|
@ -137,20 +137,25 @@ export function _t(text, variables, tags) {
|
||||||
* @return a React <span> component if any non-strings were used in substitutions, otherwise a string
|
* @return a React <span> component if any non-strings were used in substitutions, otherwise a string
|
||||||
*/
|
*/
|
||||||
export function substitute(text, variables, tags) {
|
export function substitute(text, variables, tags) {
|
||||||
const regexpMapping = {};
|
let result = text;
|
||||||
|
|
||||||
if (variables !== undefined) {
|
if (variables !== undefined) {
|
||||||
|
const regexpMapping = {};
|
||||||
for (const variable in variables) {
|
for (const variable in variables) {
|
||||||
regexpMapping[`%\\(${variable}\\)s`] = variables[variable];
|
regexpMapping[`%\\(${variable}\\)s`] = variables[variable];
|
||||||
}
|
}
|
||||||
|
result = replaceByRegexes(result, regexpMapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tags !== undefined) {
|
if (tags !== undefined) {
|
||||||
|
const regexpMapping = {};
|
||||||
for (const tag in tags) {
|
for (const tag in tags) {
|
||||||
regexpMapping[`(<${tag}>(.*?)<\\/${tag}>|<${tag}>|<${tag}\\s*\\/>)`] = tags[tag];
|
regexpMapping[`(<${tag}>(.*?)<\\/${tag}>|<${tag}>|<${tag}\\s*\\/>)`] = tags[tag];
|
||||||
}
|
}
|
||||||
|
result = replaceByRegexes(result, regexpMapping);
|
||||||
}
|
}
|
||||||
return replaceByRegexes(text, regexpMapping);
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -368,4 +368,9 @@ export const SETTINGS = {
|
||||||
displayName: _td('Order rooms in the room list by most important first instead of most recent'),
|
displayName: _td('Order rooms in the room list by most important first instead of most recent'),
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
"showHiddenEventsInTimeline": {
|
||||||
|
displayName: _td("Show hidden events in timeline"),
|
||||||
|
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -47,5 +47,6 @@ export function isContentActionable(mxEvent) {
|
||||||
|
|
||||||
export function canEditContent(mxEvent) {
|
export function canEditContent(mxEvent) {
|
||||||
return isContentActionable(mxEvent) &&
|
return isContentActionable(mxEvent) &&
|
||||||
|
mxEvent.getOriginalContent().msgtype === "m.text" &&
|
||||||
mxEvent.getSender() === MatrixClientPeg.get().getUserId();
|
mxEvent.getSender() === MatrixClientPeg.get().getUserId();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 OpenMarket Ltd
|
Copyright 2016 OpenMarket Ltd
|
||||||
|
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -14,6 +15,8 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { _t } from '../languageHandler';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* formats numbers to fit into ~3 characters, suitable for badge counts
|
* formats numbers to fit into ~3 characters, suitable for badge counts
|
||||||
* e.g: 999, 9.9K, 99K, 0.9M, 9.9M, 99M, 0.9B, 9.9B
|
* e.g: 999, 9.9K, 99K, 0.9M, 9.9M, 99M, 0.9B, 9.9B
|
||||||
|
@ -63,3 +66,31 @@ export function getUserNameColorClass(userId) {
|
||||||
const colorNumber = (hashCode(userId) % 8) + 1;
|
const colorNumber = (hashCode(userId) % 8) + 1;
|
||||||
return `mx_Username_color${colorNumber}`;
|
return `mx_Username_color${colorNumber}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a written English string representing `items`, with an optional
|
||||||
|
* limit on the number of items included in the result. If specified and if the
|
||||||
|
* length of `items` is greater than the limit, the string "and n others" will
|
||||||
|
* be appended onto the result. If `items` is empty, returns the empty string.
|
||||||
|
* If there is only one item, return it.
|
||||||
|
* @param {string[]} items the items to construct a string from.
|
||||||
|
* @param {number?} itemLimit the number by which to limit the list.
|
||||||
|
* @returns {string} a string constructed by joining `items` with a comma
|
||||||
|
* between each item, but with the last item appended as " and [lastItem]".
|
||||||
|
*/
|
||||||
|
export function formatCommaSeparatedList(items, itemLimit) {
|
||||||
|
const remaining = itemLimit === undefined ? 0 : Math.max(
|
||||||
|
items.length - itemLimit, 0,
|
||||||
|
);
|
||||||
|
if (items.length === 0) {
|
||||||
|
return "";
|
||||||
|
} else if (items.length === 1) {
|
||||||
|
return items[0];
|
||||||
|
} else if (remaining > 0) {
|
||||||
|
items = items.slice(0, itemLimit);
|
||||||
|
return _t("%(items)s and %(count)s others", { items: items.join(', '), count: remaining } );
|
||||||
|
} else {
|
||||||
|
const lastItem = items.pop();
|
||||||
|
return _t("%(items)s and %(lastItem)s", { items: items.join(', '), lastItem: lastItem });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue