Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/nvl/rich_quoting

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

# Conflicts:
#	src/components/views/messages/TextualBody.js
This commit is contained in:
Michael Telatynski 2018-01-10 11:54:58 +00:00
commit 1bc9d344ae
No known key found for this signature in database
GPG key ID: 3F879DA5AD802A5E
187 changed files with 5637 additions and 1498 deletions

View file

@ -1,115 +0,0 @@
/*
Copyright 2017 Vector Creations 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 {Store} from 'flux/utils';
import dis from '../dispatcher';
import Analytics from '../Analytics';
const INITIAL_STATE = {
allTags: [],
selectedTags: [],
// Last selected tag when shift was not being pressed
anchorTag: null,
};
/**
* A class for storing application state for filtering via TagPanel.
*/
class FilterStore extends Store {
constructor() {
super(dis);
// Initialise state
this._state = INITIAL_STATE;
}
_setState(newState) {
this._state = Object.assign(this._state, newState);
this.__emitChange();
}
__onDispatch(payload) {
switch (payload.action) {
case 'all_tags' :
this._setState({
allTags: payload.tags,
});
break;
case 'select_tag': {
let newTags = [];
// Shift-click semantics
if (payload.shiftKey) {
// Select range of tags
let start = this._state.allTags.indexOf(this._state.anchorTag);
let end = this._state.allTags.indexOf(payload.tag);
if (start === -1) {
start = end;
}
if (start > end) {
const temp = start;
start = end;
end = temp;
}
newTags = payload.ctrlOrCmdKey ? this._state.selectedTags : [];
newTags = [...new Set(
this._state.allTags.slice(start, end + 1).concat(newTags),
)];
} else {
if (payload.ctrlOrCmdKey) {
// Toggle individual tag
if (this._state.selectedTags.includes(payload.tag)) {
newTags = this._state.selectedTags.filter((t) => t !== payload.tag);
} else {
newTags = [...this._state.selectedTags, payload.tag];
}
} else {
// Select individual tag
newTags = [payload.tag];
}
// Only set the anchor tag if the tag was previously unselected, otherwise
// the next range starts with an unselected tag.
if (!this._state.selectedTags.includes(payload.tag)) {
this._setState({
anchorTag: payload.tag,
});
}
}
this._setState({
selectedTags: newTags,
});
Analytics.trackEvent('FilterStore', 'select_tag');
}
break;
case 'deselect_tags':
this._setState({
selectedTags: [],
});
Analytics.trackEvent('FilterStore', 'deselect_tags');
break;
}
}
getSelectedTags() {
return this._state.selectedTags;
}
}
if (global.singletonFilterStore === undefined) {
global.singletonFilterStore = new FilterStore();
}
export default global.singletonFilterStore;

View file

