mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-16 05:04:57 +08:00
Merge branch 'develop' into dialog-a11y
This commit is contained in:
commit
14991afbe5
@ -8,7 +8,6 @@ src/CallHandler.js
|
|||||||
src/component-index.js
|
src/component-index.js
|
||||||
src/components/structures/ContextualMenu.js
|
src/components/structures/ContextualMenu.js
|
||||||
src/components/structures/CreateRoom.js
|
src/components/structures/CreateRoom.js
|
||||||
src/components/structures/FilePanel.js
|
|
||||||
src/components/structures/LoggedInView.js
|
src/components/structures/LoggedInView.js
|
||||||
src/components/structures/login/ForgotPassword.js
|
src/components/structures/login/ForgotPassword.js
|
||||||
src/components/structures/login/Login.js
|
src/components/structures/login/Login.js
|
||||||
@ -27,16 +26,10 @@ src/components/views/dialogs/ChatCreateOrReuseDialog.js
|
|||||||
src/components/views/dialogs/DeactivateAccountDialog.js
|
src/components/views/dialogs/DeactivateAccountDialog.js
|
||||||
src/components/views/dialogs/UnknownDeviceDialog.js
|
src/components/views/dialogs/UnknownDeviceDialog.js
|
||||||
src/components/views/elements/AddressSelector.js
|
src/components/views/elements/AddressSelector.js
|
||||||
src/components/views/elements/CreateRoomButton.js
|
|
||||||
src/components/views/elements/DeviceVerifyButtons.js
|
src/components/views/elements/DeviceVerifyButtons.js
|
||||||
src/components/views/elements/DirectorySearchBox.js
|
src/components/views/elements/DirectorySearchBox.js
|
||||||
src/components/views/elements/EditableText.js
|
src/components/views/elements/EditableText.js
|
||||||
src/components/views/elements/HomeButton.js
|
|
||||||
src/components/views/elements/MemberEventListSummary.js
|
src/components/views/elements/MemberEventListSummary.js
|
||||||
src/components/views/elements/PowerSelector.js
|
|
||||||
src/components/views/elements/RoomDirectoryButton.js
|
|
||||||
src/components/views/elements/SettingsButton.js
|
|
||||||
src/components/views/elements/StartChatButton.js
|
|
||||||
src/components/views/elements/TintableSvg.js
|
src/components/views/elements/TintableSvg.js
|
||||||
src/components/views/elements/UserSelector.js
|
src/components/views/elements/UserSelector.js
|
||||||
src/components/views/login/CountryDropdown.js
|
src/components/views/login/CountryDropdown.js
|
||||||
@ -93,7 +86,6 @@ src/RichText.js
|
|||||||
src/Roles.js
|
src/Roles.js
|
||||||
src/Rooms.js
|
src/Rooms.js
|
||||||
src/ScalarAuthClient.js
|
src/ScalarAuthClient.js
|
||||||
src/Tinter.js
|
|
||||||
src/UiEffects.js
|
src/UiEffects.js
|
||||||
src/Unread.js
|
src/Unread.js
|
||||||
src/utils/DecryptFile.js
|
src/utils/DecryptFile.js
|
||||||
|
@ -3,7 +3,10 @@ dist: trusty
|
|||||||
|
|
||||||
# we don't need sudo, so can run in a container, which makes startup much
|
# we don't need sudo, so can run in a container, which makes startup much
|
||||||
# quicker.
|
# quicker.
|
||||||
sudo: false
|
#
|
||||||
|
# unfortunately we do temporarily require sudo as a workaround for
|
||||||
|
# https://github.com/travis-ci/travis-ci/issues/8836
|
||||||
|
sudo: required
|
||||||
|
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
"browser-encrypt-attachment": "^0.3.0",
|
"browser-encrypt-attachment": "^0.3.0",
|
||||||
"browser-request": "^0.3.3",
|
"browser-request": "^0.3.3",
|
||||||
"classnames": "^2.1.2",
|
"classnames": "^2.1.2",
|
||||||
"commonmark": "^0.27.0",
|
"commonmark": "^0.28.1",
|
||||||
"counterpart": "^0.18.0",
|
"counterpart": "^0.18.0",
|
||||||
"draft-js": "^0.11.0-alpha",
|
"draft-js": "^0.11.0-alpha",
|
||||||
"draft-js-export-html": "^0.6.0",
|
"draft-js-export-html": "^0.6.0",
|
||||||
@ -78,6 +78,7 @@
|
|||||||
"querystring": "^0.2.0",
|
"querystring": "^0.2.0",
|
||||||
"react": "^15.4.0",
|
"react": "^15.4.0",
|
||||||
"react-addons-css-transition-group": "15.3.2",
|
"react-addons-css-transition-group": "15.3.2",
|
||||||
|
"react-beautiful-dnd": "^4.0.0",
|
||||||
"react-dom": "^15.4.0",
|
"react-dom": "^15.4.0",
|
||||||
"react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#5e97aef",
|
"react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#5e97aef",
|
||||||
"sanitize-html": "^1.14.1",
|
"sanitize-html": "^1.14.1",
|
||||||
|
101
src/Analytics.js
101
src/Analytics.js
@ -14,25 +14,54 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getCurrentLanguage } from './languageHandler';
|
import { getCurrentLanguage, _t, _td } from './languageHandler';
|
||||||
import PlatformPeg from './PlatformPeg';
|
import PlatformPeg from './PlatformPeg';
|
||||||
import SdkConfig from './SdkConfig';
|
import SdkConfig, { DEFAULTS } from './SdkConfig';
|
||||||
|
import Modal from './Modal';
|
||||||
|
import sdk from './index';
|
||||||
|
|
||||||
|
function getRedactedHash() {
|
||||||
|
return window.location.hash.replace(/#\/(group|room|user)\/(.+)/, "#/$1/<redacted>");
|
||||||
|
}
|
||||||
|
|
||||||
function getRedactedUrl() {
|
function getRedactedUrl() {
|
||||||
const redactedHash = window.location.hash.replace(/#\/(group|room|user)\/(.+)/, "#/$1/<redacted>");
|
|
||||||
// hardcoded url to make piwik happy
|
// hardcoded url to make piwik happy
|
||||||
return 'https://riot.im/app/' + redactedHash;
|
return 'https://riot.im/app/' + getRedactedHash();
|
||||||
}
|
}
|
||||||
|
|
||||||
const customVariables = {
|
const customVariables = {
|
||||||
'App Platform': 1,
|
'App Platform': {
|
||||||
'App Version': 2,
|
id: 1,
|
||||||
'User Type': 3,
|
expl: _td('The platform you\'re on'),
|
||||||
'Chosen Language': 4,
|
},
|
||||||
'Instance': 5,
|
'App Version': {
|
||||||
'RTE: Uses Richtext Mode': 6,
|
id: 2,
|
||||||
'Homeserver URL': 7,
|
expl: _td('The version of Riot.im'),
|
||||||
'Identity Server URL': 8,
|
},
|
||||||
|
'User Type': {
|
||||||
|
id: 3,
|
||||||
|
expl: _td('Whether or not you\'re logged in (we don\'t record your user name)'),
|
||||||
|
},
|
||||||
|
'Chosen Language': {
|
||||||
|
id: 4,
|
||||||
|
expl: _td('Your language of choice'),
|
||||||
|
},
|
||||||
|
'Instance': {
|
||||||
|
id: 5,
|
||||||
|
expl: _td('Which officially provided instance you are using, if any'),
|
||||||
|
},
|
||||||
|
'RTE: Uses Richtext Mode': {
|
||||||
|
id: 6,
|
||||||
|
expl: _td('Whether or not you\'re using the Richtext mode of the Rich Text Editor'),
|
||||||
|
},
|
||||||
|
'Homeserver URL': {
|
||||||
|
id: 7,
|
||||||
|
expl: _td('Your homeserver\'s URL'),
|
||||||
|
},
|
||||||
|
'Identity Server URL': {
|
||||||
|
id: 8,
|
||||||
|
expl: _td('Your identity server\'s URL'),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function whitelistRedact(whitelist, str) {
|
function whitelistRedact(whitelist, str) {
|
||||||
@ -40,9 +69,6 @@ function whitelistRedact(whitelist, str) {
|
|||||||
return '<redacted>';
|
return '<redacted>';
|
||||||
}
|
}
|
||||||
|
|
||||||
const whitelistedHSUrls = ["https://matrix.org"];
|
|
||||||
const whitelistedISUrls = ["https://vector.im"];
|
|
||||||
|
|
||||||
class Analytics {
|
class Analytics {
|
||||||
constructor() {
|
constructor() {
|
||||||
this._paq = null;
|
this._paq = null;
|
||||||
@ -140,11 +166,16 @@ class Analytics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_setVisitVariable(key, value) {
|
_setVisitVariable(key, value) {
|
||||||
this._paq.push(['setCustomVariable', customVariables[key], key, value, 'visit']);
|
this._paq.push(['setCustomVariable', customVariables[key].id, key, value, 'visit']);
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoggedIn(isGuest, homeserverUrl, identityServerUrl) {
|
setLoggedIn(isGuest, homeserverUrl, identityServerUrl) {
|
||||||
if (this.disabled) return;
|
if (this.disabled) return;
|
||||||
|
|
||||||
|
const config = SdkConfig.get();
|
||||||
|
const whitelistedHSUrls = config.piwik.whitelistedHSUrls || DEFAULTS.piwik.whitelistedHSUrls;
|
||||||
|
const whitelistedISUrls = config.piwik.whitelistedISUrls || DEFAULTS.piwik.whitelistedISUrls;
|
||||||
|
|
||||||
this._setVisitVariable('User Type', isGuest ? 'Guest' : 'Logged In');
|
this._setVisitVariable('User Type', isGuest ? 'Guest' : 'Logged In');
|
||||||
this._setVisitVariable('Homeserver URL', whitelistRedact(whitelistedHSUrls, homeserverUrl));
|
this._setVisitVariable('Homeserver URL', whitelistRedact(whitelistedHSUrls, homeserverUrl));
|
||||||
this._setVisitVariable('Identity Server URL', whitelistRedact(whitelistedISUrls, identityServerUrl));
|
this._setVisitVariable('Identity Server URL', whitelistRedact(whitelistedISUrls, identityServerUrl));
|
||||||
@ -154,6 +185,44 @@ class Analytics {
|
|||||||
if (this.disabled) return;
|
if (this.disabled) return;
|
||||||
this._setVisitVariable('RTE: Uses Richtext Mode', state ? 'on' : 'off');
|
this._setVisitVariable('RTE: Uses Richtext Mode', state ? 'on' : 'off');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showDetailsModal() {
|
||||||
|
const Tracker = window.Piwik.getAsyncTracker();
|
||||||
|
const rows = Object.values(customVariables).map((v) => Tracker.getCustomVariable(v.id)).filter(Boolean);
|
||||||
|
|
||||||
|
const resolution = `${window.screen.width}x${window.screen.height}`;
|
||||||
|
|
||||||
|
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
|
||||||
|
Modal.createTrackedDialog('Analytics Details', '', ErrorDialog, {
|
||||||
|
title: _t('Analytics'),
|
||||||
|
description: <div>
|
||||||
|
<div>
|
||||||
|
{ _t('The information being sent to us to help make Riot.im better includes:') }
|
||||||
|
</div>
|
||||||
|
<table>
|
||||||
|
{ rows.map((row) => <tr key={row[0]}>
|
||||||
|
<td>{ _t(customVariables[row[0]].expl) }</td>
|
||||||
|
<td><code>{ row[1] }</code></td>
|
||||||
|
</tr>) }
|
||||||
|
</table>
|
||||||
|
<br />
|
||||||
|
<div>
|
||||||
|
{ _t('We also record each page you use in the app (currently <CurrentPageHash>), your User Agent'
|
||||||
|
+ ' (<CurrentUserAgent>) and your device resolution (<CurrentDeviceResolution>).',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
CurrentPageHash: <code>{ getRedactedHash() }</code>,
|
||||||
|
CurrentUserAgent: <code>{ navigator.userAgent }</code>,
|
||||||
|
CurrentDeviceResolution: <code>{ resolution }</code>,
|
||||||
|
},
|
||||||
|
) }
|
||||||
|
|
||||||
|
{ _t('Where this page includes identifiable information, such as a room, '
|
||||||
|
+ 'user or group ID, that data is removed before being sent to the server.') }
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!global.mxAnalytics) {
|
if (!global.mxAnalytics) {
|
||||||
|
102
src/DateUtils.js
102
src/DateUtils.js
@ -15,7 +15,6 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
|
|
||||||
function getDaysArray() {
|
function getDaysArray() {
|
||||||
@ -59,47 +58,70 @@ function twelveHourTime(date) {
|
|||||||
return `${hours}:${minutes}${ampm}`;
|
return `${hours}:${minutes}${ampm}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export function formatDate(date, showTwelveHour=false) {
|
||||||
formatDate: function(date, showTwelveHour=false) {
|
const now = new Date();
|
||||||
const now = new Date();
|
const days = getDaysArray();
|
||||||
const days = getDaysArray();
|
const months = getMonthsArray();
|
||||||
const months = getMonthsArray();
|
if (date.toDateString() === now.toDateString()) {
|
||||||
if (date.toDateString() === now.toDateString()) {
|
return formatTime(date, showTwelveHour);
|
||||||
return this.formatTime(date, showTwelveHour);
|
} else if (now.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) {
|
||||||
} else if (now.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) {
|
// TODO: use standard date localize function provided in counterpart
|
||||||
// TODO: use standard date localize function provided in counterpart
|
return _t('%(weekDayName)s %(time)s', {
|
||||||
return _t('%(weekDayName)s %(time)s', {
|
weekDayName: days[date.getDay()],
|
||||||
weekDayName: days[date.getDay()],
|
time: formatTime(date, showTwelveHour),
|
||||||
time: this.formatTime(date, showTwelveHour),
|
});
|
||||||
});
|
} else if (now.getFullYear() === date.getFullYear()) {
|
||||||
} else if (now.getFullYear() === date.getFullYear()) {
|
// TODO: use standard date localize function provided in counterpart
|
||||||
// TODO: use standard date localize function provided in counterpart
|
return _t('%(weekDayName)s, %(monthName)s %(day)s %(time)s', {
|
||||||
return _t('%(weekDayName)s, %(monthName)s %(day)s %(time)s', {
|
|
||||||
weekDayName: days[date.getDay()],
|
|
||||||
monthName: months[date.getMonth()],
|
|
||||||
day: date.getDate(),
|
|
||||||
time: this.formatTime(date, showTwelveHour),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return this.formatFullDate(date, showTwelveHour);
|
|
||||||
},
|
|
||||||
|
|
||||||
formatFullDate: function(date, showTwelveHour=false) {
|
|
||||||
const days = getDaysArray();
|
|
||||||
const months = getMonthsArray();
|
|
||||||
return _t('%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s', {
|
|
||||||
weekDayName: days[date.getDay()],
|
weekDayName: days[date.getDay()],
|
||||||
monthName: months[date.getMonth()],
|
monthName: months[date.getMonth()],
|
||||||
day: date.getDate(),
|
day: date.getDate(),
|
||||||
fullYear: date.getFullYear(),
|
time: formatTime(date, showTwelveHour),
|
||||||
time: this.formatTime(date, showTwelveHour),
|
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
return formatFullDate(date, showTwelveHour);
|
||||||
|
}
|
||||||
|
|
||||||
formatTime: function(date, showTwelveHour=false) {
|
export function formatFullDateNoTime(date) {
|
||||||
if (showTwelveHour) {
|
const days = getDaysArray();
|
||||||
return twelveHourTime(date);
|
const months = getMonthsArray();
|
||||||
}
|
return _t('%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s', {
|
||||||
return pad(date.getHours()) + ':' + pad(date.getMinutes());
|
weekDayName: days[date.getDay()],
|
||||||
},
|
monthName: months[date.getMonth()],
|
||||||
};
|
day: date.getDate(),
|
||||||
|
fullYear: date.getFullYear(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatFullDate(date, showTwelveHour=false) {
|
||||||
|
const days = getDaysArray();
|
||||||
|
const months = getMonthsArray();
|
||||||
|
return _t('%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s', {
|
||||||
|
weekDayName: days[date.getDay()],
|
||||||
|
monthName: months[date.getMonth()],
|
||||||
|
day: date.getDate(),
|
||||||
|
fullYear: date.getFullYear(),
|
||||||
|
time: formatTime(date, showTwelveHour),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatTime(date, showTwelveHour=false) {
|
||||||
|
if (showTwelveHour) {
|
||||||
|
return twelveHourTime(date);
|
||||||
|
}
|
||||||
|
return pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||||
|
}
|
||||||
|
|
||||||
|
const MILLIS_IN_DAY = 86400000;
|
||||||
|
export function wantsDateSeparator(prevEventDate, nextEventDate) {
|
||||||
|
if (!nextEventDate || !prevEventDate) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Return early for events that are > 24h apart
|
||||||
|
if (Math.abs(prevEventDate.getTime() - nextEventDate.getTime()) > MILLIS_IN_DAY) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare weekdays
|
||||||
|
return prevEventDate.getDay() !== nextEventDate.getDay();
|
||||||
|
}
|
||||||
|
@ -68,3 +68,12 @@ export function isOnlyCtrlOrCmdKeyEvent(ev) {
|
|||||||
return ev.ctrlKey && !ev.altKey && !ev.metaKey && !ev.shiftKey;
|
return ev.ctrlKey && !ev.altKey && !ev.metaKey && !ev.shiftKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isOnlyCtrlOrCmdIgnoreShiftKeyEvent(ev) {
|
||||||
|
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
||||||
|
if (isMac) {
|
||||||
|
return ev.metaKey && !ev.altKey && !ev.ctrlKey;
|
||||||
|
} else {
|
||||||
|
return ev.ctrlKey && !ev.altKey && !ev.metaKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -55,25 +55,6 @@ function is_multi_line(node) {
|
|||||||
return par.firstChild != par.lastChild;
|
return par.firstChild != par.lastChild;
|
||||||
}
|
}
|
||||||
|
|
||||||
import linkifyMatrix from './linkify-matrix';
|
|
||||||
import * as linkify from 'linkifyjs';
|
|
||||||
linkifyMatrix(linkify);
|
|
||||||
|
|
||||||
// Thieved from draft-js-export-markdown
|
|
||||||
function escapeMarkdown(s) {
|
|
||||||
return s.replace(/[*_`]/g, '\\$&');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace URLs, room aliases and user IDs with md-escaped URLs
|
|
||||||
function linkifyMarkdown(s) {
|
|
||||||
const links = linkify.find(s);
|
|
||||||
links.forEach((l) => {
|
|
||||||
// This may replace several instances of `l.value` at once, but that's OK
|
|
||||||
s = s.replace(l.value, escapeMarkdown(l.value));
|
|
||||||
});
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class that wraps commonmark, adding the ability to see whether
|
* Class that wraps commonmark, adding the ability to see whether
|
||||||
* a given message actually uses any markdown syntax or whether
|
* a given message actually uses any markdown syntax or whether
|
||||||
@ -81,7 +62,7 @@ function linkifyMarkdown(s) {
|
|||||||
*/
|
*/
|
||||||
export default class Markdown {
|
export default class Markdown {
|
||||||
constructor(input) {
|
constructor(input) {
|
||||||
this.input = linkifyMarkdown(input);
|
this.input = input;
|
||||||
|
|
||||||
const parser = new commonmark.Parser();
|
const parser = new commonmark.Parser();
|
||||||
this.parsed = parser.parse(this.input);
|
this.parsed = parser.parse(this.input);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
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.
|
||||||
@ -22,6 +23,7 @@ import EventTimeline from 'matrix-js-sdk/lib/models/event-timeline';
|
|||||||
import EventTimelineSet from 'matrix-js-sdk/lib/models/event-timeline-set';
|
import EventTimelineSet from 'matrix-js-sdk/lib/models/event-timeline-set';
|
||||||
import createMatrixClient from './utils/createMatrixClient';
|
import createMatrixClient from './utils/createMatrixClient';
|
||||||
import SettingsStore from './settings/SettingsStore';
|
import SettingsStore from './settings/SettingsStore';
|
||||||
|
import MatrixActionCreators from './actions/MatrixActionCreators';
|
||||||
|
|
||||||
interface MatrixClientCreds {
|
interface MatrixClientCreds {
|
||||||
homeserverUrl: string,
|
homeserverUrl: string,
|
||||||
@ -68,6 +70,8 @@ class MatrixClientPeg {
|
|||||||
|
|
||||||
unset() {
|
unset() {
|
||||||
this.matrixClient = null;
|
this.matrixClient = null;
|
||||||
|
|
||||||
|
MatrixActionCreators.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -108,6 +112,9 @@ class MatrixClientPeg {
|
|||||||
// regardless of errors, start the client. If we did error out, we'll
|
// regardless of errors, start the client. If we did error out, we'll
|
||||||
// just end up doing a full initial /sync.
|
// just end up doing a full initial /sync.
|
||||||
|
|
||||||
|
// Connect the matrix client to the dispatcher
|
||||||
|
MatrixActionCreators.start(this.matrixClient);
|
||||||
|
|
||||||
console.log(`MatrixClientPeg: really starting MatrixClient`);
|
console.log(`MatrixClientPeg: really starting MatrixClient`);
|
||||||
this.get().startClient(opts);
|
this.get().startClient(opts);
|
||||||
console.log(`MatrixClientPeg: MatrixClient started`);
|
console.log(`MatrixClientPeg: MatrixClient started`);
|
||||||
|
@ -19,6 +19,7 @@ limitations under the License.
|
|||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const ReactDOM = require('react-dom');
|
const ReactDOM = require('react-dom');
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import Analytics from './Analytics';
|
import Analytics from './Analytics';
|
||||||
import sdk from './index';
|
import sdk from './index';
|
||||||
|
|
||||||
@ -33,7 +34,7 @@ const AsyncWrapper = React.createClass({
|
|||||||
/** A function which takes a 'callback' argument which it will call
|
/** A function which takes a 'callback' argument which it will call
|
||||||
* with the real component once it loads.
|
* with the real component once it loads.
|
||||||
*/
|
*/
|
||||||
loader: React.PropTypes.func.isRequired,
|
loader: PropTypes.func.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -135,6 +135,10 @@ const Notifier = {
|
|||||||
const plaf = PlatformPeg.get();
|
const plaf = PlatformPeg.get();
|
||||||
if (!plaf) return;
|
if (!plaf) return;
|
||||||
|
|
||||||
|
// Dev note: We don't set the "notificationsEnabled" setting to true here because it is a
|
||||||
|
// calculated value. It is determined based upon whether or not the master rule is enabled
|
||||||
|
// and other flags. Setting it here would cause a circular reference.
|
||||||
|
|
||||||
Analytics.trackEvent('Notifier', 'Set Enabled', enable);
|
Analytics.trackEvent('Notifier', 'Set Enabled', enable);
|
||||||
|
|
||||||
// make sure that we persist the current setting audio_enabled setting
|
// make sure that we persist the current setting audio_enabled setting
|
||||||
@ -168,7 +172,7 @@ const Notifier = {
|
|||||||
});
|
});
|
||||||
// clear the notifications_hidden flag, so that if notifications are
|
// clear the notifications_hidden flag, so that if notifications are
|
||||||
// disabled again in the future, we will show the banner again.
|
// disabled again in the future, we will show the banner again.
|
||||||
this.setToolbarHidden(false);
|
this.setToolbarHidden(true);
|
||||||
} else {
|
} else {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: "notifier_enabled",
|
action: "notifier_enabled",
|
||||||
|
@ -85,9 +85,7 @@ function _onStartChatFinished(shouldInvite, addrs) {
|
|||||||
if (rooms.length > 0) {
|
if (rooms.length > 0) {
|
||||||
// A Direct Message room already exists for this user, so select a
|
// A Direct Message room already exists for this user, so select a
|
||||||
// room from a list that is similar to the one in MemberInfo panel
|
// room from a list that is similar to the one in MemberInfo panel
|
||||||
const ChatCreateOrReuseDialog = sdk.getComponent(
|
const ChatCreateOrReuseDialog = sdk.getComponent("views.dialogs.ChatCreateOrReuseDialog");
|
||||||
"views.dialogs.ChatCreateOrReuseDialog",
|
|
||||||
);
|
|
||||||
const close = Modal.createTrackedDialog('Create or Reuse', '', ChatCreateOrReuseDialog, {
|
const close = Modal.createTrackedDialog('Create or Reuse', '', ChatCreateOrReuseDialog, {
|
||||||
userId: addrTexts[0],
|
userId: addrTexts[0],
|
||||||
onNewDMClick: () => {
|
onNewDMClick: () => {
|
||||||
@ -115,6 +113,15 @@ function _onStartChatFinished(shouldInvite, addrs) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} else if (addrTexts.length === 1) {
|
||||||
|
// Start a new DM chat
|
||||||
|
createRoom({dmUserId: addrTexts[0]}).catch((err) => {
|
||||||
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
Modal.createTrackedDialog('Failed to invite user', '', ErrorDialog, {
|
||||||
|
title: _t("Failed to invite user"),
|
||||||
|
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||||
|
});
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// Start multi user chat
|
// Start multi user chat
|
||||||
let room;
|
let room;
|
||||||
|
@ -34,7 +34,14 @@ export function getRoomNotifsState(roomId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// for everything else, look at the room rule.
|
// for everything else, look at the room rule.
|
||||||
const roomRule = MatrixClientPeg.get().getRoomPushRule('global', roomId);
|
let roomRule = null;
|
||||||
|
try {
|
||||||
|
roomRule = MatrixClientPeg.get().getRoomPushRule('global', roomId);
|
||||||
|
} catch (err) {
|
||||||
|
// Possible that the client doesn't have pushRules yet. If so, it
|
||||||
|
// hasn't started eiher, so indicate that this room is not notifying.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// XXX: We have to assume the default is to notify for all messages
|
// XXX: We have to assume the default is to notify for all messages
|
||||||
// (in particular this will be 'wrong' for one to one rooms because
|
// (in particular this will be 'wrong' for one to one rooms because
|
||||||
@ -130,6 +137,11 @@ function setRoomNotifsStateUnmuted(roomId, newState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function findOverrideMuteRule(roomId) {
|
function findOverrideMuteRule(roomId) {
|
||||||
|
if (!MatrixClientPeg.get().pushRules ||
|
||||||
|
!MatrixClientPeg.get().pushRules['global'] ||
|
||||||
|
!MatrixClientPeg.get().pushRules['global'].override) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
for (const rule of MatrixClientPeg.get().pushRules['global'].override) {
|
for (const rule of MatrixClientPeg.get().pushRules['global'].override) {
|
||||||
if (isRuleForRoom(roomId, rule)) {
|
if (isRuleForRoom(roomId, rule)) {
|
||||||
if (isMuteRule(rule) && rule.enabled) {
|
if (isMuteRule(rule) && rule.enabled) {
|
||||||
|
22
src/Rooms.js
22
src/Rooms.js
@ -43,7 +43,7 @@ export function getOnlyOtherMember(room, me) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isConfCallRoom(room, me, conferenceHandler) {
|
function _isConfCallRoom(room, me, conferenceHandler) {
|
||||||
if (!conferenceHandler) return false;
|
if (!conferenceHandler) return false;
|
||||||
|
|
||||||
if (me.membership != "join") {
|
if (me.membership != "join") {
|
||||||
@ -58,6 +58,26 @@ export function isConfCallRoom(room, me, conferenceHandler) {
|
|||||||
if (conferenceHandler.isConferenceUser(otherMember.userId)) {
|
if (conferenceHandler.isConferenceUser(otherMember.userId)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache whether a room is a conference call. Assumes that rooms will always
|
||||||
|
// either will or will not be a conference call room.
|
||||||
|
const isConfCallRoomCache = {
|
||||||
|
// $roomId: bool
|
||||||
|
};
|
||||||
|
|
||||||
|
export function isConfCallRoom(room, me, conferenceHandler) {
|
||||||
|
if (isConfCallRoomCache[room.roomId] !== undefined) {
|
||||||
|
return isConfCallRoomCache[room.roomId];
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = _isConfCallRoom(room, me, conferenceHandler);
|
||||||
|
|
||||||
|
isConfCallRoomCache[room.roomId] = result;
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function looksLikeDirectMessageRoom(room, me) {
|
export function looksLikeDirectMessageRoom(room, me) {
|
||||||
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
const request = require('browser-request');
|
const request = require('browser-request');
|
||||||
|
|
||||||
const SdkConfig = require('./SdkConfig');
|
const SdkConfig = require('./SdkConfig');
|
||||||
@ -38,11 +39,53 @@ class ScalarAuthClient {
|
|||||||
|
|
||||||
// Returns a scalar_token string
|
// Returns a scalar_token string
|
||||||
getScalarToken() {
|
getScalarToken() {
|
||||||
const tok = window.localStorage.getItem("mx_scalar_token");
|
const token = window.localStorage.getItem("mx_scalar_token");
|
||||||
if (tok) return Promise.resolve(tok);
|
|
||||||
|
|
||||||
// No saved token, so do the dance to get one. First, we
|
if (!token) {
|
||||||
// need an openid bearer token from the HS.
|
return this.registerForToken();
|
||||||
|
} else {
|
||||||
|
return this.validateToken(token).then(userId => {
|
||||||
|
const me = MatrixClientPeg.get().getUserId();
|
||||||
|
if (userId !== me) {
|
||||||
|
throw new Error("Scalar token is owned by someone else: " + me);
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
|
// Something went wrong - try to get a new token.
|
||||||
|
console.warn("Registering for new scalar token");
|
||||||
|
return this.registerForToken();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validateToken(token) {
|
||||||
|
let url = SdkConfig.get().integrations_rest_url + "/account";
|
||||||
|
|
||||||
|
const defer = Promise.defer();
|
||||||
|
request({
|
||||||
|
method: "GET",
|
||||||
|
uri: url,
|
||||||
|
qs: {scalar_token: token},
|
||||||
|
json: true,
|
||||||
|
}, (err, response, body) => {
|
||||||
|
if (err) {
|
||||||
|
defer.reject(err);
|
||||||
|
} else if (response.statusCode / 100 !== 2) {
|
||||||
|
defer.reject({statusCode: response.statusCode});
|
||||||
|
} else if (!body || !body.user_id) {
|
||||||
|
defer.reject(new Error("Missing user_id in response"));
|
||||||
|
} else {
|
||||||
|
defer.resolve(body.user_id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return defer.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerForToken() {
|
||||||
|
// Get openid bearer token from the HS as the first part of our dance
|
||||||
return MatrixClientPeg.get().getOpenIdToken().then((token_object) => {
|
return MatrixClientPeg.get().getOpenIdToken().then((token_object) => {
|
||||||
// Now we can send that to scalar and exchange it for a scalar token
|
// Now we can send that to scalar and exchange it for a scalar token
|
||||||
return this.exchangeForScalarToken(token_object);
|
return this.exchangeForScalarToken(token_object);
|
||||||
@ -109,6 +152,7 @@ class ScalarAuthClient {
|
|||||||
let url = SdkConfig.get().integrations_ui_url;
|
let url = SdkConfig.get().integrations_ui_url;
|
||||||
url += "?scalar_token=" + encodeURIComponent(this.scalarToken);
|
url += "?scalar_token=" + encodeURIComponent(this.scalarToken);
|
||||||
url += "&room_id=" + encodeURIComponent(roomId);
|
url += "&room_id=" + encodeURIComponent(roomId);
|
||||||
|
url += "&theme=" + encodeURIComponent(SettingsStore.getValue("theme"));
|
||||||
if (id) {
|
if (id) {
|
||||||
url += '&integ_id=' + encodeURIComponent(id);
|
url += '&integ_id=' + encodeURIComponent(id);
|
||||||
}
|
}
|
||||||
|
@ -557,8 +557,16 @@ const onMessage = function(event) {
|
|||||||
//
|
//
|
||||||
// All strings start with the empty string, so for sanity return if the length
|
// All strings start with the empty string, so for sanity return if the length
|
||||||
// of the event origin is 0.
|
// of the event origin is 0.
|
||||||
|
//
|
||||||
|
// TODO -- Scalar postMessage API should be namespaced with event.data.api field
|
||||||
|
// Fix following "if" statement to respond only to specific API messages.
|
||||||
const url = SdkConfig.get().integrations_ui_url;
|
const url = SdkConfig.get().integrations_ui_url;
|
||||||
if (event.origin.length === 0 || !url.startsWith(event.origin) || !event.data.action) {
|
if (
|
||||||
|
event.origin.length === 0 ||
|
||||||
|
!url.startsWith(event.origin) ||
|
||||||
|
!event.data.action ||
|
||||||
|
event.data.api // Ignore messages with specific API set
|
||||||
|
) {
|
||||||
return; // don't log this - debugging APIs like to spam postMessage which floods the log otherwise
|
return; // don't log this - debugging APIs like to spam postMessage which floods the log otherwise
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,13 @@ const DEFAULTS = {
|
|||||||
integrations_rest_url: "https://scalar.vector.im/api",
|
integrations_rest_url: "https://scalar.vector.im/api",
|
||||||
// Where to send bug reports. If not specified, bugs cannot be sent.
|
// Where to send bug reports. If not specified, bugs cannot be sent.
|
||||||
bug_report_endpoint_url: null,
|
bug_report_endpoint_url: null,
|
||||||
|
|
||||||
|
piwik: {
|
||||||
|
url: "https://piwik.riot.im/",
|
||||||
|
whitelistedHSUrls: ["https://matrix.org"],
|
||||||
|
whitelistedISUrls: ["https://vector.im", "https://matrix.org"],
|
||||||
|
siteId: 1,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
class SdkConfig {
|
class SdkConfig {
|
||||||
@ -45,3 +52,4 @@ class SdkConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = SdkConfig;
|
module.exports = SdkConfig;
|
||||||
|
module.exports.DEFAULTS = DEFAULTS;
|
||||||
|
@ -96,6 +96,8 @@ const commands = {
|
|||||||
colorScheme.primary_color = matches[1];
|
colorScheme.primary_color = matches[1];
|
||||||
if (matches[4]) {
|
if (matches[4]) {
|
||||||
colorScheme.secondary_color = matches[4];
|
colorScheme.secondary_color = matches[4];
|
||||||
|
} else {
|
||||||
|
colorScheme.secondary_color = colorScheme.primary_color;
|
||||||
}
|
}
|
||||||
return success(
|
return success(
|
||||||
SettingsStore.setValue("roomColor", roomId, SettingLevel.ROOM_ACCOUNT, colorScheme),
|
SettingsStore.setValue("roomColor", roomId, SettingLevel.ROOM_ACCOUNT, colorScheme),
|
||||||
@ -295,7 +297,7 @@ const commands = {
|
|||||||
// Define the power level of a user
|
// Define the power level of a user
|
||||||
op: new Command("op", "<userId> [<power level>]", function(roomId, args) {
|
op: new Command("op", "<userId> [<power level>]", function(roomId, args) {
|
||||||
if (args) {
|
if (args) {
|
||||||
const matches = args.match(/^(\S+?)( +(\d+))?$/);
|
const matches = args.match(/^(\S+?)( +(-?\d+))?$/);
|
||||||
let powerLevel = 50; // default power level for op
|
let powerLevel = 50; // default power level for op
|
||||||
if (matches) {
|
if (matches) {
|
||||||
const userId = matches[1];
|
const userId = matches[1];
|
||||||
|
@ -52,8 +52,7 @@ function textForMemberEvent(ev) {
|
|||||||
case 'join':
|
case 'join':
|
||||||
if (prevContent && prevContent.membership === 'join') {
|
if (prevContent && prevContent.membership === 'join') {
|
||||||
if (prevContent.displayname && content.displayname && prevContent.displayname !== content.displayname) {
|
if (prevContent.displayname && content.displayname && prevContent.displayname !== content.displayname) {
|
||||||
return _t('%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.', {
|
return _t('%(oldDisplayName)s changed their display name to %(displayName)s.', {
|
||||||
senderName,
|
|
||||||
oldDisplayName: prevContent.displayname,
|
oldDisplayName: prevContent.displayname,
|
||||||
displayName: content.displayname,
|
displayName: content.displayname,
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
const React = require('react');
|
const React = require('react');
|
||||||
const ReactDom = require('react-dom');
|
const ReactDom = require('react-dom');
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
const Velocity = require('velocity-vector');
|
const Velocity = require('velocity-vector');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,16 +15,16 @@ module.exports = React.createClass({
|
|||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
// either a list of child nodes, or a single child.
|
// either a list of child nodes, or a single child.
|
||||||
children: React.PropTypes.any,
|
children: PropTypes.any,
|
||||||
|
|
||||||
// optional transition information for changing existing children
|
// optional transition information for changing existing children
|
||||||
transition: React.PropTypes.object,
|
transition: PropTypes.object,
|
||||||
|
|
||||||
// a list of state objects to apply to each child node in turn
|
// a list of state objects to apply to each child node in turn
|
||||||
startStyles: React.PropTypes.array,
|
startStyles: PropTypes.array,
|
||||||
|
|
||||||
// a list of transition options from the corresponding startStyle
|
// a list of transition options from the corresponding startStyle
|
||||||
enterTransitionOpts: React.PropTypes.array,
|
enterTransitionOpts: PropTypes.array,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
34
src/actions/GroupActions.js
Normal file
34
src/actions/GroupActions.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
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 { asyncAction } from './actionCreators';
|
||||||
|
|
||||||
|
const GroupActions = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an action thunk that will do an asynchronous request to fetch
|
||||||
|
* the groups to which a user is joined.
|
||||||
|
*
|
||||||
|
* @param {MatrixClient} matrixClient the matrix client to query.
|
||||||
|
* @returns {function} an action thunk that will dispatch actions
|
||||||
|
* indicating the status of the request.
|
||||||
|
* @see asyncAction
|
||||||
|
*/
|
||||||
|
GroupActions.fetchJoinedGroups = function(matrixClient) {
|
||||||
|
return asyncAction('GroupActions.fetchJoinedGroups', () => matrixClient.getJoinedGroups());
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GroupActions;
|
108
src/actions/MatrixActionCreators.js
Normal file
108
src/actions/MatrixActionCreators.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
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 dis from '../dispatcher';
|
||||||
|
|
||||||
|
// TODO: migrate from sync_state to MatrixActions.sync so that more js-sdk events
|
||||||
|
// become dispatches in the same place.
|
||||||
|
/**
|
||||||
|
* Create a MatrixActions.sync action that represents a MatrixClient `sync` event,
|
||||||
|
* each parameter mapping to a key-value in the action.
|
||||||
|
*
|
||||||
|
* @param {MatrixClient} matrixClient the matrix client
|
||||||
|
* @param {string} state the current sync state.
|
||||||
|
* @param {string} prevState the previous sync state.
|
||||||
|
* @returns {Object} an action of type MatrixActions.sync.
|
||||||
|
*/
|
||||||
|
function createSyncAction(matrixClient, state, prevState) {
|
||||||
|
return {
|
||||||
|
action: 'MatrixActions.sync',
|
||||||
|
state,
|
||||||
|
prevState,
|
||||||
|
matrixClient,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef AccountDataAction
|
||||||
|
* @type {Object}
|
||||||
|
* @property {string} action 'MatrixActions.accountData'.
|
||||||
|
* @property {MatrixEvent} event the MatrixEvent that triggered the dispatch.
|
||||||
|
* @property {string} event_type the type of the MatrixEvent, e.g. "m.direct".
|
||||||
|
* @property {Object} event_content the content of the MatrixEvent.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a MatrixActions.accountData action that represents a MatrixClient `accountData`
|
||||||
|
* matrix event.
|
||||||
|
*
|
||||||
|
* @param {MatrixClient} matrixClient the matrix client.
|
||||||
|
* @param {MatrixEvent} accountDataEvent the account data event.
|
||||||
|
* @returns {AccountDataAction} an action of type MatrixActions.accountData.
|
||||||
|
*/
|
||||||
|
function createAccountDataAction(matrixClient, accountDataEvent) {
|
||||||
|
return {
|
||||||
|
action: 'MatrixActions.accountData',
|
||||||
|
event: accountDataEvent,
|
||||||
|
event_type: accountDataEvent.getType(),
|
||||||
|
event_content: accountDataEvent.getContent(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This object is responsible for dispatching actions when certain events are emitted by
|
||||||
|
* the given MatrixClient.
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
// A list of callbacks to call to unregister all listeners added
|
||||||
|
_matrixClientListenersStop: [],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start listening to certain events from the MatrixClient and dispatch actions when
|
||||||
|
* they are emitted.
|
||||||
|
* @param {MatrixClient} matrixClient the MatrixClient to listen to events from
|
||||||
|
*/
|
||||||
|
start(matrixClient) {
|
||||||
|
this._addMatrixClientListener(matrixClient, 'sync', createSyncAction);
|
||||||
|
this._addMatrixClientListener(matrixClient, 'accountData', createAccountDataAction);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start listening to events of type eventName on matrixClient and when they are emitted,
|
||||||
|
* dispatch an action created by the actionCreator function.
|
||||||
|
* @param {MatrixClient} matrixClient a MatrixClient to register a listener with.
|
||||||
|
* @param {string} eventName the event to listen to on MatrixClient.
|
||||||
|
* @param {function} actionCreator a function that should return an action to dispatch
|
||||||
|
* when given the MatrixClient as an argument as well as
|
||||||
|
* arguments emitted in the MatrixClient event.
|
||||||
|
*/
|
||||||
|
_addMatrixClientListener(matrixClient, eventName, actionCreator) {
|
||||||
|
const listener = (...args) => {
|
||||||
|
dis.dispatch(actionCreator(matrixClient, ...args));
|
||||||
|
};
|
||||||
|
matrixClient.on(eventName, listener);
|
||||||
|
this._matrixClientListenersStop.push(() => {
|
||||||
|
matrixClient.removeListener(eventName, listener);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop listening to events.
|
||||||
|
*/
|
||||||
|
stop() {
|
||||||
|
this._matrixClientListenersStop.forEach((stopListener) => stopListener());
|
||||||
|
},
|
||||||
|
};
|
59
src/actions/TagOrderActions.js
Normal file
59
src/actions/TagOrderActions.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
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 Analytics from '../Analytics';
|
||||||
|
import { asyncAction } from './actionCreators';
|
||||||
|
import TagOrderStore from '../stores/TagOrderStore';
|
||||||
|
|
||||||
|
const TagOrderActions = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an action thunk that will do an asynchronous request to
|
||||||
|
* move a tag in TagOrderStore to destinationIx.
|
||||||
|
*
|
||||||
|
* @param {MatrixClient} matrixClient the matrix client to set the
|
||||||
|
* account data on.
|
||||||
|
* @param {string} tag the tag to move.
|
||||||
|
* @param {number} destinationIx the new position of the tag.
|
||||||
|
* @returns {function} an action thunk that will dispatch actions
|
||||||
|
* indicating the status of the request.
|
||||||
|
* @see asyncAction
|
||||||
|
*/
|
||||||
|
TagOrderActions.moveTag = function(matrixClient, tag, destinationIx) {
|
||||||
|
// Only commit tags if the state is ready, i.e. not null
|
||||||
|
let tags = TagOrderStore.getOrderedTags();
|
||||||
|
if (!tags) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tags = tags.filter((t) => t !== tag);
|
||||||
|
tags = [...tags.slice(0, destinationIx), tag, ...tags.slice(destinationIx)];
|
||||||
|
|
||||||
|
const storeId = TagOrderStore.getStoreId();
|
||||||
|
|
||||||
|
return asyncAction('TagOrderActions.moveTag', () => {
|
||||||
|
Analytics.trackEvent('TagOrderActions', 'commitTagOrdering');
|
||||||
|
return matrixClient.setAccountData(
|
||||||
|
'im.vector.web.tag_ordering',
|
||||||
|
{tags, _storeId: storeId},
|
||||||
|
);
|
||||||
|
}, () => {
|
||||||
|
// For an optimistic update
|
||||||
|
return {tags};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TagOrderActions;
|
48
src/actions/actionCreators.js
Normal file
48
src/actions/actionCreators.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an action thunk that will dispatch actions indicating the current
|
||||||
|
* status of the Promise returned by fn.
|
||||||
|
*
|
||||||
|
* @param {string} id the id to give the dispatched actions. This is given a
|
||||||
|
* suffix determining whether it is pending, successful or
|
||||||
|
* a failure.
|
||||||
|
* @param {function} fn a function that returns a Promise.
|
||||||
|
* @param {function?} pendingFn a function that returns an object to assign
|
||||||
|
* to the `request` key of the ${id}.pending
|
||||||
|
* payload.
|
||||||
|
* @returns {function} an action thunk - a function that uses its single
|
||||||
|
* argument as a dispatch function to dispatch the
|
||||||
|
* following actions:
|
||||||
|
* `${id}.pending` and either
|
||||||
|
* `${id}.success` or
|
||||||
|
* `${id}.failure`.
|
||||||
|
*/
|
||||||
|
export function asyncAction(id, fn, pendingFn) {
|
||||||
|
return (dispatch) => {
|
||||||
|
dispatch({
|
||||||
|
action: id + '.pending',
|
||||||
|
request:
|
||||||
|
typeof pendingFn === 'function' ? pendingFn() : undefined,
|
||||||
|
});
|
||||||
|
fn().then((result) => {
|
||||||
|
dispatch({action: id + '.success', result});
|
||||||
|
}).catch((err) => {
|
||||||
|
dispatch({action: id + '.failure', err});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const React = require("react");
|
const React = require("react");
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
const sdk = require('../../../index');
|
const sdk = require('../../../index');
|
||||||
const MatrixClientPeg = require("../../../MatrixClientPeg");
|
const MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||||
@ -23,8 +24,8 @@ module.exports = React.createClass({
|
|||||||
displayName: 'EncryptedEventDialog',
|
displayName: 'EncryptedEventDialog',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
event: React.PropTypes.object.isRequired,
|
event: PropTypes.object.isRequired,
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||||||
|
|
||||||
import FileSaver from 'file-saver';
|
import FileSaver from 'file-saver';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
import * as Matrix from 'matrix-js-sdk';
|
import * as Matrix from 'matrix-js-sdk';
|
||||||
@ -29,8 +30,8 @@ export default React.createClass({
|
|||||||
displayName: 'ExportE2eKeysDialog',
|
displayName: 'ExportE2eKeysDialog',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
matrixClient: React.PropTypes.instanceOf(Matrix.MatrixClient).isRequired,
|
matrixClient: PropTypes.instanceOf(Matrix.MatrixClient).isRequired,
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import * as Matrix from 'matrix-js-sdk';
|
import * as Matrix from 'matrix-js-sdk';
|
||||||
import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption';
|
import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption';
|
||||||
@ -40,8 +41,8 @@ export default React.createClass({
|
|||||||
displayName: 'ImportE2eKeysDialog',
|
displayName: 'ImportE2eKeysDialog',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
matrixClient: React.PropTypes.instanceOf(Matrix.MatrixClient).isRequired,
|
matrixClient: PropTypes.instanceOf(Matrix.MatrixClient).isRequired,
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
/* These were earlier stateless functional components but had to be converted
|
/* These were earlier stateless functional components but had to be converted
|
||||||
@ -42,10 +43,10 @@ export class TextualCompletion extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
TextualCompletion.propTypes = {
|
TextualCompletion.propTypes = {
|
||||||
title: React.PropTypes.string,
|
title: PropTypes.string,
|
||||||
subtitle: React.PropTypes.string,
|
subtitle: PropTypes.string,
|
||||||
description: React.PropTypes.string,
|
description: PropTypes.string,
|
||||||
className: React.PropTypes.string,
|
className: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export class PillCompletion extends React.Component {
|
export class PillCompletion extends React.Component {
|
||||||
@ -69,9 +70,9 @@ export class PillCompletion extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
PillCompletion.propTypes = {
|
PillCompletion.propTypes = {
|
||||||
title: React.PropTypes.string,
|
title: PropTypes.string,
|
||||||
subtitle: React.PropTypes.string,
|
subtitle: PropTypes.string,
|
||||||
description: React.PropTypes.string,
|
description: PropTypes.string,
|
||||||
initialComponent: React.PropTypes.element,
|
initialComponent: PropTypes.element,
|
||||||
className: React.PropTypes.string,
|
className: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
@ -25,6 +25,7 @@ import {PillCompletion} from './Components';
|
|||||||
import {getDisplayAliasForRoom} from '../Rooms';
|
import {getDisplayAliasForRoom} from '../Rooms';
|
||||||
import sdk from '../index';
|
import sdk from '../index';
|
||||||
import _sortBy from 'lodash/sortBy';
|
import _sortBy from 'lodash/sortBy';
|
||||||
|
import {makeRoomPermalink} from "../matrix-to";
|
||||||
|
|
||||||
const ROOM_REGEX = /(?=#)(\S*)/g;
|
const ROOM_REGEX = /(?=#)(\S*)/g;
|
||||||
|
|
||||||
@ -78,7 +79,7 @@ export default class RoomProvider extends AutocompleteProvider {
|
|||||||
return {
|
return {
|
||||||
completion: displayAlias,
|
completion: displayAlias,
|
||||||
suffix: ' ',
|
suffix: ' ',
|
||||||
href: 'https://matrix.to/#/' + displayAlias,
|
href: makeRoomPermalink(displayAlias),
|
||||||
component: (
|
component: (
|
||||||
<PillCompletion initialComponent={<RoomAvatar width={24} height={24} room={room.room} />} title={room.name} description={displayAlias} />
|
<PillCompletion initialComponent={<RoomAvatar width={24} height={24} room={room.room} />} title={room.name} description={displayAlias} />
|
||||||
),
|
),
|
||||||
|
@ -28,6 +28,7 @@ import _sortBy from 'lodash/sortBy';
|
|||||||
import MatrixClientPeg from '../MatrixClientPeg';
|
import MatrixClientPeg from '../MatrixClientPeg';
|
||||||
|
|
||||||
import type {Room, RoomMember} from 'matrix-js-sdk';
|
import type {Room, RoomMember} from 'matrix-js-sdk';
|
||||||
|
import {makeUserPermalink} from "../matrix-to";
|
||||||
|
|
||||||
const USER_REGEX = /@\S*/g;
|
const USER_REGEX = /@\S*/g;
|
||||||
|
|
||||||
@ -106,7 +107,7 @@ export default class UserProvider extends AutocompleteProvider {
|
|||||||
// relies on the length of the entity === length of the text in the decoration.
|
// relies on the length of the entity === length of the text in the decoration.
|
||||||
completion: user.rawDisplayName.replace(' (IRC)', ''),
|
completion: user.rawDisplayName.replace(' (IRC)', ''),
|
||||||
suffix: range.start === 0 ? ': ' : ' ',
|
suffix: range.start === 0 ? ': ' : ' ',
|
||||||
href: 'https://matrix.to/#/' + user.userId,
|
href: makeUserPermalink(user.userId),
|
||||||
component: (
|
component: (
|
||||||
<PillCompletion
|
<PillCompletion
|
||||||
initialComponent={<MemberAvatar member={user} width={24} height={24} />}
|
initialComponent={<MemberAvatar member={user} width={24} height={24} />}
|
||||||
|
@ -20,6 +20,7 @@ limitations under the License.
|
|||||||
const classNames = require('classnames');
|
const classNames = require('classnames');
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const ReactDOM = require('react-dom');
|
const ReactDOM = require('react-dom');
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
// Shamelessly ripped off Modal.js. There's probably a better way
|
// Shamelessly ripped off Modal.js. There's probably a better way
|
||||||
// of doing reusable widgets like dialog boxes & menus where we go and
|
// of doing reusable widgets like dialog boxes & menus where we go and
|
||||||
@ -29,11 +30,11 @@ module.exports = {
|
|||||||
ContextualMenuContainerId: "mx_ContextualMenu_Container",
|
ContextualMenuContainerId: "mx_ContextualMenu_Container",
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
menuWidth: React.PropTypes.number,
|
menuWidth: PropTypes.number,
|
||||||
menuHeight: React.PropTypes.number,
|
menuHeight: PropTypes.number,
|
||||||
chevronOffset: React.PropTypes.number,
|
chevronOffset: PropTypes.number,
|
||||||
menuColour: React.PropTypes.string,
|
menuColour: PropTypes.string,
|
||||||
chevronFace: React.PropTypes.string, // top, bottom, left, right
|
chevronFace: PropTypes.string, // top, bottom, left, right
|
||||||
},
|
},
|
||||||
|
|
||||||
getOrCreateContainer: function() {
|
getOrCreateContainer: function() {
|
||||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
import MatrixClientPeg from '../../MatrixClientPeg';
|
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||||
@ -30,8 +31,8 @@ module.exports = React.createClass({
|
|||||||
displayName: 'CreateRoom',
|
displayName: 'CreateRoom',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
onRoomCreated: React.PropTypes.func,
|
onRoomCreated: PropTypes.func,
|
||||||
collapsedRhs: React.PropTypes.bool,
|
collapsedRhs: PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
phases: {
|
phases: {
|
||||||
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import Matrix from 'matrix-js-sdk';
|
import Matrix from 'matrix-js-sdk';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
@ -28,7 +29,7 @@ const FilePanel = React.createClass({
|
|||||||
displayName: 'FilePanel',
|
displayName: 'FilePanel',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
roomId: React.PropTypes.string.isRequired,
|
roomId: PropTypes.string.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -31,6 +31,7 @@ import GroupStoreCache from '../../stores/GroupStoreCache';
|
|||||||
import GroupStore from '../../stores/GroupStore';
|
import GroupStore from '../../stores/GroupStore';
|
||||||
import { showGroupAddRoomDialog } from '../../GroupAddressPicker';
|
import { showGroupAddRoomDialog } from '../../GroupAddressPicker';
|
||||||
import GeminiScrollbar from 'react-gemini-scrollbar';
|
import GeminiScrollbar from 'react-gemini-scrollbar';
|
||||||
|
import {makeGroupPermalink, makeUserPermalink} from "../../matrix-to";
|
||||||
|
|
||||||
const LONG_DESC_PLACEHOLDER = _td(
|
const LONG_DESC_PLACEHOLDER = _td(
|
||||||
`<h1>HTML for your community's page</h1>
|
`<h1>HTML for your community's page</h1>
|
||||||
@ -209,7 +210,7 @@ const FeaturedRoom = React.createClass({
|
|||||||
|
|
||||||
let permalink = null;
|
let permalink = null;
|
||||||
if (this.props.summaryInfo.profile && this.props.summaryInfo.profile.canonical_alias) {
|
if (this.props.summaryInfo.profile && this.props.summaryInfo.profile.canonical_alias) {
|
||||||
permalink = 'https://matrix.to/#/' + this.props.summaryInfo.profile.canonical_alias;
|
permalink = makeGroupPermalink(this.props.summaryInfo.profile.canonical_alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
let roomNameNode = null;
|
let roomNameNode = null;
|
||||||
@ -366,7 +367,7 @@ const FeaturedUser = React.createClass({
|
|||||||
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
||||||
const name = this.props.summaryInfo.displayname || this.props.summaryInfo.user_id;
|
const name = this.props.summaryInfo.displayname || this.props.summaryInfo.user_id;
|
||||||
|
|
||||||
const permalink = 'https://matrix.to/#/' + this.props.summaryInfo.user_id;
|
const permalink = makeUserPermalink(this.props.summaryInfo.user_id);
|
||||||
const userNameNode = <a href={permalink} onClick={this.onClick}>{ name }</a>;
|
const userNameNode = <a href={permalink} onClick={this.onClick}>{ name }</a>;
|
||||||
const httpUrl = MatrixClientPeg.get()
|
const httpUrl = MatrixClientPeg.get()
|
||||||
.mxcUrlToHttp(this.props.summaryInfo.avatar_url, 64, 64);
|
.mxcUrlToHttp(this.props.summaryInfo.avatar_url, 64, 64);
|
||||||
@ -390,7 +391,7 @@ const FeaturedUser = React.createClass({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const GroupContext = {
|
const GroupContext = {
|
||||||
groupStore: React.PropTypes.instanceOf(GroupStore).isRequired,
|
groupStore: PropTypes.instanceOf(GroupStore).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
CategoryRoomList.contextTypes = GroupContext;
|
CategoryRoomList.contextTypes = GroupContext;
|
||||||
@ -408,7 +409,7 @@ export default React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
childContextTypes: {
|
childContextTypes: {
|
||||||
groupStore: React.PropTypes.instanceOf(GroupStore),
|
groupStore: PropTypes.instanceOf(GroupStore),
|
||||||
},
|
},
|
||||||
|
|
||||||
getChildContext: function() {
|
getChildContext: function() {
|
||||||
|
@ -18,6 +18,7 @@ import Matrix from 'matrix-js-sdk';
|
|||||||
const InteractiveAuth = Matrix.InteractiveAuth;
|
const InteractiveAuth = Matrix.InteractiveAuth;
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import {getEntryComponentForLoginType} from '../views/login/InteractiveAuthEntryComponents';
|
import {getEntryComponentForLoginType} from '../views/login/InteractiveAuthEntryComponents';
|
||||||
|
|
||||||
@ -26,18 +27,18 @@ export default React.createClass({
|
|||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
// matrix client to use for UI auth requests
|
// matrix client to use for UI auth requests
|
||||||
matrixClient: React.PropTypes.object.isRequired,
|
matrixClient: PropTypes.object.isRequired,
|
||||||
|
|
||||||
// response from initial request. If not supplied, will do a request on
|
// response from initial request. If not supplied, will do a request on
|
||||||
// mount.
|
// mount.
|
||||||
authData: React.PropTypes.shape({
|
authData: PropTypes.shape({
|
||||||
flows: React.PropTypes.array,
|
flows: PropTypes.array,
|
||||||
params: React.PropTypes.object,
|
params: PropTypes.object,
|
||||||
session: React.PropTypes.string,
|
session: PropTypes.string,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// callback
|
// callback
|
||||||
makeRequest: React.PropTypes.func.isRequired,
|
makeRequest: PropTypes.func.isRequired,
|
||||||
|
|
||||||
// callback called when the auth process has finished,
|
// callback called when the auth process has finished,
|
||||||
// successfully or unsuccessfully.
|
// successfully or unsuccessfully.
|
||||||
@ -51,22 +52,22 @@ export default React.createClass({
|
|||||||
// the auth session.
|
// the auth session.
|
||||||
// * clientSecret {string} The client secret used in auth
|
// * clientSecret {string} The client secret used in auth
|
||||||
// sessions with the ID server.
|
// sessions with the ID server.
|
||||||
onAuthFinished: React.PropTypes.func.isRequired,
|
onAuthFinished: PropTypes.func.isRequired,
|
||||||
|
|
||||||
// Inputs provided by the user to the auth process
|
// Inputs provided by the user to the auth process
|
||||||
// and used by various stages. As passed to js-sdk
|
// and used by various stages. As passed to js-sdk
|
||||||
// interactive-auth
|
// interactive-auth
|
||||||
inputs: React.PropTypes.object,
|
inputs: PropTypes.object,
|
||||||
|
|
||||||
// As js-sdk interactive-auth
|
// As js-sdk interactive-auth
|
||||||
makeRegistrationUrl: React.PropTypes.func,
|
makeRegistrationUrl: PropTypes.func,
|
||||||
sessionId: React.PropTypes.string,
|
sessionId: PropTypes.string,
|
||||||
clientSecret: React.PropTypes.string,
|
clientSecret: PropTypes.string,
|
||||||
emailSid: React.PropTypes.string,
|
emailSid: PropTypes.string,
|
||||||
|
|
||||||
// If true, poll to see if the auth flow has been completed
|
// If true, poll to see if the auth flow has been completed
|
||||||
// out-of-band
|
// out-of-band
|
||||||
poll: React.PropTypes.bool,
|
poll: PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -18,6 +18,7 @@ limitations under the License.
|
|||||||
|
|
||||||
import * as Matrix from 'matrix-js-sdk';
|
import * as Matrix from 'matrix-js-sdk';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { KeyCode, isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard';
|
import { KeyCode, isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard';
|
||||||
import Notifier from '../../Notifier';
|
import Notifier from '../../Notifier';
|
||||||
@ -38,27 +39,27 @@ import SettingsStore from "../../settings/SettingsStore";
|
|||||||
*
|
*
|
||||||
* Components mounted below us can access the matrix client via the react context.
|
* Components mounted below us can access the matrix client via the react context.
|
||||||
*/
|
*/
|
||||||
export default React.createClass({
|
const LoggedInView = React.createClass({
|
||||||
displayName: 'LoggedInView',
|
displayName: 'LoggedInView',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
matrixClient: React.PropTypes.instanceOf(Matrix.MatrixClient).isRequired,
|
matrixClient: PropTypes.instanceOf(Matrix.MatrixClient).isRequired,
|
||||||
page_type: React.PropTypes.string.isRequired,
|
page_type: PropTypes.string.isRequired,
|
||||||
onRoomCreated: React.PropTypes.func,
|
onRoomCreated: PropTypes.func,
|
||||||
onUserSettingsClose: React.PropTypes.func,
|
onUserSettingsClose: PropTypes.func,
|
||||||
|
|
||||||
// Called with the credentials of a registered user (if they were a ROU that
|
// Called with the credentials of a registered user (if they were a ROU that
|
||||||
// transitioned to PWLU)
|
// transitioned to PWLU)
|
||||||
onRegistered: React.PropTypes.func,
|
onRegistered: PropTypes.func,
|
||||||
|
|
||||||
teamToken: React.PropTypes.string,
|
teamToken: PropTypes.string,
|
||||||
|
|
||||||
// and lots and lots of other stuff.
|
// and lots and lots of other stuff.
|
||||||
},
|
},
|
||||||
|
|
||||||
childContextTypes: {
|
childContextTypes: {
|
||||||
matrixClient: React.PropTypes.instanceOf(Matrix.MatrixClient),
|
matrixClient: PropTypes.instanceOf(Matrix.MatrixClient),
|
||||||
authCache: React.PropTypes.object,
|
authCache: PropTypes.object,
|
||||||
},
|
},
|
||||||
|
|
||||||
getChildContext: function() {
|
getChildContext: function() {
|
||||||
@ -331,7 +332,6 @@ export default React.createClass({
|
|||||||
<div className={bodyClasses}>
|
<div className={bodyClasses}>
|
||||||
{ SettingsStore.isFeatureEnabled("feature_tag_panel") ? <TagPanel /> : <div /> }
|
{ SettingsStore.isFeatureEnabled("feature_tag_panel") ? <TagPanel /> : <div /> }
|
||||||
<LeftPanel
|
<LeftPanel
|
||||||
selectedRoom={this.props.currentRoomId}
|
|
||||||
collapsed={this.props.collapseLhs || false}
|
collapsed={this.props.collapseLhs || false}
|
||||||
disabled={this.props.leftDisabled}
|
disabled={this.props.leftDisabled}
|
||||||
/>
|
/>
|
||||||
@ -344,3 +344,5 @@ export default React.createClass({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default LoggedInView;
|
||||||
|
@ -19,6 +19,7 @@ limitations under the License.
|
|||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import Matrix from "matrix-js-sdk";
|
import Matrix from "matrix-js-sdk";
|
||||||
|
|
||||||
import Analytics from "../../Analytics";
|
import Analytics from "../../Analytics";
|
||||||
@ -83,7 +84,7 @@ const ONBOARDING_FLOW_STARTERS = [
|
|||||||
'view_create_group',
|
'view_create_group',
|
||||||
];
|
];
|
||||||
|
|
||||||
module.exports = React.createClass({
|
export default React.createClass({
|
||||||
// we export this so that the integration tests can use it :-S
|
// we export this so that the integration tests can use it :-S
|
||||||
statics: {
|
statics: {
|
||||||
VIEWS: VIEWS,
|
VIEWS: VIEWS,
|
||||||
@ -92,38 +93,38 @@ module.exports = React.createClass({
|
|||||||
displayName: 'MatrixChat',
|
displayName: 'MatrixChat',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
config: React.PropTypes.object,
|
config: PropTypes.object,
|
||||||
ConferenceHandler: React.PropTypes.any,
|
ConferenceHandler: PropTypes.any,
|
||||||
onNewScreen: React.PropTypes.func,
|
onNewScreen: PropTypes.func,
|
||||||
registrationUrl: React.PropTypes.string,
|
registrationUrl: PropTypes.string,
|
||||||
enableGuest: React.PropTypes.bool,
|
enableGuest: PropTypes.bool,
|
||||||
|
|
||||||
// the queryParams extracted from the [real] query-string of the URI
|
// the queryParams extracted from the [real] query-string of the URI
|
||||||
realQueryParams: React.PropTypes.object,
|
realQueryParams: PropTypes.object,
|
||||||
|
|
||||||
// the initial queryParams extracted from the hash-fragment of the URI
|
// the initial queryParams extracted from the hash-fragment of the URI
|
||||||
startingFragmentQueryParams: React.PropTypes.object,
|
startingFragmentQueryParams: PropTypes.object,
|
||||||
|
|
||||||
// called when we have completed a token login
|
// called when we have completed a token login
|
||||||
onTokenLoginCompleted: React.PropTypes.func,
|
onTokenLoginCompleted: PropTypes.func,
|
||||||
|
|
||||||
// Represents the screen to display as a result of parsing the initial
|
// Represents the screen to display as a result of parsing the initial
|
||||||
// window.location
|
// window.location
|
||||||
initialScreenAfterLogin: React.PropTypes.shape({
|
initialScreenAfterLogin: PropTypes.shape({
|
||||||
screen: React.PropTypes.string.isRequired,
|
screen: PropTypes.string.isRequired,
|
||||||
params: React.PropTypes.object,
|
params: PropTypes.object,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// displayname, if any, to set on the device when logging
|
// displayname, if any, to set on the device when logging
|
||||||
// in/registering.
|
// in/registering.
|
||||||
defaultDeviceDisplayName: React.PropTypes.string,
|
defaultDeviceDisplayName: PropTypes.string,
|
||||||
|
|
||||||
// A function that makes a registration URL
|
// A function that makes a registration URL
|
||||||
makeRegistrationUrl: React.PropTypes.func.isRequired,
|
makeRegistrationUrl: PropTypes.func.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
childContextTypes: {
|
childContextTypes: {
|
||||||
appConfig: React.PropTypes.object,
|
appConfig: PropTypes.object,
|
||||||
},
|
},
|
||||||
|
|
||||||
AuxPanel: {
|
AuxPanel: {
|
||||||
@ -846,16 +847,36 @@ module.exports = React.createClass({
|
|||||||
}).close;
|
}).close;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_leaveRoomWarnings: function(roomId) {
|
||||||
|
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
|
||||||
|
// Show a warning if there are additional complications.
|
||||||
|
const joinRules = roomToLeave.currentState.getStateEvents('m.room.join_rules', '');
|
||||||
|
const warnings = [];
|
||||||
|
if (joinRules) {
|
||||||
|
const rule = joinRules.getContent().join_rule;
|
||||||
|
if (rule !== "public") {
|
||||||
|
warnings.push((
|
||||||
|
<span className="warning" key="non_public_warning">
|
||||||
|
{ _t("This room is not public. You will not be able to rejoin without an invite.") }
|
||||||
|
</span>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return warnings;
|
||||||
|
},
|
||||||
|
|
||||||
_leaveRoom: function(roomId) {
|
_leaveRoom: function(roomId) {
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
|
||||||
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
|
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
|
||||||
|
const warnings = this._leaveRoomWarnings(roomId);
|
||||||
|
|
||||||
Modal.createTrackedDialog('Leave room', '', QuestionDialog, {
|
Modal.createTrackedDialog('Leave room', '', QuestionDialog, {
|
||||||
title: _t("Leave room"),
|
title: _t("Leave room"),
|
||||||
description: (
|
description: (
|
||||||
<span>
|
<span>
|
||||||
{ _t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name}) }
|
{ _t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name}) }
|
||||||
|
{ warnings }
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
onFinished: (shouldLeave) => {
|
onFinished: (shouldLeave) => {
|
||||||
@ -1065,10 +1086,10 @@ module.exports = React.createClass({
|
|||||||
// this if we are not scrolled up in the view. To find out, delegate to
|
// this if we are not scrolled up in the view. To find out, delegate to
|
||||||
// the timeline panel. If the timeline panel doesn't exist, then we assume
|
// the timeline panel. If the timeline panel doesn't exist, then we assume
|
||||||
// it is safe to reset the timeline.
|
// it is safe to reset the timeline.
|
||||||
if (!self.refs.loggedInView) {
|
if (!self._loggedInView || !self._loggedInView.child) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return self.refs.loggedInView.canResetTimelineInRoom(roomId);
|
return self._loggedInView.child.canResetTimelineInRoom(roomId);
|
||||||
});
|
});
|
||||||
|
|
||||||
cli.on('sync', function(state, prevState) {
|
cli.on('sync', function(state, prevState) {
|
||||||
@ -1487,6 +1508,10 @@ module.exports = React.createClass({
|
|||||||
return this.props.makeRegistrationUrl(params);
|
return this.props.makeRegistrationUrl(params);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_collectLoggedInView: function(ref) {
|
||||||
|
this._loggedInView = ref;
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
// console.log(`Rendering MatrixChat with view ${this.state.view}`);
|
// console.log(`Rendering MatrixChat with view ${this.state.view}`);
|
||||||
|
|
||||||
@ -1519,7 +1544,7 @@ module.exports = React.createClass({
|
|||||||
*/
|
*/
|
||||||
const LoggedInView = sdk.getComponent('structures.LoggedInView');
|
const LoggedInView = sdk.getComponent('structures.LoggedInView');
|
||||||
return (
|
return (
|
||||||
<LoggedInView ref="loggedInView" matrixClient={MatrixClientPeg.get()}
|
<LoggedInView ref={this._collectLoggedInView} matrixClient={MatrixClientPeg.get()}
|
||||||
onRoomCreated={this.onRoomCreated}
|
onRoomCreated={this.onRoomCreated}
|
||||||
onUserSettingsClose={this.onUserSettingsClose}
|
onUserSettingsClose={this.onUserSettingsClose}
|
||||||
onRegistered={this.onRegistered}
|
onRegistered={this.onRegistered}
|
||||||
|
@ -16,15 +16,15 @@ 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 shouldHideEvent from '../../shouldHideEvent';
|
import shouldHideEvent from '../../shouldHideEvent';
|
||||||
|
import {wantsDateSeparator} from '../../DateUtils';
|
||||||
import dis from "../../dispatcher";
|
import dis from "../../dispatcher";
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
|
|
||||||
import MatrixClientPeg from '../../MatrixClientPeg';
|
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||||
|
|
||||||
const MILLIS_IN_DAY = 86400000;
|
|
||||||
|
|
||||||
/* (almost) stateless UI component which builds the event tiles in the room timeline.
|
/* (almost) stateless UI component which builds the event tiles in the room timeline.
|
||||||
*/
|
*/
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
@ -32,63 +32,63 @@ module.exports = React.createClass({
|
|||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
// true to give the component a 'display: none' style.
|
// true to give the component a 'display: none' style.
|
||||||
hidden: React.PropTypes.bool,
|
hidden: PropTypes.bool,
|
||||||
|
|
||||||
// true to show a spinner at the top of the timeline to indicate
|
// true to show a spinner at the top of the timeline to indicate
|
||||||
// back-pagination in progress
|
// back-pagination in progress
|
||||||
backPaginating: React.PropTypes.bool,
|
backPaginating: PropTypes.bool,
|
||||||
|
|
||||||
// true to show a spinner at the end of the timeline to indicate
|
// true to show a spinner at the end of the timeline to indicate
|
||||||
// forward-pagination in progress
|
// forward-pagination in progress
|
||||||
forwardPaginating: React.PropTypes.bool,
|
forwardPaginating: PropTypes.bool,
|
||||||
|
|
||||||
// the list of MatrixEvents to display
|
// the list of MatrixEvents to display
|
||||||
events: React.PropTypes.array.isRequired,
|
events: PropTypes.array.isRequired,
|
||||||
|
|
||||||
// ID of an event to highlight. If undefined, no event will be highlighted.
|
// ID of an event to highlight. If undefined, no event will be highlighted.
|
||||||
highlightedEventId: React.PropTypes.string,
|
highlightedEventId: PropTypes.string,
|
||||||
|
|
||||||
// Should we show URL Previews
|
// Should we show URL Previews
|
||||||
showUrlPreview: React.PropTypes.bool,
|
showUrlPreview: PropTypes.bool,
|
||||||
|
|
||||||
// event after which we should show a read marker
|
// event after which we should show a read marker
|
||||||
readMarkerEventId: React.PropTypes.string,
|
readMarkerEventId: PropTypes.string,
|
||||||
|
|
||||||
// whether the read marker should be visible
|
// whether the read marker should be visible
|
||||||
readMarkerVisible: React.PropTypes.bool,
|
readMarkerVisible: PropTypes.bool,
|
||||||
|
|
||||||
// the userid of our user. This is used to suppress the read marker
|
// the userid of our user. This is used to suppress the read marker
|
||||||
// for pending messages.
|
// for pending messages.
|
||||||
ourUserId: React.PropTypes.string,
|
ourUserId: PropTypes.string,
|
||||||
|
|
||||||
// true to suppress the date at the start of the timeline
|
// true to suppress the date at the start of the timeline
|
||||||
suppressFirstDateSeparator: React.PropTypes.bool,
|
suppressFirstDateSeparator: PropTypes.bool,
|
||||||
|
|
||||||
// whether to show read receipts
|
// whether to show read receipts
|
||||||
showReadReceipts: React.PropTypes.bool,
|
showReadReceipts: PropTypes.bool,
|
||||||
|
|
||||||
// true if updates to the event list should cause the scroll panel to
|
// true if updates to the event list should cause the scroll panel to
|
||||||
// scroll down when we are at the bottom of the window. See ScrollPanel
|
// scroll down when we are at the bottom of the window. See ScrollPanel
|
||||||
// for more details.
|
// for more details.
|
||||||
stickyBottom: React.PropTypes.bool,
|
stickyBottom: PropTypes.bool,
|
||||||
|
|
||||||
// callback which is called when the panel is scrolled.
|
// callback which is called when the panel is scrolled.
|
||||||
onScroll: React.PropTypes.func,
|
onScroll: PropTypes.func,
|
||||||
|
|
||||||
// callback which is called when more content is needed.
|
// callback which is called when more content is needed.
|
||||||
onFillRequest: React.PropTypes.func,
|
onFillRequest: PropTypes.func,
|
||||||
|
|
||||||
// className for the panel
|
// className for the panel
|
||||||
className: React.PropTypes.string.isRequired,
|
className: PropTypes.string.isRequired,
|
||||||
|
|
||||||
// shape parameter to be passed to EventTiles
|
// shape parameter to be passed to EventTiles
|
||||||
tileShape: React.PropTypes.string,
|
tileShape: PropTypes.string,
|
||||||
|
|
||||||
// show twelve hour timestamps
|
// show twelve hour timestamps
|
||||||
isTwelveHour: React.PropTypes.bool,
|
isTwelveHour: PropTypes.bool,
|
||||||
|
|
||||||
// show timestamps always
|
// show timestamps always
|
||||||
alwaysShowTimestamps: React.PropTypes.bool,
|
alwaysShowTimestamps: PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
@ -325,7 +325,7 @@ module.exports = React.createClass({
|
|||||||
const key = "membereventlistsummary-" + (prevEvent ? mxEv.getId() : "initial");
|
const key = "membereventlistsummary-" + (prevEvent ? mxEv.getId() : "initial");
|
||||||
|
|
||||||
if (this._wantsDateSeparator(prevEvent, mxEv.getDate())) {
|
if (this._wantsDateSeparator(prevEvent, mxEv.getDate())) {
|
||||||
const dateSeparator = <li key={ts1+'~'}><DateSeparator key={ts1+'~'} ts={ts1} showTwelveHour={this.props.isTwelveHour} /></li>;
|
const dateSeparator = <li key={ts1+'~'}><DateSeparator key={ts1+'~'} ts={ts1} /></li>;
|
||||||
ret.push(dateSeparator);
|
ret.push(dateSeparator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -479,7 +479,7 @@ module.exports = React.createClass({
|
|||||||
|
|
||||||
// do we need a date separator since the last event?
|
// do we need a date separator since the last event?
|
||||||
if (this._wantsDateSeparator(prevEvent, eventDate)) {
|
if (this._wantsDateSeparator(prevEvent, eventDate)) {
|
||||||
const dateSeparator = <li key={ts1}><DateSeparator key={ts1} ts={ts1} showTwelveHour={this.props.isTwelveHour} /></li>;
|
const dateSeparator = <li key={ts1}><DateSeparator key={ts1} ts={ts1} /></li>;
|
||||||
ret.push(dateSeparator);
|
ret.push(dateSeparator);
|
||||||
continuation = false;
|
continuation = false;
|
||||||
}
|
}
|
||||||
@ -522,17 +522,7 @@ module.exports = React.createClass({
|
|||||||
// here.
|
// here.
|
||||||
return !this.props.suppressFirstDateSeparator;
|
return !this.props.suppressFirstDateSeparator;
|
||||||
}
|
}
|
||||||
const prevEventDate = prevEvent.getDate();
|
return wantsDateSeparator(prevEvent.getDate(), nextEventDate);
|
||||||
if (!nextEventDate || !prevEventDate) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Return early for events that are > 24h apart
|
|
||||||
if (Math.abs(prevEvent.getTs() - nextEventDate.getTime()) > MILLIS_IN_DAY) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare weekdays
|
|
||||||
return prevEventDate.getDay() !== nextEventDate.getDay();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// get a list of read receipts that should be shown next to this event
|
// get a list of read receipts that should be shown next to this event
|
||||||
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import GeminiScrollbar from 'react-gemini-scrollbar';
|
import GeminiScrollbar from 'react-gemini-scrollbar';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
@ -26,7 +27,7 @@ export default withMatrixClient(React.createClass({
|
|||||||
displayName: 'MyGroups',
|
displayName: 'MyGroups',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
matrixClient: React.PropTypes.object.isRequired,
|
matrixClient: PropTypes.object.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import Matrix from 'matrix-js-sdk';
|
import Matrix from 'matrix-js-sdk';
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
@ -23,7 +24,7 @@ import WhoIsTyping from '../../WhoIsTyping';
|
|||||||
import MatrixClientPeg from '../../MatrixClientPeg';
|
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||||
import MemberAvatar from '../views/avatars/MemberAvatar';
|
import MemberAvatar from '../views/avatars/MemberAvatar';
|
||||||
import Resend from '../../Resend';
|
import Resend from '../../Resend';
|
||||||
import { showUnknownDeviceDialogForMessages } from '../../cryptodevices';
|
import * as cryptodevices from '../../cryptodevices';
|
||||||
|
|
||||||
const STATUS_BAR_HIDDEN = 0;
|
const STATUS_BAR_HIDDEN = 0;
|
||||||
const STATUS_BAR_EXPANDED = 1;
|
const STATUS_BAR_EXPANDED = 1;
|
||||||
@ -41,59 +42,59 @@ module.exports = React.createClass({
|
|||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
// the room this statusbar is representing.
|
// the room this statusbar is representing.
|
||||||
room: React.PropTypes.object.isRequired,
|
room: PropTypes.object.isRequired,
|
||||||
|
|
||||||
// the number of messages which have arrived since we've been scrolled up
|
// the number of messages which have arrived since we've been scrolled up
|
||||||
numUnreadMessages: React.PropTypes.number,
|
numUnreadMessages: PropTypes.number,
|
||||||
|
|
||||||
// this is true if we are fully scrolled-down, and are looking at
|
// this is true if we are fully scrolled-down, and are looking at
|
||||||
// the end of the live timeline.
|
// the end of the live timeline.
|
||||||
atEndOfLiveTimeline: React.PropTypes.bool,
|
atEndOfLiveTimeline: PropTypes.bool,
|
||||||
|
|
||||||
// This is true when the user is alone in the room, but has also sent a message.
|
// This is true when the user is alone in the room, but has also sent a message.
|
||||||
// Used to suggest to the user to invite someone
|
// Used to suggest to the user to invite someone
|
||||||
sentMessageAndIsAlone: React.PropTypes.bool,
|
sentMessageAndIsAlone: PropTypes.bool,
|
||||||
|
|
||||||
// true if there is an active call in this room (means we show
|
// true if there is an active call in this room (means we show
|
||||||
// the 'Active Call' text in the status bar if there is nothing
|
// the 'Active Call' text in the status bar if there is nothing
|
||||||
// more interesting)
|
// more interesting)
|
||||||
hasActiveCall: React.PropTypes.bool,
|
hasActiveCall: PropTypes.bool,
|
||||||
|
|
||||||
// Number of names to display in typing indication. E.g. set to 3, will
|
// Number of names to display in typing indication. E.g. set to 3, will
|
||||||
// result in "X, Y, Z and 100 others are typing."
|
// result in "X, Y, Z and 100 others are typing."
|
||||||
whoIsTypingLimit: React.PropTypes.number,
|
whoIsTypingLimit: PropTypes.number,
|
||||||
|
|
||||||
// callback for when the user clicks on the 'resend all' button in the
|
// callback for when the user clicks on the 'resend all' button in the
|
||||||
// 'unsent messages' bar
|
// 'unsent messages' bar
|
||||||
onResendAllClick: React.PropTypes.func,
|
onResendAllClick: PropTypes.func,
|
||||||
|
|
||||||
// callback for when the user clicks on the 'cancel all' button in the
|
// callback for when the user clicks on the 'cancel all' button in the
|
||||||
// 'unsent messages' bar
|
// 'unsent messages' bar
|
||||||
onCancelAllClick: React.PropTypes.func,
|
onCancelAllClick: PropTypes.func,
|
||||||
|
|
||||||
// callback for when the user clicks on the 'invite others' button in the
|
// callback for when the user clicks on the 'invite others' button in the
|
||||||
// 'you are alone' bar
|
// 'you are alone' bar
|
||||||
onInviteClick: React.PropTypes.func,
|
onInviteClick: PropTypes.func,
|
||||||
|
|
||||||
// callback for when the user clicks on the 'stop warning me' button in the
|
// callback for when the user clicks on the 'stop warning me' button in the
|
||||||
// 'you are alone' bar
|
// 'you are alone' bar
|
||||||
onStopWarningClick: React.PropTypes.func,
|
onStopWarningClick: PropTypes.func,
|
||||||
|
|
||||||
// callback for when the user clicks on the 'scroll to bottom' button
|
// callback for when the user clicks on the 'scroll to bottom' button
|
||||||
onScrollToBottomClick: React.PropTypes.func,
|
onScrollToBottomClick: PropTypes.func,
|
||||||
|
|
||||||
// callback for when we do something that changes the size of the
|
// callback for when we do something that changes the size of the
|
||||||
// status bar. This is used to trigger a re-layout in the parent
|
// status bar. This is used to trigger a re-layout in the parent
|
||||||
// component.
|
// component.
|
||||||
onResize: React.PropTypes.func,
|
onResize: PropTypes.func,
|
||||||
|
|
||||||
// callback for when the status bar can be hidden from view, as it is
|
// callback for when the status bar can be hidden from view, as it is
|
||||||
// not displaying anything
|
// not displaying anything
|
||||||
onHidden: React.PropTypes.func,
|
onHidden: PropTypes.func,
|
||||||
|
|
||||||
// callback for when the status bar is displaying something and should
|
// callback for when the status bar is displaying something and should
|
||||||
// be visible
|
// be visible
|
||||||
onVisible: React.PropTypes.func,
|
onVisible: PropTypes.func,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
@ -147,6 +148,13 @@ module.exports = React.createClass({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_onSendWithoutVerifyingClick: function() {
|
||||||
|
cryptodevices.getUnknownDevicesForRoom(MatrixClientPeg.get(), this.props.room).then((devices) => {
|
||||||
|
cryptodevices.markAllDevicesKnown(MatrixClientPeg.get(), devices);
|
||||||
|
Resend.resendUnsentEvents(this.props.room);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
_onResendAllClick: function() {
|
_onResendAllClick: function() {
|
||||||
Resend.resendUnsentEvents(this.props.room);
|
Resend.resendUnsentEvents(this.props.room);
|
||||||
},
|
},
|
||||||
@ -156,7 +164,7 @@ module.exports = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_onShowDevicesClick: function() {
|
_onShowDevicesClick: function() {
|
||||||
showUnknownDeviceDialogForMessages(MatrixClientPeg.get(), this.props.room);
|
cryptodevices.showUnknownDeviceDialogForMessages(MatrixClientPeg.get(), this.props.room);
|
||||||
},
|
},
|
||||||
|
|
||||||
_onRoomLocalEchoUpdated: function(event, room, oldEventId, oldStatus) {
|
_onRoomLocalEchoUpdated: function(event, room, oldEventId, oldStatus) {
|
||||||
@ -169,8 +177,10 @@ module.exports = React.createClass({
|
|||||||
|
|
||||||
// Check whether current size is greater than 0, if yes call props.onVisible
|
// Check whether current size is greater than 0, if yes call props.onVisible
|
||||||
_checkSize: function() {
|
_checkSize: function() {
|
||||||
if (this.props.onVisible && this._getSize()) {
|
if (this._getSize()) {
|
||||||
this.props.onVisible();
|
if (this.props.onVisible) this.props.onVisible();
|
||||||
|
} else {
|
||||||
|
if (this.props.onHidden) this.props.onHidden();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -286,10 +296,11 @@ module.exports = React.createClass({
|
|||||||
if (hasUDE) {
|
if (hasUDE) {
|
||||||
title = _t("Message not sent due to unknown devices being present");
|
title = _t("Message not sent due to unknown devices being present");
|
||||||
content = _t(
|
content = _t(
|
||||||
"<showDevicesText>Show devices</showDevicesText> or <cancelText>cancel all</cancelText>.",
|
"<showDevicesText>Show devices</showDevicesText>, <sendAnywayText>send anyway</sendAnywayText> or <cancelText>cancel</cancelText>.",
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
'showDevicesText': (sub) => <a className="mx_RoomStatusBar_resend_link" key="resend" onClick={this._onShowDevicesClick}>{ sub }</a>,
|
'showDevicesText': (sub) => <a className="mx_RoomStatusBar_resend_link" key="resend" onClick={this._onShowDevicesClick}>{ sub }</a>,
|
||||||
|
'sendAnywayText': (sub) => <a className="mx_RoomStatusBar_resend_link" key="sendAnyway" onClick={this._onSendWithoutVerifyingClick}>{ sub }</a>,
|
||||||
'cancelText': (sub) => <a className="mx_RoomStatusBar_resend_link" key="cancel" onClick={this._onCancelAllClick}>{ sub }</a>,
|
'cancelText': (sub) => <a className="mx_RoomStatusBar_resend_link" key="cancel" onClick={this._onCancelAllClick}>{ sub }</a>,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -302,11 +313,11 @@ module.exports = React.createClass({
|
|||||||
) {
|
) {
|
||||||
title = unsentMessages[0].error.data.error;
|
title = unsentMessages[0].error.data.error;
|
||||||
} else {
|
} else {
|
||||||
title = _t("Some of your messages have not been sent.");
|
title = _t('%(count)s of your messages have not been sent.', { count: unsentMessages.length });
|
||||||
}
|
}
|
||||||
content = _t("<resendText>Resend all</resendText> or <cancelText>cancel all</cancelText> now. " +
|
content = _t("%(count)s <resendText>Resend all</resendText> or <cancelText>cancel all</cancelText> now. " +
|
||||||
"You can also select individual messages to resend or cancel.",
|
"You can also select individual messages to resend or cancel.",
|
||||||
{},
|
{ count: unsentMessages.length },
|
||||||
{
|
{
|
||||||
'resendText': (sub) =>
|
'resendText': (sub) =>
|
||||||
<a className="mx_RoomStatusBar_resend_link" key="resend" onClick={this._onResendAllClick}>{ sub }</a>,
|
<a className="mx_RoomStatusBar_resend_link" key="resend" onClick={this._onResendAllClick}>{ sub }</a>,
|
||||||
|
@ -24,6 +24,7 @@ import shouldHideEvent from "../../shouldHideEvent";
|
|||||||
|
|
||||||
const React = require("react");
|
const React = require("react");
|
||||||
const ReactDOM = require("react-dom");
|
const ReactDOM = require("react-dom");
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
const classNames = require("classnames");
|
const classNames = require("classnames");
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
@ -58,18 +59,18 @@ if (DEBUG) {
|
|||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'RoomView',
|
displayName: 'RoomView',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
ConferenceHandler: React.PropTypes.any,
|
ConferenceHandler: PropTypes.any,
|
||||||
|
|
||||||
// Called with the credentials of a registered user (if they were a ROU that
|
// Called with the credentials of a registered user (if they were a ROU that
|
||||||
// transitioned to PWLU)
|
// transitioned to PWLU)
|
||||||
onRegistered: React.PropTypes.func,
|
onRegistered: PropTypes.func,
|
||||||
|
|
||||||
// An object representing a third party invite to join this room
|
// An object representing a third party invite to join this room
|
||||||
// Fields:
|
// Fields:
|
||||||
// * inviteSignUrl (string) The URL used to join this room from an email invite
|
// * inviteSignUrl (string) The URL used to join this room from an email invite
|
||||||
// (given as part of the link in the invite email)
|
// (given as part of the link in the invite email)
|
||||||
// * invitedEmail (string) The email address that was invited to this room
|
// * invitedEmail (string) The email address that was invited to this room
|
||||||
thirdPartyInvite: React.PropTypes.object,
|
thirdPartyInvite: PropTypes.object,
|
||||||
|
|
||||||
// Any data about the room that would normally come from the Home Server
|
// Any data about the room that would normally come from the Home Server
|
||||||
// but has been passed out-of-band, eg. the room name and avatar URL
|
// but has been passed out-of-band, eg. the room name and avatar URL
|
||||||
@ -80,10 +81,10 @@ module.exports = React.createClass({
|
|||||||
// * avatarUrl (string) The mxc:// avatar URL for the room
|
// * avatarUrl (string) The mxc:// avatar URL for the room
|
||||||
// * inviterName (string) The display name of the person who
|
// * inviterName (string) The display name of the person who
|
||||||
// * invited us tovthe room
|
// * invited us tovthe room
|
||||||
oobData: React.PropTypes.object,
|
oobData: PropTypes.object,
|
||||||
|
|
||||||
// is the RightPanel collapsed?
|
// is the RightPanel collapsed?
|
||||||
collapsedRhs: React.PropTypes.bool,
|
collapsedRhs: PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
@ -854,9 +855,13 @@ module.exports = React.createClass({
|
|||||||
|
|
||||||
ev.dataTransfer.dropEffect = 'none';
|
ev.dataTransfer.dropEffect = 'none';
|
||||||
|
|
||||||
const items = ev.dataTransfer.items;
|
const items = [...ev.dataTransfer.items];
|
||||||
if (items.length == 1) {
|
if (items.length >= 1) {
|
||||||
if (items[0].kind == 'file') {
|
const isDraggingFiles = items.every(function(item) {
|
||||||
|
return item.kind == 'file';
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isDraggingFiles) {
|
||||||
this.setState({ draggingFile: true });
|
this.setState({ draggingFile: true });
|
||||||
ev.dataTransfer.dropEffect = 'copy';
|
ev.dataTransfer.dropEffect = 'copy';
|
||||||
}
|
}
|
||||||
@ -867,10 +872,8 @@ module.exports = React.createClass({
|
|||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
this.setState({ draggingFile: false });
|
this.setState({ draggingFile: false });
|
||||||
const files = ev.dataTransfer.files;
|
const files = [...ev.dataTransfer.files];
|
||||||
if (files.length == 1) {
|
files.forEach(this.uploadFile);
|
||||||
this.uploadFile(files[0]);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onDragLeaveOrEnd: function(ev) {
|
onDragLeaveOrEnd: function(ev) {
|
||||||
@ -1345,10 +1348,12 @@ module.exports = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
onStatusBarHidden: function() {
|
onStatusBarHidden: function() {
|
||||||
if (this.unmounted) return;
|
// This is currently not desired as it is annoying if it keeps expanding and collapsing
|
||||||
|
// TODO: Find a less annoying way of hiding the status bar
|
||||||
|
/*if (this.unmounted) return;
|
||||||
this.setState({
|
this.setState({
|
||||||
statusBarVisible: false,
|
statusBarVisible: false,
|
||||||
});
|
});*/
|
||||||
},
|
},
|
||||||
|
|
||||||
showSettings: function(show) {
|
showSettings: function(show) {
|
||||||
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||||||
|
|
||||||
const React = require("react");
|
const React = require("react");
|
||||||
const ReactDOM = require("react-dom");
|
const ReactDOM = require("react-dom");
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
const GeminiScrollbar = require('react-gemini-scrollbar');
|
const GeminiScrollbar = require('react-gemini-scrollbar');
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
import { KeyCode } from '../../Keyboard';
|
import { KeyCode } from '../../Keyboard';
|
||||||
@ -86,7 +87,7 @@ module.exports = React.createClass({
|
|||||||
* scroll down to show the new element, rather than preserving the
|
* scroll down to show the new element, rather than preserving the
|
||||||
* existing view.
|
* existing view.
|
||||||
*/
|
*/
|
||||||
stickyBottom: React.PropTypes.bool,
|
stickyBottom: PropTypes.bool,
|
||||||
|
|
||||||
/* startAtBottom: if set to true, the view is assumed to start
|
/* startAtBottom: if set to true, the view is assumed to start
|
||||||
* scrolled to the bottom.
|
* scrolled to the bottom.
|
||||||
@ -95,7 +96,7 @@ module.exports = React.createClass({
|
|||||||
* behaviour stays the same for other uses of ScrollPanel.
|
* behaviour stays the same for other uses of ScrollPanel.
|
||||||
* If so, let's remove this parameter down the line.
|
* If so, let's remove this parameter down the line.
|
||||||
*/
|
*/
|
||||||
startAtBottom: React.PropTypes.bool,
|
startAtBottom: PropTypes.bool,
|
||||||
|
|
||||||
/* onFillRequest(backwards): a callback which is called on scroll when
|
/* onFillRequest(backwards): a callback which is called on scroll when
|
||||||
* the user nears the start (backwards = true) or end (backwards =
|
* the user nears the start (backwards = true) or end (backwards =
|
||||||
@ -110,7 +111,7 @@ module.exports = React.createClass({
|
|||||||
* directon (at this time) - which will stop the pagination cycle until
|
* directon (at this time) - which will stop the pagination cycle until
|
||||||
* the user scrolls again.
|
* the user scrolls again.
|
||||||
*/
|
*/
|
||||||
onFillRequest: React.PropTypes.func,
|
onFillRequest: PropTypes.func,
|
||||||
|
|
||||||
/* onUnfillRequest(backwards): a callback which is called on scroll when
|
/* onUnfillRequest(backwards): a callback which is called on scroll when
|
||||||
* there are children elements that are far out of view and could be removed
|
* there are children elements that are far out of view and could be removed
|
||||||
@ -121,24 +122,24 @@ module.exports = React.createClass({
|
|||||||
* first element to remove if removing from the front/bottom, and last element
|
* first element to remove if removing from the front/bottom, and last element
|
||||||
* to remove if removing from the back/top.
|
* to remove if removing from the back/top.
|
||||||
*/
|
*/
|
||||||
onUnfillRequest: React.PropTypes.func,
|
onUnfillRequest: PropTypes.func,
|
||||||
|
|
||||||
/* onScroll: a callback which is called whenever any scroll happens.
|
/* onScroll: a callback which is called whenever any scroll happens.
|
||||||
*/
|
*/
|
||||||
onScroll: React.PropTypes.func,
|
onScroll: PropTypes.func,
|
||||||
|
|
||||||
/* onResize: a callback which is called whenever the Gemini scroll
|
/* onResize: a callback which is called whenever the Gemini scroll
|
||||||
* panel is resized
|
* panel is resized
|
||||||
*/
|
*/
|
||||||
onResize: React.PropTypes.func,
|
onResize: PropTypes.func,
|
||||||
|
|
||||||
/* className: classnames to add to the top-level div
|
/* className: classnames to add to the top-level div
|
||||||
*/
|
*/
|
||||||
className: React.PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
|
||||||
/* style: styles to add to the top-level div
|
/* style: styles to add to the top-level div
|
||||||
*/
|
*/
|
||||||
style: React.PropTypes.object,
|
style: PropTypes.object,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -17,79 +17,17 @@ limitations under the License.
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
import { MatrixClient } from 'matrix-js-sdk';
|
||||||
import classNames from 'classnames';
|
import TagOrderStore from '../../stores/TagOrderStore';
|
||||||
import FilterStore from '../../stores/FilterStore';
|
|
||||||
import FlairStore from '../../stores/FlairStore';
|
import GroupActions from '../../actions/GroupActions';
|
||||||
|
import TagOrderActions from '../../actions/TagOrderActions';
|
||||||
|
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher';
|
||||||
import { isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard';
|
|
||||||
|
|
||||||
const TagTile = React.createClass({
|
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
||||||
displayName: 'TagTile',
|
|
||||||
|
|
||||||
propTypes: {
|
const TagPanel = React.createClass({
|
||||||
groupProfile: PropTypes.object,
|
|
||||||
},
|
|
||||||
|
|
||||||
contextTypes: {
|
|
||||||
matrixClient: React.PropTypes.instanceOf(MatrixClient).isRequired,
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState() {
|
|
||||||
return {
|
|
||||||
hover: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
onClick: function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'select_tag',
|
|
||||||
tag: this.props.groupProfile.groupId,
|
|
||||||
ctrlOrCmdKey: isOnlyCtrlOrCmdKeyEvent(e),
|
|
||||||
shiftKey: e.shiftKey,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onMouseOver: function() {
|
|
||||||
this.setState({hover: true});
|
|
||||||
},
|
|
||||||
|
|
||||||
onMouseOut: function() {
|
|
||||||
this.setState({hover: false});
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
|
||||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
|
||||||
const RoomTooltip = sdk.getComponent('rooms.RoomTooltip');
|
|
||||||
const profile = this.props.groupProfile || {};
|
|
||||||
const name = profile.name || profile.groupId;
|
|
||||||
const avatarHeight = 35;
|
|
||||||
|
|
||||||
const httpUrl = profile.avatarUrl ? this.context.matrixClient.mxcUrlToHttp(
|
|
||||||
profile.avatarUrl, avatarHeight, avatarHeight, "crop",
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
const className = classNames({
|
|
||||||
mx_TagTile: true,
|
|
||||||
mx_TagTile_selected: this.props.selected,
|
|
||||||
});
|
|
||||||
|
|
||||||
const tip = this.state.hover ?
|
|
||||||
<RoomTooltip className="mx_TagTile_tooltip" label={name} /> :
|
|
||||||
<div />;
|
|
||||||
return <AccessibleButton className={className} onClick={this.onClick}>
|
|
||||||
<div className="mx_TagTile_avatar" onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut}>
|
|
||||||
<BaseAvatar name={name} url={httpUrl} width={avatarHeight} height={avatarHeight} />
|
|
||||||
{ tip }
|
|
||||||
</div>
|
|
||||||
</AccessibleButton>;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default React.createClass({
|
|
||||||
displayName: 'TagPanel',
|
displayName: 'TagPanel',
|
||||||
|
|
||||||
contextTypes: {
|
contextTypes: {
|
||||||
@ -98,7 +36,7 @@ export default React.createClass({
|
|||||||
|
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
return {
|
return {
|
||||||
joinedGroupProfiles: [],
|
orderedTags: [],
|
||||||
selectedTags: [],
|
selectedTags: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -106,22 +44,25 @@ export default React.createClass({
|
|||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
this.unmounted = false;
|
this.unmounted = false;
|
||||||
this.context.matrixClient.on("Group.myMembership", this._onGroupMyMembership);
|
this.context.matrixClient.on("Group.myMembership", this._onGroupMyMembership);
|
||||||
|
this.context.matrixClient.on("sync", this.onClientSync);
|
||||||
|
|
||||||
this._filterStoreToken = FilterStore.addListener(() => {
|
this._tagOrderStoreToken = TagOrderStore.addListener(() => {
|
||||||
if (this.unmounted) {
|
if (this.unmounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedTags: FilterStore.getSelectedTags(),
|
orderedTags: TagOrderStore.getOrderedTags() || [],
|
||||||
|
selectedTags: TagOrderStore.getSelectedTags(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
// This could be done by anything with a matrix client
|
||||||
this._fetchJoinedRooms();
|
dis.dispatch(GroupActions.fetchJoinedGroups(this.context.matrixClient));
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.unmounted = true;
|
this.unmounted = true;
|
||||||
this.context.matrixClient.removeListener("Group.myMembership", this._onGroupMyMembership);
|
this.context.matrixClient.removeListener("Group.myMembership", this._onGroupMyMembership);
|
||||||
|
this.context.matrixClient.removeListener("sync", this.onClientSync);
|
||||||
if (this._filterStoreToken) {
|
if (this._filterStoreToken) {
|
||||||
this._filterStoreToken.remove();
|
this._filterStoreToken.remove();
|
||||||
}
|
}
|
||||||
@ -129,10 +70,22 @@ export default React.createClass({
|
|||||||
|
|
||||||
_onGroupMyMembership() {
|
_onGroupMyMembership() {
|
||||||
if (this.unmounted) return;
|
if (this.unmounted) return;
|
||||||
this._fetchJoinedRooms();
|
dis.dispatch(GroupActions.fetchJoinedGroups(this.context.matrixClient));
|
||||||
},
|
},
|
||||||
|
|
||||||
onClick() {
|
onClientSync(syncState, prevState) {
|
||||||
|
// Consider the client reconnected if there is no error with syncing.
|
||||||
|
// This means the state could be RECONNECTING, SYNCING or PREPARED.
|
||||||
|
const reconnected = syncState !== "ERROR" && prevState !== syncState;
|
||||||
|
if (reconnected) {
|
||||||
|
// Load joined groups
|
||||||
|
dis.dispatch(GroupActions.fetchJoinedGroups(this.context.matrixClient));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onClick(e) {
|
||||||
|
// Ignore clicks on children
|
||||||
|
if (e.target !== e.currentTarget) return;
|
||||||
dis.dispatch({action: 'deselect_tags'});
|
dis.dispatch({action: 'deselect_tags'});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -141,36 +94,58 @@ export default React.createClass({
|
|||||||
dis.dispatch({action: 'view_create_group'});
|
dis.dispatch({action: 'view_create_group'});
|
||||||
},
|
},
|
||||||
|
|
||||||
async _fetchJoinedRooms() {
|
onTagTileEndDrag(result) {
|
||||||
const joinedGroupResponse = await this.context.matrixClient.getJoinedGroups();
|
// Dragged to an invalid destination, not onto a droppable
|
||||||
const joinedGroupIds = joinedGroupResponse.groups;
|
if (!result.destination) {
|
||||||
const joinedGroupProfiles = await Promise.all(joinedGroupIds.map(
|
return;
|
||||||
(groupId) => FlairStore.getGroupProfileCached(this.context.matrixClient, groupId),
|
}
|
||||||
));
|
|
||||||
dis.dispatch({
|
// Dispatch synchronously so that the TagPanel receives an
|
||||||
action: 'all_tags',
|
// optimistic update from TagOrderStore before the previous
|
||||||
tags: joinedGroupIds,
|
// state is shown.
|
||||||
});
|
dis.dispatch(TagOrderActions.moveTag(
|
||||||
this.setState({joinedGroupProfiles});
|
this.context.matrixClient,
|
||||||
|
result.draggableId,
|
||||||
|
result.destination.index,
|
||||||
|
), true);
|
||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
||||||
const tags = this.state.joinedGroupProfiles.map((groupProfile, index) => {
|
const DNDTagTile = sdk.getComponent('elements.DNDTagTile');
|
||||||
return <TagTile
|
|
||||||
key={groupProfile.groupId + '_' + index}
|
const tags = this.state.orderedTags.map((tag, index) => {
|
||||||
groupProfile={groupProfile}
|
return <DNDTagTile
|
||||||
selected={this.state.selectedTags.includes(groupProfile.groupId)}
|
key={tag}
|
||||||
|
tag={tag}
|
||||||
|
index={index}
|
||||||
|
selected={this.state.selectedTags.includes(tag)}
|
||||||
/>;
|
/>;
|
||||||
});
|
});
|
||||||
return <div className="mx_TagPanel" onClick={this.onClick}>
|
return <div className="mx_TagPanel">
|
||||||
<div className="mx_TagPanel_tagTileContainer">
|
<DragDropContext onDragEnd={this.onTagTileEndDrag}>
|
||||||
{ tags }
|
<Droppable droppableId="tag-panel-droppable">
|
||||||
</div>
|
{ (provided, snapshot) => (
|
||||||
|
<div
|
||||||
|
className="mx_TagPanel_tagTileContainer"
|
||||||
|
ref={provided.innerRef}
|
||||||
|
// react-beautiful-dnd has a bug that emits a click to the parent
|
||||||
|
// of draggables upon dropping
|
||||||
|
// https://github.com/atlassian/react-beautiful-dnd/issues/273
|
||||||
|
// so we use onMouseDown here as a workaround.
|
||||||
|
onMouseDown={this.onClick}
|
||||||
|
>
|
||||||
|
{ tags }
|
||||||
|
{ provided.placeholder }
|
||||||
|
</div>
|
||||||
|
) }
|
||||||
|
</Droppable>
|
||||||
|
</DragDropContext>
|
||||||
<AccessibleButton className="mx_TagPanel_createGroupButton" onClick={this.onCreateGroupClick}>
|
<AccessibleButton className="mx_TagPanel_createGroupButton" onClick={this.onCreateGroupClick}>
|
||||||
<TintableSvg src="img/icons-create-room.svg" width="25" height="25" />
|
<TintableSvg src="img/icons-create-room.svg" width="25" height="25" />
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>;
|
</div>;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
export default TagPanel;
|
||||||
|
@ -19,6 +19,7 @@ import SettingsStore from "../../settings/SettingsStore";
|
|||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const ReactDOM = require("react-dom");
|
const ReactDOM = require("react-dom");
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
|
|
||||||
const Matrix = require("matrix-js-sdk");
|
const Matrix = require("matrix-js-sdk");
|
||||||
@ -58,49 +59,49 @@ var TimelinePanel = React.createClass({
|
|||||||
// representing. This may or may not have a room, depending on what it's
|
// representing. This may or may not have a room, depending on what it's
|
||||||
// a timeline representing. If it has a room, we maintain RRs etc for
|
// a timeline representing. If it has a room, we maintain RRs etc for
|
||||||
// that room.
|
// that room.
|
||||||
timelineSet: React.PropTypes.object.isRequired,
|
timelineSet: PropTypes.object.isRequired,
|
||||||
|
|
||||||
showReadReceipts: React.PropTypes.bool,
|
showReadReceipts: PropTypes.bool,
|
||||||
// Enable managing RRs and RMs. These require the timelineSet to have a room.
|
// Enable managing RRs and RMs. These require the timelineSet to have a room.
|
||||||
manageReadReceipts: React.PropTypes.bool,
|
manageReadReceipts: PropTypes.bool,
|
||||||
manageReadMarkers: React.PropTypes.bool,
|
manageReadMarkers: PropTypes.bool,
|
||||||
|
|
||||||
// true to give the component a 'display: none' style.
|
// true to give the component a 'display: none' style.
|
||||||
hidden: React.PropTypes.bool,
|
hidden: PropTypes.bool,
|
||||||
|
|
||||||
// ID of an event to highlight. If undefined, no event will be highlighted.
|
// ID of an event to highlight. If undefined, no event will be highlighted.
|
||||||
// typically this will be either 'eventId' or undefined.
|
// typically this will be either 'eventId' or undefined.
|
||||||
highlightedEventId: React.PropTypes.string,
|
highlightedEventId: PropTypes.string,
|
||||||
|
|
||||||
// id of an event to jump to. If not given, will go to the end of the
|
// id of an event to jump to. If not given, will go to the end of the
|
||||||
// live timeline.
|
// live timeline.
|
||||||
eventId: React.PropTypes.string,
|
eventId: PropTypes.string,
|
||||||
|
|
||||||
// where to position the event given by eventId, in pixels from the
|
// where to position the event given by eventId, in pixels from the
|
||||||
// bottom of the viewport. If not given, will try to put the event
|
// bottom of the viewport. If not given, will try to put the event
|
||||||
// half way down the viewport.
|
// half way down the viewport.
|
||||||
eventPixelOffset: React.PropTypes.number,
|
eventPixelOffset: PropTypes.number,
|
||||||
|
|
||||||
// Should we show URL Previews
|
// Should we show URL Previews
|
||||||
showUrlPreview: React.PropTypes.bool,
|
showUrlPreview: PropTypes.bool,
|
||||||
|
|
||||||
// callback which is called when the panel is scrolled.
|
// callback which is called when the panel is scrolled.
|
||||||
onScroll: React.PropTypes.func,
|
onScroll: PropTypes.func,
|
||||||
|
|
||||||
// callback which is called when the read-up-to mark is updated.
|
// callback which is called when the read-up-to mark is updated.
|
||||||
onReadMarkerUpdated: React.PropTypes.func,
|
onReadMarkerUpdated: PropTypes.func,
|
||||||
|
|
||||||
// maximum number of events to show in a timeline
|
// maximum number of events to show in a timeline
|
||||||
timelineCap: React.PropTypes.number,
|
timelineCap: PropTypes.number,
|
||||||
|
|
||||||
// classname to use for the messagepanel
|
// classname to use for the messagepanel
|
||||||
className: React.PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
|
||||||
// shape property to be passed to EventTiles
|
// shape property to be passed to EventTiles
|
||||||
tileShape: React.PropTypes.string,
|
tileShape: PropTypes.string,
|
||||||
|
|
||||||
// placeholder text to use if the timeline is empty
|
// placeholder text to use if the timeline is empty
|
||||||
empty: React.PropTypes.string,
|
empty: PropTypes.string,
|
||||||
},
|
},
|
||||||
|
|
||||||
statics: {
|
statics: {
|
||||||
@ -301,6 +302,8 @@ var TimelinePanel = React.createClass({
|
|||||||
|
|
||||||
// set off a pagination request.
|
// set off a pagination request.
|
||||||
onMessageListFillRequest: function(backwards) {
|
onMessageListFillRequest: function(backwards) {
|
||||||
|
if (!this._shouldPaginate()) return Promise.resolve(false);
|
||||||
|
|
||||||
const dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS;
|
const dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS;
|
||||||
const canPaginateKey = backwards ? 'canBackPaginate' : 'canForwardPaginate';
|
const canPaginateKey = backwards ? 'canBackPaginate' : 'canForwardPaginate';
|
||||||
const paginatingKey = backwards ? 'backPaginating' : 'forwardPaginating';
|
const paginatingKey = backwards ? 'backPaginating' : 'forwardPaginating';
|
||||||
@ -1090,6 +1093,17 @@ var TimelinePanel = React.createClass({
|
|||||||
}, this.props.onReadMarkerUpdated);
|
}, this.props.onReadMarkerUpdated);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_shouldPaginate: function() {
|
||||||
|
// don't try to paginate while events in the timeline are
|
||||||
|
// still being decrypted. We don't render events while they're
|
||||||
|
// being decrypted, so they don't take up space in the timeline.
|
||||||
|
// This means we can pull quite a lot of events into the timeline
|
||||||
|
// and end up trying to render a lot of events.
|
||||||
|
return !this.state.events.some((e) => {
|
||||||
|
return e.isBeingDecrypted();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const MessagePanel = sdk.getComponent("structures.MessagePanel");
|
const MessagePanel = sdk.getComponent("structures.MessagePanel");
|
||||||
const Loader = sdk.getComponent("elements.Spinner");
|
const Loader = sdk.getComponent("elements.Spinner");
|
||||||
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
const ContentMessages = require('../../ContentMessages');
|
const ContentMessages = require('../../ContentMessages');
|
||||||
const dis = require('../../dispatcher');
|
const dis = require('../../dispatcher');
|
||||||
const filesize = require('filesize');
|
const filesize = require('filesize');
|
||||||
@ -22,7 +23,7 @@ import { _t } from '../../languageHandler';
|
|||||||
|
|
||||||
module.exports = React.createClass({displayName: 'UploadBar',
|
module.exports = React.createClass({displayName: 'UploadBar',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
room: React.PropTypes.object,
|
room: PropTypes.object,
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
|
@ -19,6 +19,7 @@ import SettingsStore, {SettingLevel} from "../../settings/SettingsStore";
|
|||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const ReactDOM = require('react-dom');
|
const ReactDOM = require('react-dom');
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
const sdk = require('../../index');
|
const sdk = require('../../index');
|
||||||
const MatrixClientPeg = require("../../MatrixClientPeg");
|
const MatrixClientPeg = require("../../MatrixClientPeg");
|
||||||
const PlatformPeg = require("../../PlatformPeg");
|
const PlatformPeg = require("../../PlatformPeg");
|
||||||
@ -125,8 +126,8 @@ const THEMES = [
|
|||||||
|
|
||||||
const IgnoredUser = React.createClass({
|
const IgnoredUser = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
userId: React.PropTypes.string.isRequired,
|
userId: PropTypes.string.isRequired,
|
||||||
onUnignored: React.PropTypes.func.isRequired,
|
onUnignored: PropTypes.func.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
_onUnignoreClick: function() {
|
_onUnignoreClick: function() {
|
||||||
@ -155,16 +156,16 @@ module.exports = React.createClass({
|
|||||||
displayName: 'UserSettings',
|
displayName: 'UserSettings',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
onClose: React.PropTypes.func,
|
onClose: PropTypes.func,
|
||||||
// The brand string given when creating email pushers
|
// The brand string given when creating email pushers
|
||||||
brand: React.PropTypes.string,
|
brand: PropTypes.string,
|
||||||
|
|
||||||
// The base URL to use in the referral link. Defaults to window.location.origin.
|
// The base URL to use in the referral link. Defaults to window.location.origin.
|
||||||
referralBaseUrl: React.PropTypes.string,
|
referralBaseUrl: PropTypes.string,
|
||||||
|
|
||||||
// Team token for the referral link. If falsy, the referral section will
|
// Team token for the referral link. If falsy, the referral section will
|
||||||
// not appear
|
// not appear
|
||||||
teamToken: React.PropTypes.string,
|
teamToken: PropTypes.string,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
@ -375,7 +376,7 @@ module.exports = React.createClass({
|
|||||||
{ _t("For security, logging out will delete any end-to-end " +
|
{ _t("For security, logging out will delete any end-to-end " +
|
||||||
"encryption keys from this browser. If you want to be able " +
|
"encryption keys from this browser. If you want to be able " +
|
||||||
"to decrypt your conversation history from future Riot sessions, " +
|
"to decrypt your conversation history from future Riot sessions, " +
|
||||||
"please export your room keys for safe-keeping.") }.
|
"please export your room keys for safe-keeping.") }
|
||||||
</div>,
|
</div>,
|
||||||
button: _t("Sign out"),
|
button: _t("Sign out"),
|
||||||
extraButtons: [
|
extraButtons: [
|
||||||
@ -811,6 +812,12 @@ module.exports = React.createClass({
|
|||||||
<h3>{ _t('Analytics') }</h3>
|
<h3>{ _t('Analytics') }</h3>
|
||||||
<div className="mx_UserSettings_section">
|
<div className="mx_UserSettings_section">
|
||||||
{ _t('Riot collects anonymous analytics to allow us to improve the application.') }
|
{ _t('Riot collects anonymous analytics to allow us to improve the application.') }
|
||||||
|
<br />
|
||||||
|
{ _t('Privacy is important to us, so we don\'t collect any personal'
|
||||||
|
+ ' or identifiable data for our analytics.') }
|
||||||
|
<div className="mx_UserSettings_advanced_spoiler" onClick={Analytics.showDetailsModal}>
|
||||||
|
{ _t('Learn more about how we use analytics.') }
|
||||||
|
</div>
|
||||||
{ ANALYTICS_SETTINGS.map( this._renderDeviceSetting ) }
|
{ ANALYTICS_SETTINGS.map( this._renderDeviceSetting ) }
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
|
@ -18,6 +18,7 @@ limitations under the License.
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
@ -29,13 +30,13 @@ module.exports = React.createClass({
|
|||||||
displayName: 'ForgotPassword',
|
displayName: 'ForgotPassword',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
defaultHsUrl: React.PropTypes.string,
|
defaultHsUrl: PropTypes.string,
|
||||||
defaultIsUrl: React.PropTypes.string,
|
defaultIsUrl: PropTypes.string,
|
||||||
customHsUrl: React.PropTypes.string,
|
customHsUrl: PropTypes.string,
|
||||||
customIsUrl: React.PropTypes.string,
|
customIsUrl: PropTypes.string,
|
||||||
onLoginClick: React.PropTypes.func,
|
onLoginClick: PropTypes.func,
|
||||||
onRegisterClick: React.PropTypes.func,
|
onRegisterClick: PropTypes.func,
|
||||||
onComplete: React.PropTypes.func.isRequired,
|
onComplete: PropTypes.func.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -18,6 +18,7 @@ limitations under the License.
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import * as languageHandler from '../../../languageHandler';
|
import * as languageHandler from '../../../languageHandler';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
@ -36,27 +37,27 @@ module.exports = React.createClass({
|
|||||||
displayName: 'Login',
|
displayName: 'Login',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
onLoggedIn: React.PropTypes.func.isRequired,
|
onLoggedIn: PropTypes.func.isRequired,
|
||||||
|
|
||||||
enableGuest: React.PropTypes.bool,
|
enableGuest: PropTypes.bool,
|
||||||
|
|
||||||
customHsUrl: React.PropTypes.string,
|
customHsUrl: PropTypes.string,
|
||||||
customIsUrl: React.PropTypes.string,
|
customIsUrl: PropTypes.string,
|
||||||
defaultHsUrl: React.PropTypes.string,
|
defaultHsUrl: PropTypes.string,
|
||||||
defaultIsUrl: React.PropTypes.string,
|
defaultIsUrl: PropTypes.string,
|
||||||
// Secondary HS which we try to log into if the user is using
|
// Secondary HS which we try to log into if the user is using
|
||||||
// the default HS but login fails. Useful for migrating to a
|
// the default HS but login fails. Useful for migrating to a
|
||||||
// different home server without confusing users.
|
// different home server without confusing users.
|
||||||
fallbackHsUrl: React.PropTypes.string,
|
fallbackHsUrl: PropTypes.string,
|
||||||
|
|
||||||
defaultDeviceDisplayName: React.PropTypes.string,
|
defaultDeviceDisplayName: PropTypes.string,
|
||||||
|
|
||||||
// login shouldn't know or care how registration is done.
|
// login shouldn't know or care how registration is done.
|
||||||
onRegisterClick: React.PropTypes.func.isRequired,
|
onRegisterClick: PropTypes.func.isRequired,
|
||||||
|
|
||||||
// login shouldn't care how password recovery is done.
|
// login shouldn't care how password recovery is done.
|
||||||
onForgotPasswordClick: React.PropTypes.func,
|
onForgotPasswordClick: PropTypes.func,
|
||||||
onCancelClick: React.PropTypes.func,
|
onCancelClick: PropTypes.func,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
@ -25,7 +26,7 @@ module.exports = React.createClass({
|
|||||||
displayName: 'PostRegistration',
|
displayName: 'PostRegistration',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
onComplete: React.PropTypes.func.isRequired,
|
onComplete: PropTypes.func.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -19,6 +19,7 @@ import Matrix from 'matrix-js-sdk';
|
|||||||
|
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import ServerConfig from '../../views/login/ServerConfig';
|
import ServerConfig from '../../views/login/ServerConfig';
|
||||||
@ -35,31 +36,31 @@ module.exports = React.createClass({
|
|||||||
displayName: 'Registration',
|
displayName: 'Registration',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
onLoggedIn: React.PropTypes.func.isRequired,
|
onLoggedIn: PropTypes.func.isRequired,
|
||||||
clientSecret: React.PropTypes.string,
|
clientSecret: PropTypes.string,
|
||||||
sessionId: React.PropTypes.string,
|
sessionId: PropTypes.string,
|
||||||
makeRegistrationUrl: React.PropTypes.func.isRequired,
|
makeRegistrationUrl: PropTypes.func.isRequired,
|
||||||
idSid: React.PropTypes.string,
|
idSid: PropTypes.string,
|
||||||
customHsUrl: React.PropTypes.string,
|
customHsUrl: PropTypes.string,
|
||||||
customIsUrl: React.PropTypes.string,
|
customIsUrl: PropTypes.string,
|
||||||
defaultHsUrl: React.PropTypes.string,
|
defaultHsUrl: PropTypes.string,
|
||||||
defaultIsUrl: React.PropTypes.string,
|
defaultIsUrl: PropTypes.string,
|
||||||
brand: React.PropTypes.string,
|
brand: PropTypes.string,
|
||||||
email: React.PropTypes.string,
|
email: PropTypes.string,
|
||||||
referrer: React.PropTypes.string,
|
referrer: PropTypes.string,
|
||||||
teamServerConfig: React.PropTypes.shape({
|
teamServerConfig: PropTypes.shape({
|
||||||
// Email address to request new teams
|
// Email address to request new teams
|
||||||
supportEmail: React.PropTypes.string.isRequired,
|
supportEmail: PropTypes.string.isRequired,
|
||||||
// URL of the riot-team-server to get team configurations and track referrals
|
// URL of the riot-team-server to get team configurations and track referrals
|
||||||
teamServerURL: React.PropTypes.string.isRequired,
|
teamServerURL: PropTypes.string.isRequired,
|
||||||
}),
|
}),
|
||||||
teamSelected: React.PropTypes.object,
|
teamSelected: PropTypes.object,
|
||||||
|
|
||||||
defaultDeviceDisplayName: React.PropTypes.string,
|
defaultDeviceDisplayName: PropTypes.string,
|
||||||
|
|
||||||
// registration shouldn't know or care how login is done.
|
// registration shouldn't know or care how login is done.
|
||||||
onLoginClick: React.PropTypes.func.isRequired,
|
onLoginClick: PropTypes.func.isRequired,
|
||||||
onCancelClick: React.PropTypes.func,
|
onCancelClick: PropTypes.func,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -15,6 +15,8 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { MatrixClient } from 'matrix-js-sdk';
|
||||||
import AvatarLogic from '../../../Avatar';
|
import AvatarLogic from '../../../Avatar';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
@ -23,16 +25,20 @@ module.exports = React.createClass({
|
|||||||
displayName: 'BaseAvatar',
|
displayName: 'BaseAvatar',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
name: React.PropTypes.string.isRequired, // The name (first initial used as default)
|
name: PropTypes.string.isRequired, // The name (first initial used as default)
|
||||||
idName: React.PropTypes.string, // ID for generating hash colours
|
idName: PropTypes.string, // ID for generating hash colours
|
||||||
title: React.PropTypes.string, // onHover title text
|
title: PropTypes.string, // onHover title text
|
||||||
url: React.PropTypes.string, // highest priority of them all, shortcut to set in urls[0]
|
url: PropTypes.string, // highest priority of them all, shortcut to set in urls[0]
|
||||||
urls: React.PropTypes.array, // [highest_priority, ... , lowest_priority]
|
urls: PropTypes.array, // [highest_priority, ... , lowest_priority]
|
||||||
width: React.PropTypes.number,
|
width: PropTypes.number,
|
||||||
height: React.PropTypes.number,
|
height: PropTypes.number,
|
||||||
// XXX resizeMethod not actually used.
|
// XXX resizeMethod not actually used.
|
||||||
resizeMethod: React.PropTypes.string,
|
resizeMethod: PropTypes.string,
|
||||||
defaultToInitialLetter: React.PropTypes.bool, // true to add default url
|
defaultToInitialLetter: PropTypes.bool, // true to add default url
|
||||||
|
},
|
||||||
|
|
||||||
|
contextTypes: {
|
||||||
|
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
@ -48,6 +54,16 @@ module.exports = React.createClass({
|
|||||||
return this._getState(this.props);
|
return this._getState(this.props);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.unmounted = false;
|
||||||
|
this.context.matrixClient.on('sync', this.onClientSync);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.unmounted = true;
|
||||||
|
this.context.matrixClient.removeListener('sync', this.onClientSync);
|
||||||
|
},
|
||||||
|
|
||||||
componentWillReceiveProps: function(nextProps) {
|
componentWillReceiveProps: function(nextProps) {
|
||||||
// work out if we need to call setState (if the image URLs array has changed)
|
// work out if we need to call setState (if the image URLs array has changed)
|
||||||
const newState = this._getState(nextProps);
|
const newState = this._getState(nextProps);
|
||||||
@ -66,6 +82,23 @@ module.exports = React.createClass({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onClientSync(syncState, prevState) {
|
||||||
|
if (this.unmounted) return;
|
||||||
|
|
||||||
|
// Consider the client reconnected if there is no error with syncing.
|
||||||
|
// This means the state could be RECONNECTING, SYNCING or PREPARED.
|
||||||
|
const reconnected = syncState !== "ERROR" && prevState !== syncState;
|
||||||
|
if (reconnected &&
|
||||||
|
// Did we fall back?
|
||||||
|
this.state.urlsIndex > 0
|
||||||
|
) {
|
||||||
|
// Start from the highest priority URL again
|
||||||
|
this.setState({
|
||||||
|
urlsIndex: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
_getState: function(props) {
|
_getState: function(props) {
|
||||||
// work out the full set of urls to try to load. This is formed like so:
|
// work out the full set of urls to try to load. This is formed like so:
|
||||||
// imageUrls: [ props.url, props.urls, default image ]
|
// imageUrls: [ props.url, props.urls, default image ]
|
||||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
const Avatar = require('../../../Avatar');
|
const Avatar = require('../../../Avatar');
|
||||||
const sdk = require("../../../index");
|
const sdk = require("../../../index");
|
||||||
const dispatcher = require("../../../dispatcher");
|
const dispatcher = require("../../../dispatcher");
|
||||||
@ -25,15 +26,15 @@ module.exports = React.createClass({
|
|||||||
displayName: 'MemberAvatar',
|
displayName: 'MemberAvatar',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
member: React.PropTypes.object.isRequired,
|
member: PropTypes.object.isRequired,
|
||||||
width: React.PropTypes.number,
|
width: PropTypes.number,
|
||||||
height: React.PropTypes.number,
|
height: PropTypes.number,
|
||||||
resizeMethod: React.PropTypes.string,
|
resizeMethod: PropTypes.string,
|
||||||
// The onClick to give the avatar
|
// The onClick to give the avatar
|
||||||
onClick: React.PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
// Whether the onClick of the avatar should be overriden to dispatch 'view_user'
|
// Whether the onClick of the avatar should be overriden to dispatch 'view_user'
|
||||||
viewUserOnClick: React.PropTypes.bool,
|
viewUserOnClick: PropTypes.bool,
|
||||||
title: React.PropTypes.string,
|
title: PropTypes.string,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import * as sdk from "../../../index";
|
import * as sdk from "../../../index";
|
||||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
@ -30,10 +31,10 @@ module.exports = React.createClass({
|
|||||||
displayName: 'MemberPresenceAvatar',
|
displayName: 'MemberPresenceAvatar',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
member: React.PropTypes.object.isRequired,
|
member: PropTypes.object.isRequired,
|
||||||
width: React.PropTypes.number,
|
width: PropTypes.number,
|
||||||
height: React.PropTypes.number,
|
height: PropTypes.number,
|
||||||
resizeMethod: React.PropTypes.string,
|
resizeMethod: PropTypes.string,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import {ContentRepo} from "matrix-js-sdk";
|
import {ContentRepo} from "matrix-js-sdk";
|
||||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||||
import sdk from "../../../index";
|
import sdk from "../../../index";
|
||||||
@ -25,11 +26,11 @@ module.exports = React.createClass({
|
|||||||
// oobData.avatarUrl should be set (else there
|
// oobData.avatarUrl should be set (else there
|
||||||
// would be nowhere to get the avatar from)
|
// would be nowhere to get the avatar from)
|
||||||
propTypes: {
|
propTypes: {
|
||||||
room: React.PropTypes.object,
|
room: PropTypes.object,
|
||||||
oobData: React.PropTypes.object,
|
oobData: PropTypes.object,
|
||||||
width: React.PropTypes.number,
|
width: PropTypes.number,
|
||||||
height: React.PropTypes.number,
|
height: PropTypes.number,
|
||||||
resizeMethod: React.PropTypes.string,
|
resizeMethod: PropTypes.string,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -17,11 +17,12 @@ limitations under the License.
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'CreateRoomButton',
|
displayName: 'CreateRoomButton',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
onCreateRoom: React.PropTypes.func,
|
onCreateRoom: PropTypes.func,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
const Presets = {
|
const Presets = {
|
||||||
@ -28,8 +29,8 @@ const Presets = {
|
|||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'CreateRoomPresets',
|
displayName: 'CreateRoomPresets',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
onChange: React.PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
preset: React.PropTypes.string,
|
preset: PropTypes.string,
|
||||||
},
|
},
|
||||||
|
|
||||||
Presets: Presets,
|
Presets: Presets,
|
||||||
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
@ -22,9 +23,9 @@ module.exports = React.createClass({
|
|||||||
propTypes: {
|
propTypes: {
|
||||||
// Specifying a homeserver will make magical things happen when you,
|
// Specifying a homeserver will make magical things happen when you,
|
||||||
// e.g. start typing in the room alias box.
|
// e.g. start typing in the room alias box.
|
||||||
homeserver: React.PropTypes.string,
|
homeserver: PropTypes.string,
|
||||||
alias: React.PropTypes.string,
|
alias: PropTypes.string,
|
||||||
onChange: React.PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -20,7 +20,6 @@ import PropTypes from 'prop-types';
|
|||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
import { addressTypes, getAddressType } from '../../../UserAddress.js';
|
import { addressTypes, getAddressType } from '../../../UserAddress.js';
|
||||||
import GroupStoreCache from '../../../stores/GroupStoreCache';
|
import GroupStoreCache from '../../../stores/GroupStoreCache';
|
||||||
@ -507,7 +506,8 @@ module.exports = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
const AddressSelector = sdk.getComponent("elements.AddressSelector");
|
const AddressSelector = sdk.getComponent("elements.AddressSelector");
|
||||||
this.scrollElement = null;
|
this.scrollElement = null;
|
||||||
|
|
||||||
@ -580,14 +580,8 @@ module.exports = React.createClass({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_ChatInviteDialog" onKeyDown={this.onKeyDown}>
|
<BaseDialog className="mx_ChatInviteDialog" onKeyDown={this.onKeyDown}
|
||||||
<div className="mx_Dialog_title">
|
onFinished={this.props.onFinished} title={this.props.title}>
|
||||||
{ this.props.title }
|
|
||||||
</div>
|
|
||||||
<AccessibleButton className="mx_ChatInviteDialog_cancel"
|
|
||||||
onClick={this.onCancel} >
|
|
||||||
<TintableSvg src="img/icons-close-button.svg" width="35" height="35" />
|
|
||||||
</AccessibleButton>
|
|
||||||
<div className="mx_ChatInviteDialog_label">
|
<div className="mx_ChatInviteDialog_label">
|
||||||
<label htmlFor="textinput">{ this.props.description }</label>
|
<label htmlFor="textinput">{ this.props.description }</label>
|
||||||
</div>
|
</div>
|
||||||
@ -597,12 +591,10 @@ module.exports = React.createClass({
|
|||||||
{ addressSelector }
|
{ addressSelector }
|
||||||
{ this.props.extraNode }
|
{ this.props.extraNode }
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_buttons">
|
<DialogButtons primaryButton={this.props.button}
|
||||||
<button className="mx_Dialog_primary" onClick={this.onButtonClick}>
|
onPrimaryButtonClick={this.onButtonClick}
|
||||||
{ this.props.button }
|
onCancel={this.onCancel} />
|
||||||
</button>
|
</BaseDialog>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import FocusTrap from 'focus-trap-react';
|
import FocusTrap from 'focus-trap-react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { KeyCode } from '../../../Keyboard';
|
import { KeyCode } from '../../../Keyboard';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
@ -32,17 +33,20 @@ export default React.createClass({
|
|||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
// onFinished callback to call when Escape is pressed
|
// onFinished callback to call when Escape is pressed
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
|
|
||||||
|
// called when a key is pressed
|
||||||
|
onKeyDown: PropTypes.func,
|
||||||
|
|
||||||
// CSS class to apply to dialog div
|
// CSS class to apply to dialog div
|
||||||
className: React.PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
|
||||||
// Title for the dialog.
|
// Title for the dialog.
|
||||||
// (could probably actually be something more complicated than a string if desired)
|
// (could probably actually be something more complicated than a string if desired)
|
||||||
title: React.PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
|
|
||||||
// children should be the content of the dialog
|
// children should be the content of the dialog
|
||||||
children: React.PropTypes.node,
|
children: PropTypes.node,
|
||||||
|
|
||||||
// Id of content element
|
// Id of content element
|
||||||
// If provided, this is used to add a aria-describedby attribute
|
// If provided, this is used to add a aria-describedby attribute
|
||||||
@ -50,6 +54,9 @@ export default React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_onKeyDown: function(e) {
|
_onKeyDown: function(e) {
|
||||||
|
if (this.props.onKeyDown) {
|
||||||
|
this.props.onKeyDown(e);
|
||||||
|
}
|
||||||
if (e.keyCode === KeyCode.ESCAPE) {
|
if (e.keyCode === KeyCode.ESCAPE) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -76,7 +83,7 @@ export default React.createClass({
|
|||||||
>
|
>
|
||||||
<TintableSvg src="img/icons-close-button.svg" width="35" height="35" />
|
<TintableSvg src="img/icons-close-button.svg" width="35" height="35" />
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
<div className='mx_Dialog_title' id='mx_BaseDialog_title'>
|
<div className={'mx_Dialog_title ' + this.props.titleClass} id='mx_BaseDialog_title'>
|
||||||
{ this.props.title }
|
{ this.props.title }
|
||||||
</div>
|
</div>
|
||||||
{ this.props.children }
|
{ this.props.children }
|
||||||
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
@ -137,6 +138,7 @@ export default class ChatCreateOrReuseDialog extends React.Component {
|
|||||||
} else {
|
} else {
|
||||||
// Show the avatar, name and a button to confirm that a new chat is requested
|
// Show the avatar, name and a button to confirm that a new chat is requested
|
||||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||||
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
const Spinner = sdk.getComponent('elements.Spinner');
|
const Spinner = sdk.getComponent('elements.Spinner');
|
||||||
title = _t('Start chatting');
|
title = _t('Start chatting');
|
||||||
|
|
||||||
@ -166,11 +168,8 @@ export default class ChatCreateOrReuseDialog extends React.Component {
|
|||||||
</p>
|
</p>
|
||||||
{ profile }
|
{ profile }
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_buttons">
|
<DialogButtons primaryButton={_t('Start Chatting')}
|
||||||
<button className="mx_Dialog_primary" onClick={this.props.onNewDMClick} autoFocus="true">
|
onPrimaryButtonClick={this.props.onNewDMClick} focus="true" />
|
||||||
{ _t('Start Chatting') }
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,10 +186,10 @@ export default class ChatCreateOrReuseDialog extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatCreateOrReuseDialog.propTypes = {
|
ChatCreateOrReuseDialog.propTyps = {
|
||||||
userId: React.PropTypes.string.isRequired,
|
userId: PropTypes.string.isRequired,
|
||||||
// Called when clicking outside of the dialog
|
// Called when clicking outside of the dialog
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
onNewDMClick: React.PropTypes.func.isRequired,
|
onNewDMClick: PropTypes.func.isRequired,
|
||||||
onExistingRoomSelected: React.PropTypes.func.isRequired,
|
onExistingRoomSelected: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
@ -15,10 +15,10 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
import { MatrixClient } from 'matrix-js-sdk';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import classnames from 'classnames';
|
|
||||||
import { GroupMemberType } from '../../../groups';
|
import { GroupMemberType } from '../../../groups';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -33,20 +33,20 @@ export default React.createClass({
|
|||||||
displayName: 'ConfirmUserActionDialog',
|
displayName: 'ConfirmUserActionDialog',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
// matrix-js-sdk (room) member object. Supply either this or 'groupMember'
|
// matrix-js-sdk (room) member object. Supply either this or 'groupMember'
|
||||||
member: React.PropTypes.object,
|
member: PropTypes.object,
|
||||||
// group member object. Supply either this or 'member'
|
// group member object. Supply either this or 'member'
|
||||||
groupMember: GroupMemberType,
|
groupMember: GroupMemberType,
|
||||||
// needed if a group member is specified
|
// needed if a group member is specified
|
||||||
matrixClient: React.PropTypes.instanceOf(MatrixClient),
|
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||||
action: React.PropTypes.string.isRequired, // eg. 'Ban'
|
action: PropTypes.string.isRequired, // eg. 'Ban'
|
||||||
title: React.PropTypes.string.isRequired, // eg. 'Ban this user?'
|
title: 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
|
||||||
// be the string entered.
|
// be the string entered.
|
||||||
askReason: React.PropTypes.bool,
|
askReason: PropTypes.bool,
|
||||||
danger: React.PropTypes.bool,
|
danger: PropTypes.bool,
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
defaultProps: {
|
defaultProps: {
|
||||||
@ -76,13 +76,11 @@ export default React.createClass({
|
|||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
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 confirmButtonClass = classnames({
|
const confirmButtonClass = this.props.danger ? 'danger' : '';
|
||||||
'mx_Dialog_primary': true,
|
|
||||||
'danger': this.props.danger,
|
|
||||||
});
|
|
||||||
|
|
||||||
let reasonBox;
|
let reasonBox;
|
||||||
if (this.props.askReason) {
|
if (this.props.askReason) {
|
||||||
@ -127,17 +125,11 @@ export default React.createClass({
|
|||||||
<div className="mx_ConfirmUserActionDialog_userId">{ userId }</div>
|
<div className="mx_ConfirmUserActionDialog_userId">{ userId }</div>
|
||||||
</div>
|
</div>
|
||||||
{ reasonBox }
|
{ reasonBox }
|
||||||
<div className="mx_Dialog_buttons">
|
<DialogButtons primaryButton={this.props.action}
|
||||||
<button className={confirmButtonClass}
|
onPrimaryButtonClick={this.onOk}
|
||||||
onClick={this.onOk} autoFocus={!this.props.askReason}
|
primaryButtonClass={confirmButtonClass}
|
||||||
>
|
focus={!this.props.askReason}
|
||||||
{ this.props.action }
|
onCancel={this.onCancel} />
|
||||||
</button>
|
|
||||||
|
|
||||||
<button onClick={this.onCancel}>
|
|
||||||
{ _t("Cancel") }
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -55,11 +55,15 @@ export default React.createClass({
|
|||||||
|
|
||||||
_checkGroupId: function(e) {
|
_checkGroupId: function(e) {
|
||||||
let error = null;
|
let error = null;
|
||||||
if (!/^[a-z0-9=_\-\.\/]*$/.test(this.state.groupId)) {
|
if (!this.state.groupId) {
|
||||||
|
error = _t("Community IDs cannot not be empty.");
|
||||||
|
} else if (!/^[a-z0-9=_\-\.\/]*$/.test(this.state.groupId)) {
|
||||||
error = _t("Community IDs may only contain characters a-z, 0-9, or '=_-./'");
|
error = _t("Community IDs may only contain characters a-z, 0-9, or '=_-./'");
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
groupIdError: error,
|
groupIdError: error,
|
||||||
|
// Reset createError to get rid of now stale error message
|
||||||
|
createError: null,
|
||||||
});
|
});
|
||||||
return error;
|
return error;
|
||||||
},
|
},
|
||||||
@ -158,10 +162,10 @@ export default React.createClass({
|
|||||||
{ createErrorNode }
|
{ createErrorNode }
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
|
<input type="submit" value={_t('Create')} className="mx_Dialog_primary" />
|
||||||
<button onClick={this._onCancel}>
|
<button onClick={this._onCancel}>
|
||||||
{ _t("Cancel") }
|
{ _t("Cancel") }
|
||||||
</button>
|
</button>
|
||||||
<input type="submit" value={_t('Create')} className="mx_Dialog_primary" />
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import SdkConfig from '../../../SdkConfig';
|
import SdkConfig from '../../../SdkConfig';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
@ -22,7 +23,7 @@ import { _t } from '../../../languageHandler';
|
|||||||
export default React.createClass({
|
export default React.createClass({
|
||||||
displayName: 'CreateRoomDialog',
|
displayName: 'CreateRoomDialog',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
@ -41,6 +42,7 @@ export default React.createClass({
|
|||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_CreateRoomDialog" onFinished={this.props.onFinished}
|
<BaseDialog className="mx_CreateRoomDialog" onFinished={this.props.onFinished}
|
||||||
title={_t('Create Room')}
|
title={_t('Create Room')}
|
||||||
@ -53,27 +55,11 @@ export default React.createClass({
|
|||||||
<div>
|
<div>
|
||||||
<input id="textinput" ref="textinput" className="mx_CreateRoomDialog_input" autoFocus={true} size="64" />
|
<input id="textinput" ref="textinput" className="mx_CreateRoomDialog_input" autoFocus={true} size="64" />
|
||||||
</div>
|
</div>
|
||||||
<br />
|
|
||||||
|
|
||||||
<details className="mx_CreateRoomDialog_details">
|
|
||||||
<summary className="mx_CreateRoomDialog_details_summary">{ _t('Advanced options') }</summary>
|
|
||||||
<div>
|
|
||||||
<input type="checkbox" id="checkbox" ref="checkbox" defaultChecked={this.defaultNoFederate} />
|
|
||||||
<label htmlFor="checkbox">
|
|
||||||
{ _t('Block users on other matrix homeservers from joining this room') }
|
|
||||||
<br />
|
|
||||||
({ _t('This setting cannot be changed later!') })
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
</div>
|
|
||||||
<div className="mx_Dialog_buttons">
|
|
||||||
<button onClick={this.onCancel}>
|
|
||||||
{ _t('Cancel') }
|
|
||||||
</button>
|
|
||||||
<input type="submit" className="mx_Dialog_primary" value={_t('Create Room')} />
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<DialogButtons primaryButton={_t('Create Room')}
|
||||||
|
onPrimaryButtonClick={this.onOk}
|
||||||
|
onCancel={this.onCancel} />
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import Analytics from '../../../Analytics';
|
import Analytics from '../../../Analytics';
|
||||||
@ -77,6 +78,7 @@ export default class DeactivateAccountDialog extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
const Loader = sdk.getComponent("elements.Spinner");
|
const Loader = sdk.getComponent("elements.Spinner");
|
||||||
let passwordBoxClass = '';
|
let passwordBoxClass = '';
|
||||||
|
|
||||||
@ -99,10 +101,11 @@ export default class DeactivateAccountDialog extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_DeactivateAccountDialog">
|
<BaseDialog className="mx_DeactivateAccountDialog"
|
||||||
<div className="mx_Dialog_title danger">
|
onFinished={this.props.onFinished}
|
||||||
{ _t("Deactivate Account") }
|
onEnterPressed={this.onOk}
|
||||||
</div>
|
titleClass="danger"
|
||||||
|
title={_t("Deactivate Account")}>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
<p>{ _t("This will make your account permanently unusable. You will not be able to re-register the same user ID.") }</p>
|
<p>{ _t("This will make your account permanently unusable. You will not be able to re-register the same user ID.") }</p>
|
||||||
|
|
||||||
@ -130,11 +133,11 @@ export default class DeactivateAccountDialog extends React.Component {
|
|||||||
|
|
||||||
{ cancelButton }
|
{ cancelButton }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DeactivateAccountDialog.propTypes = {
|
DeactivateAccountDialog.propTypes = {
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import * as FormattingUtils from '../../../utils/FormattingUtils';
|
import * as FormattingUtils from '../../../utils/FormattingUtils';
|
||||||
@ -71,7 +72,7 @@ export default function DeviceVerifyDialog(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DeviceVerifyDialog.propTypes = {
|
DeviceVerifyDialog.propTypes = {
|
||||||
userId: React.PropTypes.string.isRequired,
|
userId: PropTypes.string.isRequired,
|
||||||
device: React.PropTypes.object.isRequired,
|
device: PropTypes.object.isRequired,
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
@ -26,20 +26,21 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
export default React.createClass({
|
export default React.createClass({
|
||||||
displayName: 'ErrorDialog',
|
displayName: 'ErrorDialog',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
title: React.PropTypes.string,
|
title: PropTypes.string,
|
||||||
description: React.PropTypes.oneOfType([
|
description: PropTypes.oneOfType([
|
||||||
React.PropTypes.element,
|
PropTypes.element,
|
||||||
React.PropTypes.string,
|
PropTypes.string,
|
||||||
]),
|
]),
|
||||||
button: React.PropTypes.string,
|
button: PropTypes.string,
|
||||||
focus: React.PropTypes.bool,
|
focus: PropTypes.bool,
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
@ -27,22 +28,22 @@ export default React.createClass({
|
|||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
// matrix client to use for UI auth requests
|
// matrix client to use for UI auth requests
|
||||||
matrixClient: React.PropTypes.object.isRequired,
|
matrixClient: PropTypes.object.isRequired,
|
||||||
|
|
||||||
// response from initial request. If not supplied, will do a request on
|
// response from initial request. If not supplied, will do a request on
|
||||||
// mount.
|
// mount.
|
||||||
authData: React.PropTypes.shape({
|
authData: PropTypes.shape({
|
||||||
flows: React.PropTypes.array,
|
flows: PropTypes.array,
|
||||||
params: React.PropTypes.object,
|
params: PropTypes.object,
|
||||||
session: React.PropTypes.string,
|
session: PropTypes.string,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// callback
|
// callback
|
||||||
makeRequest: React.PropTypes.func.isRequired,
|
makeRequest: PropTypes.func.isRequired,
|
||||||
|
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
|
|
||||||
title: React.PropTypes.string,
|
title: PropTypes.string,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||||||
|
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
|
|
||||||
import { _t, _td } from '../../../languageHandler';
|
import { _t, _td } from '../../../languageHandler';
|
||||||
@ -30,10 +31,10 @@ import { _t, _td } from '../../../languageHandler';
|
|||||||
*/
|
*/
|
||||||
export default React.createClass({
|
export default React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
matrixClient: React.PropTypes.object.isRequired,
|
matrixClient: PropTypes.object.isRequired,
|
||||||
userId: React.PropTypes.string.isRequired,
|
userId: PropTypes.string.isRequired,
|
||||||
deviceId: React.PropTypes.string.isRequired,
|
deviceId: PropTypes.string.isRequired,
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -16,20 +16,20 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import classnames from 'classnames';
|
|
||||||
|
|
||||||
export default React.createClass({
|
export default React.createClass({
|
||||||
displayName: 'QuestionDialog',
|
displayName: 'QuestionDialog',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
title: React.PropTypes.string,
|
title: PropTypes.string,
|
||||||
description: React.PropTypes.node,
|
description: PropTypes.node,
|
||||||
extraButtons: React.PropTypes.node,
|
extraButtons: PropTypes.node,
|
||||||
button: React.PropTypes.string,
|
button: PropTypes.string,
|
||||||
danger: React.PropTypes.bool,
|
danger: PropTypes.bool,
|
||||||
focus: React.PropTypes.bool,
|
focus: PropTypes.bool,
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
@ -53,15 +53,11 @@ export default React.createClass({
|
|||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
const cancelButton = this.props.hasCancelButton ? (
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
<button onClick={this.onCancel}>
|
let primaryButtonClass = "";
|
||||||
{ _t("Cancel") }
|
if (this.props.danger) {
|
||||||
</button>
|
primaryButtonClass = "danger";
|
||||||
) : null;
|
}
|
||||||
const buttonClasses = classnames({
|
|
||||||
mx_Dialog_primary: true,
|
|
||||||
danger: this.props.danger,
|
|
||||||
});
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_QuestionDialog" onFinished={this.props.onFinished}
|
<BaseDialog className="mx_QuestionDialog" onFinished={this.props.onFinished}
|
||||||
title={this.props.title}
|
title={this.props.title}
|
||||||
@ -70,13 +66,14 @@ export default React.createClass({
|
|||||||
<div className="mx_Dialog_content" id='mx_Dialog_content'>
|
<div className="mx_Dialog_content" id='mx_Dialog_content'>
|
||||||
{ this.props.description }
|
{ this.props.description }
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_buttons">
|
<DialogButtons primaryButton={this.props.button || _t('OK')}
|
||||||
<button className={buttonClasses} onClick={this.onOk} autoFocus={this.props.focus}>
|
onPrimaryButtonClick={this.onOk}
|
||||||
{ this.props.button || _t('OK') }
|
primaryButtonClass={primaryButtonClass}
|
||||||
</button>
|
focus={this.props.focus}
|
||||||
|
onCancel={this.onCancel}
|
||||||
|
>
|
||||||
{ this.props.extraButtons }
|
{ this.props.extraButtons }
|
||||||
{ cancelButton }
|
</DialogButtons>
|
||||||
</div>
|
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import SdkConfig from '../../../SdkConfig';
|
import SdkConfig from '../../../SdkConfig';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
@ -25,8 +26,8 @@ export default React.createClass({
|
|||||||
displayName: 'SessionRestoreErrorDialog',
|
displayName: 'SessionRestoreErrorDialog',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
error: React.PropTypes.string.isRequired,
|
error: PropTypes.string.isRequired,
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
@ -46,6 +47,7 @@ export default React.createClass({
|
|||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
let bugreport;
|
let bugreport;
|
||||||
|
|
||||||
if (SdkConfig.get().bug_report_endpoint_url) {
|
if (SdkConfig.get().bug_report_endpoint_url) {
|
||||||
@ -78,11 +80,9 @@ export default React.createClass({
|
|||||||
|
|
||||||
{ bugreport }
|
{ bugreport }
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_buttons">
|
<DialogButtons primaryButton={_t("Continue anyway")}
|
||||||
<button className="mx_Dialog_primary" onClick={this._continueClicked} autoFocus={shouldFocusContinueButton}>
|
onPrimaryButtonClick={this._continueClicked} focus={shouldFocusContinueButton}
|
||||||
{ _t("Continue anyway") }
|
onCancel={this.props.onFinished} />
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import Email from '../../../email';
|
import Email from '../../../email';
|
||||||
import AddThreepid from '../../../AddThreepid';
|
import AddThreepid from '../../../AddThreepid';
|
||||||
@ -30,7 +31,7 @@ import Modal from '../../../Modal';
|
|||||||
export default React.createClass({
|
export default React.createClass({
|
||||||
displayName: 'SetEmailDialog',
|
displayName: 'SetEmailDialog',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
|
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
@ -35,11 +36,11 @@ const USERNAME_CHECK_DEBOUNCE_MS = 250;
|
|||||||
export default React.createClass({
|
export default React.createClass({
|
||||||
displayName: 'SetMxIdDialog',
|
displayName: 'SetMxIdDialog',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
// Called when the user requests to register with a different homeserver
|
// Called when the user requests to register with a different homeserver
|
||||||
onDifferentServerClicked: React.PropTypes.func.isRequired,
|
onDifferentServerClicked: PropTypes.func.isRequired,
|
||||||
// Called if the user wants to switch to login instead
|
// Called if the user wants to switch to login instead
|
||||||
onLoginClick: React.PropTypes.func.isRequired,
|
onLoginClick: PropTypes.func.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -15,21 +15,21 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
|
||||||
|
|
||||||
export default React.createClass({
|
export default React.createClass({
|
||||||
displayName: 'TextInputDialog',
|
displayName: 'TextInputDialog',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
title: React.PropTypes.string,
|
title: PropTypes.string,
|
||||||
description: React.PropTypes.oneOfType([
|
description: PropTypes.oneOfType([
|
||||||
React.PropTypes.element,
|
PropTypes.element,
|
||||||
React.PropTypes.string,
|
PropTypes.string,
|
||||||
]),
|
]),
|
||||||
value: React.PropTypes.string,
|
value: PropTypes.string,
|
||||||
button: React.PropTypes.string,
|
button: PropTypes.string,
|
||||||
focus: React.PropTypes.bool,
|
focus: PropTypes.bool,
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
@ -58,6 +58,7 @@ export default React.createClass({
|
|||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_TextInputDialog" onFinished={this.props.onFinished}
|
<BaseDialog className="mx_TextInputDialog" onFinished={this.props.onFinished}
|
||||||
title={this.props.title}
|
title={this.props.title}
|
||||||
@ -71,13 +72,10 @@ export default React.createClass({
|
|||||||
<input id="textinput" ref="textinput" className="mx_TextInputDialog_input" defaultValue={this.props.value} autoFocus={this.props.focus} size="64" />
|
<input id="textinput" ref="textinput" className="mx_TextInputDialog_input" defaultValue={this.props.value} autoFocus={this.props.focus} size="64" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_buttons">
|
|
||||||
<button onClick={this.onCancel}>
|
|
||||||
{ _t("Cancel") }
|
|
||||||
</button>
|
|
||||||
<input type="submit" className="mx_Dialog_primary" value={this.props.button} />
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
|
<DialogButtons primaryButton={this.props.button}
|
||||||
|
onPrimaryButtonClick={this.onOk}
|
||||||
|
onCancel={this.onCancel} />
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -23,14 +23,7 @@ import GeminiScrollbar from 'react-gemini-scrollbar';
|
|||||||
import Resend from '../../../Resend';
|
import Resend from '../../../Resend';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import { markAllDevicesKnown } from '../../../cryptodevices';
|
||||||
function markAllDevicesKnown(devices) {
|
|
||||||
Object.keys(devices).forEach((userId) => {
|
|
||||||
Object.keys(devices[userId]).map((deviceId) => {
|
|
||||||
MatrixClientPeg.get().setDeviceKnown(userId, deviceId, true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function DeviceListEntry(props) {
|
function DeviceListEntry(props) {
|
||||||
const {userId, device} = props;
|
const {userId, device} = props;
|
||||||
@ -141,7 +134,7 @@ export default React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_onSendAnywayClicked: function() {
|
_onSendAnywayClicked: function() {
|
||||||
markAllDevicesKnown(this.props.devices);
|
markAllDevicesKnown(MatrixClientPeg.get(), this.props.devices);
|
||||||
|
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
this.props.onSend();
|
this.props.onSend();
|
||||||
@ -187,18 +180,11 @@ export default React.createClass({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
let sendButton;
|
const sendButtonOnClick = haveUnknownDevices ? this._onSendAnywayClicked : this._onSendClicked;
|
||||||
if (haveUnknownDevices) {
|
const sendButtonLabel = haveUnknownDevices ? this.props.sendAnywayLabel : this.props.sendAnywayLabel;
|
||||||
sendButton = <button onClick={this._onSendAnywayClicked}>
|
|
||||||
{ this.props.sendAnywayLabel }
|
|
||||||
</button>;
|
|
||||||
} else {
|
|
||||||
sendButton = <button onClick={this._onSendClicked}>
|
|
||||||
{ this.props.sendLabel }
|
|
||||||
</button>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
return (
|
return (
|
||||||
<BaseDialog className='mx_UnknownDeviceDialog'
|
<BaseDialog className='mx_UnknownDeviceDialog'
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
@ -214,14 +200,9 @@ export default React.createClass({
|
|||||||
|
|
||||||
<UnknownDeviceList devices={this.props.devices} />
|
<UnknownDeviceList devices={this.props.devices} />
|
||||||
</GeminiScrollbar>
|
</GeminiScrollbar>
|
||||||
<div className="mx_Dialog_buttons">
|
<DialogButtons primaryButton={sendButtonLabel}
|
||||||
{sendButton}
|
onPrimaryButtonClick={sendButtonOnClick}
|
||||||
<button className="mx_Dialog_primary" autoFocus={true}
|
onCancel={this._onDismissClicked} />
|
||||||
onClick={this._onDismissClicked}
|
|
||||||
>
|
|
||||||
{_t("Dismiss")}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
// XXX: do we want to give the user the option to enable blacklistUnverifiedDevices for this room (or globally) at this point?
|
// XXX: do we want to give the user the option to enable blacklistUnverifiedDevices for this room (or globally) at this point?
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { KeyCode } from '../../../Keyboard';
|
import { KeyCode } from '../../../Keyboard';
|
||||||
|
|
||||||
@ -72,9 +73,9 @@ export default function AccessibleButton(props) {
|
|||||||
* implemented exactly like a normal onClick handler.
|
* implemented exactly like a normal onClick handler.
|
||||||
*/
|
*/
|
||||||
AccessibleButton.propTypes = {
|
AccessibleButton.propTypes = {
|
||||||
children: React.PropTypes.node,
|
children: PropTypes.node,
|
||||||
element: React.PropTypes.string,
|
element: PropTypes.string,
|
||||||
onClick: React.PropTypes.func.isRequired,
|
onClick: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
AccessibleButton.defaultProps = {
|
AccessibleButton.defaultProps = {
|
||||||
|
@ -18,6 +18,7 @@ limitations under the License.
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { UserAddressType } from '../../../UserAddress';
|
import { UserAddressType } from '../../../UserAddress';
|
||||||
@ -26,17 +27,17 @@ export default React.createClass({
|
|||||||
displayName: 'AddressSelector',
|
displayName: 'AddressSelector',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
onSelected: React.PropTypes.func.isRequired,
|
onSelected: PropTypes.func.isRequired,
|
||||||
|
|
||||||
// List of the addresses to display
|
// List of the addresses to display
|
||||||
addressList: React.PropTypes.arrayOf(UserAddressType).isRequired,
|
addressList: PropTypes.arrayOf(UserAddressType).isRequired,
|
||||||
// Whether to show the address on the address tiles
|
// Whether to show the address on the address tiles
|
||||||
showAddress: React.PropTypes.bool,
|
showAddress: PropTypes.bool,
|
||||||
truncateAt: React.PropTypes.number.isRequired,
|
truncateAt: PropTypes.number.isRequired,
|
||||||
selected: React.PropTypes.number,
|
selected: PropTypes.number,
|
||||||
|
|
||||||
// Element to put as a header on top of the list
|
// Element to put as a header on top of the list
|
||||||
header: React.PropTypes.node,
|
header: PropTypes.node,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import sdk from "../../../index";
|
import sdk from "../../../index";
|
||||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||||
@ -28,9 +29,9 @@ export default React.createClass({
|
|||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
address: UserAddressType.isRequired,
|
address: UserAddressType.isRequired,
|
||||||
canDismiss: React.PropTypes.bool,
|
canDismiss: PropTypes.bool,
|
||||||
onDismissed: React.PropTypes.func,
|
onDismissed: PropTypes.func,
|
||||||
justified: React.PropTypes.bool,
|
justified: PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -19,6 +19,7 @@ limitations under the License.
|
|||||||
import url from 'url';
|
import url from 'url';
|
||||||
import qs from 'querystring';
|
import qs from 'querystring';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import PlatformPeg from '../../../PlatformPeg';
|
import PlatformPeg from '../../../PlatformPeg';
|
||||||
import ScalarAuthClient from '../../../ScalarAuthClient';
|
import ScalarAuthClient from '../../../ScalarAuthClient';
|
||||||
@ -40,19 +41,19 @@ export default React.createClass({
|
|||||||
displayName: 'AppTile',
|
displayName: 'AppTile',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
id: React.PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
url: React.PropTypes.string.isRequired,
|
url: PropTypes.string.isRequired,
|
||||||
name: React.PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
room: React.PropTypes.object.isRequired,
|
room: PropTypes.object.isRequired,
|
||||||
type: React.PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
// Specifying 'fullWidth' as true will render the app tile to fill the width of the app drawer continer.
|
// Specifying 'fullWidth' as true will render the app tile to fill the width of the app drawer continer.
|
||||||
// This should be set to true when there is only one widget in the app drawer, otherwise it should be false.
|
// This should be set to true when there is only one widget in the app drawer, otherwise it should be false.
|
||||||
fullWidth: React.PropTypes.bool,
|
fullWidth: PropTypes.bool,
|
||||||
// UserId of the current user
|
// UserId of the current user
|
||||||
userId: React.PropTypes.string.isRequired,
|
userId: PropTypes.string.isRequired,
|
||||||
// UserId of the entity that added / modified the widget
|
// UserId of the entity that added / modified the widget
|
||||||
creatorUserId: React.PropTypes.string,
|
creatorUserId: PropTypes.string,
|
||||||
waitForIframeLoad: React.PropTypes.bool,
|
waitForIframeLoad: PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps() {
|
getDefaultProps() {
|
||||||
|
43
src/components/views/elements/DNDTagTile.js
Normal file
43
src/components/views/elements/DNDTagTile.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/* eslint new-cap: "off" */
|
||||||
|
/*
|
||||||
|
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 TagTile from './TagTile';
|
||||||
|
|
||||||
|
import { Draggable } from 'react-beautiful-dnd';
|
||||||
|
|
||||||
|
export default function DNDTagTile(props) {
|
||||||
|
return <div>
|
||||||
|
<Draggable
|
||||||
|
key={props.tag}
|
||||||
|
draggableId={props.tag}
|
||||||
|
index={props.index}
|
||||||
|
>
|
||||||
|
{ (provided, snapshot) => (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
ref={provided.innerRef}
|
||||||
|
{...provided.draggableProps}
|
||||||
|
{...provided.dragHandleProps}
|
||||||
|
>
|
||||||
|
<TagTile {...props} />
|
||||||
|
</div>
|
||||||
|
{ provided.placeholder }
|
||||||
|
</div>
|
||||||
|
) }
|
||||||
|
</Draggable>
|
||||||
|
</div>;
|
||||||
|
}
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
@ -24,8 +25,8 @@ export default React.createClass({
|
|||||||
displayName: 'DeviceVerifyButtons',
|
displayName: 'DeviceVerifyButtons',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
userId: React.PropTypes.string.isRequired,
|
userId: PropTypes.string.isRequired,
|
||||||
device: React.PropTypes.object.isRequired,
|
device: PropTypes.object.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
62
src/components/views/elements/DialogButtons.js
Normal file
62
src/components/views/elements/DialogButtons.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 Aidan Gauland
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic container for buttons in modal dialogs.
|
||||||
|
*/
|
||||||
|
module.exports = React.createClass({
|
||||||
|
displayName: "DialogButtons",
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
// The primary button which is styled differently and has default focus.
|
||||||
|
primaryButton: PropTypes.node.isRequired,
|
||||||
|
|
||||||
|
// onClick handler for the primary button.
|
||||||
|
onPrimaryButtonClick: PropTypes.func.isRequired,
|
||||||
|
|
||||||
|
// onClick handler for the cancel button.
|
||||||
|
onCancel: PropTypes.func.isRequired,
|
||||||
|
|
||||||
|
focus: PropTypes.bool,
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
let primaryButtonClassName = "mx_Dialog_primary";
|
||||||
|
if (this.props.primaryButtonClass) {
|
||||||
|
primaryButtonClassName += " " + this.props.primaryButtonClass;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="mx_Dialog_buttons">
|
||||||
|
<button className={primaryButtonClassName}
|
||||||
|
onClick={this.props.onPrimaryButtonClick}
|
||||||
|
autoFocus={this.props.focus}
|
||||||
|
>
|
||||||
|
{ this.props.primaryButton }
|
||||||
|
</button>
|
||||||
|
{ this.props.children }
|
||||||
|
<button onClick={this.props.onCancel}>
|
||||||
|
{ _t("Cancel") }
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
export default class DirectorySearchBox extends React.Component {
|
export default class DirectorySearchBox extends React.Component {
|
||||||
@ -105,10 +106,10 @@ export default class DirectorySearchBox extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DirectorySearchBox.propTypes = {
|
DirectorySearchBox.propTypes = {
|
||||||
className: React.PropTypes.string,
|
className: PropTypes.string,
|
||||||
onChange: React.PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
onClear: React.PropTypes.func,
|
onClear: PropTypes.func,
|
||||||
onJoinClick: React.PropTypes.func,
|
onJoinClick: PropTypes.func,
|
||||||
placeholder: React.PropTypes.string,
|
placeholder: PropTypes.string,
|
||||||
showJoinButton: React.PropTypes.bool,
|
showJoinButton: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import AccessibleButton from './AccessibleButton';
|
import AccessibleButton from './AccessibleButton';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
@ -56,14 +57,14 @@ class MenuOption extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MenuOption.propTypes = {
|
MenuOption.propTypes = {
|
||||||
children: React.PropTypes.oneOfType([
|
children: PropTypes.oneOfType([
|
||||||
React.PropTypes.arrayOf(React.PropTypes.node),
|
PropTypes.arrayOf(React.PropTypes.node),
|
||||||
React.PropTypes.node,
|
PropTypes.node,
|
||||||
]),
|
]),
|
||||||
highlighted: React.PropTypes.bool,
|
highlighted: PropTypes.bool,
|
||||||
dropdownKey: React.PropTypes.string,
|
dropdownKey: PropTypes.string,
|
||||||
onClick: React.PropTypes.func.isRequired,
|
onClick: PropTypes.func.isRequired,
|
||||||
onMouseEnter: React.PropTypes.func.isRequired,
|
onMouseEnter: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -322,20 +323,20 @@ Dropdown.propTypes = {
|
|||||||
// The width that the dropdown should be. If specified,
|
// The width that the dropdown should be. If specified,
|
||||||
// the dropped-down part of the menu will be set to this
|
// the dropped-down part of the menu will be set to this
|
||||||
// width.
|
// width.
|
||||||
menuWidth: React.PropTypes.number,
|
menuWidth: PropTypes.number,
|
||||||
// Called when the selected option changes
|
// Called when the selected option changes
|
||||||
onOptionChange: React.PropTypes.func.isRequired,
|
onOptionChange: PropTypes.func.isRequired,
|
||||||
// Called when the value of the search field changes
|
// Called when the value of the search field changes
|
||||||
onSearchChange: React.PropTypes.func,
|
onSearchChange: PropTypes.func,
|
||||||
searchEnabled: React.PropTypes.bool,
|
searchEnabled: PropTypes.bool,
|
||||||
// Function that, given the key of an option, returns
|
// Function that, given the key of an option, returns
|
||||||
// a node representing that option to be displayed in the
|
// a node representing that option to be displayed in the
|
||||||
// box itself as the currently-selected option (ie. as
|
// box itself as the currently-selected option (ie. as
|
||||||
// opposed to in the actual dropped-down part). If
|
// opposed to in the actual dropped-down part). If
|
||||||
// unspecified, the appropriate child element is used as
|
// unspecified, the appropriate child element is used as
|
||||||
// in the dropped-down menu.
|
// in the dropped-down menu.
|
||||||
getShortOption: React.PropTypes.func,
|
getShortOption: PropTypes.func,
|
||||||
value: React.PropTypes.string,
|
value: PropTypes.string,
|
||||||
// negative for consistency with HTML
|
// negative for consistency with HTML
|
||||||
disabled: React.PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const KEY_TAB = 9;
|
const KEY_TAB = 9;
|
||||||
const KEY_SHIFT = 16;
|
const KEY_SHIFT = 16;
|
||||||
@ -26,18 +27,18 @@ module.exports = React.createClass({
|
|||||||
displayName: 'EditableText',
|
displayName: 'EditableText',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
onValueChanged: React.PropTypes.func,
|
onValueChanged: PropTypes.func,
|
||||||
initialValue: React.PropTypes.string,
|
initialValue: PropTypes.string,
|
||||||
label: React.PropTypes.string,
|
label: PropTypes.string,
|
||||||
placeholder: React.PropTypes.string,
|
placeholder: PropTypes.string,
|
||||||
className: React.PropTypes.string,
|
className: PropTypes.string,
|
||||||
labelClassName: React.PropTypes.string,
|
labelClassName: PropTypes.string,
|
||||||
placeholderClassName: React.PropTypes.string,
|
placeholderClassName: PropTypes.string,
|
||||||
// Overrides blurToSubmit if true
|
// Overrides blurToSubmit if true
|
||||||
blurToCancel: React.PropTypes.bool,
|
blurToCancel: PropTypes.bool,
|
||||||
// Will cause onValueChanged(value, true) to fire on blur
|
// Will cause onValueChanged(value, true) to fire on blur
|
||||||
blurToSubmit: React.PropTypes.bool,
|
blurToSubmit: PropTypes.bool,
|
||||||
editable: React.PropTypes.bool,
|
editable: PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
Phases: {
|
Phases: {
|
||||||
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
|
|
||||||
@ -126,21 +127,21 @@ export default class EditableTextContainer extends React.Component {
|
|||||||
|
|
||||||
EditableTextContainer.propTypes = {
|
EditableTextContainer.propTypes = {
|
||||||
/* callback to retrieve the initial value. */
|
/* callback to retrieve the initial value. */
|
||||||
getInitialValue: React.PropTypes.func,
|
getInitialValue: PropTypes.func,
|
||||||
|
|
||||||
/* initial value; used if getInitialValue is not given */
|
/* initial value; used if getInitialValue is not given */
|
||||||
initialValue: React.PropTypes.string,
|
initialValue: PropTypes.string,
|
||||||
|
|
||||||
/* placeholder text to use when the value is empty (and not being
|
/* placeholder text to use when the value is empty (and not being
|
||||||
* edited) */
|
* edited) */
|
||||||
placeholder: React.PropTypes.string,
|
placeholder: PropTypes.string,
|
||||||
|
|
||||||
/* callback to update the value. Called with a single argument: the new
|
/* callback to update the value. Called with a single argument: the new
|
||||||
* value. */
|
* value. */
|
||||||
onSubmit: React.PropTypes.func,
|
onSubmit: PropTypes.func,
|
||||||
|
|
||||||
/* should the input submit when focus is lost? */
|
/* should the input submit when focus is lost? */
|
||||||
blurToSubmit: React.PropTypes.bool,
|
blurToSubmit: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import {emojifyText, containsEmoji} from '../../../HtmlUtils';
|
import {emojifyText, containsEmoji} from '../../../HtmlUtils';
|
||||||
|
|
||||||
export default function EmojiText(props) {
|
export default function EmojiText(props) {
|
||||||
@ -32,8 +33,8 @@ export default function EmojiText(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EmojiText.propTypes = {
|
EmojiText.propTypes = {
|
||||||
element: React.PropTypes.string,
|
element: PropTypes.string,
|
||||||
children: React.PropTypes.string.isRequired,
|
children: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
EmojiText.defaultProps = {
|
EmojiText.defaultProps = {
|
||||||
|
@ -63,7 +63,7 @@ FlairAvatar.propTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
FlairAvatar.contextTypes = {
|
FlairAvatar.contextTypes = {
|
||||||
matrixClient: React.PropTypes.instanceOf(MatrixClient).isRequired,
|
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class Flair extends React.Component {
|
export default class Flair extends React.Component {
|
||||||
@ -107,7 +107,11 @@ export default class Flair extends React.Component {
|
|||||||
}
|
}
|
||||||
const profiles = await this._getGroupProfiles(groups);
|
const profiles = await this._getGroupProfiles(groups);
|
||||||
if (!this.unmounted) {
|
if (!this.unmounted) {
|
||||||
this.setState({profiles: profiles.filter((profile) => {return profile.avatarUrl;})});
|
this.setState({
|
||||||
|
profiles: profiles.filter((profile) => {
|
||||||
|
return profile ? profile.avatarUrl : false;
|
||||||
|
}),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,5 +138,5 @@ Flair.propTypes = {
|
|||||||
// this.context.matrixClient everywhere instead of this.props.matrixClient.
|
// this.context.matrixClient everywhere instead of this.props.matrixClient.
|
||||||
// See https://github.com/vector-im/riot-web/issues/4951.
|
// See https://github.com/vector-im/riot-web/issues/4951.
|
||||||
Flair.contextTypes = {
|
Flair.contextTypes = {
|
||||||
matrixClient: React.PropTypes.instanceOf(MatrixClient).isRequired,
|
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
||||||
};
|
};
|
||||||
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import * as languageHandler from '../../../languageHandler';
|
import * as languageHandler from '../../../languageHandler';
|
||||||
@ -114,7 +115,7 @@ export default class LanguageDropdown extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LanguageDropdown.propTypes = {
|
LanguageDropdown.propTypes = {
|
||||||
className: React.PropTypes.string,
|
className: PropTypes.string,
|
||||||
onOptionChange: React.PropTypes.func.isRequired,
|
onOptionChange: PropTypes.func.isRequired,
|
||||||
value: React.PropTypes.string,
|
value: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
const MemberAvatar = require('../avatars/MemberAvatar.js');
|
const MemberAvatar = require('../avatars/MemberAvatar.js');
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
@ -23,19 +24,19 @@ module.exports = React.createClass({
|
|||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
// An array of member events to summarise
|
// An array of member events to summarise
|
||||||
events: React.PropTypes.array.isRequired,
|
events: PropTypes.array.isRequired,
|
||||||
// An array of EventTiles to render when expanded
|
// An array of EventTiles to render when expanded
|
||||||
children: React.PropTypes.array.isRequired,
|
children: PropTypes.array.isRequired,
|
||||||
// The maximum number of names to show in either each summary e.g. 2 would result "A, B and 234 others left"
|
// The maximum number of names to show in either each summary e.g. 2 would result "A, B and 234 others left"
|
||||||
summaryLength: React.PropTypes.number,
|
summaryLength: PropTypes.number,
|
||||||
// The maximum number of avatars to display in the summary
|
// The maximum number of avatars to display in the summary
|
||||||
avatarsMaxLength: React.PropTypes.number,
|
avatarsMaxLength: PropTypes.number,
|
||||||
// The minimum number of events needed to trigger summarisation
|
// The minimum number of events needed to trigger summarisation
|
||||||
threshold: React.PropTypes.number,
|
threshold: PropTypes.number,
|
||||||
// Called when the MELS expansion is toggled
|
// Called when the MELS expansion is toggled
|
||||||
onToggle: React.PropTypes.func,
|
onToggle: PropTypes.func,
|
||||||
// Whether or not to begin with state.expanded=true
|
// Whether or not to begin with state.expanded=true
|
||||||
startExpanded: React.PropTypes.bool,
|
startExpanded: PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -17,7 +17,7 @@ import React from 'react';
|
|||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Room, RoomMember } from 'matrix-js-sdk';
|
import { Room, RoomMember, MatrixClient } from 'matrix-js-sdk';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import { MATRIXTO_URL_PATTERN } from '../../../linkify-matrix';
|
import { MATRIXTO_URL_PATTERN } from '../../../linkify-matrix';
|
||||||
@ -61,6 +61,17 @@ const Pill = React.createClass({
|
|||||||
shouldShowPillAvatar: PropTypes.bool,
|
shouldShowPillAvatar: PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
childContextTypes: {
|
||||||
|
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||||
|
},
|
||||||
|
|
||||||
|
getChildContext() {
|
||||||
|
return {
|
||||||
|
matrixClient: this._matrixClient,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
return {
|
return {
|
||||||
// ID/alias of the room/user
|
// ID/alias of the room/user
|
||||||
@ -135,6 +146,7 @@ const Pill = React.createClass({
|
|||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this._unmounted = false;
|
this._unmounted = false;
|
||||||
|
this._matrixClient = MatrixClientPeg.get();
|
||||||
this.componentWillReceiveProps(this.props);
|
this.componentWillReceiveProps(this.props);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import * as Roles from '../../../Roles';
|
import * as Roles from '../../../Roles';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
@ -24,23 +25,23 @@ module.exports = React.createClass({
|
|||||||
displayName: 'PowerSelector',
|
displayName: 'PowerSelector',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
value: React.PropTypes.number.isRequired,
|
value: PropTypes.number.isRequired,
|
||||||
// The maximum value that can be set with the power selector
|
// The maximum value that can be set with the power selector
|
||||||
maxValue: React.PropTypes.number.isRequired,
|
maxValue: PropTypes.number.isRequired,
|
||||||
|
|
||||||
// Default user power level for the room
|
// Default user power level for the room
|
||||||
usersDefault: React.PropTypes.number.isRequired,
|
usersDefault: PropTypes.number.isRequired,
|
||||||
|
|
||||||
// if true, the <select/> should be a 'controlled' form element and updated by React
|
// if true, the <select/> should be a 'controlled' form element and updated by React
|
||||||
// to reflect the current value, rather than left freeform.
|
// to reflect the current value, rather than left freeform.
|
||||||
// MemberInfo uses controlled; RoomSettings uses non-controlled.
|
// MemberInfo uses controlled; RoomSettings uses non-controlled.
|
||||||
//
|
//
|
||||||
// ignored if disabled is truthy. false by default.
|
// ignored if disabled is truthy. false by default.
|
||||||
controlled: React.PropTypes.bool,
|
controlled: PropTypes.bool,
|
||||||
|
|
||||||
// should the user be able to change the value? false by default.
|
// should the user be able to change the value? false by default.
|
||||||
disabled: React.PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
onChange: React.PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -17,12 +17,13 @@ limitations under the License.
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'ProgressBar',
|
displayName: 'ProgressBar',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
value: React.PropTypes.number,
|
value: PropTypes.number,
|
||||||
max: React.PropTypes.number,
|
max: PropTypes.number,
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
188
src/components/views/elements/Quote.js
Normal file
188
src/components/views/elements/Quote.js
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
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 sdk from '../../../index';
|
||||||
|
import {_t} from '../../../languageHandler';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
|
import {wantsDateSeparator} from '../../../DateUtils';
|
||||||
|
import {MatrixEvent} from 'matrix-js-sdk';
|
||||||
|
import {makeUserPermalink} from "../../../matrix-to";
|
||||||
|
|
||||||
|
// For URLs of matrix.to links in the timeline which have been reformatted by
|
||||||
|
// HttpUtils transformTags to relative links. This excludes event URLs (with `[^\/]*`)
|
||||||
|
const REGEX_LOCAL_MATRIXTO = /^#\/room\/([\#\!][^\/]*)\/(\$[^\/]*)$/;
|
||||||
|
|
||||||
|
export default class Quote extends React.Component {
|
||||||
|
static isMessageUrl(url) {
|
||||||
|
return !!REGEX_LOCAL_MATRIXTO.exec(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
static childContextTypes = {
|
||||||
|
matrixClient: PropTypes.object,
|
||||||
|
addRichQuote: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
// The matrix.to url of the event
|
||||||
|
url: PropTypes.string,
|
||||||
|
// The original node that was rendered
|
||||||
|
node: PropTypes.instanceOf(Element),
|
||||||
|
// The parent event
|
||||||
|
parentEv: PropTypes.instanceOf(MatrixEvent),
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
// The event related to this quote and their nested rich quotes
|
||||||
|
events: [],
|
||||||
|
// Whether the top (oldest) event should be shown or spoilered
|
||||||
|
show: true,
|
||||||
|
// Whether an error was encountered fetching nested older event, show node if it does
|
||||||
|
err: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.onQuoteClick = this.onQuoteClick.bind(this);
|
||||||
|
this.addRichQuote = this.addRichQuote.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
getChildContext() {
|
||||||
|
return {
|
||||||
|
matrixClient: MatrixClientPeg.get(),
|
||||||
|
addRichQuote: this.addRichQuote,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
parseUrl(url) {
|
||||||
|
if (!url) return;
|
||||||
|
|
||||||
|
// Default to the empty array if no match for simplicity
|
||||||
|
// resource and prefix will be undefined instead of throwing
|
||||||
|
const matrixToMatch = REGEX_LOCAL_MATRIXTO.exec(url) || [];
|
||||||
|
|
||||||
|
const [, roomIdentifier, eventId] = matrixToMatch;
|
||||||
|
return {roomIdentifier, eventId};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
const {roomIdentifier, eventId} = this.parseUrl(nextProps.url);
|
||||||
|
if (!roomIdentifier || !eventId) return;
|
||||||
|
|
||||||
|
const room = this.getRoom(roomIdentifier);
|
||||||
|
if (!room) return;
|
||||||
|
|
||||||
|
// Only try and load the event if we know about the room
|
||||||
|
// otherwise we just leave a `Quote` anchor which can be used to navigate/join the room manually.
|
||||||
|
this.setState({ events: [] });
|
||||||
|
if (room) this.getEvent(room, eventId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.componentWillReceiveProps(this.props);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRoom(id) {
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
if (id[0] === '!') return cli.getRoom(id);
|
||||||
|
|
||||||
|
return cli.getRooms().find((r) => {
|
||||||
|
return r.getAliases().includes(id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getEvent(room, eventId, show) {
|
||||||
|
const event = room.findEventById(eventId);
|
||||||
|
if (event) {
|
||||||
|
this.addEvent(event, show);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await MatrixClientPeg.get().getEventTimeline(room.getUnfilteredTimelineSet(), eventId);
|
||||||
|
this.addEvent(room.findEventById(eventId), show);
|
||||||
|
}
|
||||||
|
|
||||||
|
addEvent(event, show) {
|
||||||
|
const events = [event].concat(this.state.events);
|
||||||
|
this.setState({events, show});
|
||||||
|
}
|
||||||
|
|
||||||
|
// addRichQuote(roomId, eventId) {
|
||||||
|
addRichQuote(href) {
|
||||||
|
const {roomIdentifier, eventId} = this.parseUrl(href);
|
||||||
|
if (!roomIdentifier || !eventId) {
|
||||||
|
this.setState({ err: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const room = this.getRoom(roomIdentifier);
|
||||||
|
if (!room) {
|
||||||
|
this.setState({ err: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getEvent(room, eventId, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
onQuoteClick() {
|
||||||
|
this.setState({ show: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const events = this.state.events.slice();
|
||||||
|
if (events.length) {
|
||||||
|
const evTiles = [];
|
||||||
|
|
||||||
|
if (!this.state.show) {
|
||||||
|
const oldestEv = events.shift();
|
||||||
|
const Pill = sdk.getComponent('elements.Pill');
|
||||||
|
const room = MatrixClientPeg.get().getRoom(oldestEv.getRoomId());
|
||||||
|
|
||||||
|
evTiles.push(<blockquote className="mx_Quote" key="load">
|
||||||
|
{
|
||||||
|
_t('<a>In reply to</a> <pill>', {}, {
|
||||||
|
'a': (sub) => <a onClick={this.onQuoteClick} className="mx_Quote_show">{ sub }</a>,
|
||||||
|
'pill': <Pill type={Pill.TYPE_USER_MENTION} room={room}
|
||||||
|
url={makeUserPermalink(oldestEv.getSender())} shouldShowPillAvatar={true} />,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</blockquote>);
|
||||||
|
}
|
||||||
|
|
||||||
|
const EventTile = sdk.getComponent('views.rooms.EventTile');
|
||||||
|
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||||
|
events.forEach((ev) => {
|
||||||
|
let dateSep = null;
|
||||||
|
|
||||||
|
if (wantsDateSeparator(this.props.parentEv.getDate(), ev.getDate())) {
|
||||||
|
dateSep = <a href={this.props.url}><DateSeparator ts={ev.getTs()} /></a>;
|
||||||
|
}
|
||||||
|
|
||||||
|
evTiles.push(<blockquote className="mx_Quote" key={ev.getId()}>
|
||||||
|
{ dateSep }
|
||||||
|
<EventTile mxEvent={ev} tileShape="quote" />
|
||||||
|
</blockquote>);
|
||||||
|
});
|
||||||
|
|
||||||
|
return <div>{ evTiles }</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deliberately render nothing if the URL isn't recognised
|
||||||
|
// in case we get an undefined/falsey node, replace it with null to make React happy
|
||||||
|
return this.props.node || null;
|
||||||
|
}
|
||||||
|
}
|
@ -15,23 +15,24 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'SettingsFlag',
|
displayName: 'SettingsFlag',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
name: React.PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
level: React.PropTypes.string.isRequired,
|
level: PropTypes.string.isRequired,
|
||||||
roomId: React.PropTypes.string, // for per-room settings
|
roomId: PropTypes.string, // for per-room settings
|
||||||
label: React.PropTypes.string, // untranslated
|
label: PropTypes.string, // untranslated
|
||||||
onChange: React.PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
isExplicit: React.PropTypes.bool,
|
isExplicit: PropTypes.bool,
|
||||||
manualSave: React.PropTypes.bool,
|
manualSave: PropTypes.bool,
|
||||||
|
|
||||||
// If group is supplied, then this will create a radio button instead.
|
// If group is supplied, then this will create a radio button instead.
|
||||||
group: React.PropTypes.string,
|
group: PropTypes.string,
|
||||||
value: React.PropTypes.any, // the value for the radio button
|
value: PropTypes.any, // the value for the radio button
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
119
src/components/views/elements/TagTile.js
Normal file
119
src/components/views/elements/TagTile.js
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
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 PropTypes from 'prop-types';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { MatrixClient } from 'matrix-js-sdk';
|
||||||
|
import sdk from '../../../index';
|
||||||
|
import dis from '../../../dispatcher';
|
||||||
|
import { isOnlyCtrlOrCmdIgnoreShiftKeyEvent } from '../../../Keyboard';
|
||||||
|
|
||||||
|
import FlairStore from '../../../stores/FlairStore';
|
||||||
|
|
||||||
|
// A class for a child of TagPanel (possibly wrapped in a DNDTagTile) that represents
|
||||||
|
// a thing to click on for the user to filter the visible rooms in the RoomList to:
|
||||||
|
// - Rooms that are part of the group
|
||||||
|
// - Direct messages with members of the group
|
||||||
|
// with the intention that this could be expanded to arbitrary tags in future.
|
||||||
|
export default React.createClass({
|
||||||
|
displayName: 'TagTile',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
// A string tag such as "m.favourite" or a group ID such as "+groupid:domain.bla"
|
||||||
|
// For now, only group IDs are handled.
|
||||||
|
tag: PropTypes.string,
|
||||||
|
},
|
||||||
|
|
||||||
|
contextTypes: {
|
||||||
|
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return {
|
||||||
|
// Whether the mouse is over the tile
|
||||||
|
hover: false,
|
||||||
|
// The profile data of the group if this.props.tag is a group ID
|
||||||
|
profile: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.unmounted = false;
|
||||||
|
if (this.props.tag[0] === '+') {
|
||||||
|
FlairStore.getGroupProfileCached(
|
||||||
|
this.context.matrixClient,
|
||||||
|
this.props.tag,
|
||||||
|
).then((profile) => {
|
||||||
|
if (this.unmounted) return;
|
||||||
|
this.setState({profile});
|
||||||
|
}).catch((err) => {
|
||||||
|
console.warn('Could not fetch group profile for ' + this.props.tag, err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.unmounted = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
onClick: function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'select_tag',
|
||||||
|
tag: this.props.tag,
|
||||||
|
ctrlOrCmdKey: isOnlyCtrlOrCmdIgnoreShiftKeyEvent(e),
|
||||||
|
shiftKey: e.shiftKey,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onMouseOver: function() {
|
||||||
|
this.setState({hover: true});
|
||||||
|
},
|
||||||
|
|
||||||
|
onMouseOut: function() {
|
||||||
|
this.setState({hover: false});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||||
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
|
const RoomTooltip = sdk.getComponent('rooms.RoomTooltip');
|
||||||
|
const profile = this.state.profile || {};
|
||||||
|
const name = profile.name || this.props.tag;
|
||||||
|
const avatarHeight = 35;
|
||||||
|
|
||||||
|
const httpUrl = profile.avatarUrl ? this.context.matrixClient.mxcUrlToHttp(
|
||||||
|
profile.avatarUrl, avatarHeight, avatarHeight, "crop",
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
const className = classNames({
|
||||||
|
mx_TagTile: true,
|
||||||
|
mx_TagTile_selected: this.props.selected,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tip = this.state.hover ?
|
||||||
|
<RoomTooltip className="mx_TagTile_tooltip" label={name} /> :
|
||||||
|
<div />;
|
||||||
|
return <AccessibleButton className={className} onClick={this.onClick}>
|
||||||
|
<div className="mx_TagTile_avatar" onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut}>
|
||||||
|
<BaseAvatar name={name} url={httpUrl} width={avatarHeight} height={avatarHeight} />
|
||||||
|
{ tip }
|
||||||
|
</div>
|
||||||
|
</AccessibleButton>;
|
||||||
|
},
|
||||||
|
});
|
@ -18,16 +18,17 @@ limitations under the License.
|
|||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const ReactDOM = require("react-dom");
|
const ReactDOM = require("react-dom");
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
const Tinter = require("../../../Tinter");
|
const Tinter = require("../../../Tinter");
|
||||||
|
|
||||||
var TintableSvg = React.createClass({
|
var TintableSvg = React.createClass({
|
||||||
displayName: 'TintableSvg',
|
displayName: 'TintableSvg',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
src: React.PropTypes.string.isRequired,
|
src: PropTypes.string.isRequired,
|
||||||
width: React.PropTypes.string.isRequired,
|
width: PropTypes.string.isRequired,
|
||||||
height: React.PropTypes.string.isRequired,
|
height: PropTypes.string.isRequired,
|
||||||
className: React.PropTypes.string,
|
className: PropTypes.string,
|
||||||
},
|
},
|
||||||
|
|
||||||
statics: {
|
statics: {
|
||||||
|
@ -17,14 +17,15 @@ limitations under the License.
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'UserSelector',
|
displayName: 'UserSelector',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
onChange: React.PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
selected_users: React.PropTypes.arrayOf(React.PropTypes.string),
|
selected_users: PropTypes.arrayOf(React.PropTypes.string),
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -66,7 +66,7 @@ const GroupRoomTile = React.createClass({
|
|||||||
});
|
});
|
||||||
|
|
||||||
GroupRoomTile.contextTypes = {
|
GroupRoomTile.contextTypes = {
|
||||||
matrixClient: React.PropTypes.instanceOf(MatrixClient).isRequired,
|
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ const GroupTile = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
contextTypes: {
|
contextTypes: {
|
||||||
matrixClient: React.PropTypes.instanceOf(MatrixClient).isRequired,
|
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
|
@ -18,6 +18,7 @@ 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 { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
const DIV_ID = 'mx_recaptcha';
|
const DIV_ID = 'mx_recaptcha';
|
||||||
@ -29,10 +30,10 @@ module.exports = React.createClass({
|
|||||||
displayName: 'CaptchaForm',
|
displayName: 'CaptchaForm',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
sitePublicKey: React.PropTypes.string,
|
sitePublicKey: PropTypes.string,
|
||||||
|
|
||||||
// called with the captcha response
|
// called with the captcha response
|
||||||
onCaptchaResponse: React.PropTypes.func,
|
onCaptchaResponse: PropTypes.func,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -17,13 +17,14 @@ limitations under the License.
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'CasLogin',
|
displayName: 'CasLogin',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
onSubmit: React.PropTypes.func, // fn()
|
onSubmit: PropTypes.func, // fn()
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user