mirror of
https://github.com/element-hq/element-web
synced 2024-11-22 17:25:50 +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 {
|
||||
color: $username-variant8-color;
|
||||
}
|
||||
|
||||
|
|
|
@ -120,10 +120,12 @@
|
|||
@import "./views/messages/_ReactionDimension.scss";
|
||||
@import "./views/messages/_ReactionsRow.scss";
|
||||
@import "./views/messages/_ReactionsRowButton.scss";
|
||||
@import "./views/messages/_ReactionsRowButtonTooltip.scss";
|
||||
@import "./views/messages/_RoomAvatarEvent.scss";
|
||||
@import "./views/messages/_SenderProfile.scss";
|
||||
@import "./views/messages/_TextualEvent.scss";
|
||||
@import "./views/messages/_UnknownBody.scss";
|
||||
@import "./views/messages/_ViewSourceEvent.scss";
|
||||
@import "./views/room_settings/_AliasSettings.scss";
|
||||
@import "./views/room_settings/_ColorSettings.scss";
|
||||
@import "./views/rooms/_AppsDrawer.scss";
|
||||
|
|
|
@ -82,8 +82,13 @@ limitations under the License.
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
.mx_DevTools_content .mx_Field_input + .mx_Field_input {
|
||||
margin-left: 42px;
|
||||
.mx_DevTools_eventTypeStateKeyGroup {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.mx_DevTools_content .mx_Field_input:first-of-type {
|
||||
margin-right: 42px;
|
||||
}
|
||||
|
||||
.mx_DevTools_tgl {
|
||||
|
|
|
@ -16,17 +16,22 @@ limitations under the License.
|
|||
|
||||
.mx_MessageEditor {
|
||||
border-radius: 4px;
|
||||
background-color: $header-panel-bg-color;
|
||||
padding: 11px 13px 7px 56px;
|
||||
padding: 3px;
|
||||
// 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 {
|
||||
border-radius: 4px;
|
||||
border: solid 1px #e9edf1;
|
||||
background-color: #ffffff;
|
||||
padding: 10px;
|
||||
border: solid 1px $primary-hairline-color;
|
||||
background-color: $primary-bg-color;
|
||||
padding: 3px 6px;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
outline: none;
|
||||
max-height: 200px;
|
||||
overflow-x: auto;
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
|
@ -48,8 +53,15 @@ limitations under the License.
|
|||
.mx_MessageEditor_buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: end;
|
||||
padding: 5px 0;
|
||||
justify-content: flex-end;
|
||||
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 {
|
||||
margin-left: 5px;
|
||||
|
@ -62,3 +74,8 @@ limitations under the License.
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.mx_EventTile_isEditing {
|
||||
background-color: $header-panel-bg-color;
|
||||
}
|
||||
|
||||
.mx_EventTile .mx_SenderProfile {
|
||||
color: $primary-fg-color;
|
||||
font-size: 14px;
|
||||
|
@ -72,6 +76,10 @@ limitations under the License.
|
|||
}
|
||||
}
|
||||
|
||||
.mx_EventTile_isEditing .mx_MessageTimestamp {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.mx_EventTile .mx_MessageTimestamp {
|
||||
display: block;
|
||||
visibility: hidden;
|
||||
|
@ -377,6 +385,14 @@ limitations under the License.
|
|||
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 */
|
||||
|
||||
.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-border-color: $accent-color;
|
||||
|
||||
$tooltip-timeline-bg-color: $tagpanel-bg-color;
|
||||
$tooltip-timeline-fg-color: #ffffff;
|
||||
|
||||
// ***** Mixins! *****
|
||||
|
||||
@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-border-color: $accent-color;
|
||||
|
||||
$tooltip-timeline-bg-color: $tagpanel-bg-color;
|
||||
$tooltip-timeline-fg-color: #ffffff;
|
||||
|
||||
// ***** Mixins! *****
|
||||
|
||||
@define-mixin mx_DialogButton {
|
||||
|
|
|
@ -361,7 +361,7 @@ async function _startCallApp(roomId, type) {
|
|||
|
||||
Modal.createTrackedDialog('Could not connect to the integration server', '', ErrorDialog, {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ export function showGroupInviteDialog(groupId) {
|
|||
Modal.createTrackedDialog('Group Invite', '', AddressPickerDialog, {
|
||||
title: _t("Invite new community members"),
|
||||
description: description,
|
||||
placeholder: _t("Name or matrix ID"),
|
||||
placeholder: _t("Name or Matrix ID"),
|
||||
button: _t("Invite to Community"),
|
||||
validAddressTypes: ['mx-user-id'],
|
||||
onFinished: (success, addrs) => {
|
||||
|
|
|
@ -107,6 +107,17 @@ function unicodeToImage(str, addAlt) {
|
|||
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
|
||||
* character number), return an image node with the corresponding
|
||||
|
@ -530,8 +541,8 @@ export function bodyToHtml(content, highlights, opts={}) {
|
|||
});
|
||||
|
||||
return isDisplayedWithHtml ?
|
||||
<span className={className} dangerouslySetInnerHTML={{ __html: safeBody }} dir="auto" /> :
|
||||
<span className={className} dir="auto">{ strippedBody }</span>;
|
||||
<span key="body" className={className} dangerouslySetInnerHTML={{ __html: safeBody }} dir="auto" /> :
|
||||
<span key="body" className={className} dir="auto">{ strippedBody }</span>;
|
||||
}
|
||||
|
||||
export function emojifyText(text, addAlt) {
|
||||
|
|
|
@ -45,7 +45,7 @@ export function showStartChatInviteDialog() {
|
|||
Modal.createTrackedDialog('Start a chat', '', AddressPickerDialog, {
|
||||
title: _t('Start a chat'),
|
||||
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'],
|
||||
button: _t("Start Chat"),
|
||||
onFinished: _onStartChatFinished,
|
||||
|
@ -58,7 +58,7 @@ export function showRoomInviteDialog(roomId) {
|
|||
title: _t('Invite new room members'),
|
||||
description: _t('Who would you like to add to this room?'),
|
||||
button: _t('Send Invites'),
|
||||
placeholder: _t("Email, name or matrix ID"),
|
||||
placeholder: _t("Email, name or Matrix ID"),
|
||||
onFinished: (shouldInvite, addrs) => {
|
||||
_onRoomInviteFinished(roomId, shouldInvite, addrs);
|
||||
},
|
||||
|
|
|
@ -518,7 +518,7 @@ export const CommandMap = {
|
|||
unban: new Command({
|
||||
name: 'unban',
|
||||
args: '<user-id>',
|
||||
description: _td('Unbans user with given id'),
|
||||
description: _td('Unbans user with given ID'),
|
||||
runFn: function(roomId, args) {
|
||||
if (args) {
|
||||
const matches = args.match(/^(\S+)$/);
|
||||
|
|
|
@ -265,7 +265,7 @@ const RoleUserList = React.createClass({
|
|||
Modal.createTrackedDialog('Add Users to Group Summary', '', AddressPickerDialog, {
|
||||
title: _t('Add users to the community 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"),
|
||||
validAddressTypes: ['mx-user-id'],
|
||||
groupId: this.props.groupId,
|
||||
|
|
|
@ -24,6 +24,7 @@ import {wantsDateSeparator} from '../../DateUtils';
|
|||
import sdk from '../../index';
|
||||
|
||||
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||
import SettingsStore from '../../settings/SettingsStore';
|
||||
|
||||
const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes
|
||||
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)
|
||||
}
|
||||
|
||||
if (SettingsStore.getValue("showHiddenEventsInTimeline")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const EventTile = sdk.getComponent('rooms.EventTile');
|
||||
if (!EventTile.haveTileForEvent(mxEv)) {
|
||||
return false; // no tile = no show
|
||||
|
@ -450,14 +455,10 @@ module.exports = React.createClass({
|
|||
|
||||
_getTilesForEvent: function(prevEvent, mxEv, last) {
|
||||
const EventTile = sdk.getComponent('rooms.EventTile');
|
||||
const MessageEditor = sdk.getComponent('elements.MessageEditor');
|
||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||
const ret = [];
|
||||
|
||||
if (this.props.editEvent && this.props.editEvent.getId() === mxEv.getId()) {
|
||||
return [<MessageEditor key={mxEv.getId()} event={mxEv} />];
|
||||
}
|
||||
|
||||
const isEditing = this.props.editEvent && this.props.editEvent.getId() === mxEv.getId();
|
||||
// is this a continuation of the previous message?
|
||||
let continuation = false;
|
||||
|
||||
|
@ -527,6 +528,7 @@ module.exports = React.createClass({
|
|||
continuation={continuation}
|
||||
isRedacted={mxEv.isRedacted()}
|
||||
replacingEventId={mxEv.replacingEventId()}
|
||||
isEditing={isEditing}
|
||||
onHeightChanged={this._onHeightChanged}
|
||||
readReceipts={readReceipts}
|
||||
readReceiptMap={this._readReceiptMap}
|
||||
|
@ -714,7 +716,7 @@ module.exports = React.createClass({
|
|||
);
|
||||
|
||||
let whoIsTyping;
|
||||
if (this.props.room) {
|
||||
if (this.props.room && !this.props.tileShape) {
|
||||
whoIsTyping = (<WhoIsTypingTile
|
||||
room={this.props.room}
|
||||
onShown={this._onTypingShown}
|
||||
|
|
|
@ -128,8 +128,10 @@ class SendCustomEvent extends GenericEditor {
|
|||
|
||||
return <div>
|
||||
<div className="mx_DevTools_content">
|
||||
{ this.textInput('eventType', _t('Event Type')) }
|
||||
{ this.state.isStateEvent && this.textInput('stateKey', _t('State Key')) }
|
||||
<div className="mx_DevTools_eventTypeStateKeyGroup">
|
||||
{ this.textInput('eventType', _t('Event Type')) }
|
||||
{ this.state.isStateEvent && this.textInput('stateKey', _t('State Key')) }
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
|
|
|
@ -114,7 +114,8 @@ export default class ShareDialog extends React.Component {
|
|||
top: y,
|
||||
message: successful ? _t('Copied!') : _t('Failed to copy'),
|
||||
}, false);
|
||||
e.target.onmouseleave = close;
|
||||
// Drop a reference to this close handler for componentWillUnmount
|
||||
this.closeCopiedTooltip = e.target.onmouseleave = close;
|
||||
}
|
||||
|
||||
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() {
|
||||
let title;
|
||||
let matrixToUrl;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
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.
|
||||
|
@ -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
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import sdk from '../../../index';
|
||||
const MemberAvatar = require('../avatars/MemberAvatar.js');
|
||||
import MemberAvatar from '../avatars/MemberAvatar';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { formatCommaSeparatedList } from '../../../utils/FormattingUtils';
|
||||
|
||||
module.exports = React.createClass({
|
||||
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 });
|
||||
});
|
||||
|
@ -132,7 +135,7 @@ module.exports = React.createClass({
|
|||
* included before "and [n] others".
|
||||
*/
|
||||
_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;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
const avatars = roomMembers.slice(0, this.props.avatarsMaxLength).map((m) => {
|
||||
return (
|
||||
|
|
|
@ -77,35 +77,46 @@ export default class MessageEditor extends React.Component {
|
|||
}
|
||||
|
||||
_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) {
|
||||
return;
|
||||
}
|
||||
if (!this.model.autoComplete) {
|
||||
return;
|
||||
if (this.model.autoComplete) {
|
||||
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});
|
||||
}
|
||||
|
||||
_onSaveClicked = () => {
|
||||
_sendEdit = () => {
|
||||
const newContent = {
|
||||
"msgtype": "m.text",
|
||||
"body": textSerialize(this.model),
|
||||
|
@ -144,12 +155,7 @@ export default class MessageEditor extends React.Component {
|
|||
|
||||
componentDidMount() {
|
||||
this._updateEditorState();
|
||||
const sel = document.getSelection();
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(this._editorRef);
|
||||
range.collapse(false);
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
setCaretPosition(this._editorRef, this.model, this.model.getPositionAtEnd());
|
||||
this._editorRef.focus();
|
||||
}
|
||||
|
||||
|
@ -181,8 +187,8 @@ export default class MessageEditor extends React.Component {
|
|||
ref={ref => this._editorRef = ref}
|
||||
></div>
|
||||
<div className="mx_MessageEditor_buttons">
|
||||
<AccessibleButton kind="secondary" onClick={this._onCancelClicked}>{_t("Cancel")}</AccessibleButton>
|
||||
<AccessibleButton kind="primary" onClick={this._onSaveClicked}>{_t("Save")}</AccessibleButton>
|
||||
<AccessibleButton kind="secondary" onClick={this._cancelEdit}>{_t("Cancel")}</AccessibleButton>
|
||||
<AccessibleButton kind="primary" onClick={this._sendEdit}>{_t("Save")}</AccessibleButton>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -90,6 +90,7 @@ module.exports = React.createClass({
|
|||
tileShape={this.props.tileShape}
|
||||
maxImageHeight={this.props.maxImageHeight}
|
||||
replacingEventId={this.props.replacingEventId}
|
||||
isEditing={this.props.isEditing}
|
||||
onHeightChanged={this.props.onHeightChanged} />;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -168,6 +168,7 @@ export default class ReactionDimension extends React.PureComponent {
|
|||
|
||||
return <span className="mx_ReactionDimension"
|
||||
title={this.props.title}
|
||||
aria-hidden={true}
|
||||
>
|
||||
{items}
|
||||
</span>;
|
||||
|
|
|
@ -116,8 +116,8 @@ export default class ReactionsRow extends React.PureComponent {
|
|||
return <ReactionsRowButton
|
||||
key={content}
|
||||
content={content}
|
||||
count={count}
|
||||
mxEvent={mxEvent}
|
||||
reactionEvents={events}
|
||||
myReactionEvent={myReactionEvent}
|
||||
/>;
|
||||
});
|
||||
|
|
|
@ -19,17 +19,28 @@ import PropTypes from 'prop-types';
|
|||
import classNames from 'classnames';
|
||||
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import sdk from '../../../index';
|
||||
|
||||
export default class ReactionsRowButton 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,
|
||||
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
|
||||
myReactionEvent: PropTypes.object,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
tooltipVisible: false,
|
||||
};
|
||||
}
|
||||
|
||||
onClick = (ev) => {
|
||||
const { mxEvent, myReactionEvent, content } = this.props;
|
||||
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() {
|
||||
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({
|
||||
mx_ReactionsRowButton: true,
|
||||
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}
|
||||
onClick={this.onClick}
|
||||
onMouseOver={this.onMouseOver}
|
||||
onMouseOut={this.onMouseOut}
|
||||
>
|
||||
{content} {count}
|
||||
{tooltip}
|
||||
</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 highlight from 'highlight.js';
|
||||
import * as HtmlUtils from '../../../HtmlUtils';
|
||||
import {formatDate} from '../../../DateUtils';
|
||||
import sdk from '../../../index';
|
||||
import ScalarAuthClient from '../../../ScalarAuthClient';
|
||||
import Modal from '../../../Modal';
|
||||
|
@ -88,7 +89,9 @@ module.exports = React.createClass({
|
|||
|
||||
componentDidMount: function() {
|
||||
this._unmounted = false;
|
||||
this._applyFormatting();
|
||||
if (!this.props.isEditing) {
|
||||
this._applyFormatting();
|
||||
}
|
||||
},
|
||||
|
||||
_applyFormatting() {
|
||||
|
@ -127,11 +130,14 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
componentDidUpdate: function(prevProps) {
|
||||
const messageWasEdited = prevProps.replacingEventId !== this.props.replacingEventId;
|
||||
if (messageWasEdited) {
|
||||
this._applyFormatting();
|
||||
if (!this.props.isEditing) {
|
||||
const stoppedEditing = prevProps.isEditing && !this.props.isEditing;
|
||||
const messageWasEdited = prevProps.replacingEventId !== this.props.replacingEventId;
|
||||
if (messageWasEdited || stoppedEditing) {
|
||||
this._applyFormatting();
|
||||
}
|
||||
this.calculateUrlPreview();
|
||||
}
|
||||
this.calculateUrlPreview();
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
|
@ -147,7 +153,9 @@ module.exports = React.createClass({
|
|||
nextProps.replacingEventId !== this.props.replacingEventId ||
|
||||
nextProps.highlightLink !== this.props.highlightLink ||
|
||||
nextProps.showUrlPreview !== this.props.showUrlPreview ||
|
||||
nextProps.isEditing !== this.props.isEditing ||
|
||||
nextState.links !== this.state.links ||
|
||||
nextState.editedMarkerHovered !== this.state.editedMarkerHovered ||
|
||||
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() {
|
||||
if (this.props.isEditing) {
|
||||
const MessageEditor = sdk.getComponent('elements.MessageEditor');
|
||||
return <MessageEditor event={this.props.mxEvent} />;
|
||||
}
|
||||
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||
const mxEvent = this.props.mxEvent;
|
||||
const content = mxEvent.getContent();
|
||||
|
@ -443,6 +483,9 @@ module.exports = React.createClass({
|
|||
// Part of Replies fallback support
|
||||
stripReplyFallback: stripReply,
|
||||
});
|
||||
if (this.props.replacingEventId) {
|
||||
body = [body, this._renderEditedMarker()];
|
||||
}
|
||||
|
||||
if (this.props.highlightLink) {
|
||||
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'
|
||||
);
|
||||
|
||||
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
|
||||
// before trying to instantiate us
|
||||
if (!tileHandler) {
|
||||
|
@ -540,6 +543,7 @@ module.exports = withMatrixClient(React.createClass({
|
|||
|
||||
const classes = classNames({
|
||||
mx_EventTile: true,
|
||||
mx_EventTile_isEditing: this.props.isEditing,
|
||||
mx_EventTile_info: isInfoMessage,
|
||||
mx_EventTile_12hr: this.props.isTwelveHour,
|
||||
mx_EventTile_encrypting: this.props.eventSendStatus === 'encrypting',
|
||||
|
@ -617,14 +621,14 @@ module.exports = withMatrixClient(React.createClass({
|
|||
}
|
||||
|
||||
const MessageActionBar = sdk.getComponent('messages.MessageActionBar');
|
||||
const actionBar = <MessageActionBar
|
||||
const actionBar = !this.props.isEditing ? <MessageActionBar
|
||||
mxEvent={this.props.mxEvent}
|
||||
reactions={this.state.reactions}
|
||||
permalinkCreator={this.props.permalinkCreator}
|
||||
getTile={this.getTile}
|
||||
getReplyThread={this.getReplyThread}
|
||||
onFocusChange={this.onActionBarFocusChange}
|
||||
/>;
|
||||
/> : undefined;
|
||||
|
||||
const timestamp = this.props.mxEvent.getTs() ?
|
||||
<MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.props.mxEvent.getTs()} /> : null;
|
||||
|
@ -780,6 +784,7 @@ module.exports = withMatrixClient(React.createClass({
|
|||
<EventTileType ref="tile"
|
||||
mxEvent={this.props.mxEvent}
|
||||
replacingEventId={this.props.replacingEventId}
|
||||
isEditing={this.props.isEditing}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
showUrlPreview={this.props.showUrlPreview}
|
||||
|
|
|
@ -52,6 +52,7 @@ export default class LabsUserSettingsTab extends React.Component {
|
|||
<div className="mx_SettingsTab_section">
|
||||
{flags}
|
||||
<SettingsFlag name={"enableWidgetScreenshots"} level={SettingLevel.ACCOUNT} />
|
||||
<SettingsFlag name={"showHiddenEventsInTimeline"} level={SettingLevel.DEVICE} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -61,6 +61,16 @@ export default class EditorModel {
|
|||
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() {
|
||||
return this._parts.map(({type, text}) => {return {type, text};});
|
||||
}
|
||||
|
@ -88,7 +98,8 @@ export default class EditorModel {
|
|||
}
|
||||
this._mergeAdjacentParts();
|
||||
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._updateCallback(newPosition);
|
||||
}
|
||||
|
@ -172,21 +183,26 @@ export default class EditorModel {
|
|||
// part might be undefined here
|
||||
let part = this._parts[index];
|
||||
const amount = Math.min(len, part.text.length - offset);
|
||||
if (part.canEdit) {
|
||||
const replaceWith = part.remove(offset, amount);
|
||||
if (typeof replaceWith === "string") {
|
||||
this._replacePart(index, this._partCreator.createDefaultPart(replaceWith));
|
||||
}
|
||||
part = this._parts[index];
|
||||
// remove empty part
|
||||
if (!part.text.length) {
|
||||
this._removePart(index);
|
||||
// don't allow 0 amount deletions
|
||||
if (amount) {
|
||||
if (part.canEdit) {
|
||||
const replaceWith = part.remove(offset, amount);
|
||||
if (typeof replaceWith === "string") {
|
||||
this._replacePart(index, this._partCreator.createDefaultPart(replaceWith));
|
||||
}
|
||||
part = this._parts[index];
|
||||
// remove empty part
|
||||
if (!part.text.length) {
|
||||
this._removePart(index);
|
||||
} else {
|
||||
index += 1;
|
||||
}
|
||||
} else {
|
||||
index += 1;
|
||||
removedOffsetDecrease += offset;
|
||||
this._removePart(index);
|
||||
}
|
||||
} else {
|
||||
removedOffsetDecrease += offset;
|
||||
this._removePart(index);
|
||||
index += 1;
|
||||
}
|
||||
len -= amount;
|
||||
offset = 0;
|
||||
|
@ -261,4 +277,13 @@ class DocumentPosition {
|
|||
get 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) {
|
||||
for (let i = 0; i < str.length; ++i) {
|
||||
const chr = str.charAt(i);
|
||||
if (!this.acceptsInsertion(chr)) {
|
||||
if (!this.acceptsInsertion(chr, i)) {
|
||||
this._text = this._text + str.substr(0, i);
|
||||
return str.substr(i);
|
||||
}
|
||||
|
@ -180,8 +180,8 @@ class PillPart extends BasePart {
|
|||
}
|
||||
|
||||
export class NewlinePart extends BasePart {
|
||||
acceptsInsertion(chr) {
|
||||
return this.text.length === 0 && chr === "\n";
|
||||
acceptsInsertion(chr, i) {
|
||||
return (this.text.length + i) === 0 && chr === "\n";
|
||||
}
|
||||
|
||||
acceptsRemoval(position, chr) {
|
||||
|
@ -205,6 +205,14 @@ export class NewlinePart extends BasePart {
|
|||
get type() {
|
||||
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 {
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
"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.",
|
||||
"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",
|
||||
"A call is currently being placed!": "A call is currently being placed!",
|
||||
"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?",
|
||||
"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",
|
||||
"Name or matrix ID": "Name or matrix ID",
|
||||
"Name or Matrix ID": "Name or Matrix ID",
|
||||
"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?",
|
||||
"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",
|
||||
"Start a chat": "Start a chat",
|
||||
"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",
|
||||
"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?",
|
||||
|
@ -157,7 +157,7 @@
|
|||
"Unrecognised room alias:": "Unrecognised room alias:",
|
||||
"Kicks user with given id": "Kicks 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",
|
||||
"Ignored user": "Ignored user",
|
||||
"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.",
|
||||
"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...",
|
||||
"%(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",
|
||||
"Not a valid Riot keyfile": "Not a valid Riot keyfile",
|
||||
"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",
|
||||
"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",
|
||||
"Show hidden events in timeline": "Show hidden events in timeline",
|
||||
"Collecting app version information": "Collecting app version information",
|
||||
"Collecting logs": "Collecting logs",
|
||||
"Uploading report": "Uploading report",
|
||||
|
@ -909,6 +913,7 @@
|
|||
"Invalid file%(extra)s": "Invalid file%(extra)s",
|
||||
"Error decrypting image": "Error decrypting image",
|
||||
"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 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/>",
|
||||
|
@ -918,6 +923,8 @@
|
|||
"Failed to copy": "Failed to copy",
|
||||
"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?",
|
||||
"Edited at %(date)s": "Edited at %(date)s",
|
||||
"edited": "edited",
|
||||
"Removed or unknown message type": "Removed or unknown message type",
|
||||
"Message removed by %(userId)s": "Message removed by %(userId)s",
|
||||
"Message removed": "Message removed",
|
||||
|
@ -1044,9 +1051,6 @@
|
|||
"%(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|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",
|
||||
"expand": "expand",
|
||||
"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.",
|
||||
"Spanner": "Wrench",
|
||||
"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",
|
||||
"Failed to invite": "Kutsu 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",
|
||||
"Unknown error": "Tuntematon virhe",
|
||||
"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 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ä",
|
||||
"%(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ä.",
|
||||
"Seen by %(userName)s at %(dateTime)s": "Käyttäjän %(userName)s näkemä %(dateTime)s",
|
||||
"Send Reset Email": "Lähetä salasanan palautusviesti",
|
||||
|
@ -943,7 +943,7 @@
|
|||
"Send Custom Event": "Lähetä mukautettu tapahtuma",
|
||||
"Advanced notification settings": "Lisäasetukset ilmoituksille",
|
||||
"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",
|
||||
"#example": "#esimerkki",
|
||||
"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.",
|
||||
"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 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",
|
||||
"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.",
|
||||
"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 already in progress!": "Puhelu on jo meneillään!",
|
||||
"Permission Required": "Lisäoikeuksia tarvitaan",
|
||||
|
@ -1760,5 +1760,82 @@
|
|||
"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.",
|
||||
"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|one": "Feltölt %(count)s másik fájlt",
|
||||
"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
|
||||
*/
|
||||
export function substitute(text, variables, tags) {
|
||||
const regexpMapping = {};
|
||||
let result = text;
|
||||
|
||||
if (variables !== undefined) {
|
||||
const regexpMapping = {};
|
||||
for (const variable in variables) {
|
||||
regexpMapping[`%\\(${variable}\\)s`] = variables[variable];
|
||||
}
|
||||
result = replaceByRegexes(result, regexpMapping);
|
||||
}
|
||||
|
||||
if (tags !== undefined) {
|
||||
const regexpMapping = {};
|
||||
for (const tag in tags) {
|
||||
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'),
|
||||
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) {
|
||||
return isContentActionable(mxEvent) &&
|
||||
mxEvent.getOriginalContent().msgtype === "m.text" &&
|
||||
mxEvent.getSender() === MatrixClientPeg.get().getUserId();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
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.
|
||||
|
@ -14,6 +15,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { _t } from '../languageHandler';
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -63,3 +66,31 @@ export function getUserNameColorClass(userId) {
|
|||
const colorNumber = (hashCode(userId) % 8) + 1;
|
||||
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