@ -39,6 +39,9 @@ class FlairStore {
// avatar_url: 'mxc://...'
// }
};
this._groupProfilesPromise = {
// $groupId: Promise
};
this._usersPending = {
// $userId: {
// prom: Promise
@ -149,13 +152,29 @@ class FlairStore {
return this._groupProfiles[groupId];
}
const profile = await matrixClient.getGroupProfile(groupId);
// No request yet, start one
if (!this._groupProfilesPromise[groupId]) {
this._groupProfilesPromise[groupId] = matrixClient.getGroupProfile(groupId);
}
let profile;
try {
profile = await this._groupProfilesPromise[groupId];
} catch (e) {
console.log('Failed to get group profile for ' + groupId, e);
// Don't retry, but allow a retry when the profile is next requested
delete this._groupProfilesPromise[groupId];
return;
}
this._groupProfiles[groupId] = {
groupId,
avatarUrl: profile.avatar_url,
name: profile.name,
shortDescription: profile.short_description,
};
delete this._groupProfilesPromise[groupId];
setTimeout(() => {
delete this._groupProfiles[groupId];
}, GROUP_PROFILES_CACHE_BUST_MS);

View file

@ -103,6 +103,22 @@ export default class GroupStore extends EventEmitter {
this.emit('update');
}
/**
* Register a listener to recieve updates from the store. This also
* immediately triggers an update to send the current state of the
* store (which could be the initial state).
*
* XXX: This also causes a fetch of all group data, which effectively
* causes 4 separate HTTP requests. This is bad, we should at least
* deduplicate these in order to fix:
* https://github.com/vector-im/riot-web/issues/5901
*
* @param {function} fn the function to call when the store updates.
* @return {Object} tok a registration "token" with a single
* property `unregister`, a function that can
* be called to unregister the listener such
* that it won't be called any more.
*/
registerListener(fn) {
this.on('update', fn);
// Call to set initial state (before fetching starts)
@ -110,6 +126,14 @@ export default class GroupStore extends EventEmitter {
this._fetchSummary();
this._fetchRooms();
this._fetchMembers();
// Similar to the Store of flux/utils, we return a "token" that
// can be used to unregister the listener.
return {
unregister: () => {
this.unregisterListener(fn);
},
};
}
unregisterListener(fn) {

200
src/stores/TagOrderStore.js Normal file
View file

@ -0,0 +1,200 @@
/*
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 {Store} from 'flux/utils';
import dis from '../dispatcher';
import Analytics from '../Analytics';
const INITIAL_STATE = {
orderedTags: null,
orderedTagsAccountData: null,
hasSynced: false,
joinedGroupIds: null,
selectedTags: [],
// Last selected tag when shift was not being pressed
anchorTag: null,
};
/**
* A class for storing application state for ordering tags in the TagPanel.
*/
class TagOrderStore extends Store {
constructor() {
super(dis);
// Initialise state
this._state = Object.assign({}, INITIAL_STATE);
}
_setState(newState) {
this._state = Object.assign(this._state, newState);
this.__emitChange();
}
__onDispatch(payload) {
switch (payload.action) {
// Initialise state after initial sync
case 'MatrixActions.sync': {
if (!(payload.prevState === 'PREPARED' && payload.state === 'SYNCING')) {
break;
}
const tagOrderingEvent = payload.matrixClient.getAccountData('im.vector.web.tag_ordering');
const tagOrderingEventContent = tagOrderingEvent ? tagOrderingEvent.getContent() : {};
this._setState({
orderedTagsAccountData: tagOrderingEventContent.tags || null,
hasSynced: true,
});
this._updateOrderedTags();
break;
}
// Get ordering from account data
case 'MatrixActions.accountData': {
if (payload.event_type !== 'im.vector.web.tag_ordering') break;
this._setState({
orderedTagsAccountData: payload.event_content ? payload.event_content.tags : null,
});
this._updateOrderedTags();
break;
}
// Initialise the state such that if account data is unset, default to joined groups
case 'GroupActions.fetchJoinedGroups.success': {
this._setState({
joinedGroupIds: payload.result.groups.sort(), // Sort lexically
hasFetchedJoinedGroups: true,
});
this._updateOrderedTags();
break;
}
// Puts payload.tag at payload.targetTag, placing the targetTag before or after the tag
case 'order_tag': {
if (!this._state.orderedTags ||
!payload.tag ||
!payload.targetTag ||
payload.tag === payload.targetTag
) return;
const tags = this._state.orderedTags;
let orderedTags = tags.filter((t) => t !== payload.tag);
const newIndex = orderedTags.indexOf(payload.targetTag) + (payload.after ? 1 : 0);
orderedTags = [
...orderedTags.slice(0, newIndex),
payload.tag,
...orderedTags.slice(newIndex),
];
this._setState({orderedTags});
break;
}
case 'select_tag': {
let newTags = [];
// Shift-click semantics
if (payload.shiftKey) {
// Select range of tags
let start = this._state.orderedTags.indexOf(this._state.anchorTag);
let end = this._state.orderedTags.indexOf(payload.tag);
if (start === -1) {
start = end;
}
if (start > end) {
const temp = start;
start = end;
end = temp;
}
newTags = payload.ctrlOrCmdKey ? this._state.selectedTags : [];
newTags = [...new Set(
this._state.orderedTags.slice(start, end + 1).concat(newTags),
)];
} else {
if (payload.ctrlOrCmdKey) {
// Toggle individual tag
if (this._state.selectedTags.includes(payload.tag)) {
newTags = this._state.selectedTags.filter((t) => t !== payload.tag);
} else {
newTags = [...this._state.selectedTags, payload.tag];
}
} else {
// Select individual tag
newTags = [payload.tag];
}
// Only set the anchor tag if the tag was previously unselected, otherwise
// the next range starts with an unselected tag.
if (!this._state.selectedTags.includes(payload.tag)) {
this._setState({
anchorTag: payload.tag,
});
}
}
this._setState({
selectedTags: newTags,
});
Analytics.trackEvent('FilterStore', 'select_tag');
}
break;
case 'deselect_tags':
this._setState({
selectedTags: [],
});
Analytics.trackEvent('FilterStore', 'deselect_tags');
break;
case 'on_logged_out': {
// Reset state without pushing an update to the view, which generally assumes that
// the matrix client isn't `null` and so causing a re-render will cause NPEs.
this._state = Object.assign({}, INITIAL_STATE);
break;
}
}
}
_updateOrderedTags() {
this._setState({
orderedTags:
this._state.hasSynced &&
this._state.hasFetchedJoinedGroups ?
this._mergeGroupsAndTags() : null,
});
}
_mergeGroupsAndTags() {
const groupIds = this._state.joinedGroupIds || [];
const tags = this._state.orderedTagsAccountData || [];
const tagsToKeep = tags.filter(
(t) => t[0] !== '+' || groupIds.includes(t),
);
const groupIdsToAdd = groupIds.filter(
(groupId) => !tags.includes(groupId),
);
return tagsToKeep.concat(groupIdsToAdd);
}
getOrderedTags() {
return this._state.orderedTags;
}
getSelectedTags() {
return this._state.selectedTags;
}
}
if (global.singletonTagOrderStore === undefined) {
global.singletonTagOrderStore = new TagOrderStore();
}
export default global.singletonTagOrderStore;