merge
This commit is contained in:
commit
6747390333
35 changed files with 767 additions and 465 deletions
|
@ -68,10 +68,8 @@ module.exports = {
|
||||||
const names = whoIsTyping.map(function(m) {
|
const names = whoIsTyping.map(function(m) {
|
||||||
return m.name;
|
return m.name;
|
||||||
});
|
});
|
||||||
if (othersCount==1) {
|
if (othersCount>=1) {
|
||||||
return _t('%(names)s and one other are typing', {names: names.slice(0, limit - 1).join(', ')});
|
return _t('%(names)s and %(count)s others are typing', {names: names.slice(0, limit - 1).join(', '), count: othersCount});
|
||||||
} else if (othersCount>1) {
|
|
||||||
return _t('%(names)s and %(count)s others are typing', {names: names.slice(0, limit - 1).join(', '), count: othersCount});
|
|
||||||
} else {
|
} else {
|
||||||
const lastPerson = names.pop();
|
const lastPerson = names.pop();
|
||||||
return _t('%(names)s and %(lastPerson)s are typing', {names: names.join(', '), lastPerson: lastPerson});
|
return _t('%(names)s and %(lastPerson)s are typing', {names: names.join(', '), lastPerson: lastPerson});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 Aviral Dasgupta
|
Copyright 2016 Aviral Dasgupta
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
Copyright 2017 New Vector Ltd
|
||||||
|
|
||||||
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.
|
||||||
|
@ -28,6 +29,10 @@ export default class AutocompleteProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
// stub
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Of the matched commands in the query, returns the first that contains or is contained by the selection, or null.
|
* Of the matched commands in the query, returns the first that contains or is contained by the selection, or null.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 Aviral Dasgupta
|
Copyright 2016 Aviral Dasgupta
|
||||||
|
Copyright 2017 New Vector Ltd
|
||||||
|
|
||||||
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.
|
||||||
|
@ -45,41 +46,56 @@ const PROVIDERS = [
|
||||||
EmojiProvider,
|
EmojiProvider,
|
||||||
CommandProvider,
|
CommandProvider,
|
||||||
DuckDuckGoProvider,
|
DuckDuckGoProvider,
|
||||||
].map((completer) => completer.getInstance());
|
];
|
||||||
|
|
||||||
// Providers will get rejected if they take longer than this.
|
// Providers will get rejected if they take longer than this.
|
||||||
const PROVIDER_COMPLETION_TIMEOUT = 3000;
|
const PROVIDER_COMPLETION_TIMEOUT = 3000;
|
||||||
|
|
||||||
export async function getCompletions(query: string, selection: SelectionRange, force: boolean = false): Array<Completion> {
|
export default class Autocompleter {
|
||||||
/* Note: That this waits for all providers to return is *intentional*
|
constructor(room) {
|
||||||
otherwise, we run into a condition where new completions are displayed
|
this.room = room;
|
||||||
while the user is interacting with the list, which makes it difficult
|
this.providers = PROVIDERS.map((p) => {
|
||||||
to predict whether an action will actually do what is intended
|
return new p(room);
|
||||||
*/
|
});
|
||||||
const completionsList = await Promise.all(
|
}
|
||||||
// Array of inspections of promises that might timeout. Instead of allowing a
|
|
||||||
// single timeout to reject the Promise.all, reflect each one and once they've all
|
|
||||||
// settled, filter for the fulfilled ones
|
|
||||||
PROVIDERS.map((provider) => {
|
|
||||||
return provider
|
|
||||||
.getCompletions(query, selection, force)
|
|
||||||
.timeout(PROVIDER_COMPLETION_TIMEOUT)
|
|
||||||
.reflect();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return completionsList.filter(
|
destroy() {
|
||||||
(inspection) => inspection.isFulfilled(),
|
this.providers.forEach((p) => {
|
||||||
).map((completionsState, i) => {
|
p.destroy();
|
||||||
return {
|
});
|
||||||
completions: completionsState.value(),
|
}
|
||||||
provider: PROVIDERS[i],
|
|
||||||
|
|
||||||
/* the currently matched "command" the completer tried to complete
|
async getCompletions(query: string, selection: SelectionRange, force: boolean = false): Array<Completion> {
|
||||||
* we pass this through so that Autocomplete can figure out when to
|
/* Note: This intentionally waits for all providers to return,
|
||||||
* re-show itself once hidden.
|
otherwise, we run into a condition where new completions are displayed
|
||||||
*/
|
while the user is interacting with the list, which makes it difficult
|
||||||
command: PROVIDERS[i].getCurrentCommand(query, selection, force),
|
to predict whether an action will actually do what is intended
|
||||||
};
|
*/
|
||||||
});
|
const completionsList = await Promise.all(
|
||||||
|
// Array of inspections of promises that might timeout. Instead of allowing a
|
||||||
|
// single timeout to reject the Promise.all, reflect each one and once they've all
|
||||||
|
// settled, filter for the fulfilled ones
|
||||||
|
this.providers.map((provider) => {
|
||||||
|
return provider
|
||||||
|
.getCompletions(query, selection, force)
|
||||||
|
.timeout(PROVIDER_COMPLETION_TIMEOUT)
|
||||||
|
.reflect();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return completionsList.filter(
|
||||||
|
(inspection) => inspection.isFulfilled(),
|
||||||
|
).map((completionsState, i) => {
|
||||||
|
return {
|
||||||
|
completions: completionsState.value(),
|
||||||
|
provider: this.providers[i],
|
||||||
|
|
||||||
|
/* the currently matched "command" the completer tried to complete
|
||||||
|
* we pass this through so that Autocomplete can figure out when to
|
||||||
|
* re-show itself once hidden.
|
||||||
|
*/
|
||||||
|
command: this.providers[i].getCurrentCommand(query, selection, force),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 Aviral Dasgupta
|
Copyright 2016 Aviral Dasgupta
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
Copyright 2017 New Vector Ltd
|
||||||
|
|
||||||
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.
|
||||||
|
@ -109,8 +110,6 @@ const COMMANDS = [
|
||||||
|
|
||||||
const COMMAND_RE = /(^\/\w*)/g;
|
const COMMAND_RE = /(^\/\w*)/g;
|
||||||
|
|
||||||
let instance = null;
|
|
||||||
|
|
||||||
export default class CommandProvider extends AutocompleteProvider {
|
export default class CommandProvider extends AutocompleteProvider {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(COMMAND_RE);
|
super(COMMAND_RE);
|
||||||
|
@ -142,12 +141,6 @@ export default class CommandProvider extends AutocompleteProvider {
|
||||||
return '*️⃣ ' + _t('Commands');
|
return '*️⃣ ' + _t('Commands');
|
||||||
}
|
}
|
||||||
|
|
||||||
static getInstance(): CommandProvider {
|
|
||||||
if (instance === null) instance = new CommandProvider();
|
|
||||||
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCompletions(completions: [React.Component]): ?React.Component {
|
renderCompletions(completions: [React.Component]): ?React.Component {
|
||||||
return <div className="mx_Autocomplete_Completion_container_block">
|
return <div className="mx_Autocomplete_Completion_container_block">
|
||||||
{ completions }
|
{ completions }
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 Aviral Dasgupta
|
Copyright 2016 Aviral Dasgupta
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
Copyright 2017 New Vector Ltd
|
||||||
|
|
||||||
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.
|
||||||
|
@ -25,8 +26,6 @@ import {TextualCompletion} from './Components';
|
||||||
const DDG_REGEX = /\/ddg\s+(.+)$/g;
|
const DDG_REGEX = /\/ddg\s+(.+)$/g;
|
||||||
const REFERRER = 'vector';
|
const REFERRER = 'vector';
|
||||||
|
|
||||||
let instance = null;
|
|
||||||
|
|
||||||
export default class DuckDuckGoProvider extends AutocompleteProvider {
|
export default class DuckDuckGoProvider extends AutocompleteProvider {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(DDG_REGEX);
|
super(DDG_REGEX);
|
||||||
|
@ -96,13 +95,6 @@ export default class DuckDuckGoProvider extends AutocompleteProvider {
|
||||||
return '🔍 ' + _t('Results from DuckDuckGo');
|
return '🔍 ' + _t('Results from DuckDuckGo');
|
||||||
}
|
}
|
||||||
|
|
||||||
static getInstance(): DuckDuckGoProvider {
|
|
||||||
if (instance == null) {
|
|
||||||
instance = new DuckDuckGoProvider();
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCompletions(completions: [React.Component]): ?React.Component {
|
renderCompletions(completions: [React.Component]): ?React.Component {
|
||||||
return <div className="mx_Autocomplete_Completion_container_block">
|
return <div className="mx_Autocomplete_Completion_container_block">
|
||||||
{ completions }
|
{ completions }
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 Aviral Dasgupta
|
Copyright 2016 Aviral Dasgupta
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
Copyright 2017 New Vector Ltd
|
||||||
|
|
||||||
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.
|
||||||
|
@ -70,8 +71,6 @@ const EMOJI_SHORTNAMES = Object.keys(EmojiData).map((key) => EmojiData[key]).sor
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
let instance = null;
|
|
||||||
|
|
||||||
function score(query, space) {
|
function score(query, space) {
|
||||||
const index = space.indexOf(query);
|
const index = space.indexOf(query);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
|
@ -151,11 +150,6 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
return '😃 ' + _t('Emoji');
|
return '😃 ' + _t('Emoji');
|
||||||
}
|
}
|
||||||
|
|
||||||
static getInstance() {
|
|
||||||
if (instance == null) {instance = new EmojiProvider();}
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCompletions(completions: [React.Component]): ?React.Component {
|
renderCompletions(completions: [React.Component]): ?React.Component {
|
||||||
return <div className="mx_Autocomplete_Completion_container_pill">
|
return <div className="mx_Autocomplete_Completion_container_pill">
|
||||||
{ completions }
|
{ completions }
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 Aviral Dasgupta
|
Copyright 2016 Aviral Dasgupta
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
Copyright 2017 New Vector Ltd
|
||||||
|
|
||||||
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.
|
||||||
|
@ -27,8 +28,6 @@ import _sortBy from 'lodash/sortBy';
|
||||||
|
|
||||||
const ROOM_REGEX = /(?=#)(\S*)/g;
|
const ROOM_REGEX = /(?=#)(\S*)/g;
|
||||||
|
|
||||||
let instance = null;
|
|
||||||
|
|
||||||
function score(query, space) {
|
function score(query, space) {
|
||||||
const index = space.indexOf(query);
|
const index = space.indexOf(query);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
|
@ -96,14 +95,6 @@ export default class RoomProvider extends AutocompleteProvider {
|
||||||
return '💬 ' + _t('Rooms');
|
return '💬 ' + _t('Rooms');
|
||||||
}
|
}
|
||||||
|
|
||||||
static getInstance() {
|
|
||||||
if (instance == null) {
|
|
||||||
instance = new RoomProvider();
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCompletions(completions: [React.Component]): ?React.Component {
|
renderCompletions(completions: [React.Component]): ?React.Component {
|
||||||
return <div className="mx_Autocomplete_Completion_container_pill mx_Autocomplete_Completion_container_truncate">
|
return <div className="mx_Autocomplete_Completion_container_pill mx_Autocomplete_Completion_container_truncate">
|
||||||
{ completions }
|
{ completions }
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 Aviral Dasgupta
|
Copyright 2016 Aviral Dasgupta
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
Copyright 2017 New Vector Ltd
|
||||||
|
|
||||||
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.
|
||||||
|
@ -30,20 +31,55 @@ import type {Room, RoomMember} from 'matrix-js-sdk';
|
||||||
|
|
||||||
const USER_REGEX = /@\S*/g;
|
const USER_REGEX = /@\S*/g;
|
||||||
|
|
||||||
let instance = null;
|
|
||||||
|
|
||||||
export default class UserProvider extends AutocompleteProvider {
|
export default class UserProvider extends AutocompleteProvider {
|
||||||
users: Array<RoomMember> = null;
|
users: Array<RoomMember> = null;
|
||||||
room: Room = null;
|
room: Room = null;
|
||||||
|
|
||||||
constructor() {
|
constructor(room) {
|
||||||
super(USER_REGEX, {
|
super(USER_REGEX, {
|
||||||
keys: ['name'],
|
keys: ['name'],
|
||||||
});
|
});
|
||||||
|
this.room = room;
|
||||||
this.matcher = new FuzzyMatcher([], {
|
this.matcher = new FuzzyMatcher([], {
|
||||||
keys: ['name', 'userId'],
|
keys: ['name', 'userId'],
|
||||||
shouldMatchPrefix: true,
|
shouldMatchPrefix: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this._onRoomTimelineBound = this._onRoomTimeline.bind(this);
|
||||||
|
this._onRoomStateMemberBound = this._onRoomStateMember.bind(this);
|
||||||
|
|
||||||
|
MatrixClientPeg.get().on("Room.timeline", this._onRoomTimelineBound);
|
||||||
|
MatrixClientPeg.get().on("RoomState.members", this._onRoomStateMemberBound);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
MatrixClientPeg.get().removeListener("Room.timeline", this._onRoomTimelineBound);
|
||||||
|
MatrixClientPeg.get().removeListener("RoomState.members", this._onRoomStateMemberBound);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onRoomTimeline(ev, room, toStartOfTimeline, removed, data) {
|
||||||
|
if (!room) return;
|
||||||
|
if (removed) return;
|
||||||
|
if (room.roomId !== this.room.roomId) return;
|
||||||
|
|
||||||
|
// ignore events from filtered timelines
|
||||||
|
if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return;
|
||||||
|
|
||||||
|
// ignore anything but real-time updates at the end of the room:
|
||||||
|
// updates from pagination will happen when the paginate completes.
|
||||||
|
if (toStartOfTimeline || !data || !data.liveEvent) return;
|
||||||
|
|
||||||
|
this.onUserSpoke(ev.sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onRoomStateMember(ev, state, member) {
|
||||||
|
// ignore members in other rooms
|
||||||
|
if (member.roomId !== this.room.roomId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// blow away the users cache
|
||||||
|
this.users = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCompletions(query: string, selection: {start: number, end: number}, force = false) {
|
async getCompletions(query: string, selection: {start: number, end: number}, force = false) {
|
||||||
|
@ -86,11 +122,6 @@ export default class UserProvider extends AutocompleteProvider {
|
||||||
return '👥 ' + _t('Users');
|
return '👥 ' + _t('Users');
|
||||||
}
|
}
|
||||||
|
|
||||||
setUserListFromRoom(room: Room) {
|
|
||||||
this.room = room;
|
|
||||||
this.users = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_makeUsers() {
|
_makeUsers() {
|
||||||
const events = this.room.getLiveTimeline().getEvents();
|
const events = this.room.getLiveTimeline().getEvents();
|
||||||
const lastSpoken = {};
|
const lastSpoken = {};
|
||||||
|
@ -123,13 +154,6 @@ export default class UserProvider extends AutocompleteProvider {
|
||||||
this.matcher.setObjects(this.users);
|
this.matcher.setObjects(this.users);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getInstance(): UserProvider {
|
|
||||||
if (instance == null) {
|
|
||||||
instance = new UserProvider();
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCompletions(completions: [React.Component]): ?React.Component {
|
renderCompletions(completions: [React.Component]): ?React.Component {
|
||||||
return <div className="mx_Autocomplete_Completion_container_pill mx_Autocomplete_Completion_container_truncate">
|
return <div className="mx_Autocomplete_Completion_container_pill mx_Autocomplete_Completion_container_truncate">
|
||||||
{ completions }
|
{ completions }
|
||||||
|
|
|
@ -15,13 +15,14 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import GeminiScrollbar from 'react-gemini-scrollbar';
|
||||||
import {MatrixClient} from 'matrix-js-sdk';
|
import {MatrixClient} from 'matrix-js-sdk';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
import { _t, _tJsx } from '../../languageHandler';
|
import { _t, _tJsx } from '../../languageHandler';
|
||||||
import withMatrixClient from '../../wrappers/withMatrixClient';
|
import withMatrixClient from '../../wrappers/withMatrixClient';
|
||||||
import AccessibleButton from '../views/elements/AccessibleButton';
|
import AccessibleButton from '../views/elements/AccessibleButton';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import Modal from '../../Modal';
|
import Modal from '../../Modal';
|
||||||
|
|
||||||
import FlairStore from '../../stores/FlairStore';
|
import FlairStore from '../../stores/FlairStore';
|
||||||
|
@ -115,18 +116,17 @@ export default withMatrixClient(React.createClass({
|
||||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
|
let contentHeader;
|
||||||
if (this.state.groups) {
|
if (this.state.groups) {
|
||||||
const groupNodes = [];
|
const groupNodes = [];
|
||||||
this.state.groups.forEach((g) => {
|
this.state.groups.forEach((g) => {
|
||||||
groupNodes.push(<GroupTile groupId={g} />);
|
groupNodes.push(<GroupTile groupId={g} />);
|
||||||
});
|
});
|
||||||
|
contentHeader = groupNodes.length > 0 ? <h3>{ _t('Your Communities') }</h3> : <div />;
|
||||||
content = groupNodes.length > 0 ?
|
content = groupNodes.length > 0 ?
|
||||||
<div>
|
<GeminiScrollbar className="mx_MyGroups_joinedGroups">
|
||||||
<h3>{ _t('Your Communities') }</h3>
|
{ groupNodes }
|
||||||
<div className="mx_MyGroups_joinedGroups">
|
</GeminiScrollbar> :
|
||||||
{ groupNodes }
|
|
||||||
</div>
|
|
||||||
</div> :
|
|
||||||
<div className="mx_MyGroups_placeholder">
|
<div className="mx_MyGroups_placeholder">
|
||||||
{ _t(
|
{ _t(
|
||||||
"You're not currently a member of any communities.",
|
"You're not currently a member of any communities.",
|
||||||
|
@ -176,6 +176,7 @@ export default withMatrixClient(React.createClass({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_MyGroups_content">
|
<div className="mx_MyGroups_content">
|
||||||
|
{ contentHeader }
|
||||||
{ content }
|
{ content }
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -44,8 +44,6 @@ const Rooms = require('../../Rooms');
|
||||||
|
|
||||||
import KeyCode from '../../KeyCode';
|
import KeyCode from '../../KeyCode';
|
||||||
|
|
||||||
import UserProvider from '../../autocomplete/UserProvider';
|
|
||||||
|
|
||||||
import RoomViewStore from '../../stores/RoomViewStore';
|
import RoomViewStore from '../../stores/RoomViewStore';
|
||||||
import RoomScrollStateStore from '../../stores/RoomScrollStateStore';
|
import RoomScrollStateStore from '../../stores/RoomScrollStateStore';
|
||||||
|
|
||||||
|
@ -541,12 +539,6 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the tab complete list as it depends on who most recently spoke,
|
|
||||||
// and that has probably just changed
|
|
||||||
if (ev.sender) {
|
|
||||||
UserProvider.getInstance().onUserSpoke(ev.sender);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomName: function(room) {
|
onRoomName: function(room) {
|
||||||
|
@ -568,7 +560,6 @@ module.exports = React.createClass({
|
||||||
this._warnAboutEncryption(room);
|
this._warnAboutEncryption(room);
|
||||||
this._calculatePeekRules(room);
|
this._calculatePeekRules(room);
|
||||||
this._updatePreviewUrlVisibility(room);
|
this._updatePreviewUrlVisibility(room);
|
||||||
UserProvider.getInstance().setUserListFromRoom(room);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_warnAboutEncryption: function(room) {
|
_warnAboutEncryption: function(room) {
|
||||||
|
@ -722,9 +713,6 @@ module.exports = React.createClass({
|
||||||
// refresh the conf call notification state
|
// refresh the conf call notification state
|
||||||
this._updateConfCallNotification();
|
this._updateConfCallNotification();
|
||||||
|
|
||||||
// refresh the tab complete list
|
|
||||||
UserProvider.getInstance().setUserListFromRoom(this.state.room);
|
|
||||||
|
|
||||||
// if we are now a member of the room, where we were not before, that
|
// if we are now a member of the room, where we were not before, that
|
||||||
// means we have finished joining a room we were previously peeking
|
// means we have finished joining a room we were previously peeking
|
||||||
// into.
|
// into.
|
||||||
|
|
|
@ -168,7 +168,7 @@ module.exports = React.createClass({
|
||||||
} else if (this.state.progress === "sent_email") {
|
} else if (this.state.progress === "sent_email") {
|
||||||
resetPasswordJsx = (
|
resetPasswordJsx = (
|
||||||
<div className="mx_Login_prompt">
|
<div className="mx_Login_prompt">
|
||||||
{ _t('An email has been sent to') } { this.state.email }. { _t("Once you've followed the link it contains, click below") }.
|
{ _t("An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.", { emailAddress: this.state.email }) }
|
||||||
<br />
|
<br />
|
||||||
<input className="mx_Login_submit" type="button" onClick={this.onVerify}
|
<input className="mx_Login_submit" type="button" onClick={this.onVerify}
|
||||||
value={_t('I have verified my email address')} />
|
value={_t('I have verified my email address')} />
|
||||||
|
|
|
@ -54,11 +54,11 @@ export default React.createClass({
|
||||||
// extract the props we use from props so we can pass any others through
|
// extract the props we use from props so we can pass any others through
|
||||||
// should consider adding this as a global rule in js-sdk?
|
// should consider adding this as a global rule in js-sdk?
|
||||||
/*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/
|
/*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/
|
||||||
const {groupId, groupAvatarUrl, ...otherProps} = this.props;
|
const {groupId, groupAvatarUrl, groupName, ...otherProps} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseAvatar
|
<BaseAvatar
|
||||||
name={this.props.groupName || this.props.groupId[1]}
|
name={groupName || this.props.groupId[1]}
|
||||||
idName={this.props.groupId}
|
idName={this.props.groupId}
|
||||||
url={this.getGroupAvatarUrl()}
|
url={this.getGroupAvatarUrl()}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
|
|
|
@ -36,6 +36,7 @@ export default React.createClass({
|
||||||
// group member object. Supply either this or 'member'
|
// group member object. Supply either this or 'member'
|
||||||
groupMember: GroupMemberType,
|
groupMember: GroupMemberType,
|
||||||
action: React.PropTypes.string.isRequired, // eg. 'Ban'
|
action: React.PropTypes.string.isRequired, // eg. 'Ban'
|
||||||
|
title: React.PropTypes.string.isRequired, // eg. 'Ban this user?'
|
||||||
|
|
||||||
// Whether to display a text field for a reason
|
// Whether to display a text field for a reason
|
||||||
// If true, the second argument to onFinished will
|
// If true, the second argument to onFinished will
|
||||||
|
@ -75,7 +76,6 @@ export default React.createClass({
|
||||||
const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");
|
const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");
|
||||||
const BaseAvatar = sdk.getComponent("views.avatars.BaseAvatar");
|
const BaseAvatar = sdk.getComponent("views.avatars.BaseAvatar");
|
||||||
|
|
||||||
const title = _t("%(actionVerb)s this person?", { actionVerb: this.props.action});
|
|
||||||
const confirmButtonClass = classnames({
|
const confirmButtonClass = classnames({
|
||||||
'mx_Dialog_primary': true,
|
'mx_Dialog_primary': true,
|
||||||
'danger': this.props.danger,
|
'danger': this.props.danger,
|
||||||
|
@ -113,7 +113,7 @@ export default React.createClass({
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_ConfirmUserActionDialog" onFinished={this.props.onFinished}
|
<BaseDialog className="mx_ConfirmUserActionDialog" onFinished={this.props.onFinished}
|
||||||
onEnterPressed={this.onOk}
|
onEnterPressed={this.onOk}
|
||||||
title={title}
|
title={this.props.title}
|
||||||
>
|
>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
<div className="mx_ConfirmUserActionDialog_avatar">
|
<div className="mx_ConfirmUserActionDialog_avatar">
|
||||||
|
|
|
@ -86,7 +86,6 @@ module.exports = React.createClass({
|
||||||
const summaries = orderedTransitionSequences.map((transitions) => {
|
const summaries = orderedTransitionSequences.map((transitions) => {
|
||||||
const userNames = eventAggregates[transitions];
|
const userNames = eventAggregates[transitions];
|
||||||
const nameList = this._renderNameList(userNames);
|
const nameList = this._renderNameList(userNames);
|
||||||
const plural = userNames.length > 1;
|
|
||||||
|
|
||||||
const splitTransitions = transitions.split(',');
|
const splitTransitions = transitions.split(',');
|
||||||
|
|
||||||
|
@ -101,13 +100,13 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
const descs = coalescedTransitions.map((t) => {
|
const descs = coalescedTransitions.map((t) => {
|
||||||
return this._getDescriptionForTransition(
|
return this._getDescriptionForTransition(
|
||||||
t.transitionType, plural, t.repeats,
|
t.transitionType, userNames.length, t.repeats,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const desc = this._renderCommaSeparatedList(descs);
|
const desc = this._renderCommaSeparatedList(descs);
|
||||||
|
|
||||||
return nameList + " " + desc;
|
return _t('%(nameList)s %(transitionList)s', { nameList: nameList, transitionList: desc });
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!summaries) {
|
if (!summaries) {
|
||||||
|
@ -208,148 +207,75 @@ module.exports = React.createClass({
|
||||||
* For a certain transition, t, describe what happened to the users that
|
* For a certain transition, t, describe what happened to the users that
|
||||||
* underwent the transition.
|
* underwent the transition.
|
||||||
* @param {string} t the transition type.
|
* @param {string} t the transition type.
|
||||||
* @param {boolean} plural whether there were multiple users undergoing the same
|
* @param {integer} userCount number of usernames
|
||||||
* transition.
|
|
||||||
* @param {number} repeats the number of times the transition was repeated in a row.
|
* @param {number} repeats the number of times the transition was repeated in a row.
|
||||||
* @returns {string} the written Human Readable equivalent of the transition.
|
* @returns {string} the written Human Readable equivalent of the transition.
|
||||||
*/
|
*/
|
||||||
_getDescriptionForTransition(t, plural, repeats) {
|
_getDescriptionForTransition(t, userCount, repeats) {
|
||||||
// The empty interpolations 'severalUsers' and 'oneUser'
|
// The empty interpolations 'severalUsers' and 'oneUser'
|
||||||
// are there only to show translators to non-English languages
|
// are there only to show translators to non-English languages
|
||||||
// that the verb is conjugated to plural or singular Subject.
|
// that the verb is conjugated to plural or singular Subject.
|
||||||
let res = null;
|
let res = null;
|
||||||
switch(t) {
|
switch(t) {
|
||||||
case "joined":
|
case "joined":
|
||||||
if (repeats > 1) {
|
res = (userCount > 1)
|
||||||
res = (plural)
|
? _t("%(severalUsers)sjoined %(count)s times", { severalUsers: "", count: repeats })
|
||||||
? _t("%(severalUsers)sjoined %(repeats)s times", { severalUsers: "", repeats: repeats })
|
: _t("%(oneUser)sjoined %(count)s times", { oneUser: "", count: repeats });
|
||||||
: _t("%(oneUser)sjoined %(repeats)s times", { oneUser: "", repeats: repeats });
|
|
||||||
} else {
|
|
||||||
res = (plural)
|
|
||||||
? _t("%(severalUsers)sjoined", { severalUsers: "" })
|
|
||||||
: _t("%(oneUser)sjoined", { oneUser: "" });
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "left":
|
case "left":
|
||||||
if (repeats > 1) {
|
res = (userCount > 1)
|
||||||
res = (plural)
|
? _t("%(severalUsers)sleft %(count)s times", { severalUsers: "", count: repeats })
|
||||||
? _t("%(severalUsers)sleft %(repeats)s times", { severalUsers: "", repeats: repeats })
|
: _t("%(oneUser)sleft %(count)s times", { oneUser: "", count: repeats });
|
||||||
: _t("%(oneUser)sleft %(repeats)s times", { oneUser: "", repeats: repeats });
|
break;
|
||||||
} else {
|
|
||||||
res = (plural)
|
|
||||||
? _t("%(severalUsers)sleft", { severalUsers: "" })
|
|
||||||
: _t("%(oneUser)sleft", { oneUser: "" });
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "joined_and_left":
|
case "joined_and_left":
|
||||||
if (repeats > 1) {
|
res = (userCount > 1)
|
||||||
res = (plural)
|
? _t("%(severalUsers)sjoined and left %(count)s times", { severalUsers: "", count: repeats })
|
||||||
? _t("%(severalUsers)sjoined and left %(repeats)s times", { severalUsers: "", repeats: repeats })
|
: _t("%(oneUser)sjoined and left %(count)s times", { oneUser: "", count: repeats });
|
||||||
: _t("%(oneUser)sjoined and left %(repeats)s times", { oneUser: "", repeats: repeats });
|
|
||||||
} else {
|
|
||||||
res = (plural)
|
|
||||||
? _t("%(severalUsers)sjoined and left", { severalUsers: "" })
|
|
||||||
: _t("%(oneUser)sjoined and left", { oneUser: "" });
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "left_and_joined":
|
case "left_and_joined":
|
||||||
if (repeats > 1) {
|
res = (userCount > 1)
|
||||||
res = (plural)
|
? _t("%(severalUsers)sleft and rejoined %(count)s times", { severalUsers: "", count: repeats })
|
||||||
? _t("%(severalUsers)sleft and rejoined %(repeats)s times", { severalUsers: "", repeats: repeats })
|
: _t("%(oneUser)sleft and rejoined %(count)s times", { oneUser: "", count: repeats });
|
||||||
: _t("%(oneUser)sleft and rejoined %(repeats)s times", { oneUser: "", repeats: repeats });
|
|
||||||
} else {
|
|
||||||
res = (plural)
|
|
||||||
? _t("%(severalUsers)sleft and rejoined", { severalUsers: "" })
|
|
||||||
: _t("%(oneUser)sleft and rejoined", { oneUser: "" });
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "invite_reject":
|
case "invite_reject":
|
||||||
if (repeats > 1) {
|
res = (userCount > 1)
|
||||||
res = (plural)
|
? _t("%(severalUsers)srejected their invitations %(count)s times", { severalUsers: "", count: repeats })
|
||||||
? _t("%(severalUsers)srejected their invitations %(repeats)s times", { severalUsers: "", repeats: repeats })
|
: _t("%(oneUser)srejected their invitation %(count)s times", { oneUser: "", count: repeats });
|
||||||
: _t("%(oneUser)srejected their invitation %(repeats)s times", { oneUser: "", repeats: repeats });
|
|
||||||
} else {
|
|
||||||
res = (plural)
|
|
||||||
? _t("%(severalUsers)srejected their invitations", { severalUsers: "" })
|
|
||||||
: _t("%(oneUser)srejected their invitation", { oneUser: "" });
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "invite_withdrawal":
|
case "invite_withdrawal":
|
||||||
if (repeats > 1) {
|
res = (userCount > 1)
|
||||||
res = (plural)
|
? _t("%(severalUsers)shad their invitations withdrawn %(count)s times", { severalUsers: "", count: repeats })
|
||||||
? _t("%(severalUsers)shad their invitations withdrawn %(repeats)s times", { severalUsers: "", repeats: repeats })
|
: _t("%(oneUser)shad their invitation withdrawn %(count)s times", { oneUser: "", count: repeats });
|
||||||
: _t("%(oneUser)shad their invitation withdrawn %(repeats)s times", { oneUser: "", repeats: repeats });
|
|
||||||
} else {
|
|
||||||
res = (plural)
|
|
||||||
? _t("%(severalUsers)shad their invitations withdrawn", { severalUsers: "" })
|
|
||||||
: _t("%(oneUser)shad their invitation withdrawn", { oneUser: "" });
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "invited":
|
case "invited":
|
||||||
if (repeats > 1) {
|
res = (userCount > 1)
|
||||||
res = (plural)
|
? _t("were invited %(count)s times", { count: repeats })
|
||||||
? _t("were invited %(repeats)s times", { repeats: repeats })
|
: _t("was invited %(count)s times", { count: repeats });
|
||||||
: _t("was invited %(repeats)s times", { repeats: repeats });
|
|
||||||
} else {
|
|
||||||
res = (plural)
|
|
||||||
? _t("were invited")
|
|
||||||
: _t("was invited");
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "banned":
|
case "banned":
|
||||||
if (repeats > 1) {
|
res = (userCount > 1)
|
||||||
res = (plural)
|
? _t("were banned %(count)s times", { count: repeats })
|
||||||
? _t("were banned %(repeats)s times", { repeats: repeats })
|
: _t("was banned %(count)s times", { count: repeats });
|
||||||
: _t("was banned %(repeats)s times", { repeats: repeats });
|
|
||||||
} else {
|
|
||||||
res = (plural)
|
|
||||||
? _t("were banned")
|
|
||||||
: _t("was banned");
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "unbanned":
|
case "unbanned":
|
||||||
if (repeats > 1) {
|
res = (userCount > 1)
|
||||||
res = (plural)
|
? _t("were unbanned %(count)s times", { count: repeats })
|
||||||
? _t("were unbanned %(repeats)s times", { repeats: repeats })
|
: _t("was unbanned %(count)s times", { count: repeats });
|
||||||
: _t("was unbanned %(repeats)s times", { repeats: repeats });
|
|
||||||
} else {
|
|
||||||
res = (plural)
|
|
||||||
? _t("were unbanned")
|
|
||||||
: _t("was unbanned");
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "kicked":
|
case "kicked":
|
||||||
if (repeats > 1) {
|
res = (userCount > 1)
|
||||||
res = (plural)
|
? _t("were kicked %(count)s times", { count: repeats })
|
||||||
? _t("were kicked %(repeats)s times", { repeats: repeats })
|
: _t("was kicked %(count)s times", { count: repeats });
|
||||||
: _t("was kicked %(repeats)s times", { repeats: repeats });
|
|
||||||
} else {
|
|
||||||
res = (plural)
|
|
||||||
? _t("were kicked")
|
|
||||||
: _t("was kicked");
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "changed_name":
|
case "changed_name":
|
||||||
if (repeats > 1) {
|
res = (userCount > 1)
|
||||||
res = (plural)
|
? _t("%(severalUsers)schanged their name %(count)s times", { severalUsers: "", count: repeats })
|
||||||
? _t("%(severalUsers)schanged their name %(repeats)s times", { severalUsers: "", repeats: repeats })
|
: _t("%(oneUser)schanged their name %(count)s times", { oneUser: "", count: repeats });
|
||||||
: _t("%(oneUser)schanged their name %(repeats)s times", { oneUser: "", repeats: repeats });
|
|
||||||
} else {
|
|
||||||
res = (plural)
|
|
||||||
? _t("%(severalUsers)schanged their name", { severalUsers: "" })
|
|
||||||
: _t("%(oneUser)schanged their name", { oneUser: "" });
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "changed_avatar":
|
case "changed_avatar":
|
||||||
if (repeats > 1) {
|
res = (userCount > 1)
|
||||||
res = (plural)
|
? _t("%(severalUsers)schanged their avatar %(count)s times", { severalUsers: "", count: repeats })
|
||||||
? _t("%(severalUsers)schanged their avatar %(repeats)s times", { severalUsers: "", repeats: repeats })
|
: _t("%(oneUser)schanged their avatar %(count)s times", { oneUser: "", count: repeats });
|
||||||
: _t("%(oneUser)schanged their avatar %(repeats)s times", { oneUser: "", repeats: repeats });
|
|
||||||
} else {
|
|
||||||
res = (plural)
|
|
||||||
? _t("%(severalUsers)schanged their avatar", { severalUsers: "" })
|
|
||||||
: _t("%(oneUser)schanged their avatar", { oneUser: "" });
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,11 +302,9 @@ module.exports = React.createClass({
|
||||||
return "";
|
return "";
|
||||||
} else if (items.length === 1) {
|
} else if (items.length === 1) {
|
||||||
return items[0];
|
return items[0];
|
||||||
} else if (remaining) {
|
} else if (remaining > 0) {
|
||||||
items = items.slice(0, itemLimit);
|
items = items.slice(0, itemLimit);
|
||||||
return (remaining > 1)
|
return _t("%(items)s and %(count)s others", { items: items.join(', '), count: remaining } )
|
||||||
? _t("%(items)s and %(remaining)s others", { items: items.join(', '), remaining: remaining } )
|
|
||||||
: _t("%(items)s and one other", { items: items.join(', ') });
|
|
||||||
} else {
|
} else {
|
||||||
const lastItem = items.pop();
|
const lastItem = items.pop();
|
||||||
return _t("%(items)s and %(lastItem)s", { items: items.join(', '), lastItem: lastItem });
|
return _t("%(items)s and %(lastItem)s", { items: items.join(', '), lastItem: lastItem });
|
||||||
|
|
|
@ -37,11 +37,20 @@ const Pill = React.createClass({
|
||||||
isMessagePillUrl: (url) => {
|
isMessagePillUrl: (url) => {
|
||||||
return !!REGEX_LOCAL_MATRIXTO.exec(url);
|
return !!REGEX_LOCAL_MATRIXTO.exec(url);
|
||||||
},
|
},
|
||||||
|
roomNotifPos: (text) => {
|
||||||
|
return text.indexOf("@room");
|
||||||
|
},
|
||||||
|
roomNotifLen: () => {
|
||||||
|
return "@room".length;
|
||||||
|
},
|
||||||
TYPE_USER_MENTION: 'TYPE_USER_MENTION',
|
TYPE_USER_MENTION: 'TYPE_USER_MENTION',
|
||||||
TYPE_ROOM_MENTION: 'TYPE_ROOM_MENTION',
|
TYPE_ROOM_MENTION: 'TYPE_ROOM_MENTION',
|
||||||
|
TYPE_AT_ROOM_MENTION: 'TYPE_AT_ROOM_MENTION', // '@room' mention
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
|
// The Type of this Pill. If url is given, this is auto-detected.
|
||||||
|
type: PropTypes.string,
|
||||||
// The URL to pillify (no validation is done, see isPillUrl and isMessagePillUrl)
|
// The URL to pillify (no validation is done, see isPillUrl and isMessagePillUrl)
|
||||||
url: PropTypes.string,
|
url: PropTypes.string,
|
||||||
// Whether the pill is in a message
|
// Whether the pill is in a message
|
||||||
|
@ -72,14 +81,20 @@ const Pill = React.createClass({
|
||||||
regex = REGEX_LOCAL_MATRIXTO;
|
regex = REGEX_LOCAL_MATRIXTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default to the empty array if no match for simplicity
|
let matrixToMatch;
|
||||||
// resource and prefix will be undefined instead of throwing
|
let resourceId;
|
||||||
const matrixToMatch = regex.exec(nextProps.url) || [];
|
let prefix;
|
||||||
|
|
||||||
const resourceId = matrixToMatch[1]; // The room/user ID
|
if (nextProps.url) {
|
||||||
const prefix = matrixToMatch[2]; // The first character of prefix
|
// Default to the empty array if no match for simplicity
|
||||||
|
// resource and prefix will be undefined instead of throwing
|
||||||
|
matrixToMatch = regex.exec(nextProps.url) || [];
|
||||||
|
|
||||||
const pillType = {
|
resourceId = matrixToMatch[1]; // The room/user ID
|
||||||
|
prefix = matrixToMatch[2]; // The first character of prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
const pillType = this.props.type || {
|
||||||
'@': Pill.TYPE_USER_MENTION,
|
'@': Pill.TYPE_USER_MENTION,
|
||||||
'#': Pill.TYPE_ROOM_MENTION,
|
'#': Pill.TYPE_ROOM_MENTION,
|
||||||
'!': Pill.TYPE_ROOM_MENTION,
|
'!': Pill.TYPE_ROOM_MENTION,
|
||||||
|
@ -88,6 +103,10 @@ const Pill = React.createClass({
|
||||||
let member;
|
let member;
|
||||||
let room;
|
let room;
|
||||||
switch (pillType) {
|
switch (pillType) {
|
||||||
|
case Pill.TYPE_AT_ROOM_MENTION: {
|
||||||
|
room = nextProps.room;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case Pill.TYPE_USER_MENTION: {
|
case Pill.TYPE_USER_MENTION: {
|
||||||
const localMember = nextProps.room.getMember(resourceId);
|
const localMember = nextProps.room.getMember(resourceId);
|
||||||
member = localMember;
|
member = localMember;
|
||||||
|
@ -160,6 +179,17 @@ const Pill = React.createClass({
|
||||||
let href = this.props.url;
|
let href = this.props.url;
|
||||||
let onClick;
|
let onClick;
|
||||||
switch (this.state.pillType) {
|
switch (this.state.pillType) {
|
||||||
|
case Pill.TYPE_AT_ROOM_MENTION: {
|
||||||
|
const room = this.props.room;
|
||||||
|
if (room) {
|
||||||
|
linkText = "@room";
|
||||||
|
if (this.props.shouldShowPillAvatar) {
|
||||||
|
avatar = <RoomAvatar room={room} width={16} height={16} />;
|
||||||
|
}
|
||||||
|
pillClass = 'mx_AtRoomPill';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
case Pill.TYPE_USER_MENTION: {
|
case Pill.TYPE_USER_MENTION: {
|
||||||
// If this user is not a member of this room, default to the empty member
|
// If this user is not a member of this room, default to the empty member
|
||||||
const member = this.state.member;
|
const member = this.state.member;
|
||||||
|
|
|
@ -44,21 +44,21 @@ export default React.createClass({
|
||||||
|
|
||||||
const label = <EmojiText
|
const label = <EmojiText
|
||||||
element="div"
|
element="div"
|
||||||
title={groupName}
|
title={this.props.group.groupId}
|
||||||
className="mx_GroupInviteTile_name"
|
className="mx_RoomTile_name"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
>
|
>
|
||||||
{ groupName }
|
{ groupName }
|
||||||
</EmojiText>;
|
</EmojiText>;
|
||||||
|
|
||||||
const badge = <div className="mx_GroupInviteTile_badge">!</div>;
|
const badge = <div className="mx_RoomSubList_badge mx_RoomSubList_badgeHighlight">!</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccessibleButton className="mx_GroupInviteTile" onClick={this.onClick}>
|
<AccessibleButton className="mx_RoomTile mx_RoomTile_highlight" onClick={this.onClick}>
|
||||||
<div className="mx_GroupInviteTile_avatarContainer">
|
<div className="mx_RoomTile_avatar">
|
||||||
{ av }
|
{ av }
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_GroupInviteTile_nameContainer">
|
<div className="mx_RoomTile_nameContainer">
|
||||||
{ label }
|
{ label }
|
||||||
{ badge }
|
{ badge }
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -85,6 +85,8 @@ module.exports = React.createClass({
|
||||||
Modal.createDialog(ConfirmUserActionDialog, {
|
Modal.createDialog(ConfirmUserActionDialog, {
|
||||||
groupMember: this.props.groupMember,
|
groupMember: this.props.groupMember,
|
||||||
action: this.state.isUserInvited ? _t('Disinvite') : _t('Remove from community'),
|
action: this.state.isUserInvited ? _t('Disinvite') : _t('Remove from community'),
|
||||||
|
title: this.state.isUserInvited ? _t('Disinvite this user from community?')
|
||||||
|
: _t('Remove this user from community?'),
|
||||||
danger: true,
|
danger: true,
|
||||||
onFinished: (proceed) => {
|
onFinished: (proceed) => {
|
||||||
if (!proceed) return;
|
if (!proceed) return;
|
||||||
|
|
242
src/components/views/groups/GroupRoomInfo.js
Normal file
242
src/components/views/groups/GroupRoomInfo.js
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 New Vector Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import { MatrixClient } from 'matrix-js-sdk';
|
||||||
|
import dis from '../../../dispatcher';
|
||||||
|
import Modal from '../../../Modal';
|
||||||
|
import sdk from '../../../index';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
import GroupStoreCache from '../../../stores/GroupStoreCache';
|
||||||
|
import GeminiScrollbar from 'react-gemini-scrollbar';
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
displayName: 'GroupRoomInfo',
|
||||||
|
|
||||||
|
contextTypes: {
|
||||||
|
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||||
|
},
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
groupId: PropTypes.string,
|
||||||
|
groupRoomId: PropTypes.string,
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
isUserPrivilegedInGroup: null,
|
||||||
|
groupRoom: null,
|
||||||
|
groupRoomPublicityLoading: false,
|
||||||
|
groupRoomRemoveLoading: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillMount: function() {
|
||||||
|
this._initGroupStore(this.props.groupId);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillReceiveProps(newProps) {
|
||||||
|
if (newProps.groupId !== this.props.groupId) {
|
||||||
|
this._unregisterGroupStore();
|
||||||
|
this._initGroupStore(newProps.groupId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this._unregisterGroupStore();
|
||||||
|
},
|
||||||
|
|
||||||
|
_initGroupStore(groupId) {
|
||||||
|
this._groupStore = GroupStoreCache.getGroupStore(
|
||||||
|
this.context.matrixClient, this.props.groupId,
|
||||||
|
);
|
||||||
|
this._groupStore.registerListener(this.onGroupStoreUpdated);
|
||||||
|
},
|
||||||
|
|
||||||
|
_unregisterGroupStore() {
|
||||||
|
if (this._groupStore) {
|
||||||
|
this._groupStore.unregisterListener(this.onGroupStoreUpdated);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateGroupRoom() {
|
||||||
|
this.setState({
|
||||||
|
groupRoom: this._groupStore.getGroupRooms().find(
|
||||||
|
(r) => r.roomId === this.props.groupRoomId,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onGroupStoreUpdated: function() {
|
||||||
|
this.setState({
|
||||||
|
isUserPrivilegedInGroup: this._groupStore.isUserPrivileged(),
|
||||||
|
});
|
||||||
|
this._updateGroupRoom();
|
||||||
|
},
|
||||||
|
|
||||||
|
_onRemove: function(e) {
|
||||||
|
const groupId = this.props.groupId;
|
||||||
|
const roomName = this.state.groupRoom.displayname;
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
|
Modal.createTrackedDialog('Confirm removal of group from room', '', QuestionDialog, {
|
||||||
|
title: _t("Are you sure you want to remove '%(roomName)s' from %(groupId)s?", {roomName, groupId}),
|
||||||
|
description: _t("Removing a room from the community will also remove it from the community page."),
|
||||||
|
button: _t("Remove"),
|
||||||
|
onFinished: (proceed) => {
|
||||||
|
if (!proceed) return;
|
||||||
|
this.setState({groupRoomRemoveLoading: true});
|
||||||
|
const groupId = this.props.groupId;
|
||||||
|
const roomId = this.props.groupRoomId;
|
||||||
|
this._groupStore.removeRoomFromGroup(roomId).then(() => {
|
||||||
|
dis.dispatch({
|
||||||
|
action: "view_group_room_list",
|
||||||
|
});
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error(`Error whilst removing ${roomId} from ${groupId}`, err);
|
||||||
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
Modal.createTrackedDialog('Failed to remove room from group', '', ErrorDialog, {
|
||||||
|
title: _t("Failed to remove room from community"),
|
||||||
|
description: _t(
|
||||||
|
"Failed to remove '%(roomName)s' from %(groupId)s", {groupId, roomName},
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}).finally(() => {
|
||||||
|
this.setState({groupRoomRemoveLoading: false});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_onCancel: function(e) {
|
||||||
|
dis.dispatch({
|
||||||
|
action: "view_group_room_list",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_changeGroupRoomPublicity(e) {
|
||||||
|
const isPublic = e.target.value === "public";
|
||||||
|
this.setState({
|
||||||
|
groupRoomPublicityLoading: true,
|
||||||
|
});
|
||||||
|
const groupId = this.props.groupId;
|
||||||
|
const roomId = this.props.groupRoomId;
|
||||||
|
const roomName = this.state.groupRoom.displayname;
|
||||||
|
this._groupStore.updateGroupRoomAssociation(roomId, isPublic).catch((err) => {
|
||||||
|
console.error(`Error whilst changing visibility of ${roomId} in ${groupId} to ${isPublic}`, err);
|
||||||
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
Modal.createTrackedDialog('Failed to remove room from group', '', ErrorDialog, {
|
||||||
|
title: _t("Something went wrong!"),
|
||||||
|
description: _t(
|
||||||
|
"The visibility of '%(roomName)s' in %(groupId)s could not be updated.",
|
||||||
|
{roomName, groupId},
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}).finally(() => {
|
||||||
|
this.setState({
|
||||||
|
groupRoomPublicityLoading: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||||
|
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||||
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
|
const InlineSpinner = sdk.getComponent('elements.InlineSpinner');
|
||||||
|
if (this.state.groupRoomRemoveLoading || !this.state.groupRoom) {
|
||||||
|
const Spinner = sdk.getComponent("elements.Spinner");
|
||||||
|
return <div className="mx_MemberInfo">
|
||||||
|
<Spinner />
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
let adminTools;
|
||||||
|
if (this.state.isUserPrivilegedInGroup) {
|
||||||
|
adminTools =
|
||||||
|
<div className="mx_MemberInfo_adminTools">
|
||||||
|
<h3>{ _t("Admin Tools") }</h3>
|
||||||
|
<div className="mx_MemberInfo_buttons">
|
||||||
|
<AccessibleButton className="mx_MemberInfo_field" onClick={this._onRemove}>
|
||||||
|
{ _t('Remove from community') }
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
<h3>
|
||||||
|
{ _t('Visibility in Room List') }
|
||||||
|
{ this.state.groupRoomPublicityLoading ?
|
||||||
|
<InlineSpinner /> : <div />
|
||||||
|
}
|
||||||
|
</h3>
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
<input type="radio"
|
||||||
|
value="public"
|
||||||
|
checked={this.state.groupRoom.isPublic}
|
||||||
|
onClick={this._changeGroupRoomPublicity}
|
||||||
|
/>
|
||||||
|
<div className="mx_MemberInfo_label_text">
|
||||||
|
{ _t('Visible to everyone') }
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
<input type="radio"
|
||||||
|
value="private"
|
||||||
|
checked={!this.state.groupRoom.isPublic}
|
||||||
|
onClick={this._changeGroupRoomPublicity}
|
||||||
|
/>
|
||||||
|
<div className="mx_MemberInfo_label_text">
|
||||||
|
{ _t('Only visible to community members') }
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const avatarUrl = this.context.matrixClient.mxcUrlToHttp(
|
||||||
|
this.state.groupRoom.avatarUrl,
|
||||||
|
36, 36, 'crop',
|
||||||
|
);
|
||||||
|
|
||||||
|
const groupRoomName = this.state.groupRoom.displayname;
|
||||||
|
const avatar = <BaseAvatar name={groupRoomName} width={36} height={36} url={avatarUrl} />;
|
||||||
|
return (
|
||||||
|
<div className="mx_MemberInfo">
|
||||||
|
<GeminiScrollbar autoshow={true}>
|
||||||
|
<AccessibleButton className="mx_MemberInfo_cancel" onClick={this._onCancel}>
|
||||||
|
<img src="img/cancel.svg" width="18" height="18" className="mx_filterFlipColor" />
|
||||||
|
</AccessibleButton>
|
||||||
|
<div className="mx_MemberInfo_avatar">
|
||||||
|
{ avatar }
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<EmojiText element="h2">{ groupRoomName }</EmojiText>
|
||||||
|
|
||||||
|
<div className="mx_MemberInfo_profile">
|
||||||
|
<div className="mx_MemberInfo_profileField">
|
||||||
|
{ this.state.groupRoom.canonical_alias }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{ adminTools }
|
||||||
|
</GeminiScrollbar>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
|
@ -16,13 +16,10 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {MatrixClient} from 'matrix-js-sdk';
|
import {MatrixClient} from 'matrix-js-sdk';
|
||||||
import { _t } from '../../../languageHandler';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import { GroupRoomType } from '../../../groups';
|
import { GroupRoomType } from '../../../groups';
|
||||||
import GroupStoreCache from '../../../stores/GroupStoreCache';
|
|
||||||
import Modal from '../../../Modal';
|
|
||||||
|
|
||||||
const GroupRoomTile = React.createClass({
|
const GroupRoomTile = React.createClass({
|
||||||
displayName: 'GroupRoomTile',
|
displayName: 'GroupRoomTile',
|
||||||
|
@ -32,68 +29,11 @@ const GroupRoomTile = React.createClass({
|
||||||
groupRoom: GroupRoomType.isRequired,
|
groupRoom: GroupRoomType.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
name: this.calculateRoomName(this.props.groupRoom),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillReceiveProps: function(newProps) {
|
|
||||||
this.setState({
|
|
||||||
name: this.calculateRoomName(newProps.groupRoom),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
calculateRoomName: function(groupRoom) {
|
|
||||||
return groupRoom.name || groupRoom.canonicalAlias || _t("Unnamed Room");
|
|
||||||
},
|
|
||||||
|
|
||||||
removeRoomFromGroup: function() {
|
|
||||||
const groupId = this.props.groupId;
|
|
||||||
const groupStore = GroupStoreCache.getGroupStore(this.context.matrixClient, groupId);
|
|
||||||
const roomName = this.state.name;
|
|
||||||
const roomId = this.props.groupRoom.roomId;
|
|
||||||
groupStore.removeRoomFromGroup(roomId)
|
|
||||||
.catch((err) => {
|
|
||||||
console.error(`Error whilst removing ${roomId} from ${groupId}`, err);
|
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
Modal.createTrackedDialog('Failed to remove room from group', '', ErrorDialog, {
|
|
||||||
title: _t("Failed to remove room from community"),
|
|
||||||
description: _t("Failed to remove '%(roomName)s' from %(groupId)s", {groupId, roomName}),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onClick: function(e) {
|
onClick: function(e) {
|
||||||
let roomId;
|
|
||||||
let roomAlias;
|
|
||||||
if (this.props.groupRoom.canonicalAlias) {
|
|
||||||
roomAlias = this.props.groupRoom.canonicalAlias;
|
|
||||||
} else {
|
|
||||||
roomId = this.props.groupRoom.roomId;
|
|
||||||
}
|
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'view_room',
|
action: 'view_group_room',
|
||||||
room_id: roomId,
|
groupId: this.props.groupId,
|
||||||
room_alias: roomAlias,
|
groupRoomId: this.props.groupRoom.roomId,
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onDeleteClick: function(e) {
|
|
||||||
const groupId = this.props.groupId;
|
|
||||||
const roomName = this.state.name;
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
|
||||||
Modal.createTrackedDialog('Confirm removal of group from room', '', QuestionDialog, {
|
|
||||||
title: _t("Are you sure you want to remove '%(roomName)s' from %(groupId)s?", {roomName, groupId}),
|
|
||||||
description: _t("Removing a room from the community will also remove it from the community page."),
|
|
||||||
button: _t("Remove"),
|
|
||||||
onFinished: (success) => {
|
|
||||||
if (success) {
|
|
||||||
this.removeRoomFromGroup();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -106,7 +46,7 @@ const GroupRoomTile = React.createClass({
|
||||||
);
|
);
|
||||||
|
|
||||||
const av = (
|
const av = (
|
||||||
<BaseAvatar name={this.state.name}
|
<BaseAvatar name={this.props.groupRoom.displayname}
|
||||||
width={36} height={36}
|
width={36} height={36}
|
||||||
url={avatarUrl}
|
url={avatarUrl}
|
||||||
/>
|
/>
|
||||||
|
@ -118,14 +58,8 @@ const GroupRoomTile = React.createClass({
|
||||||
{ av }
|
{ av }
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_GroupRoomTile_name">
|
<div className="mx_GroupRoomTile_name">
|
||||||
{ this.state.name }
|
{ this.props.groupRoom.displayname }
|
||||||
</div>
|
</div>
|
||||||
<AccessibleButton className="mx_GroupRoomTile_delete"
|
|
||||||
onClick={this.onDeleteClick}
|
|
||||||
tooltip={_t("Remove this room from the community")}
|
|
||||||
>
|
|
||||||
<img src="img/cancel.svg" width="15" height="15" className="mx_filterFlipColor" />
|
|
||||||
</AccessibleButton>
|
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -20,7 +20,7 @@ import url from 'url';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t, _tJsx } from '../../../languageHandler';
|
||||||
|
|
||||||
/* This file contains a collection of components which are used by the
|
/* This file contains a collection of components which are used by the
|
||||||
* InteractiveAuth to prompt the user to enter the information needed
|
* InteractiveAuth to prompt the user to enter the information needed
|
||||||
|
@ -256,7 +256,7 @@ export const EmailIdentityAuthEntry = React.createClass({
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>{ _t("An email has been sent to") } <i>{ this.props.inputs.emailAddress }</i></p>
|
<p>{ _tJsx("An email has been sent to %(emailAddress)s", /%\(emailAddress\)s/, (sub) => <i>{this.props.inputs.emailAddress}</i>) }</p>
|
||||||
<p>{ _t("Please check your email to continue registration.") }</p>
|
<p>{ _t("Please check your email to continue registration.") }</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -370,7 +370,7 @@ export const MsisdnAuthEntry = React.createClass({
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>{ _t("A text message has been sent to") } +<i>{ this._msisdn }</i></p>
|
<p>{ _tJsx("A text message has been sent to %(msisdn)s", /%\(msisdn\)s/, (sub) => <i>{this._msisdn}</i>) }</p>
|
||||||
<p>{ _t("Please enter the code it contains:") }</p>
|
<p>{ _t("Please enter the code it contains:") }</p>
|
||||||
<div className="mx_InteractiveAuthEntryComponents_msisdnWrapper">
|
<div className="mx_InteractiveAuthEntryComponents_msisdnWrapper">
|
||||||
<form onSubmit={this._onFormSubmit}>
|
<form onSubmit={this._onFormSubmit}>
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import Flair from '../elements/Flair.js';
|
import Flair from '../elements/Flair.js';
|
||||||
|
import { _tJsx } from '../../../languageHandler';
|
||||||
|
|
||||||
export default function SenderProfile(props) {
|
export default function SenderProfile(props) {
|
||||||
const EmojiText = sdk.getComponent('elements.EmojiText');
|
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||||
|
@ -30,23 +31,39 @@ export default function SenderProfile(props) {
|
||||||
return <span />; // emote message must include the name so don't duplicate it
|
return <span />; // emote message must include the name so don't duplicate it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name + flair
|
||||||
|
const nameElem = [
|
||||||
|
<EmojiText key='name' className="mx_SenderProfile_name">{ name || '' }</EmojiText>,
|
||||||
|
props.enableFlair ?
|
||||||
|
<Flair key='flair'
|
||||||
|
userId={mxEvent.getSender()}
|
||||||
|
roomId={mxEvent.getRoomId()}
|
||||||
|
showRelated={true} />
|
||||||
|
: null,
|
||||||
|
];
|
||||||
|
|
||||||
|
let content = '';
|
||||||
|
|
||||||
|
if(props.text) {
|
||||||
|
// Replace senderName, and wrap surrounding text in spans with the right class
|
||||||
|
content = _tJsx(props.text, /^(.*)\%\(senderName\)s(.*)$/m, (p1, p2) => [
|
||||||
|
p1 ? <span className='mx_SenderProfile_aux'>{ p1 }</span> : null,
|
||||||
|
nameElem,
|
||||||
|
p2 ? <span className='mx_SenderProfile_aux'>{ p2 }</span> : null,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
content = nameElem;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_SenderProfile" dir="auto" onClick={props.onClick}>
|
<div className="mx_SenderProfile" dir="auto" onClick={props.onClick}>
|
||||||
<EmojiText className="mx_SenderProfile_name">{ name || '' }</EmojiText>
|
{ content }
|
||||||
{ props.enableFlair ?
|
|
||||||
<Flair
|
|
||||||
userId={mxEvent.getSender()}
|
|
||||||
roomId={mxEvent.getRoomId()}
|
|
||||||
showRelated={true} />
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
{ props.aux ? <EmojiText className="mx_SenderProfile_aux"> { props.aux }</EmojiText> : null }
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
SenderProfile.propTypes = {
|
SenderProfile.propTypes = {
|
||||||
mxEvent: React.PropTypes.object.isRequired, // event whose sender we're showing
|
mxEvent: React.PropTypes.object.isRequired, // event whose sender we're showing
|
||||||
aux: React.PropTypes.string, // stuff to go after the sender name, if anything
|
text: React.PropTypes.string, // Text to show. Defaults to sender name
|
||||||
onClick: React.PropTypes.func,
|
onClick: React.PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
|
@ -34,6 +34,7 @@ import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import ContextualMenu from '../../structures/ContextualMenu';
|
import ContextualMenu from '../../structures/ContextualMenu';
|
||||||
import {RoomMember} from 'matrix-js-sdk';
|
import {RoomMember} from 'matrix-js-sdk';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import PushProcessor from 'matrix-js-sdk/lib/pushprocessor';
|
||||||
|
|
||||||
linkifyMatrix(linkify);
|
linkifyMatrix(linkify);
|
||||||
|
|
||||||
|
@ -169,8 +170,10 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
pillifyLinks: function(nodes) {
|
pillifyLinks: function(nodes) {
|
||||||
const shouldShowPillAvatar = !UserSettingsStore.getSyncedSetting("Pill.shouldHidePillAvatar", false);
|
const shouldShowPillAvatar = !UserSettingsStore.getSyncedSetting("Pill.shouldHidePillAvatar", false);
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
let node = nodes[0];
|
||||||
const node = nodes[i];
|
while (node) {
|
||||||
|
let pillified = false;
|
||||||
|
|
||||||
if (node.tagName === "A" && node.getAttribute("href")) {
|
if (node.tagName === "A" && node.getAttribute("href")) {
|
||||||
const href = node.getAttribute("href");
|
const href = node.getAttribute("href");
|
||||||
|
|
||||||
|
@ -189,10 +192,71 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
ReactDOM.render(pill, pillContainer);
|
ReactDOM.render(pill, pillContainer);
|
||||||
node.parentNode.replaceChild(pillContainer, node);
|
node.parentNode.replaceChild(pillContainer, node);
|
||||||
|
// Pills within pills aren't going to go well, so move on
|
||||||
|
pillified = true;
|
||||||
|
|
||||||
|
// update the current node with one that's now taken its place
|
||||||
|
node = pillContainer;
|
||||||
|
}
|
||||||
|
} else if (node.nodeType == Node.TEXT_NODE) {
|
||||||
|
const Pill = sdk.getComponent('elements.Pill');
|
||||||
|
|
||||||
|
let currentTextNode = node;
|
||||||
|
const roomNotifTextNodes = [];
|
||||||
|
|
||||||
|
// Take a textNode and break it up to make all the instances of @room their
|
||||||
|
// own textNode, adding those nodes to roomNotifTextNodes
|
||||||
|
while (currentTextNode !== null) {
|
||||||
|
const roomNotifPos = Pill.roomNotifPos(currentTextNode.textContent);
|
||||||
|
let nextTextNode = null;
|
||||||
|
if (roomNotifPos > -1) {
|
||||||
|
let roomTextNode = currentTextNode;
|
||||||
|
|
||||||
|
if (roomNotifPos > 0) roomTextNode = roomTextNode.splitText(roomNotifPos);
|
||||||
|
if (roomTextNode.textContent.length > Pill.roomNotifLen()) {
|
||||||
|
nextTextNode = roomTextNode.splitText(Pill.roomNotifLen());
|
||||||
|
}
|
||||||
|
roomNotifTextNodes.push(roomTextNode);
|
||||||
|
}
|
||||||
|
currentTextNode = nextTextNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (roomNotifTextNodes.length > 0) {
|
||||||
|
const pushProcessor = new PushProcessor(MatrixClientPeg.get());
|
||||||
|
const atRoomRule = pushProcessor.getPushRuleById(".m.rule.roomnotif");
|
||||||
|
if (pushProcessor.ruleMatchesEvent(atRoomRule, this.props.mxEvent)) {
|
||||||
|
// Now replace all those nodes with Pills
|
||||||
|
for (const roomNotifTextNode of roomNotifTextNodes) {
|
||||||
|
const pillContainer = document.createElement('span');
|
||||||
|
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
||||||
|
const pill = <Pill
|
||||||
|
type={Pill.TYPE_AT_ROOM_MENTION}
|
||||||
|
inMessage={true}
|
||||||
|
room={room}
|
||||||
|
shouldShowPillAvatar={true}
|
||||||
|
/>;
|
||||||
|
|
||||||
|
ReactDOM.render(pill, pillContainer);
|
||||||
|
roomNotifTextNode.parentNode.replaceChild(pillContainer, roomNotifTextNode);
|
||||||
|
|
||||||
|
// Set the next node to be processed to the one after the node
|
||||||
|
// we're adding now, since we've just inserted nodes into the structure
|
||||||
|
// we're iterating over.
|
||||||
|
// Note we've checked roomNotifTextNodes.length > 0 so we'll do this at least once
|
||||||
|
node = roomNotifTextNode.nextSibling;
|
||||||
|
}
|
||||||
|
// Nothing else to do for a text node (and we don't need to advance
|
||||||
|
// the loop pointer because we did it above)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (node.children && node.children.length) {
|
|
||||||
this.pillifyLinks(node.children);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node.childNodes && node.childNodes.length && !pillified) {
|
||||||
|
this.pillifyLinks(node.childNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
node = node.nextSibling;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,23 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 Aviral Dasgupta
|
||||||
|
Copyright 2017 New Vector Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import flatMap from 'lodash/flatMap';
|
import flatMap from 'lodash/flatMap';
|
||||||
import isEqual from 'lodash/isEqual';
|
import isEqual from 'lodash/isEqual';
|
||||||
|
@ -7,8 +25,9 @@ import sdk from '../../../index';
|
||||||
import type {Completion} from '../../../autocomplete/Autocompleter';
|
import type {Completion} from '../../../autocomplete/Autocompleter';
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
import UserSettingsStore from '../../../UserSettingsStore';
|
import UserSettingsStore from '../../../UserSettingsStore';
|
||||||
|
import { Room } from 'matrix-js-sdk';
|
||||||
|
|
||||||
import {getCompletions} from '../../../autocomplete/Autocompleter';
|
import Autocompleter from '../../../autocomplete/Autocompleter';
|
||||||
|
|
||||||
const COMPOSER_SELECTED = 0;
|
const COMPOSER_SELECTED = 0;
|
||||||
|
|
||||||
|
@ -17,6 +36,7 @@ export default class Autocomplete extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
this.autocompleter = new Autocompleter(props.room);
|
||||||
this.completionPromise = null;
|
this.completionPromise = null;
|
||||||
this.hide = this.hide.bind(this);
|
this.hide = this.hide.bind(this);
|
||||||
this.onCompletionClicked = this.onCompletionClicked.bind(this);
|
this.onCompletionClicked = this.onCompletionClicked.bind(this);
|
||||||
|
@ -41,6 +61,11 @@ export default class Autocomplete extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(newProps, state) {
|
componentWillReceiveProps(newProps, state) {
|
||||||
|
if (this.props.room.roomId !== newProps.room.roomId) {
|
||||||
|
this.autocompleter.destroy();
|
||||||
|
this.autocompleter = new Autocompleter(newProps.room);
|
||||||
|
}
|
||||||
|
|
||||||
// Query hasn't changed so don't try to complete it
|
// Query hasn't changed so don't try to complete it
|
||||||
if (newProps.query === this.props.query) {
|
if (newProps.query === this.props.query) {
|
||||||
return;
|
return;
|
||||||
|
@ -49,6 +74,10 @@ export default class Autocomplete extends React.Component {
|
||||||
this.complete(newProps.query, newProps.selection);
|
this.complete(newProps.query, newProps.selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.autocompleter.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
complete(query, selection) {
|
complete(query, selection) {
|
||||||
this.queryRequested = query;
|
this.queryRequested = query;
|
||||||
if (this.debounceCompletionsRequest) {
|
if (this.debounceCompletionsRequest) {
|
||||||
|
@ -83,7 +112,7 @@ export default class Autocomplete extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
processQuery(query, selection) {
|
processQuery(query, selection) {
|
||||||
return getCompletions(
|
return this.autocompleter.getCompletions(
|
||||||
query, selection, this.state.forceComplete,
|
query, selection, this.state.forceComplete,
|
||||||
).then((completions) => {
|
).then((completions) => {
|
||||||
// Only ever process the completions for the most recent query being processed
|
// Only ever process the completions for the most recent query being processed
|
||||||
|
@ -267,8 +296,11 @@ export default class Autocomplete extends React.Component {
|
||||||
|
|
||||||
Autocomplete.propTypes = {
|
Autocomplete.propTypes = {
|
||||||
// the query string for which to show autocomplete suggestions
|
// the query string for which to show autocomplete suggestions
|
||||||
query: React.PropTypes.string.isRequired,
|
query: PropTypes.string.isRequired,
|
||||||
|
|
||||||
// method invoked with range and text content when completion is confirmed
|
// method invoked with range and text content when completion is confirmed
|
||||||
onConfirm: React.PropTypes.func.isRequired,
|
onConfirm: PropTypes.func.isRequired,
|
||||||
|
|
||||||
|
// The room in which we're autocompleting
|
||||||
|
room: PropTypes.instanceOf(Room),
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,7 +19,7 @@ limitations under the License.
|
||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const classNames = require("classnames");
|
const classNames = require("classnames");
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t, _td } from '../../../languageHandler';
|
||||||
const Modal = require('../../../Modal');
|
const Modal = require('../../../Modal');
|
||||||
|
|
||||||
const sdk = require('../../../index');
|
const sdk = require('../../../index');
|
||||||
|
@ -502,12 +502,12 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needsSenderProfile) {
|
if (needsSenderProfile) {
|
||||||
let aux = null;
|
let text = null;
|
||||||
if (!this.props.tileShape) {
|
if (!this.props.tileShape) {
|
||||||
if (msgtype === 'm.image') aux = _t('sent an image');
|
if (msgtype === 'm.image') text = _td('%(senderName)s sent an image');
|
||||||
else if (msgtype === 'm.video') aux = _t('sent a video');
|
else if (msgtype === 'm.video') text = _td('%(senderName)s sent a video');
|
||||||
else if (msgtype === 'm.file') aux = _t('uploaded a file');
|
else if (msgtype === 'm.file') text = _td('%(senderName)s uploaded a file');
|
||||||
sender = <SenderProfile onClick={this.onSenderProfileClick} mxEvent={this.props.mxEvent} enableFlair={!aux} aux={aux} />;
|
sender = <SenderProfile onClick={this.onSenderProfileClick} mxEvent={this.props.mxEvent} enableFlair={!text} text={text} />;
|
||||||
} else {
|
} else {
|
||||||
sender = <SenderProfile mxEvent={this.props.mxEvent} enableFlair={true} />;
|
sender = <SenderProfile mxEvent={this.props.mxEvent} enableFlair={true} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -256,11 +256,11 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
|
|
||||||
onKick: function() {
|
onKick: function() {
|
||||||
const membership = this.props.member.membership;
|
const membership = this.props.member.membership;
|
||||||
const kickLabel = membership === "invite" ? _t("Disinvite") : _t("Kick");
|
|
||||||
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
||||||
Modal.createTrackedDialog('Confirm User Action Dialog', 'onKick', ConfirmUserActionDialog, {
|
Modal.createTrackedDialog('Confirm User Action Dialog', 'onKick', ConfirmUserActionDialog, {
|
||||||
member: this.props.member,
|
member: this.props.member,
|
||||||
action: kickLabel,
|
action: membership === "invite" ? _t("Disinvite") : _t("Kick"),
|
||||||
|
title: membership === "invite" ? _t("Disinvite this user?") : _t("Kick this user?"),
|
||||||
askReason: membership === "join",
|
askReason: membership === "join",
|
||||||
danger: true,
|
danger: true,
|
||||||
onFinished: (proceed, reason) => {
|
onFinished: (proceed, reason) => {
|
||||||
|
@ -294,6 +294,7 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
Modal.createTrackedDialog('Confirm User Action Dialog', 'onBanOrUnban', ConfirmUserActionDialog, {
|
Modal.createTrackedDialog('Confirm User Action Dialog', 'onBanOrUnban', ConfirmUserActionDialog, {
|
||||||
member: this.props.member,
|
member: this.props.member,
|
||||||
action: this.props.member.membership === 'ban' ? _t("Unban") : _t("Ban"),
|
action: this.props.member.membership === 'ban' ? _t("Unban") : _t("Ban"),
|
||||||
|
title: this.props.member.membership === 'ban' ? _t("Unban this user?") : _t("Ban this user?"),
|
||||||
askReason: this.props.member.membership !== 'ban',
|
askReason: this.props.member.membership !== 'ban',
|
||||||
danger: this.props.member.membership !== 'ban',
|
danger: this.props.member.membership !== 'ban',
|
||||||
onFinished: (proceed, reason) => {
|
onFinished: (proceed, reason) => {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2017 New Vector Ltd
|
||||||
|
|
||||||
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.
|
||||||
|
@ -1130,10 +1131,12 @@ export default class MessageComposerInput extends React.Component {
|
||||||
<div className="mx_MessageComposer_autocomplete_wrapper">
|
<div className="mx_MessageComposer_autocomplete_wrapper">
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
ref={(e) => this.autocomplete = e}
|
ref={(e) => this.autocomplete = e}
|
||||||
|
room={this.props.room}
|
||||||
onConfirm={this.setDisplayedCompletion}
|
onConfirm={this.setDisplayedCompletion}
|
||||||
onSelectionChange={this.setDisplayedCompletion}
|
onSelectionChange={this.setDisplayedCompletion}
|
||||||
query={this.getAutocompleteQuery(content)}
|
query={this.getAutocompleteQuery(content)}
|
||||||
selection={selection} />
|
selection={selection}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<img className="mx_MessageComposer_input_markdownIndicator mx_filterFlipColor"
|
<img className="mx_MessageComposer_input_markdownIndicator mx_filterFlipColor"
|
||||||
|
|
|
@ -29,18 +29,20 @@ function getDisplayAliasForRoom(room) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const RoomDetailRow = React.createClass({
|
const RoomDetailRow = React.createClass({
|
||||||
propTypes: PropTypes.shape({
|
propTypes: {
|
||||||
name: PropTypes.string,
|
room: PropTypes.shape({
|
||||||
topic: PropTypes.string,
|
name: PropTypes.string,
|
||||||
roomId: PropTypes.string,
|
topic: PropTypes.string,
|
||||||
avatarUrl: PropTypes.string,
|
roomId: PropTypes.string,
|
||||||
numJoinedMembers: PropTypes.number,
|
avatarUrl: PropTypes.string,
|
||||||
canonicalAlias: PropTypes.string,
|
numJoinedMembers: PropTypes.number,
|
||||||
aliases: PropTypes.arrayOf(PropTypes.string),
|
canonicalAlias: PropTypes.string,
|
||||||
|
aliases: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
|
||||||
worldReadable: PropTypes.bool,
|
worldReadable: PropTypes.bool,
|
||||||
guestCanJoin: PropTypes.bool,
|
guestCanJoin: PropTypes.bool,
|
||||||
}),
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
onClick: function(ev) {
|
onClick: function(ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
|
@ -34,27 +34,18 @@ const Receipt = require('../../../utils/Receipt');
|
||||||
const HIDE_CONFERENCE_CHANS = true;
|
const HIDE_CONFERENCE_CHANS = true;
|
||||||
|
|
||||||
function phraseForSection(section) {
|
function phraseForSection(section) {
|
||||||
// These would probably be better as individual strings,
|
|
||||||
// but for some reason we have translations for these strings
|
|
||||||
// as-is, so keeping it like this for now.
|
|
||||||
let verb;
|
|
||||||
switch (section) {
|
switch (section) {
|
||||||
case 'm.favourite':
|
case 'm.favourite':
|
||||||
verb = _t('to favourite');
|
return _t('Drop here to favourite');
|
||||||
break;
|
|
||||||
case 'im.vector.fake.direct':
|
case 'im.vector.fake.direct':
|
||||||
verb = _t('to tag direct chat');
|
return _t('Drop here to tag direct chat');
|
||||||
break;
|
|
||||||
case 'im.vector.fake.recent':
|
case 'im.vector.fake.recent':
|
||||||
verb = _t('to restore');
|
return _t('Drop here to restore');
|
||||||
break;
|
|
||||||
case 'm.lowpriority':
|
case 'm.lowpriority':
|
||||||
verb = _t('to demote');
|
return _t('Drop here to demote');
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
return _t('Drop here to tag %(section)s', {section: section});
|
return _t('Drop here to tag %(section)s', {section: section});
|
||||||
}
|
}
|
||||||
return _t('Drop here %(toAction)s', {toAction: verb});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
|
@ -564,13 +555,23 @@ module.exports = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
const RoomSubList = sdk.getComponent('structures.RoomSubList');
|
const RoomSubList = sdk.getComponent('structures.RoomSubList');
|
||||||
|
|
||||||
const inviteSectionExtraTiles = this._makeGroupInviteTiles();
|
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
return (
|
return (
|
||||||
<GeminiScrollbar className="mx_RoomList_scrollbar"
|
<GeminiScrollbar className="mx_RoomList_scrollbar"
|
||||||
autoshow={true} onScroll={self._whenScrolling} ref="gemscroll">
|
autoshow={true} onScroll={self._whenScrolling} ref="gemscroll">
|
||||||
<div className="mx_RoomList">
|
<div className="mx_RoomList">
|
||||||
|
<RoomSubList list={[]}
|
||||||
|
extraTiles={this._makeGroupInviteTiles()}
|
||||||
|
label={_t('Community Invites')}
|
||||||
|
editable={false}
|
||||||
|
order="recent"
|
||||||
|
isInvite={true}
|
||||||
|
collapsed={self.props.collapsed}
|
||||||
|
searchFilter={self.props.searchFilter}
|
||||||
|
onHeaderClick={self.onSubListHeaderClick}
|
||||||
|
onShowMoreRooms={self.onShowMoreRooms}
|
||||||
|
/>
|
||||||
|
|
||||||
<RoomSubList list={self.state.lists['im.vector.fake.invite']}
|
<RoomSubList list={self.state.lists['im.vector.fake.invite']}
|
||||||
label={_t('Invites')}
|
label={_t('Invites')}
|
||||||
editable={false}
|
editable={false}
|
||||||
|
@ -582,7 +583,6 @@ module.exports = React.createClass({
|
||||||
searchFilter={self.props.searchFilter}
|
searchFilter={self.props.searchFilter}
|
||||||
onHeaderClick={self.onSubListHeaderClick}
|
onHeaderClick={self.onSubListHeaderClick}
|
||||||
onShowMoreRooms={self.onShowMoreRooms}
|
onShowMoreRooms={self.onShowMoreRooms}
|
||||||
extraTiles={inviteSectionExtraTiles}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<RoomSubList list={self.state.lists['m.favourite']}
|
<RoomSubList list={self.state.lists['m.favourite']}
|
||||||
|
|
|
@ -83,10 +83,8 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_roomNameElement: function(fallback) {
|
_roomNameElement: function() {
|
||||||
fallback = fallback || _t('a room');
|
return this.props.room ? this.props.room.name : (this.props.room_alias || "");
|
||||||
const name = this.props.room ? this.props.room.name : (this.props.room_alias || "");
|
|
||||||
return name ? name : fallback;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
@ -150,7 +148,7 @@ module.exports = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (kicked || banned) {
|
} else if (kicked || banned) {
|
||||||
const roomName = this._roomNameElement(_t('This room'));
|
const roomName = this._roomNameElement();
|
||||||
const kickerMember = this.props.room.currentState.getMember(
|
const kickerMember = this.props.room.currentState.getMember(
|
||||||
myMember.events.member.getSender(),
|
myMember.events.member.getSender(),
|
||||||
);
|
);
|
||||||
|
@ -167,9 +165,17 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
let actionText;
|
let actionText;
|
||||||
if (kicked) {
|
if (kicked) {
|
||||||
actionText = _t("You have been kicked from %(roomName)s by %(userName)s.", {roomName: roomName, userName: kickerName});
|
if(roomName) {
|
||||||
|
actionText = _t("You have been kicked from %(roomName)s by %(userName)s.", {roomName: roomName, userName: kickerName});
|
||||||
|
} else {
|
||||||
|
actionText = _t("You have been kicked from this room by %(userName)s.", {userName: kickerName});
|
||||||
|
}
|
||||||
} else if (banned) {
|
} else if (banned) {
|
||||||
actionText = _t("You have been banned from %(roomName)s by %(userName)s.", {roomName: roomName, userName: kickerName});
|
if(roomName) {
|
||||||
|
actionText = _t("You have been banned from %(roomName)s by %(userName)s.", {roomName: roomName, userName: kickerName});
|
||||||
|
} else {
|
||||||
|
actionText = _t("You have been banned from this room by %(userName)s.", {userName: kickerName});
|
||||||
|
}
|
||||||
} // no other options possible due to the kicked || banned check above.
|
} // no other options possible due to the kicked || banned check above.
|
||||||
|
|
||||||
joinBlock = (
|
joinBlock = (
|
||||||
|
@ -203,7 +209,7 @@ module.exports = React.createClass({
|
||||||
joinBlock = (
|
joinBlock = (
|
||||||
<div>
|
<div>
|
||||||
<div className="mx_RoomPreviewBar_join_text">
|
<div className="mx_RoomPreviewBar_join_text">
|
||||||
{ _t('You are trying to access %(roomName)s.', {roomName: name}) }
|
{ name ? _t('You are trying to access %(roomName)s.', {roomName: name}) : _t('You are trying to access a room.') }
|
||||||
<br />
|
<br />
|
||||||
{ _tJsx("<a>Click here</a> to join the discussion!",
|
{ _tJsx("<a>Click here</a> to join the discussion!",
|
||||||
/<a>(.*?)<\/a>/,
|
/<a>(.*?)<\/a>/,
|
||||||
|
|
|
@ -71,6 +71,7 @@ const BannedUser = React.createClass({
|
||||||
Modal.createTrackedDialog('Confirm User Action Dialog', 'onUnbanClick', ConfirmUserActionDialog, {
|
Modal.createTrackedDialog('Confirm User Action Dialog', 'onUnbanClick', ConfirmUserActionDialog, {
|
||||||
member: this.props.member,
|
member: this.props.member,
|
||||||
action: _t('Unban'),
|
action: _t('Unban'),
|
||||||
|
title: _t('Unban this user?'),
|
||||||
danger: false,
|
danger: false,
|
||||||
onFinished: (proceed) => {
|
onFinished: (proceed) => {
|
||||||
if (!proceed) return;
|
if (!proceed) return;
|
||||||
|
@ -866,21 +867,21 @@ module.exports = React.createClass({
|
||||||
disabled={!roomState.mayClientSendStateEvent("m.room.history_visibility", cli)}
|
disabled={!roomState.mayClientSendStateEvent("m.room.history_visibility", cli)}
|
||||||
checked={historyVisibility === "shared"}
|
checked={historyVisibility === "shared"}
|
||||||
onChange={this._onHistoryRadioToggle} />
|
onChange={this._onHistoryRadioToggle} />
|
||||||
{ _t('Members only') } ({ _t('since the point in time of selecting this option') })
|
{ _t('Members only (since the point in time of selecting this option)') }
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<input type="radio" name="historyVis" value="invited"
|
<input type="radio" name="historyVis" value="invited"
|
||||||
disabled={!roomState.mayClientSendStateEvent("m.room.history_visibility", cli)}
|
disabled={!roomState.mayClientSendStateEvent("m.room.history_visibility", cli)}
|
||||||
checked={historyVisibility === "invited"}
|
checked={historyVisibility === "invited"}
|
||||||
onChange={this._onHistoryRadioToggle} />
|
onChange={this._onHistoryRadioToggle} />
|
||||||
{ _t('Members only') } ({ _t('since they were invited') })
|
{ _t('Members only (since they were invited)') }
|
||||||
</label>
|
</label>
|
||||||
<label >
|
<label >
|
||||||
<input type="radio" name="historyVis" value="joined"
|
<input type="radio" name="historyVis" value="joined"
|
||||||
disabled={!roomState.mayClientSendStateEvent("m.room.history_visibility", cli)}
|
disabled={!roomState.mayClientSendStateEvent("m.room.history_visibility", cli)}
|
||||||
checked={historyVisibility === "joined"}
|
checked={historyVisibility === "joined"}
|
||||||
onChange={this._onHistoryRadioToggle} />
|
onChange={this._onHistoryRadioToggle} />
|
||||||
{ _t('Members only') } ({ _t('since they joined') })
|
{ _t('Members only (since they joined)') }
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { _t } from './languageHandler.js';
|
||||||
|
|
||||||
export const GroupMemberType = PropTypes.shape({
|
export const GroupMemberType = PropTypes.shape({
|
||||||
userId: PropTypes.string.isRequired,
|
userId: PropTypes.string.isRequired,
|
||||||
|
@ -23,6 +24,7 @@ export const GroupMemberType = PropTypes.shape({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const GroupRoomType = PropTypes.shape({
|
export const GroupRoomType = PropTypes.shape({
|
||||||
|
displayname: PropTypes.string,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
roomId: PropTypes.string.isRequired,
|
roomId: PropTypes.string.isRequired,
|
||||||
canonicalAlias: PropTypes.string,
|
canonicalAlias: PropTypes.string,
|
||||||
|
@ -39,6 +41,7 @@ export function groupMemberFromApiObject(apiObject) {
|
||||||
|
|
||||||
export function groupRoomFromApiObject(apiObject) {
|
export function groupRoomFromApiObject(apiObject) {
|
||||||
return {
|
return {
|
||||||
|
displayname: apiObject.name || apiObject.canonical_alias || _t("Unnamed Room"),
|
||||||
name: apiObject.name,
|
name: apiObject.name,
|
||||||
roomId: apiObject.room_id,
|
roomId: apiObject.room_id,
|
||||||
canonicalAlias: apiObject.canonical_alias,
|
canonicalAlias: apiObject.canonical_alias,
|
||||||
|
@ -47,5 +50,6 @@ export function groupRoomFromApiObject(apiObject) {
|
||||||
numJoinedMembers: apiObject.num_joined_members,
|
numJoinedMembers: apiObject.num_joined_members,
|
||||||
worldReadable: apiObject.world_readable,
|
worldReadable: apiObject.world_readable,
|
||||||
guestCanJoin: apiObject.guest_can_join,
|
guestCanJoin: apiObject.guest_can_join,
|
||||||
|
isPublic: apiObject.is_public !== false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,11 +153,12 @@
|
||||||
"Communities": "Communities",
|
"Communities": "Communities",
|
||||||
"Message Pinning": "Message Pinning",
|
"Message Pinning": "Message Pinning",
|
||||||
"%(displayName)s is typing": "%(displayName)s is typing",
|
"%(displayName)s is typing": "%(displayName)s is typing",
|
||||||
"%(names)s and one other are typing": "%(names)s and one other are typing",
|
|
||||||
"%(names)s and %(count)s others are typing|other": "%(names)s and %(count)s others are typing",
|
"%(names)s and %(count)s others are typing|other": "%(names)s and %(count)s others are typing",
|
||||||
|
"%(names)s and %(count)s others are typing|one": "%(names)s and one other is typing",
|
||||||
"%(names)s and %(lastPerson)s are typing": "%(names)s and %(lastPerson)s are typing",
|
"%(names)s and %(lastPerson)s are typing": "%(names)s and %(lastPerson)s are typing",
|
||||||
"Failure to create room": "Failure to create room",
|
"Failure to create room": "Failure to create room",
|
||||||
"Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.",
|
"Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.",
|
||||||
|
"Unnamed Room": "Unnamed Room",
|
||||||
"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?",
|
||||||
|
@ -210,9 +211,9 @@
|
||||||
" (unsupported)": " (unsupported)",
|
" (unsupported)": " (unsupported)",
|
||||||
"Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.": "Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.",
|
"Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.": "Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.",
|
||||||
"Ongoing conference call%(supportedText)s.": "Ongoing conference call%(supportedText)s.",
|
"Ongoing conference call%(supportedText)s.": "Ongoing conference call%(supportedText)s.",
|
||||||
"sent an image": "sent an image",
|
"%(senderName)s sent an image": "%(senderName)s sent an image",
|
||||||
"sent a video": "sent a video",
|
"%(senderName)s sent a video": "%(senderName)s sent a video",
|
||||||
"uploaded a file": "uploaded a file",
|
"%(senderName)s uploaded a file": "%(senderName)s uploaded a file",
|
||||||
"Options": "Options",
|
"Options": "Options",
|
||||||
"Undecryptable": "Undecryptable",
|
"Undecryptable": "Undecryptable",
|
||||||
"Encrypted by a verified device": "Encrypted by a verified device",
|
"Encrypted by a verified device": "Encrypted by a verified device",
|
||||||
|
@ -225,9 +226,13 @@
|
||||||
"device id: ": "device id: ",
|
"device id: ": "device id: ",
|
||||||
"Disinvite": "Disinvite",
|
"Disinvite": "Disinvite",
|
||||||
"Kick": "Kick",
|
"Kick": "Kick",
|
||||||
|
"Disinvite this user?": "Disinvite this user?",
|
||||||
|
"Kick this user?": "Kick this user?",
|
||||||
"Failed to kick": "Failed to kick",
|
"Failed to kick": "Failed to kick",
|
||||||
"Unban": "Unban",
|
"Unban": "Unban",
|
||||||
"Ban": "Ban",
|
"Ban": "Ban",
|
||||||
|
"Unban this user?": "Unban this user?",
|
||||||
|
"Ban this user?": "Ban this user?",
|
||||||
"Failed to ban user": "Failed to ban user",
|
"Failed to ban user": "Failed to ban user",
|
||||||
"Failed to mute user": "Failed to mute user",
|
"Failed to mute user": "Failed to mute user",
|
||||||
"Failed to toggle moderator status": "Failed to toggle moderator status",
|
"Failed to toggle moderator status": "Failed to toggle moderator status",
|
||||||
|
@ -314,35 +319,36 @@
|
||||||
"Forget room": "Forget room",
|
"Forget room": "Forget room",
|
||||||
"Search": "Search",
|
"Search": "Search",
|
||||||
"Show panel": "Show panel",
|
"Show panel": "Show panel",
|
||||||
"to favourite": "to favourite",
|
"Drop here to favourite": "Drop here to favourite",
|
||||||
"to tag direct chat": "to tag direct chat",
|
"Drop here to tag direct chat": "Drop here to tag direct chat",
|
||||||
"to restore": "to restore",
|
"Drop here to restore": "Drop here to restore",
|
||||||
"to demote": "to demote",
|
"Drop here to demote": "Drop here to demote",
|
||||||
"Drop here to tag %(section)s": "Drop here to tag %(section)s",
|
"Drop here to tag %(section)s": "Drop here to tag %(section)s",
|
||||||
"Drop here %(toAction)s": "Drop here %(toAction)s",
|
|
||||||
"Press <StartChatButton> to start a chat with someone": "Press <StartChatButton> to start a chat with someone",
|
"Press <StartChatButton> to start a chat with someone": "Press <StartChatButton> to start a chat with someone",
|
||||||
"You're not in any rooms yet! Press <CreateRoomButton> to make a room or <RoomDirectoryButton> to browse the directory": "You're not in any rooms yet! Press <CreateRoomButton> to make a room or <RoomDirectoryButton> to browse the directory",
|
"You're not in any rooms yet! Press <CreateRoomButton> to make a room or <RoomDirectoryButton> to browse the directory": "You're not in any rooms yet! Press <CreateRoomButton> to make a room or <RoomDirectoryButton> to browse the directory",
|
||||||
|
"Community Invites": "Community Invites",
|
||||||
"Invites": "Invites",
|
"Invites": "Invites",
|
||||||
"Favourites": "Favourites",
|
"Favourites": "Favourites",
|
||||||
"People": "People",
|
"People": "People",
|
||||||
"Rooms": "Rooms",
|
"Rooms": "Rooms",
|
||||||
"Low priority": "Low priority",
|
"Low priority": "Low priority",
|
||||||
"Historical": "Historical",
|
"Historical": "Historical",
|
||||||
"Unnamed Room": "Unnamed Room",
|
|
||||||
"a room": "a room",
|
|
||||||
"Unable to ascertain that the address this invite was sent to matches one associated with your account.": "Unable to ascertain that the address this invite was sent to matches one associated with your account.",
|
"Unable to ascertain that the address this invite was sent to matches one associated with your account.": "Unable to ascertain that the address this invite was sent to matches one associated with your account.",
|
||||||
"This invitation was sent to an email address which is not associated with this account:": "This invitation was sent to an email address which is not associated with this account:",
|
"This invitation was sent to an email address which is not associated with this account:": "This invitation was sent to an email address which is not associated with this account:",
|
||||||
"You may wish to login with a different account, or add this email to this account.": "You may wish to login with a different account, or add this email to this account.",
|
"You may wish to login with a different account, or add this email to this account.": "You may wish to login with a different account, or add this email to this account.",
|
||||||
"You have been invited to join this room by %(inviterName)s": "You have been invited to join this room by %(inviterName)s",
|
"You have been invited to join this room by %(inviterName)s": "You have been invited to join this room by %(inviterName)s",
|
||||||
"Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?": "Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?",
|
"Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?": "Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?",
|
||||||
"This room": "This room",
|
|
||||||
"Reason: %(reasonText)s": "Reason: %(reasonText)s",
|
"Reason: %(reasonText)s": "Reason: %(reasonText)s",
|
||||||
"Rejoin": "Rejoin",
|
"Rejoin": "Rejoin",
|
||||||
"You have been kicked from %(roomName)s by %(userName)s.": "You have been kicked from %(roomName)s by %(userName)s.",
|
"You have been kicked from %(roomName)s by %(userName)s.": "You have been kicked from %(roomName)s by %(userName)s.",
|
||||||
|
"You have been kicked from this room by %(userName)s.": "You have been kicked from this room by %(userName)s.",
|
||||||
"You have been banned from %(roomName)s by %(userName)s.": "You have been banned from %(roomName)s by %(userName)s.",
|
"You have been banned from %(roomName)s by %(userName)s.": "You have been banned from %(roomName)s by %(userName)s.",
|
||||||
|
"You have been banned from this room by %(userName)s.": "You have been banned from this room by %(userName)s.",
|
||||||
|
"This room": "This room",
|
||||||
"%(roomName)s does not exist.": "%(roomName)s does not exist.",
|
"%(roomName)s does not exist.": "%(roomName)s does not exist.",
|
||||||
"%(roomName)s is not accessible at this time.": "%(roomName)s is not accessible at this time.",
|
"%(roomName)s is not accessible at this time.": "%(roomName)s is not accessible at this time.",
|
||||||
"You are trying to access %(roomName)s.": "You are trying to access %(roomName)s.",
|
"You are trying to access %(roomName)s.": "You are trying to access %(roomName)s.",
|
||||||
|
"You are trying to access a room.": "You are trying to access a room.",
|
||||||
"<a>Click here</a> to join the discussion!": "<a>Click here</a> to join the discussion!",
|
"<a>Click here</a> to join the discussion!": "<a>Click here</a> to join the discussion!",
|
||||||
"This is a preview of this room. Room interactions have been disabled": "This is a preview of this room. Room interactions have been disabled",
|
"This is a preview of this room. Room interactions have been disabled": "This is a preview of this room. Room interactions have been disabled",
|
||||||
"To change the room's avatar, you must be a": "To change the room's avatar, you must be a",
|
"To change the room's avatar, you must be a": "To change the room's avatar, you must be a",
|
||||||
|
@ -387,10 +393,9 @@
|
||||||
"Publish this room to the public in %(domain)s's room directory?": "Publish this room to the public in %(domain)s's room directory?",
|
"Publish this room to the public in %(domain)s's room directory?": "Publish this room to the public in %(domain)s's room directory?",
|
||||||
"Who can read history?": "Who can read history?",
|
"Who can read history?": "Who can read history?",
|
||||||
"Anyone": "Anyone",
|
"Anyone": "Anyone",
|
||||||
"Members only": "Members only",
|
"Members only (since the point in time of selecting this option)": "Members only (since the point in time of selecting this option)",
|
||||||
"since the point in time of selecting this option": "since the point in time of selecting this option",
|
"Members only (since they were invited)": "Members only (since they were invited)",
|
||||||
"since they were invited": "since they were invited",
|
"Members only (since they joined)": "Members only (since they joined)",
|
||||||
"since they joined": "since they joined",
|
|
||||||
"Room Colour": "Room Colour",
|
"Room Colour": "Room Colour",
|
||||||
"Permissions": "Permissions",
|
"Permissions": "Permissions",
|
||||||
"The default role for new room members is": "The default role for new room members is",
|
"The default role for new room members is": "The default role for new room members is",
|
||||||
|
@ -463,10 +468,10 @@
|
||||||
"Dismiss": "Dismiss",
|
"Dismiss": "Dismiss",
|
||||||
"To continue, please enter your password.": "To continue, please enter your password.",
|
"To continue, please enter your password.": "To continue, please enter your password.",
|
||||||
"Password:": "Password:",
|
"Password:": "Password:",
|
||||||
"An email has been sent to": "An email has been sent to",
|
"An email has been sent to %(emailAddress)s": "An email has been sent to %(emailAddress)s",
|
||||||
"Please check your email to continue registration.": "Please check your email to continue registration.",
|
"Please check your email to continue registration.": "Please check your email to continue registration.",
|
||||||
"Token incorrect": "Token incorrect",
|
"Token incorrect": "Token incorrect",
|
||||||
"A text message has been sent to": "A text message has been sent to",
|
"A text message has been sent to %(msisdn)s": "A text message has been sent to %(msisdn)s",
|
||||||
"Please enter the code it contains:": "Please enter the code it contains:",
|
"Please enter the code it contains:": "Please enter the code it contains:",
|
||||||
"Start authentication": "Start authentication",
|
"Start authentication": "Start authentication",
|
||||||
"powered by Matrix": "powered by Matrix",
|
"powered by Matrix": "powered by Matrix",
|
||||||
|
@ -489,16 +494,22 @@
|
||||||
"Identity server URL": "Identity server URL",
|
"Identity server URL": "Identity server URL",
|
||||||
"What does this mean?": "What does this mean?",
|
"What does this mean?": "What does this mean?",
|
||||||
"Remove from community": "Remove from community",
|
"Remove from community": "Remove from community",
|
||||||
|
"Disinvite this user from community?": "Disinvite this user from community?",
|
||||||
|
"Remove this user from community?": "Remove this user from community?",
|
||||||
"Failed to withdraw invitation": "Failed to withdraw invitation",
|
"Failed to withdraw invitation": "Failed to withdraw invitation",
|
||||||
"Failed to remove user from community": "Failed to remove user from community",
|
"Failed to remove user from community": "Failed to remove user from community",
|
||||||
"Filter community members": "Filter community members",
|
"Filter community members": "Filter community members",
|
||||||
"Filter community rooms": "Filter community rooms",
|
|
||||||
"Failed to remove room from community": "Failed to remove room from community",
|
|
||||||
"Failed to remove '%(roomName)s' from %(groupId)s": "Failed to remove '%(roomName)s' from %(groupId)s",
|
|
||||||
"Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Are you sure you want to remove '%(roomName)s' from %(groupId)s?",
|
"Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Are you sure you want to remove '%(roomName)s' from %(groupId)s?",
|
||||||
"Removing a room from the community will also remove it from the community page.": "Removing a room from the community will also remove it from the community page.",
|
"Removing a room from the community will also remove it from the community page.": "Removing a room from the community will also remove it from the community page.",
|
||||||
"Remove": "Remove",
|
"Remove": "Remove",
|
||||||
"Remove this room from the community": "Remove this room from the community",
|
"Failed to remove room from community": "Failed to remove room from community",
|
||||||
|
"Failed to remove '%(roomName)s' from %(groupId)s": "Failed to remove '%(roomName)s' from %(groupId)s",
|
||||||
|
"Something went wrong!": "Something went wrong!",
|
||||||
|
"The visibility of '%(roomName)s' in %(groupId)s could not be updated.": "The visibility of '%(roomName)s' in %(groupId)s could not be updated.",
|
||||||
|
"Visibility in Room List": "Visibility in Room List",
|
||||||
|
"Visible to everyone": "Visible to everyone",
|
||||||
|
"Only visible to community members": "Only visible to community members",
|
||||||
|
"Filter community rooms": "Filter community rooms",
|
||||||
"Unknown Address": "Unknown Address",
|
"Unknown Address": "Unknown Address",
|
||||||
"NOTE: Apps are not end-to-end encrypted": "NOTE: Apps are not end-to-end encrypted",
|
"NOTE: Apps are not end-to-end encrypted": "NOTE: Apps are not end-to-end encrypted",
|
||||||
"Do you want to load widget from URL:": "Do you want to load widget from URL:",
|
"Do you want to load widget from URL:": "Do you want to load widget from URL:",
|
||||||
|
@ -518,56 +529,57 @@
|
||||||
"Integrations Error": "Integrations Error",
|
"Integrations Error": "Integrations Error",
|
||||||
"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",
|
||||||
"Manage Integrations": "Manage Integrations",
|
"Manage Integrations": "Manage Integrations",
|
||||||
"%(severalUsers)sjoined %(repeats)s times": "%(severalUsers)sjoined %(repeats)s times",
|
"%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s",
|
||||||
"%(oneUser)sjoined %(repeats)s times": "%(oneUser)sjoined %(repeats)s times",
|
"%(severalUsers)sjoined %(count)s times|other": "%(severalUsers)sjoined %(count)s times",
|
||||||
"%(severalUsers)sjoined": "%(severalUsers)sjoined",
|
"%(severalUsers)sjoined %(count)s times|one": "%(severalUsers)sjoined",
|
||||||
"%(oneUser)sjoined": "%(oneUser)sjoined",
|
"%(oneUser)sjoined %(count)s times|other": "%(oneUser)sjoined %(count)s times",
|
||||||
"%(severalUsers)sleft %(repeats)s times": "%(severalUsers)sleft %(repeats)s times",
|
"%(oneUser)sjoined %(count)s times|one": "%(oneUser)sjoined",
|
||||||
"%(oneUser)sleft %(repeats)s times": "%(oneUser)sleft %(repeats)s times",
|
"%(severalUsers)sleft %(count)s times|other": "%(severalUsers)sleft %(count)s times",
|
||||||
"%(severalUsers)sleft": "%(severalUsers)sleft",
|
"%(severalUsers)sleft %(count)s times|one": "%(severalUsers)sleft",
|
||||||
"%(oneUser)sleft": "%(oneUser)sleft",
|
"%(oneUser)sleft %(count)s times|other": "%(oneUser)sleft %(count)s times",
|
||||||
"%(severalUsers)sjoined and left %(repeats)s times": "%(severalUsers)sjoined and left %(repeats)s times",
|
"%(oneUser)sleft %(count)s times|one": "%(oneUser)sleft",
|
||||||
"%(oneUser)sjoined and left %(repeats)s times": "%(oneUser)sjoined and left %(repeats)s times",
|
"%(severalUsers)sjoined and left %(count)s times|other": "%(severalUsers)sjoined and left %(count)s times",
|
||||||
"%(severalUsers)sjoined and left": "%(severalUsers)sjoined and left",
|
"%(severalUsers)sjoined and left %(count)s times|one": "%(severalUsers)sjoined and left",
|
||||||
"%(oneUser)sjoined and left": "%(oneUser)sjoined and left",
|
"%(oneUser)sjoined and left %(count)s times|other": "%(oneUser)sjoined and left %(count)s times",
|
||||||
"%(severalUsers)sleft and rejoined %(repeats)s times": "%(severalUsers)sleft and rejoined %(repeats)s times",
|
"%(oneUser)sjoined and left %(count)s times|one": "%(oneUser)sjoined and left",
|
||||||
"%(oneUser)sleft and rejoined %(repeats)s times": "%(oneUser)sleft and rejoined %(repeats)s times",
|
"%(severalUsers)sleft and rejoined %(count)s times|other": "%(severalUsers)sleft and rejoined %(count)s times",
|
||||||
"%(severalUsers)sleft and rejoined": "%(severalUsers)sleft and rejoined",
|
"%(severalUsers)sleft and rejoined %(count)s times|one": "%(severalUsers)sleft and rejoined",
|
||||||
"%(oneUser)sleft and rejoined": "%(oneUser)sleft and rejoined",
|
"%(oneUser)sleft and rejoined %(count)s times|other": "%(oneUser)sleft and rejoined %(count)s times",
|
||||||
"%(severalUsers)srejected their invitations %(repeats)s times": "%(severalUsers)srejected their invitations %(repeats)s times",
|
"%(oneUser)sleft and rejoined %(count)s times|one": "%(oneUser)sleft and rejoined",
|
||||||
"%(oneUser)srejected their invitation %(repeats)s times": "%(oneUser)srejected their invitation %(repeats)s times",
|
"%(severalUsers)srejected their invitations %(count)s times|other": "%(severalUsers)srejected their invitations %(count)s times",
|
||||||
"%(severalUsers)srejected their invitations": "%(severalUsers)srejected their invitations",
|
"%(severalUsers)srejected their invitations %(count)s times|one": "%(severalUsers)srejected their invitations",
|
||||||
"%(oneUser)srejected their invitation": "%(oneUser)srejected their invitation",
|
"%(oneUser)srejected their invitation %(count)s times|other": "%(oneUser)srejected their invitation %(count)s times",
|
||||||
"%(severalUsers)shad their invitations withdrawn %(repeats)s times": "%(severalUsers)shad their invitations withdrawn %(repeats)s times",
|
"%(oneUser)srejected their invitation %(count)s times|one": "%(oneUser)srejected their invitation",
|
||||||
"%(oneUser)shad their invitation withdrawn %(repeats)s times": "%(oneUser)shad their invitation withdrawn %(repeats)s times",
|
"%(severalUsers)shad their invitations withdrawn %(count)s times|other": "%(severalUsers)shad their invitations withdrawn %(count)s times",
|
||||||
"%(severalUsers)shad their invitations withdrawn": "%(severalUsers)shad their invitations withdrawn",
|
"%(severalUsers)shad their invitations withdrawn %(count)s times|one": "%(severalUsers)shad their invitations withdrawn",
|
||||||
"%(oneUser)shad their invitation withdrawn": "%(oneUser)shad their invitation withdrawn",
|
"%(oneUser)shad their invitation withdrawn %(count)s times|other": "%(oneUser)shad their invitation withdrawn %(count)s times",
|
||||||
"were invited %(repeats)s times": "were invited %(repeats)s times",
|
"%(oneUser)shad their invitation withdrawn %(count)s times|one": "%(oneUser)shad their invitation withdrawn",
|
||||||
"was invited %(repeats)s times": "was invited %(repeats)s times",
|
"were invited %(count)s times|other": "were invited %(count)s times",
|
||||||
"were invited": "were invited",
|
"were invited %(count)s times|one": "were invited",
|
||||||
"was invited": "was invited",
|
"was invited %(count)s times|other": "was invited %(count)s times",
|
||||||
"were banned %(repeats)s times": "were banned %(repeats)s times",
|
"was invited %(count)s times|one": "was invited",
|
||||||
"was banned %(repeats)s times": "was banned %(repeats)s times",
|
"were banned %(count)s times|other": "were banned %(count)s times",
|
||||||
"were banned": "were banned",
|
"were banned %(count)s times|one": "were banned",
|
||||||
"was banned": "was banned",
|
"was banned %(count)s times|other": "was banned %(count)s times",
|
||||||
"were unbanned %(repeats)s times": "were unbanned %(repeats)s times",
|
"was banned %(count)s times|one": "was banned",
|
||||||
"was unbanned %(repeats)s times": "was unbanned %(repeats)s times",
|
"were unbanned %(count)s times|other": "were unbanned %(count)s times",
|
||||||
"were unbanned": "were unbanned",
|
"were unbanned %(count)s times|one": "were unbanned",
|
||||||
"was unbanned": "was unbanned",
|
"was unbanned %(count)s times|other": "was unbanned %(count)s times",
|
||||||
"were kicked %(repeats)s times": "were kicked %(repeats)s times",
|
"was unbanned %(count)s times|one": "was unbanned",
|
||||||
"was kicked %(repeats)s times": "was kicked %(repeats)s times",
|
"were kicked %(count)s times|other": "were kicked %(count)s times",
|
||||||
"were kicked": "were kicked",
|
"were kicked %(count)s times|one": "were kicked",
|
||||||
"was kicked": "was kicked",
|
"was kicked %(count)s times|other": "was kicked %(count)s times",
|
||||||
"%(severalUsers)schanged their name %(repeats)s times": "%(severalUsers)schanged their name %(repeats)s times",
|
"was kicked %(count)s times|one": "was kicked",
|
||||||
"%(oneUser)schanged their name %(repeats)s times": "%(oneUser)schanged their name %(repeats)s times",
|
"%(severalUsers)schanged their name %(count)s times|other": "%(severalUsers)schanged their name %(count)s times",
|
||||||
"%(severalUsers)schanged their name": "%(severalUsers)schanged their name",
|
"%(severalUsers)schanged their name %(count)s times|one": "%(severalUsers)schanged their name",
|
||||||
"%(oneUser)schanged their name": "%(oneUser)schanged their name",
|
"%(oneUser)schanged their name %(count)s times|other": "%(oneUser)schanged their name %(count)s times",
|
||||||
"%(severalUsers)schanged their avatar %(repeats)s times": "%(severalUsers)schanged their avatar %(repeats)s times",
|
"%(oneUser)schanged their name %(count)s times|one": "%(oneUser)schanged their name",
|
||||||
"%(oneUser)schanged their avatar %(repeats)s times": "%(oneUser)schanged their avatar %(repeats)s times",
|
"%(severalUsers)schanged their avatar %(count)s times|other": "%(severalUsers)schanged their avatar %(count)s times",
|
||||||
"%(severalUsers)schanged their avatar": "%(severalUsers)schanged their avatar",
|
"%(severalUsers)schanged their avatar %(count)s times|one": "%(severalUsers)schanged their avatar",
|
||||||
"%(oneUser)schanged their avatar": "%(oneUser)schanged their avatar",
|
"%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)schanged their avatar %(count)s times",
|
||||||
"%(items)s and %(remaining)s others": "%(items)s and %(remaining)s others",
|
"%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)schanged their avatar",
|
||||||
"%(items)s and one other": "%(items)s and one other",
|
"%(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",
|
"%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s",
|
||||||
"Custom level": "Custom level",
|
"Custom level": "Custom level",
|
||||||
"Room directory": "Room directory",
|
"Room directory": "Room directory",
|
||||||
|
@ -575,7 +587,6 @@
|
||||||
"And %(count)s more...|other": "And %(count)s more...",
|
"And %(count)s more...|other": "And %(count)s more...",
|
||||||
"ex. @bob:example.com": "ex. @bob:example.com",
|
"ex. @bob:example.com": "ex. @bob:example.com",
|
||||||
"Add User": "Add User",
|
"Add User": "Add User",
|
||||||
"Something went wrong!": "Something went wrong!",
|
|
||||||
"Matrix ID": "Matrix ID",
|
"Matrix ID": "Matrix ID",
|
||||||
"Matrix Room ID": "Matrix Room ID",
|
"Matrix Room ID": "Matrix Room ID",
|
||||||
"email address": "email address",
|
"email address": "email address",
|
||||||
|
@ -589,7 +600,6 @@
|
||||||
"Start Chatting": "Start Chatting",
|
"Start Chatting": "Start Chatting",
|
||||||
"Confirm Removal": "Confirm Removal",
|
"Confirm Removal": "Confirm Removal",
|
||||||
"Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.",
|
"Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.",
|
||||||
"%(actionVerb)s this person?": "%(actionVerb)s this person?",
|
|
||||||
"Community IDs may only contain characters a-z, 0-9, or '=_-./'": "Community IDs may only contain characters a-z, 0-9, or '=_-./'",
|
"Community IDs may only contain characters a-z, 0-9, or '=_-./'": "Community IDs may only contain characters a-z, 0-9, or '=_-./'",
|
||||||
"Something went wrong whilst creating your community": "Something went wrong whilst creating your community",
|
"Something went wrong whilst creating your community": "Something went wrong whilst creating your community",
|
||||||
"Create Community": "Create Community",
|
"Create Community": "Create Community",
|
||||||
|
@ -833,7 +843,7 @@
|
||||||
"A new password must be entered.": "A new password must be entered.",
|
"A new password must be entered.": "A new password must be entered.",
|
||||||
"New passwords must match each other.": "New passwords must match each other.",
|
"New passwords must match each other.": "New passwords must match each other.",
|
||||||
"Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.",
|
"Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.",
|
||||||
"Once you've followed the link it contains, click below": "Once you've followed the link it contains, click below",
|
"An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.",
|
||||||
"I have verified my email address": "I have verified my email address",
|
"I have verified my email address": "I have verified my email address",
|
||||||
"Your password has been reset": "Your password has been reset",
|
"Your password has been reset": "Your password has been reset",
|
||||||
"You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device": "You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device",
|
"You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device": "You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device",
|
||||||
|
|
|
@ -252,6 +252,26 @@ function getLangsJson() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function weblateToCounterpart(inTrs) {
|
||||||
|
const outTrs = {};
|
||||||
|
|
||||||
|
for (const key of Object.keys(inTrs)) {
|
||||||
|
const keyParts = key.split('|', 2);
|
||||||
|
if (keyParts.length === 2) {
|
||||||
|
let obj = outTrs[keyParts[0]];
|
||||||
|
if (obj === undefined) {
|
||||||
|
obj = {};
|
||||||
|
outTrs[keyParts[0]] = obj;
|
||||||
|
}
|
||||||
|
obj[keyParts[1]] = inTrs[key];
|
||||||
|
} else {
|
||||||
|
outTrs[key] = inTrs[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return outTrs;
|
||||||
|
}
|
||||||
|
|
||||||
function getLanguage(langPath) {
|
function getLanguage(langPath) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
request(
|
request(
|
||||||
|
@ -261,7 +281,7 @@ function getLanguage(langPath) {
|
||||||
reject({err: err, response: response});
|
reject({err: err, response: response});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
resolve(JSON.parse(body));
|
resolve(weblateToCounterpart(JSON.parse(body)));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -66,7 +66,7 @@ class FlairStore extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bulk lookup ongoing, return promise to resolve/reject
|
// Bulk lookup ongoing, return promise to resolve/reject
|
||||||
if (this._usersPending[userId]) {
|
if (this._usersPending[userId] || this._usersInFlight[userId]) {
|
||||||
return this._usersPending[userId].prom;
|
return this._usersPending[userId].prom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ class FlairStore extends EventEmitter {
|
||||||
console.error('Could not get groups for user', this.props.userId, err);
|
console.error('Could not get groups for user', this.props.userId, err);
|
||||||
throw err;
|
throw err;
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
delete this._usersPending[userId];
|
delete this._usersInFlight[userId];
|
||||||
});
|
});
|
||||||
|
|
||||||
// This debounce will allow consecutive requests for the public groups of users that
|
// This debounce will allow consecutive requests for the public groups of users that
|
||||||
|
@ -113,23 +113,25 @@ class FlairStore extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _batchedGetPublicGroups(matrixClient) {
|
async _batchedGetPublicGroups(matrixClient) {
|
||||||
// Take the userIds from the keys of this._usersPending
|
// Move users pending to users in flight
|
||||||
const usersInFlight = Object.keys(this._usersPending);
|
this._usersInFlight = this._usersPending;
|
||||||
|
this._usersPending = {};
|
||||||
|
|
||||||
let resp = {
|
let resp = {
|
||||||
users: [],
|
users: [],
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
resp = await matrixClient.getPublicisedGroups(usersInFlight);
|
resp = await matrixClient.getPublicisedGroups(Object.keys(this._usersInFlight));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Propagate the same error to all usersInFlight
|
// Propagate the same error to all usersInFlight
|
||||||
usersInFlight.forEach((userId) => {
|
Object.keys(this._usersInFlight).forEach((userId) => {
|
||||||
this._usersPending[userId].reject(err);
|
this._usersInFlight[userId].reject(err);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const updatedUserGroups = resp.users;
|
const updatedUserGroups = resp.users;
|
||||||
usersInFlight.forEach((userId) => {
|
Object.keys(this._usersInFlight).forEach((userId) => {
|
||||||
this._usersPending[userId].resolve(updatedUserGroups[userId] || []);
|
this._usersInFlight[userId].resolve(updatedUserGroups[userId] || []);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -141,9 +141,15 @@ export default class GroupStore extends EventEmitter {
|
||||||
return this._summary.user ? this._summary.user.is_privileged : null;
|
return this._summary.user ? this._summary.user.is_privileged : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
addRoomToGroup(roomId) {
|
addRoomToGroup(roomId, isPublic) {
|
||||||
return this._matrixClient
|
return this._matrixClient
|
||||||
.addRoomToGroup(this.groupId, roomId)
|
.addRoomToGroup(this.groupId, roomId, isPublic)
|
||||||
|
.then(this._fetchRooms.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateGroupRoomAssociation(roomId, isPublic) {
|
||||||
|
return this._matrixClient
|
||||||
|
.updateGroupRoomAssociation(this.groupId, roomId, isPublic)
|
||||||
.then(this._fetchRooms.bind(this));
|
.then(this._fetchRooms.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue