Merge remote-tracking branch 'origin/notif_sync' into unread_sync
This commit is contained in:
commit
ba51c68844
81 changed files with 1788 additions and 778 deletions
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -74,6 +74,32 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
/* the MatrixEvent to show */
|
||||
mxEvent: React.PropTypes.object.isRequired,
|
||||
|
||||
/* true if this is a continuation of the previous event (which has the
|
||||
* effect of not showing another avatar/displayname
|
||||
*/
|
||||
continuation: React.PropTypes.bool,
|
||||
|
||||
/* true if this is the last event in the timeline (which has the effect
|
||||
* of always showing the timestamp)
|
||||
*/
|
||||
last: React.PropTypes.bool,
|
||||
|
||||
/* true if this is search context (which has the effect of greying out
|
||||
* the text
|
||||
*/
|
||||
contextual: React.PropTypes.bool,
|
||||
|
||||
/* a list of words to highlight */
|
||||
highlights: React.PropTypes.array,
|
||||
|
||||
/* a function to be called when the highlight is clicked */
|
||||
onHighlightClick: React.PropTypes.func,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {menu: false, allReadAvatars: false};
|
||||
},
|
||||
|
@ -134,6 +160,9 @@ module.exports = React.createClass({
|
|||
|
||||
for (var i = 0; i < receipts.length; ++i) {
|
||||
var member = room.getMember(receipts[i].userId);
|
||||
if (!member) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Using react refs here would mean both getting Velociraptor to expose
|
||||
// them and making them scoped to the whole RoomView. Not impossible, but
|
||||
|
@ -280,7 +309,8 @@ module.exports = React.createClass({
|
|||
{ avatar }
|
||||
{ sender }
|
||||
<div className="mx_EventTile_line">
|
||||
<EventTileType mxEvent={this.props.mxEvent} highlights={this.props.highlights} />
|
||||
<EventTileType mxEvent={this.props.mxEvent} highlights={this.props.highlights}
|
||||
onHighlightClick={this.props.onHighlightClick} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -254,7 +254,7 @@ module.exports = React.createClass({
|
|||
} else {
|
||||
return (
|
||||
<form onSubmit={this.onPopulateInvite}>
|
||||
<input className="mx_MemberList_invite" ref="invite" placeholder="Invite another user"/>
|
||||
<input className="mx_MemberList_invite" ref="invite" placeholder="Invite user (email)"/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -31,6 +31,7 @@ var MatrixClientPeg = require("../../../MatrixClientPeg");
|
|||
var SlashCommands = require("../../../SlashCommands");
|
||||
var Modal = require("../../../Modal");
|
||||
var CallHandler = require('../../../CallHandler');
|
||||
var MemberEntry = require("../../../TabCompleteEntries").MemberEntry;
|
||||
var sdk = require('../../../index');
|
||||
|
||||
var dis = require("../../../dispatcher");
|
||||
|
@ -64,14 +65,13 @@ function mdownToHtml(mdown) {
|
|||
module.exports = React.createClass({
|
||||
displayName: 'MessageComposer',
|
||||
|
||||
propTypes: {
|
||||
tabComplete: React.PropTypes.any
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this.oldScrollHeight = 0;
|
||||
this.markdownEnabled = MARKDOWN_ENABLED;
|
||||
this.tabStruct = {
|
||||
completing: false,
|
||||
original: null,
|
||||
index: 0
|
||||
};
|
||||
var self = this;
|
||||
this.sentHistory = {
|
||||
// The list of typed messages. Index 0 is more recent
|
||||
|
@ -172,6 +172,9 @@ module.exports = React.createClass({
|
|||
this.props.room.roomId
|
||||
);
|
||||
this.resizeInput();
|
||||
if (this.props.tabComplete) {
|
||||
this.props.tabComplete.setTextArea(this.refs.textarea);
|
||||
}
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
|
@ -197,13 +200,6 @@ module.exports = React.createClass({
|
|||
this.sentHistory.push(input);
|
||||
this.onEnter(ev);
|
||||
}
|
||||
else if (ev.keyCode === KeyCode.TAB) {
|
||||
var members = [];
|
||||
if (this.props.room) {
|
||||
members = this.props.room.getJoinedMembers();
|
||||
}
|
||||
this.onTab(ev, members);
|
||||
}
|
||||
else if (ev.keyCode === KeyCode.UP) {
|
||||
var input = this.refs.textarea.value;
|
||||
var offset = this.refs.textarea.selectionStart || 0;
|
||||
|
@ -222,10 +218,9 @@ module.exports = React.createClass({
|
|||
this.resizeInput();
|
||||
}
|
||||
}
|
||||
else if (ev.keyCode !== KeyCode.SHIFT && this.tabStruct.completing) {
|
||||
// they're resuming typing; reset tab complete state vars.
|
||||
this.tabStruct.completing = false;
|
||||
this.tabStruct.index = 0;
|
||||
|
||||
if (this.props.tabComplete) {
|
||||
this.props.tabComplete.onKeyDown(ev);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
@ -349,104 +344,6 @@ module.exports = React.createClass({
|
|||
ev.preventDefault();
|
||||
},
|
||||
|
||||
onTab: function(ev, sortedMembers) {
|
||||
var textArea = this.refs.textarea;
|
||||
if (!this.tabStruct.completing) {
|
||||
this.tabStruct.completing = true;
|
||||
this.tabStruct.index = 0;
|
||||
// cache starting text
|
||||
this.tabStruct.original = textArea.value;
|
||||
}
|
||||
|
||||
// loop in the right direction
|
||||
if (ev.shiftKey) {
|
||||
this.tabStruct.index --;
|
||||
if (this.tabStruct.index < 0) {
|
||||
// wrap to the last search match, and fix up to a real index
|
||||
// value after we've matched.
|
||||
this.tabStruct.index = Number.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.tabStruct.index++;
|
||||
}
|
||||
|
||||
var searchIndex = 0;
|
||||
var targetIndex = this.tabStruct.index;
|
||||
var text = this.tabStruct.original;
|
||||
|
||||
var search = /@?([a-zA-Z0-9_\-:\.]+)$/.exec(text);
|
||||
// console.log("Searched in '%s' - got %s", text, search);
|
||||
if (targetIndex === 0) { // 0 is always the original text
|
||||
textArea.value = text;
|
||||
}
|
||||
else if (search && search[1]) {
|
||||
// console.log("search found: " + search+" from "+text);
|
||||
var expansion;
|
||||
|
||||
// FIXME: could do better than linear search here
|
||||
for (var i=0; i<sortedMembers.length; i++) {
|
||||
var member = sortedMembers[i];
|
||||
if (member.name && searchIndex < targetIndex) {
|
||||
if (member.name.toLowerCase().indexOf(search[1].toLowerCase()) === 0) {
|
||||
expansion = member.name;
|
||||
searchIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (searchIndex < targetIndex) { // then search raw mxids
|
||||
for (var i=0; i<sortedMembers.length; i++) {
|
||||
if (searchIndex >= targetIndex) {
|
||||
break;
|
||||
}
|
||||
var userId = sortedMembers[i].userId;
|
||||
// === 1 because mxids are @username
|
||||
if (userId.toLowerCase().indexOf(search[1].toLowerCase()) === 1) {
|
||||
expansion = userId;
|
||||
searchIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (searchIndex === targetIndex ||
|
||||
targetIndex === Number.MAX_VALUE) {
|
||||
// xchat-style tab complete, add a colon if tab
|
||||
// completing at the start of the text
|
||||
if (search[0].length === text.length) {
|
||||
expansion += ": ";
|
||||
}
|
||||
else {
|
||||
expansion += " ";
|
||||
}
|
||||
textArea.value = text.replace(
|
||||
/@?([a-zA-Z0-9_\-:\.]+)$/, expansion
|
||||
);
|
||||
// cancel blink
|
||||
textArea.style["background-color"] = "";
|
||||
if (targetIndex === Number.MAX_VALUE) {
|
||||
// wrap the index around to the last index found
|
||||
this.tabStruct.index = searchIndex;
|
||||
targetIndex = searchIndex;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// console.log("wrapped!");
|
||||
textArea.style["background-color"] = "#faa";
|
||||
setTimeout(function() {
|
||||
textArea.style["background-color"] = "";
|
||||
}, 150);
|
||||
textArea.value = text;
|
||||
this.tabStruct.index = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.tabStruct.index = 0;
|
||||
}
|
||||
// prevent the default TAB operation (typically focus shifting)
|
||||
ev.preventDefault();
|
||||
},
|
||||
|
||||
onTypingActivity: function() {
|
||||
this.isTyping = true;
|
||||
if (!this.userTypingTimer) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -73,10 +73,15 @@ module.exports = React.createClass({
|
|||
|
||||
var header;
|
||||
if (this.props.simpleHeader) {
|
||||
var cancel;
|
||||
if (this.props.onCancelClick) {
|
||||
cancel = <img className="mx_RoomHeader_simpleHeaderCancel" src="img/cancel-black.png" onClick={ this.props.onCancelClick } alt="Close" width="18" height="18"/>
|
||||
}
|
||||
header =
|
||||
<div className="mx_RoomHeader_wrapper">
|
||||
<div className="mx_RoomHeader_simpleHeader">
|
||||
{ this.props.simpleHeader }
|
||||
{ cancel }
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
@ -104,8 +109,8 @@ module.exports = React.createClass({
|
|||
|
||||
var searchStatus;
|
||||
// don't display the search count until the search completes and
|
||||
// gives us a non-null searchCount.
|
||||
if (this.props.searchInfo && this.props.searchInfo.searchCount !== null) {
|
||||
// gives us a valid (possibly zero) searchCount.
|
||||
if (this.props.searchInfo && this.props.searchInfo.searchCount !== undefined && this.props.searchInfo.searchCount !== null) {
|
||||
searchStatus = <div className="mx_RoomHeader_searchStatus"> (~{ this.props.searchInfo.searchCount } results)</div>;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -127,8 +127,16 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
onRoomReceipt: function(receiptEvent, room) {
|
||||
// because if we read a message it will affect notification / unread counts
|
||||
this.refreshRoomList();
|
||||
// because if we read a notification, it will affect notification count
|
||||
// only bother updating if there's a receipt from us
|
||||
var receiptKeys = Object.keys(receiptEvent.getContent());
|
||||
for (var i = 0; i < receiptKeys.length; ++i) {
|
||||
var rcpt = receiptEvent.getContent()[receiptKeys[i]];
|
||||
if (rcpt['m.read'] && rcpt['m.read'][MatrixClientPeg.get().credentials.userId]) {
|
||||
this.refreshRoomList();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onRoomName: function(room) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
64
src/components/views/rooms/SearchResultTile.js
Normal file
64
src/components/views/rooms/SearchResultTile.js
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
Copyright 2015 OpenMarket 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var sdk = require('../../../index');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'SearchResult',
|
||||
|
||||
propTypes: {
|
||||
// a matrix-js-sdk SearchResult containing the details of this result
|
||||
searchResult: React.PropTypes.object.isRequired,
|
||||
|
||||
// a list of strings to be highlighted in the results
|
||||
searchHighlights: React.PropTypes.array,
|
||||
|
||||
// callback to be called when the user selects this result
|
||||
onSelect: React.PropTypes.func,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||
var EventTile = sdk.getComponent('rooms.EventTile');
|
||||
var result = this.props.searchResult;
|
||||
var mxEv = result.context.getEvent();
|
||||
var eventId = mxEv.getId();
|
||||
|
||||
var ts1 = mxEv.getTs();
|
||||
var ret = [<DateSeparator key={ts1 + "-search"} ts={ts1}/>];
|
||||
|
||||
var timeline = result.context.getTimeline();
|
||||
for (var j = 0; j < timeline.length; j++) {
|
||||
var ev = timeline[j];
|
||||
var highlights;
|
||||
var contextual = (j != result.context.getOurEventIndex());
|
||||
if (!contextual) {
|
||||
highlights = this.props.searchHighlights;
|
||||
}
|
||||
if (EventTile.haveTileForEvent(ev)) {
|
||||
ret.push(<EventTile key={eventId+"+"+j} mxEvent={ev} contextual={contextual} highlights={highlights}
|
||||
onHighlightClick={this.props.onSelect}/>)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<li data-scroll-token={eventId+"+"+j}>
|
||||
{ret}
|
||||
</li>);
|
||||
},
|
||||
});
|
46
src/components/views/rooms/TabCompleteBar.js
Normal file
46
src/components/views/rooms/TabCompleteBar.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'TabCompleteBar',
|
||||
|
||||
propTypes: {
|
||||
entries: React.PropTypes.array.isRequired
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div className="mx_TabCompleteBar">
|
||||
{this.props.entries.map(function(entry, i) {
|
||||
return (
|
||||
<div key={entry.getKey() || i + ""} className="mx_TabCompleteBar_item"
|
||||
onClick={entry.onClick.bind(entry)} >
|
||||
{entry.getImageJsx()}
|
||||
<span className="mx_TabCompleteBar_text">
|
||||
{entry.getText()}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue