PLT-3506 Added flagged posts functionality (#3679)
* Added flagged posts functionality * UI Improvements to flags (#3697) * Added flag functionality for mobile * Updating flagged text (#3699) * Add back button to RHS thread when coming from flagged posts * Updating position of flags (#3708) * Plt 3506 - Reverting flag position (#3724) * Revert "Updating position of flags (#3708)" This reverts commit aaa05632c5d9eda35a048300a5bd7e99584c5b58. * Fixing the icon in search * Help text and white space improvements (#3730) * Updatng help text and some white spacing. * Updating help text
Этот коммит содержится в:
коммит произвёл
enahum
родитель
9b50b50283
Коммит
0184d6059b
28
api/post.go
28
api/post.go
@@ -34,6 +34,7 @@ func InitPost() {
|
||||
l4g.Debug(utils.T("api.post.init.debug"))
|
||||
|
||||
BaseRoutes.NeedTeam.Handle("/posts/search", ApiUserRequired(searchPosts)).Methods("POST")
|
||||
BaseRoutes.NeedTeam.Handle("/posts/flagged/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequiredActivity(getFlaggedPosts, false)).Methods("GET")
|
||||
BaseRoutes.NeedTeam.Handle("/posts/{post_id}", ApiUserRequired(getPostById)).Methods("GET")
|
||||
BaseRoutes.NeedTeam.Handle("/pltmp/{post_id}", ApiUserRequired(getPermalinkTmp)).Methods("GET")
|
||||
|
||||
@@ -1034,6 +1035,33 @@ func updatePost(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func getFlaggedPosts(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
|
||||
offset, err := strconv.Atoi(params["offset"])
|
||||
if err != nil {
|
||||
c.SetInvalidParam("getFlaggedPosts", "offset")
|
||||
return
|
||||
}
|
||||
|
||||
limit, err := strconv.Atoi(params["limit"])
|
||||
if err != nil {
|
||||
c.SetInvalidParam("getFlaggedPosts", "limit")
|
||||
return
|
||||
}
|
||||
|
||||
posts := &model.PostList{}
|
||||
|
||||
if result := <-Srv.Store.Post().GetFlaggedPosts(c.Session.UserId, offset, limit); result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
posts = result.Data.(*model.PostList)
|
||||
}
|
||||
|
||||
w.Write([]byte(posts.ToJson()))
|
||||
}
|
||||
|
||||
func getPosts(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
|
||||
|
||||
@@ -924,3 +924,38 @@ func TestGetOutOfChannelMentions(t *testing.T) {
|
||||
t.Fatalf("getOutOfChannelMentions returned %v when two users on a different team were mentioned", mentioned)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFlaggedPosts(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
Client := th.BasicClient
|
||||
user1 := th.BasicUser
|
||||
post1 := th.BasicPost
|
||||
|
||||
preferences := &model.Preferences{
|
||||
{
|
||||
UserId: user1.Id,
|
||||
Category: model.PREFERENCE_CATEGORY_FLAGGED_POST,
|
||||
Name: post1.Id,
|
||||
Value: "true",
|
||||
},
|
||||
}
|
||||
Client.Must(Client.SetPreferences(preferences))
|
||||
|
||||
r1 := Client.Must(Client.GetFlaggedPosts(0, 2)).Data.(*model.PostList)
|
||||
|
||||
if len(r1.Order) == 0 {
|
||||
t.Fatal("should have gotten a flagged post")
|
||||
}
|
||||
|
||||
if _, ok := r1.Posts[post1.Id]; !ok {
|
||||
t.Fatal("missing flagged post")
|
||||
}
|
||||
|
||||
Client.DeletePreferences(preferences)
|
||||
|
||||
r2 := Client.Must(Client.GetFlaggedPosts(0, 2)).Data.(*model.PostList)
|
||||
|
||||
if len(r2.Order) != 0 {
|
||||
t.Fatal("should not have gotten a flagged post")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1228,6 +1228,18 @@ func (c *Client) SearchPosts(terms string, isOrSearch bool) (*Result, *AppError)
|
||||
}
|
||||
}
|
||||
|
||||
// GetFlaggedPosts will return a post list of posts that have been flagged by the user.
|
||||
// The page is set by the integer parameters offset and limit.
|
||||
func (c *Client) GetFlaggedPosts(offset int, limit int) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(c.GetTeamRoute()+fmt.Sprintf("/posts/flagged/%v/%v", offset, limit), "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) UploadProfileFile(data []byte, contentType string) (*Result, *AppError) {
|
||||
return c.uploadFile(c.ApiUrl+"/users/newimage", data, contentType)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ const (
|
||||
PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW = "direct_channel_show"
|
||||
PREFERENCE_CATEGORY_TUTORIAL_STEPS = "tutorial_step"
|
||||
PREFERENCE_CATEGORY_ADVANCED_SETTINGS = "advanced_settings"
|
||||
PREFERENCE_CATEGORY_FLAGGED_POST = "flagged_post"
|
||||
|
||||
PREFERENCE_CATEGORY_DISPLAY_SETTINGS = "display_settings"
|
||||
PREFERENCE_NAME_COLLAPSE_SETTING = "collapse_previews"
|
||||
|
||||
@@ -142,6 +142,31 @@ func (s SqlPostStore) Update(oldPost *model.Post, newMessage string, newHashtags
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlPostStore) GetFlaggedPosts(userId string, offset int, limit int) StoreChannel {
|
||||
storeChannel := make(StoreChannel)
|
||||
go func() {
|
||||
result := StoreResult{}
|
||||
pl := &model.PostList{}
|
||||
|
||||
var posts []*model.Post
|
||||
if _, err := s.GetReplica().Select(&posts, "SELECT * FROM Posts WHERE Id IN (SELECT Name FROM Preferences WHERE UserId = :UserId AND Category = :Category) ORDER BY CreateAt ASC LIMIT :Limit OFFSET :Offset", map[string]interface{}{"UserId": userId, "Category": model.PREFERENCE_CATEGORY_FLAGGED_POST, "Offset": offset, "Limit": limit}); err != nil {
|
||||
result.Err = model.NewLocAppError("SqlPostStore.GetFlaggedPosts", "store.sql_post.get_flagged_posts.app_error", nil, err.Error())
|
||||
} else {
|
||||
for _, post := range posts {
|
||||
pl.AddPost(post)
|
||||
pl.AddOrder(post.Id)
|
||||
}
|
||||
}
|
||||
|
||||
result.Data = pl
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlPostStore) Get(id string) StoreChannel {
|
||||
storeChannel := make(StoreChannel)
|
||||
|
||||
|
||||
@@ -905,3 +905,67 @@ func TestPostCountsByDay(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostStoreGetFlaggedPosts(t *testing.T) {
|
||||
Setup()
|
||||
|
||||
o1 := &model.Post{}
|
||||
o1.ChannelId = model.NewId()
|
||||
o1.UserId = model.NewId()
|
||||
o1.Message = "a" + model.NewId() + "b"
|
||||
o1 = (<-store.Post().Save(o1)).Data.(*model.Post)
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
|
||||
o2 := &model.Post{}
|
||||
o2.ChannelId = o1.ChannelId
|
||||
o2.UserId = model.NewId()
|
||||
o2.Message = "a" + model.NewId() + "b"
|
||||
o2 = (<-store.Post().Save(o2)).Data.(*model.Post)
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
|
||||
r1 := (<-store.Post().GetFlaggedPosts(o1.ChannelId, 0, 2)).Data.(*model.PostList)
|
||||
|
||||
if len(r1.Order) != 0 {
|
||||
t.Fatal("should be empty")
|
||||
}
|
||||
|
||||
preferences := model.Preferences{
|
||||
{
|
||||
UserId: o1.UserId,
|
||||
Category: model.PREFERENCE_CATEGORY_FLAGGED_POST,
|
||||
Name: o1.Id,
|
||||
Value: "true",
|
||||
},
|
||||
}
|
||||
|
||||
Must(store.Preference().Save(&preferences))
|
||||
|
||||
r2 := (<-store.Post().GetFlaggedPosts(o1.UserId, 0, 2)).Data.(*model.PostList)
|
||||
|
||||
if len(r2.Order) != 1 {
|
||||
t.Fatal("should have 1 post")
|
||||
}
|
||||
|
||||
preferences = model.Preferences{
|
||||
{
|
||||
UserId: o1.UserId,
|
||||
Category: model.PREFERENCE_CATEGORY_FLAGGED_POST,
|
||||
Name: o2.Id,
|
||||
Value: "true",
|
||||
},
|
||||
}
|
||||
|
||||
Must(store.Preference().Save(&preferences))
|
||||
|
||||
r3 := (<-store.Post().GetFlaggedPosts(o1.UserId, 0, 1)).Data.(*model.PostList)
|
||||
|
||||
if len(r3.Order) != 1 {
|
||||
t.Fatal("should have 1 post")
|
||||
}
|
||||
|
||||
r4 := (<-store.Post().GetFlaggedPosts(o1.UserId, 0, 2)).Data.(*model.PostList)
|
||||
|
||||
if len(r4.Order) != 2 {
|
||||
t.Fatal("should have 2 posts")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,7 @@ type PostStore interface {
|
||||
Delete(postId string, time int64) StoreChannel
|
||||
PermanentDeleteByUser(userId string) StoreChannel
|
||||
GetPosts(channelId string, offset int, limit int) StoreChannel
|
||||
GetFlaggedPosts(userId string, offset int, limit int) StoreChannel
|
||||
GetPostsBefore(channelId string, postId string, numPosts int, offset int) StoreChannel
|
||||
GetPostsAfter(channelId string, postId string, numPosts int, offset int) StoreChannel
|
||||
GetPostsSince(channelId string, time int64) StoreChannel
|
||||
|
||||
@@ -190,7 +190,8 @@ export function emitPostFocusRightHandSideFromSearch(post, isMentionSearch) {
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECEIVED_POST_SELECTED,
|
||||
postId: Utils.getRootId(post),
|
||||
from_search: SearchStore.getSearchTerm()
|
||||
from_search: SearchStore.getSearchTerm(),
|
||||
from_flagged_posts: SearchStore.getIsFlaggedPosts()
|
||||
});
|
||||
|
||||
AppDispatcher.handleServerAction({
|
||||
|
||||
@@ -9,12 +9,13 @@ import TeamStore from 'stores/team_store.jsx';
|
||||
import UserStore from 'stores/user_store.jsx';
|
||||
|
||||
import * as PostUtils from 'utils/post_utils.jsx';
|
||||
import Constants from 'utils/constants.jsx';
|
||||
const ActionTypes = Constants.ActionTypes;
|
||||
|
||||
import Client from 'client/web_client.jsx';
|
||||
import * as AsyncClient from 'utils/async_client.jsx';
|
||||
|
||||
import Constants from 'utils/constants.jsx';
|
||||
const ActionTypes = Constants.ActionTypes;
|
||||
const Preferences = Constants.Preferences;
|
||||
|
||||
export function handleNewPost(post, msg) {
|
||||
if (ChannelStore.getCurrentId() === post.channel_id) {
|
||||
if (window.isActive) {
|
||||
@@ -116,3 +117,38 @@ export function setUnreadPost(channelId, postId) {
|
||||
ChannelStore.emitLastViewed(lastViewed, ownNewMessage);
|
||||
}
|
||||
}
|
||||
|
||||
export function flagPost(postId) {
|
||||
AsyncClient.savePreference(Preferences.CATEGORY_FLAGGED_POST, postId, 'true');
|
||||
}
|
||||
|
||||
export function unflagPost(postId, success) {
|
||||
const pref = {
|
||||
user_id: UserStore.getCurrentId(),
|
||||
category: Preferences.CATEGORY_FLAGGED_POST,
|
||||
name: postId
|
||||
};
|
||||
AsyncClient.deletePreferences([pref], success);
|
||||
}
|
||||
|
||||
export function getFlaggedPosts() {
|
||||
Client.getFlaggedPosts(0, Constants.POST_CHUNK_SIZE,
|
||||
(data) => {
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECEIVED_SEARCH,
|
||||
results: data,
|
||||
is_flagged_posts: true
|
||||
});
|
||||
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECEIVED_SEARCH_TERM,
|
||||
term: null,
|
||||
do_search: false,
|
||||
is_mention_search: false
|
||||
});
|
||||
},
|
||||
(err) => {
|
||||
AsyncClient.dispatchError(err, 'getFlaggedPosts');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1434,6 +1434,15 @@ export default class Client {
|
||||
end(this.handleResponse.bind(this, 'getPostsAfter', success, error));
|
||||
}
|
||||
|
||||
getFlaggedPosts(offset, limit, success, error) {
|
||||
request.
|
||||
get(`${this.getTeamNeededRoute()}/posts/flagged/${offset}/${limit}`).
|
||||
set(this.defaultHeaders).
|
||||
type('application/json').
|
||||
accept('application/json').
|
||||
end(this.handleResponse.bind(this, 'getFlaggedPosts', success, error));
|
||||
}
|
||||
|
||||
// Routes for Files
|
||||
|
||||
getFileInfo(filename, success, error) {
|
||||
|
||||
@@ -26,19 +26,18 @@ import PreferenceStore from 'stores/preference_store.jsx';
|
||||
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
import * as TextFormatting from 'utils/text_formatting.jsx';
|
||||
import * as AsyncClient from 'utils/async_client.jsx';
|
||||
import Client from 'client/web_client.jsx';
|
||||
import * as AsyncClient from 'utils/async_client.jsx';
|
||||
import {getFlaggedPosts} from 'actions/post_actions.jsx';
|
||||
|
||||
import Constants from 'utils/constants.jsx';
|
||||
const UserStatuses = Constants.UserStatuses;
|
||||
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {browserHistory} from 'react-router/es6';
|
||||
|
||||
const ActionTypes = Constants.ActionTypes;
|
||||
|
||||
import {Tooltip, OverlayTrigger, Popover} from 'react-bootstrap';
|
||||
|
||||
import React from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {browserHistory} from 'react-router/es6';
|
||||
import {Tooltip, OverlayTrigger, Popover} from 'react-bootstrap';
|
||||
|
||||
export default class ChannelHeader extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -50,6 +49,7 @@ export default class ChannelHeader extends React.Component {
|
||||
this.showRenameChannelModal = this.showRenameChannelModal.bind(this);
|
||||
this.hideRenameChannelModal = this.hideRenameChannelModal.bind(this);
|
||||
this.openRecentMentions = this.openRecentMentions.bind(this);
|
||||
this.getFlagged = this.getFlagged.bind(this);
|
||||
|
||||
const state = this.getStateFromStores();
|
||||
state.showEditChannelPurposeModal = false;
|
||||
@@ -159,6 +159,11 @@ export default class ChannelHeader extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
getFlagged(e) {
|
||||
e.preventDefault();
|
||||
getFlaggedPosts();
|
||||
}
|
||||
|
||||
openRecentMentions(e) {
|
||||
if (Utils.cmdOrCtrlPressed(e) && e.shiftKey && e.keyCode === Constants.KeyCodes.M) {
|
||||
e.preventDefault();
|
||||
@@ -220,6 +225,8 @@ export default class ChannelHeader extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const flagIcon = Constants.FLAG_ICON_OUTLINE_SVG;
|
||||
|
||||
if (!this.validState()) {
|
||||
return null;
|
||||
}
|
||||
@@ -233,6 +240,16 @@ export default class ChannelHeader extends React.Component {
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const flaggedTooltip = (
|
||||
<Tooltip id='flaggedTooltip'>
|
||||
<FormattedMessage
|
||||
id='channel_header.flagged'
|
||||
defaultMessage='Flagged Posts'
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const popoverContent = (
|
||||
<Popover
|
||||
id='header-popover'
|
||||
@@ -592,6 +609,26 @@ export default class ChannelHeader extends React.Component {
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
</th>
|
||||
<th>
|
||||
<div className='dropdown channel-header__links'>
|
||||
<OverlayTrigger
|
||||
delayShow={Constants.OVERLAY_TIME_DELAY}
|
||||
placement='bottom'
|
||||
overlay={flaggedTooltip}
|
||||
>
|
||||
<a
|
||||
href='#'
|
||||
type='button'
|
||||
onClick={this.getFlagged}
|
||||
>
|
||||
<span
|
||||
className='icon icon__flag'
|
||||
dangerouslySetInnerHTML={{__html: flagIcon}}
|
||||
/>
|
||||
</a>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -100,6 +100,10 @@ export default class Post extends React.Component {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nextProps.isFlagged !== this.props.isFlagged) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) {
|
||||
return true;
|
||||
}
|
||||
@@ -245,6 +249,7 @@ export default class Post extends React.Component {
|
||||
compactDisplay={this.props.compactDisplay}
|
||||
displayNameType={this.props.displayNameType}
|
||||
useMilitaryTime={this.props.useMilitaryTime}
|
||||
isFlagged={this.props.isFlagged}
|
||||
/>
|
||||
<PostBody
|
||||
post={post}
|
||||
@@ -281,5 +286,6 @@ Post.propTypes = {
|
||||
commentCount: React.PropTypes.number,
|
||||
isCommentMention: React.PropTypes.bool,
|
||||
useMilitaryTime: React.PropTypes.bool.isRequired,
|
||||
emojis: React.PropTypes.object.isRequired
|
||||
emojis: React.PropTypes.object.isRequired,
|
||||
isFlagged: React.PropTypes.bool
|
||||
};
|
||||
|
||||
@@ -72,6 +72,7 @@ export default class PostHeader extends React.Component {
|
||||
currentUser={this.props.currentUser}
|
||||
compactDisplay={this.props.compactDisplay}
|
||||
useMilitaryTime={this.props.useMilitaryTime}
|
||||
isFlagged={this.props.isFlagged}
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -97,5 +98,6 @@ PostHeader.propTypes = {
|
||||
sameUser: React.PropTypes.bool.isRequired,
|
||||
compactDisplay: React.PropTypes.bool,
|
||||
displayNameType: React.PropTypes.string,
|
||||
useMilitaryTime: React.PropTypes.bool.isRequired
|
||||
useMilitaryTime: React.PropTypes.bool.isRequired,
|
||||
isFlagged: React.PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
@@ -2,17 +2,21 @@
|
||||
// See License.txt for license information.
|
||||
|
||||
import $ from 'jquery';
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
|
||||
import PostTime from './post_time.jsx';
|
||||
|
||||
import * as GlobalActions from 'actions/global_actions.jsx';
|
||||
import * as PostActions from 'actions/post_actions.jsx';
|
||||
|
||||
import TeamStore from 'stores/team_store.jsx';
|
||||
import UserStore from 'stores/user_store.jsx';
|
||||
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
import Constants from 'utils/constants.jsx';
|
||||
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
|
||||
|
||||
import React from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
export default class PostInfo extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -21,7 +25,10 @@ export default class PostInfo extends React.Component {
|
||||
this.handleDropdownClick = this.handleDropdownClick.bind(this);
|
||||
this.handlePermalink = this.handlePermalink.bind(this);
|
||||
this.removePost = this.removePost.bind(this);
|
||||
this.flagPost = this.flagPost.bind(this);
|
||||
this.unflagPost = this.unflagPost.bind(this);
|
||||
}
|
||||
|
||||
handleDropdownClick(e) {
|
||||
var position = $('#post-list').height() - $(e.target).offset().top;
|
||||
var dropdown = $(e.target).closest('.col__reply').find('.dropdown-menu');
|
||||
@@ -29,10 +36,12 @@ export default class PostInfo extends React.Component {
|
||||
dropdown.addClass('bottom');
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
$('#post_dropdown' + this.props.post.id).on('shown.bs.dropdown', () => this.props.handleDropdownOpened(true));
|
||||
$('#post_dropdown' + this.props.post.id).on('hidden.bs.dropdown', () => this.props.handleDropdownOpened(false));
|
||||
}
|
||||
|
||||
createDropdown() {
|
||||
var post = this.props.post;
|
||||
var isOwner = this.props.currentUser.id === post.user_id;
|
||||
@@ -74,6 +83,44 @@ export default class PostInfo extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
if (Utils.isMobile()) {
|
||||
if (this.props.isFlagged) {
|
||||
dropdownContents.push(
|
||||
<li
|
||||
key='mobileFlag'
|
||||
role='presentation'
|
||||
>
|
||||
<a
|
||||
href='#'
|
||||
onClick={this.unflagPost}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='rhs_root.mobile.unflag'
|
||||
defaultMessage='Unflag'
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
} else {
|
||||
dropdownContents.push(
|
||||
<li
|
||||
key='mobileFlag'
|
||||
role='presentation'
|
||||
>
|
||||
<a
|
||||
href='#'
|
||||
onClick={this.flagPost}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='rhs_root.mobile.flag'
|
||||
defaultMessage='Flag'
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
dropdownContents.push(
|
||||
<li
|
||||
key='copyLink'
|
||||
@@ -186,12 +233,23 @@ export default class PostInfo extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
flagPost(e) {
|
||||
e.preventDefault();
|
||||
PostActions.flagPost(this.props.post.id);
|
||||
}
|
||||
|
||||
unflagPost(e) {
|
||||
e.preventDefault();
|
||||
PostActions.unflagPost(this.props.post.id);
|
||||
}
|
||||
|
||||
render() {
|
||||
var post = this.props.post;
|
||||
var comments = '';
|
||||
var showCommentClass = '';
|
||||
var highlightMentionClass = '';
|
||||
var commentCountText = this.props.commentCount;
|
||||
const flagIcon = Constants.FLAG_ICON_SVG;
|
||||
|
||||
if (this.props.commentCount >= 1) {
|
||||
showCommentClass = ' icon--show';
|
||||
@@ -240,6 +298,44 @@ export default class PostInfo extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
let flag;
|
||||
let flagFunc;
|
||||
let flagVisible = '';
|
||||
let flagTooltip = (
|
||||
<Tooltip id='flagTooltip'>
|
||||
<FormattedMessage
|
||||
id='flag_post.flag'
|
||||
defaultMessage='Flag for follow up'
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
if (this.props.isFlagged) {
|
||||
flagVisible = 'visible';
|
||||
flag = (
|
||||
<span
|
||||
className='icon'
|
||||
dangerouslySetInnerHTML={{__html: flagIcon}}
|
||||
/>
|
||||
);
|
||||
flagFunc = this.unflagPost;
|
||||
flagTooltip = (
|
||||
<Tooltip id='flagTooltip'>
|
||||
<FormattedMessage
|
||||
id='flag_post.unflag'
|
||||
defaultMessage='Unflag'
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
} else {
|
||||
flag = (
|
||||
<span
|
||||
className='icon'
|
||||
dangerouslySetInnerHTML={{__html: flagIcon}}
|
||||
/>
|
||||
);
|
||||
flagFunc = this.flagPost;
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className='post__header--info'>
|
||||
<li className='col'>
|
||||
@@ -249,6 +345,20 @@ export default class PostInfo extends React.Component {
|
||||
compactDisplay={this.props.compactDisplay}
|
||||
useMilitaryTime={this.props.useMilitaryTime}
|
||||
/>
|
||||
<OverlayTrigger
|
||||
key={'flagtooltipkey' + flagVisible}
|
||||
delayShow={Constants.OVERLAY_TIME_DELAY}
|
||||
placement='top'
|
||||
overlay={flagTooltip}
|
||||
>
|
||||
<a
|
||||
href='#'
|
||||
className={'flag-icon__container ' + flagVisible}
|
||||
onClick={flagFunc}
|
||||
>
|
||||
{flag}
|
||||
</a>
|
||||
</OverlayTrigger>
|
||||
</li>
|
||||
{options}
|
||||
</ul>
|
||||
@@ -274,5 +384,6 @@ PostInfo.propTypes = {
|
||||
sameUser: React.PropTypes.bool.isRequired,
|
||||
currentUser: React.PropTypes.object.isRequired,
|
||||
compactDisplay: React.PropTypes.bool,
|
||||
useMilitaryTime: React.PropTypes.bool.isRequired
|
||||
useMilitaryTime: React.PropTypes.bool.isRequired,
|
||||
isFlagged: React.PropTypes.bool
|
||||
};
|
||||
|
||||
@@ -284,6 +284,11 @@ export default class PostList extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
let isFlagged = false;
|
||||
if (this.props.flaggedPosts) {
|
||||
isFlagged = this.props.flaggedPosts.get(post.id) === 'true';
|
||||
}
|
||||
|
||||
const postCtl = (
|
||||
<Post
|
||||
key={keyPrefix + 'postKey'}
|
||||
@@ -305,6 +310,7 @@ export default class PostList extends React.Component {
|
||||
previewCollapsed={this.props.previewsCollapsed}
|
||||
useMilitaryTime={this.props.useMilitaryTime}
|
||||
emojis={this.props.emojis}
|
||||
isFlagged={isFlagged}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -572,5 +578,6 @@ PostList.propTypes = {
|
||||
previewsCollapsed: React.PropTypes.string,
|
||||
useMilitaryTime: React.PropTypes.bool.isRequired,
|
||||
isFocusPost: React.PropTypes.bool,
|
||||
emojis: React.PropTypes.object.isRequired
|
||||
emojis: React.PropTypes.object.isRequired,
|
||||
flaggedPosts: React.PropTypes.object
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import EmojiStore from 'stores/emoji_store.jsx';
|
||||
import PostStore from 'stores/post_store.jsx';
|
||||
import UserStore from 'stores/user_store.jsx';
|
||||
import ChannelStore from 'stores/channel_store.jsx';
|
||||
import PreferenceStore from 'stores/preference_store.jsx';
|
||||
|
||||
import Constants from 'utils/constants.jsx';
|
||||
const ScrollTypes = Constants.ScrollTypes;
|
||||
@@ -22,6 +23,7 @@ export default class PostFocusView extends React.Component {
|
||||
this.onPostsChange = this.onPostsChange.bind(this);
|
||||
this.onUserChange = this.onUserChange.bind(this);
|
||||
this.onEmojiChange = this.onEmojiChange.bind(this);
|
||||
this.onPreferenceChange = this.onPreferenceChange.bind(this);
|
||||
this.onPostListScroll = this.onPostListScroll.bind(this);
|
||||
|
||||
const focusedPostId = PostStore.getFocusedPostId();
|
||||
@@ -41,7 +43,8 @@ export default class PostFocusView extends React.Component {
|
||||
scrollPostId: focusedPostId,
|
||||
atTop: PostStore.getVisibilityAtTop(focusedPostId),
|
||||
atBottom: PostStore.getVisibilityAtBottom(focusedPostId),
|
||||
emojis: EmojiStore.getEmojis()
|
||||
emojis: EmojiStore.getEmojis(),
|
||||
flaggedPosts: PreferenceStore.getCategory(Constants.Preferences.CATEGORY_FLAGGED_POST)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -50,6 +53,7 @@ export default class PostFocusView extends React.Component {
|
||||
PostStore.addChangeListener(this.onPostsChange);
|
||||
UserStore.addChangeListener(this.onUserChange);
|
||||
EmojiStore.addChangeListener(this.onEmojiChange);
|
||||
PreferenceStore.addChangeListener(this.onPreferenceChange);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@@ -98,6 +102,12 @@ export default class PostFocusView extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
onPreferenceChange() {
|
||||
this.setState({
|
||||
flaggedPosts: PreferenceStore.getCategory(Constants.Preferences.CATEGORY_FLAGGED_POST)
|
||||
});
|
||||
}
|
||||
|
||||
onPostListScroll() {
|
||||
this.setState({scrollType: ScrollTypes.FREE});
|
||||
}
|
||||
@@ -128,6 +138,7 @@ export default class PostFocusView extends React.Component {
|
||||
postsToHighlight={postsToHighlight}
|
||||
isFocusPost={true}
|
||||
emojis={this.state.emojis}
|
||||
flaggedPosts={this.state.flaggedPosts}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,8 @@ export default class PostViewController extends React.Component {
|
||||
compactDisplay: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT,
|
||||
previewsCollapsed: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.COLLAPSE_DISPLAY, 'false'),
|
||||
useMilitaryTime: PreferenceStore.getBool(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.USE_MILITARY_TIME, false),
|
||||
emojis: EmojiStore.getEmojis()
|
||||
emojis: EmojiStore.getEmojis(),
|
||||
flaggedPosts: PreferenceStore.getCategory(Constants.Preferences.CATEGORY_FLAGGED_POST)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -87,7 +88,8 @@ export default class PostViewController extends React.Component {
|
||||
displayPostsInCenter: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_CENTERED,
|
||||
compactDisplay: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT,
|
||||
previewsCollapsed: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.COLLAPSE_DISPLAY, 'false') + previewSuffix,
|
||||
useMilitaryTime: PreferenceStore.getBool(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.USE_MILITARY_TIME, false)
|
||||
useMilitaryTime: PreferenceStore.getBool(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.USE_MILITARY_TIME, false),
|
||||
flaggedPosts: PreferenceStore.getCategory(Constants.Preferences.CATEGORY_FLAGGED_POST)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -224,6 +226,10 @@ export default class PostViewController extends React.Component {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Utils.areObjectsEqual(nextState.flaggedPosts, this.state.flaggedPosts)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nextState.lastViewed !== this.state.lastViewed) {
|
||||
return true;
|
||||
}
|
||||
@@ -292,6 +298,7 @@ export default class PostViewController extends React.Component {
|
||||
compactDisplay={this.state.compactDisplay}
|
||||
previewsCollapsed={this.state.previewsCollapsed}
|
||||
useMilitaryTime={this.state.useMilitaryTime}
|
||||
flaggedPosts={this.state.flaggedPosts}
|
||||
lastViewed={this.state.lastViewed}
|
||||
emojis={this.state.emojis}
|
||||
ownNewMessage={this.state.ownNewMessage}
|
||||
|
||||
@@ -9,12 +9,14 @@ import TeamStore from 'stores/team_store.jsx';
|
||||
import UserStore from 'stores/user_store.jsx';
|
||||
|
||||
import * as GlobalActions from 'actions/global_actions.jsx';
|
||||
import {flagPost, unflagPost} from 'actions/post_actions.jsx';
|
||||
|
||||
import * as TextFormatting from 'utils/text_formatting.jsx';
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
import Client from 'client/web_client.jsx';
|
||||
|
||||
import Constants from 'utils/constants.jsx';
|
||||
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
|
||||
|
||||
import {FormattedMessage, FormattedDate} from 'react-intl';
|
||||
|
||||
@@ -27,13 +29,17 @@ export default class RhsComment extends React.Component {
|
||||
super(props);
|
||||
|
||||
this.handlePermalink = this.handlePermalink.bind(this);
|
||||
this.flagPost = this.flagPost.bind(this);
|
||||
this.unflagPost = this.unflagPost.bind(this);
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
handlePermalink(e) {
|
||||
e.preventDefault();
|
||||
GlobalActions.showGetPostLinkModal(this.props.post);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
if (nextProps.compactDisplay !== this.props.compactDisplay) {
|
||||
return true;
|
||||
@@ -43,6 +49,10 @@ export default class RhsComment extends React.Component {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nextProps.isFlagged !== this.props.isFlagged) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Utils.areObjectsEqual(nextProps.post, this.props.post)) {
|
||||
return true;
|
||||
}
|
||||
@@ -53,6 +63,17 @@ export default class RhsComment extends React.Component {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
flagPost(e) {
|
||||
e.preventDefault();
|
||||
flagPost(this.props.post.id);
|
||||
}
|
||||
|
||||
unflagPost(e) {
|
||||
e.preventDefault();
|
||||
unflagPost(this.props.post.id);
|
||||
}
|
||||
|
||||
createDropdown() {
|
||||
var post = this.props.post;
|
||||
|
||||
@@ -66,6 +87,44 @@ export default class RhsComment extends React.Component {
|
||||
|
||||
var dropdownContents = [];
|
||||
|
||||
if (Utils.isMobile()) {
|
||||
if (this.props.isFlagged) {
|
||||
dropdownContents.push(
|
||||
<li
|
||||
key='mobileFlag'
|
||||
role='presentation'
|
||||
>
|
||||
<a
|
||||
href='#'
|
||||
onClick={this.unflagPost}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='rhs_root.mobile.unflag'
|
||||
defaultMessage='Unflag'
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
} else {
|
||||
dropdownContents.push(
|
||||
<li
|
||||
key='mobileFlag'
|
||||
role='presentation'
|
||||
>
|
||||
<a
|
||||
href='#'
|
||||
onClick={this.flagPost}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='rhs_root.mobile.flag'
|
||||
defaultMessage='Flag'
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
dropdownContents.push(
|
||||
<li
|
||||
key='rhs-root-permalink'
|
||||
@@ -151,8 +210,10 @@ export default class RhsComment extends React.Component {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
var post = this.props.post;
|
||||
const flagIcon = Constants.FLAG_ICON_SVG;
|
||||
|
||||
var currentUserCss = '';
|
||||
if (this.props.currentUser === post.user_id) {
|
||||
@@ -225,6 +286,44 @@ export default class RhsComment extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
let flag;
|
||||
let flagFunc;
|
||||
let flagVisible = '';
|
||||
let flagTooltip = (
|
||||
<Tooltip id='flagTooltip'>
|
||||
<FormattedMessage
|
||||
id='flag_post.flag'
|
||||
defaultMessage='Flag for follow up'
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
if (this.props.isFlagged) {
|
||||
flagVisible = 'visible';
|
||||
flag = (
|
||||
<span
|
||||
className='icon'
|
||||
dangerouslySetInnerHTML={{__html: flagIcon}}
|
||||
/>
|
||||
);
|
||||
flagFunc = this.unflagPost;
|
||||
flagTooltip = (
|
||||
<Tooltip id='flagTooltip'>
|
||||
<FormattedMessage
|
||||
id='flag_post.unflag'
|
||||
defaultMessage='Unflag'
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
} else {
|
||||
flag = (
|
||||
<span
|
||||
className='icon'
|
||||
dangerouslySetInnerHTML={{__html: flagIcon}}
|
||||
/>
|
||||
);
|
||||
flagFunc = this.flagPost;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={'post post--thread ' + currentUserCss + ' ' + compactClass}>
|
||||
<div className='post__content'>
|
||||
@@ -247,6 +346,20 @@ export default class RhsComment extends React.Component {
|
||||
minute='2-digit'
|
||||
/>
|
||||
</time>
|
||||
<OverlayTrigger
|
||||
key={'commentflagtooltipkey' + flagVisible}
|
||||
delayShow={Constants.OVERLAY_TIME_DELAY}
|
||||
placement='top'
|
||||
overlay={flagTooltip}
|
||||
>
|
||||
<a
|
||||
href='#'
|
||||
className={'flag-icon__container ' + flagVisible}
|
||||
onClick={flagFunc}
|
||||
>
|
||||
{flag}
|
||||
</a>
|
||||
</OverlayTrigger>
|
||||
</li>
|
||||
<li className='col col__reply'>
|
||||
{dropdown}
|
||||
@@ -271,5 +384,6 @@ RhsComment.propTypes = {
|
||||
user: React.PropTypes.object.isRequired,
|
||||
currentUser: React.PropTypes.object.isRequired,
|
||||
compactDisplay: React.PropTypes.bool,
|
||||
useMilitaryTime: React.PropTypes.bool.isRequired
|
||||
useMilitaryTime: React.PropTypes.bool.isRequired,
|
||||
isFlagged: React.PropTypes.bool
|
||||
};
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
|
||||
import Constants from 'utils/constants.jsx';
|
||||
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
|
||||
|
||||
import * as GlobalActions from 'actions/global_actions.jsx';
|
||||
import {getFlaggedPosts} from 'actions/post_actions.jsx';
|
||||
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
@@ -34,17 +36,21 @@ export default class RhsHeaderPost extends React.Component {
|
||||
handleBack(e) {
|
||||
e.preventDefault();
|
||||
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECEIVED_SEARCH_TERM,
|
||||
term: this.props.fromSearch,
|
||||
do_search: true,
|
||||
is_mention_search: this.props.isMentionSearch
|
||||
});
|
||||
if (this.props.fromSearch) {
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECEIVED_SEARCH_TERM,
|
||||
term: this.props.fromSearch,
|
||||
do_search: true,
|
||||
is_mention_search: this.props.isMentionSearch
|
||||
});
|
||||
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECEIVED_POST_SELECTED,
|
||||
postId: null
|
||||
});
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECEIVED_POST_SELECTED,
|
||||
postId: null
|
||||
});
|
||||
} else if (this.props.fromFlaggedPosts) {
|
||||
getFlaggedPosts();
|
||||
}
|
||||
}
|
||||
render() {
|
||||
let back;
|
||||
@@ -57,14 +63,26 @@ export default class RhsHeaderPost extends React.Component {
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const backToResultsTooltip = (
|
||||
<Tooltip id='backToResultsTooltip'>
|
||||
<FormattedMessage
|
||||
id='rhs_header.backToResultsTooltip'
|
||||
defaultMessage='Back to Search Results'
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
let backToResultsTooltip;
|
||||
if (this.props.fromSearch) {
|
||||
backToResultsTooltip = (
|
||||
<Tooltip id='backToResultsTooltip'>
|
||||
<FormattedMessage
|
||||
id='rhs_header.backToResultsTooltip'
|
||||
defaultMessage='Back to Search Results'
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
} else if (this.props.fromFlaggedPosts) {
|
||||
backToResultsTooltip = (
|
||||
<Tooltip id='backToResultsTooltip'>
|
||||
<FormattedMessage
|
||||
id='rhs_header.backToFlaggedTooltip'
|
||||
defaultMessage='Back to Flagged Posts'
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
const expandSidebarTooltip = (
|
||||
<Tooltip id='expandSidebarTooltip'>
|
||||
@@ -84,7 +102,7 @@ export default class RhsHeaderPost extends React.Component {
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
if (this.props.fromSearch) {
|
||||
if (this.props.fromSearch || this.props.fromFlaggedPosts) {
|
||||
back = (
|
||||
<a
|
||||
href='#'
|
||||
@@ -161,6 +179,7 @@ RhsHeaderPost.defaultProps = {
|
||||
RhsHeaderPost.propTypes = {
|
||||
isMentionSearch: React.PropTypes.bool,
|
||||
fromSearch: React.PropTypes.string,
|
||||
fromFlaggedPosts: React.PropTypes.bool,
|
||||
toggleSize: React.PropTypes.function,
|
||||
shrink: React.PropTypes.function
|
||||
};
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import ChannelStore from 'stores/channel_store.jsx';
|
||||
import UserProfile from './user_profile.jsx';
|
||||
import PostBodyAdditionalContent from 'components/post_view/components/post_body_additional_content.jsx';
|
||||
import FileAttachmentList from './file_attachment_list.jsx';
|
||||
|
||||
import ChannelStore from 'stores/channel_store.jsx';
|
||||
import UserStore from 'stores/user_store.jsx';
|
||||
import TeamStore from 'stores/team_store.jsx';
|
||||
import * as TextFormatting from 'utils/text_formatting.jsx';
|
||||
import FileAttachmentList from './file_attachment_list.jsx';
|
||||
import PostBodyAdditionalContent from 'components/post_view/components/post_body_additional_content.jsx';
|
||||
|
||||
import * as GlobalActions from 'actions/global_actions.jsx';
|
||||
import {flagPost, unflagPost} from 'actions/post_actions.jsx';
|
||||
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
import * as PostUtils from 'utils/post_utils.jsx';
|
||||
import * as TextFormatting from 'utils/text_formatting.jsx';
|
||||
|
||||
import Constants from 'utils/constants.jsx';
|
||||
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
|
||||
|
||||
import {FormattedMessage, FormattedDate} from 'react-intl';
|
||||
|
||||
@@ -24,13 +28,17 @@ export default class RhsRootPost extends React.Component {
|
||||
super(props);
|
||||
|
||||
this.handlePermalink = this.handlePermalink.bind(this);
|
||||
this.flagPost = this.flagPost.bind(this);
|
||||
this.unflagPost = this.unflagPost.bind(this);
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
handlePermalink(e) {
|
||||
e.preventDefault();
|
||||
GlobalActions.showGetPostLinkModal(this.props.post);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
if (nextProps.compactDisplay !== this.props.compactDisplay) {
|
||||
return true;
|
||||
@@ -40,6 +48,10 @@ export default class RhsRootPost extends React.Component {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nextProps.isFlagged !== this.props.isFlagged) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Utils.areObjectsEqual(nextProps.post, this.props.post)) {
|
||||
return true;
|
||||
}
|
||||
@@ -50,6 +62,17 @@ export default class RhsRootPost extends React.Component {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
flagPost(e) {
|
||||
e.preventDefault();
|
||||
flagPost(this.props.post.id);
|
||||
}
|
||||
|
||||
unflagPost(e) {
|
||||
e.preventDefault();
|
||||
unflagPost(this.props.post.id);
|
||||
}
|
||||
|
||||
render() {
|
||||
const post = this.props.post;
|
||||
const user = this.props.user;
|
||||
@@ -59,6 +82,7 @@ export default class RhsRootPost extends React.Component {
|
||||
const isSystemMessage = post.type && post.type.startsWith(Constants.SYSTEM_MESSAGE_PREFIX);
|
||||
var timestamp = UserStore.getProfile(post.user_id).update_at;
|
||||
var channel = ChannelStore.get(post.channel_id);
|
||||
const flagIcon = Constants.FLAG_ICON_SVG;
|
||||
|
||||
var type = 'Post';
|
||||
if (post.root_id.length > 0) {
|
||||
@@ -91,6 +115,44 @@ export default class RhsRootPost extends React.Component {
|
||||
|
||||
var dropdownContents = [];
|
||||
|
||||
if (Utils.isMobile()) {
|
||||
if (this.props.isFlagged) {
|
||||
dropdownContents.push(
|
||||
<li
|
||||
key='mobileFlag'
|
||||
role='presentation'
|
||||
>
|
||||
<a
|
||||
href='#'
|
||||
onClick={this.unflagPost}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='rhs_root.mobile.unflag'
|
||||
defaultMessage='Unflag'
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
} else {
|
||||
dropdownContents.push(
|
||||
<li
|
||||
key='mobileFlag'
|
||||
role='presentation'
|
||||
>
|
||||
<a
|
||||
href='#'
|
||||
onClick={this.flagPost}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='rhs_root.mobile.flag'
|
||||
defaultMessage='Flag'
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
dropdownContents.push(
|
||||
<li
|
||||
key='rhs-root-permalink'
|
||||
@@ -246,6 +308,44 @@ export default class RhsRootPost extends React.Component {
|
||||
/>
|
||||
);
|
||||
|
||||
let flag;
|
||||
let flagFunc;
|
||||
let flagVisible = '';
|
||||
let flagTooltip = (
|
||||
<Tooltip id='flagTooltip'>
|
||||
<FormattedMessage
|
||||
id='flag_post.flag'
|
||||
defaultMessage='Flag for follow up'
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
if (this.props.isFlagged) {
|
||||
flagVisible = 'visible';
|
||||
flag = (
|
||||
<span
|
||||
className='icon'
|
||||
dangerouslySetInnerHTML={{__html: flagIcon}}
|
||||
/>
|
||||
);
|
||||
flagFunc = this.unflagPost;
|
||||
flagTooltip = (
|
||||
<Tooltip id='flagTooltip'>
|
||||
<FormattedMessage
|
||||
id='flag_post.unflag'
|
||||
defaultMessage='Unflag'
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
} else {
|
||||
flag = (
|
||||
<span
|
||||
className='icon'
|
||||
dangerouslySetInnerHTML={{__html: flagIcon}}
|
||||
/>
|
||||
);
|
||||
flagFunc = this.flagPost;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={'post post--root post--thread ' + userCss + ' ' + systemMessageClass + ' ' + compactClass}>
|
||||
<div className='post-right-channel__name'>{channelName}</div>
|
||||
@@ -267,11 +367,23 @@ export default class RhsRootPost extends React.Component {
|
||||
minute='2-digit'
|
||||
/>
|
||||
</time>
|
||||
<OverlayTrigger
|
||||
key={'rootpostflagtooltipkey' + flagVisible}
|
||||
delayShow={Constants.OVERLAY_TIME_DELAY}
|
||||
placement='top'
|
||||
overlay={flagTooltip}
|
||||
>
|
||||
<a
|
||||
href='#'
|
||||
className={'flag-icon__container ' + flagVisible}
|
||||
onClick={flagFunc}
|
||||
>
|
||||
{flag}
|
||||
</a>
|
||||
</OverlayTrigger>
|
||||
</li>
|
||||
<li className='col col__reply'>
|
||||
<div>
|
||||
{rootOptions}
|
||||
</div>
|
||||
{rootOptions}
|
||||
</li>
|
||||
</ul>
|
||||
<div className='post__body'>
|
||||
@@ -297,5 +409,6 @@ RhsRootPost.propTypes = {
|
||||
currentUser: React.PropTypes.object.isRequired,
|
||||
commentCount: React.PropTypes.number,
|
||||
compactDisplay: React.PropTypes.bool,
|
||||
useMilitaryTime: React.PropTypes.bool.isRequired
|
||||
useMilitaryTime: React.PropTypes.bool.isRequired,
|
||||
isFlagged: React.PropTypes.bool
|
||||
};
|
||||
|
||||
@@ -62,6 +62,7 @@ export default class RhsThread extends React.Component {
|
||||
state.windowHeight = Utils.windowHeight();
|
||||
state.profiles = JSON.parse(JSON.stringify(UserStore.getProfiles()));
|
||||
state.compactDisplay = PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT;
|
||||
state.flaggedPosts = PreferenceStore.getCategory(Constants.Preferences.CATEGORY_FLAGGED_POST);
|
||||
|
||||
this.state = state;
|
||||
}
|
||||
@@ -121,6 +122,10 @@ export default class RhsThread extends React.Component {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Utils.areObjectsEqual(nextState.flaggedPosts, this.state.flaggedPosts)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Utils.areObjectsEqual(nextState.profiles, this.state.profiles)) {
|
||||
return true;
|
||||
}
|
||||
@@ -151,7 +156,8 @@ export default class RhsThread extends React.Component {
|
||||
|
||||
onPreferenceChange() {
|
||||
this.setState({
|
||||
compactDisplay: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT
|
||||
compactDisplay: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT,
|
||||
flaggedPosts: PreferenceStore.getCategory(Constants.Preferences.CATEGORY_FLAGGED_POST)
|
||||
});
|
||||
this.forceUpdateInfo();
|
||||
}
|
||||
@@ -240,12 +246,18 @@ export default class RhsThread extends React.Component {
|
||||
profile = profiles[selected.user_id];
|
||||
}
|
||||
|
||||
let isRootFlagged = false;
|
||||
if (this.state.flaggedPosts) {
|
||||
isRootFlagged = this.state.flaggedPosts.get(selected.id) === 'true';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='post-right__container'>
|
||||
<FileUploadOverlay overlayType='right'/>
|
||||
<div className='search-bar__container sidebar--right__search-header'>{searchForm}</div>
|
||||
<div className='sidebar-right__body'>
|
||||
<RhsHeaderPost
|
||||
fromFlaggedPosts={this.props.fromFlaggedPosts}
|
||||
fromSearch={this.props.fromSearch}
|
||||
isMentionSearch={this.props.isMentionSearch}
|
||||
toggleSize={this.props.toggleSize}
|
||||
@@ -268,6 +280,7 @@ export default class RhsThread extends React.Component {
|
||||
currentUser={this.props.currentUser}
|
||||
compactDisplay={this.state.compactDisplay}
|
||||
useMilitaryTime={this.props.useMilitaryTime}
|
||||
isFlagged={isRootFlagged}
|
||||
/>
|
||||
<div className='post-right-comments-container'>
|
||||
{postsArray.map((comPost) => {
|
||||
@@ -277,6 +290,11 @@ export default class RhsThread extends React.Component {
|
||||
} else {
|
||||
p = profiles[comPost.user_id];
|
||||
}
|
||||
|
||||
let isFlagged = false;
|
||||
if (this.state.flaggedPosts) {
|
||||
isFlagged = this.state.flaggedPosts.get(comPost.id) === 'true';
|
||||
}
|
||||
return (
|
||||
<Comment
|
||||
ref={comPost.id}
|
||||
@@ -286,6 +304,7 @@ export default class RhsThread extends React.Component {
|
||||
currentUser={this.props.currentUser}
|
||||
compactDisplay={this.state.compactDisplay}
|
||||
useMilitaryTime={this.props.useMilitaryTime}
|
||||
isFlagged={isFlagged}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -311,6 +330,7 @@ RhsThread.defaultProps = {
|
||||
|
||||
RhsThread.propTypes = {
|
||||
fromSearch: React.PropTypes.string,
|
||||
fromFlaggedPosts: React.PropTypes.bool,
|
||||
isMentionSearch: React.PropTypes.bool,
|
||||
currentUser: React.PropTypes.object.isRequired,
|
||||
useMilitaryTime: React.PropTypes.bool.isRequired,
|
||||
|
||||
@@ -7,6 +7,7 @@ import SearchStore from 'stores/search_store.jsx';
|
||||
import UserStore from 'stores/user_store.jsx';
|
||||
import SearchBox from './search_bar.jsx';
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
import Constants from 'utils/constants.jsx';
|
||||
import SearchResultsHeader from './search_results_header.jsx';
|
||||
import SearchResultsItem from './search_results_item.jsx';
|
||||
|
||||
@@ -122,10 +123,44 @@ export default class SearchResults extends React.Component {
|
||||
var noResults = (!results || !results.order || !results.order.length);
|
||||
const searchTerm = this.state.searchTerm;
|
||||
const profiles = this.state.profiles || {};
|
||||
const flagIcon = Constants.FLAG_ICON_SVG;
|
||||
|
||||
var ctls = null;
|
||||
|
||||
if (!searchTerm && noResults) {
|
||||
if (this.props.isFlaggedPosts && noResults) {
|
||||
ctls = (
|
||||
<div className='sidebar--right__subheader'>
|
||||
<ul>
|
||||
<li>
|
||||
<FormattedHTMLMessage
|
||||
id='search_results.usageFlag1'
|
||||
defaultMessage="You haven't flagged any messages yet."
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<FormattedHTMLMessage
|
||||
id='search_results.usageFlag2'
|
||||
defaultMessage='You can add a flag to messages and comments by clicking the '
|
||||
/>
|
||||
<span
|
||||
className='usage__icon'
|
||||
dangerouslySetInnerHTML={{__html: flagIcon}}
|
||||
/>
|
||||
<FormattedHTMLMessage
|
||||
id='search_results.usageFlag3'
|
||||
defaultMessage=' icon next to the timestamp.'
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<FormattedHTMLMessage
|
||||
id='search_results.usageFlag4'
|
||||
defaultMessage='Flags are a way to mark messages for follow up. Your flags are personal, and cannot be seen by other users.'
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
} else if (!searchTerm && noResults) {
|
||||
ctls = (
|
||||
<div className='sidebar--right__subheader'>
|
||||
<FormattedHTMLMessage
|
||||
@@ -172,6 +207,7 @@ export default class SearchResults extends React.Component {
|
||||
isMentionSearch={this.props.isMentionSearch}
|
||||
useMilitaryTime={this.props.useMilitaryTime}
|
||||
shrink={this.props.shrink}
|
||||
isFlagged={this.props.isFlaggedPosts}
|
||||
/>
|
||||
);
|
||||
}, this);
|
||||
@@ -185,6 +221,7 @@ export default class SearchResults extends React.Component {
|
||||
isMentionSearch={this.props.isMentionSearch}
|
||||
toggleSize={this.props.toggleSize}
|
||||
shrink={this.props.shrink}
|
||||
isFlaggedPosts={this.props.isFlaggedPosts}
|
||||
/>
|
||||
<div
|
||||
id='search-items-container'
|
||||
@@ -202,5 +239,6 @@ SearchResults.propTypes = {
|
||||
isMentionSearch: React.PropTypes.bool,
|
||||
useMilitaryTime: React.PropTypes.bool.isRequired,
|
||||
toggleSize: React.PropTypes.function,
|
||||
shrink: React.PropTypes.function
|
||||
shrink: React.PropTypes.function,
|
||||
isFlaggedPosts: React.PropTypes.bool
|
||||
};
|
||||
|
||||
@@ -89,6 +89,13 @@ export default class SearchResultsHeader extends React.Component {
|
||||
defaultMessage='Recent Mentions'
|
||||
/>
|
||||
);
|
||||
} else if (this.props.isFlaggedPosts) {
|
||||
title = (
|
||||
<FormattedMessage
|
||||
id='search_header.title3'
|
||||
defaultMessage='Flagged Posts'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -140,5 +147,6 @@ export default class SearchResultsHeader extends React.Component {
|
||||
SearchResultsHeader.propTypes = {
|
||||
isMentionSearch: React.PropTypes.bool,
|
||||
toggleSize: React.PropTypes.function,
|
||||
shrink: React.PropTypes.function
|
||||
shrink: React.PropTypes.function,
|
||||
isFlaggedPosts: React.PropTypes.bool
|
||||
};
|
||||
|
||||
@@ -7,16 +7,20 @@ import UserProfile from './user_profile.jsx';
|
||||
import TeamStore from 'stores/team_store.jsx';
|
||||
import UserStore from 'stores/user_store.jsx';
|
||||
|
||||
import * as GlobalActions from 'actions/global_actions.jsx';
|
||||
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
|
||||
import * as GlobalActions from 'actions/global_actions.jsx';
|
||||
import {unflagPost, getFlaggedPosts} from 'actions/post_actions.jsx';
|
||||
|
||||
import * as TextFormatting from 'utils/text_formatting.jsx';
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
import * as PostUtils from 'utils/post_utils.jsx';
|
||||
|
||||
import Constants from 'utils/constants.jsx';
|
||||
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
|
||||
const ActionTypes = Constants.ActionTypes;
|
||||
|
||||
import {FormattedMessage, FormattedDate} from 'react-intl';
|
||||
import React from 'react';
|
||||
import {FormattedMessage, FormattedDate} from 'react-intl';
|
||||
import {browserHistory} from 'react-router/es6';
|
||||
|
||||
export default class SearchResultsItem extends React.Component {
|
||||
@@ -25,6 +29,7 @@ export default class SearchResultsItem extends React.Component {
|
||||
|
||||
this.handleFocusRHSClick = this.handleFocusRHSClick.bind(this);
|
||||
this.shrinkSidebar = this.shrinkSidebar.bind(this);
|
||||
this.unflagPost = this.unflagPost.bind(this);
|
||||
}
|
||||
|
||||
hideSidebar() {
|
||||
@@ -42,12 +47,20 @@ export default class SearchResultsItem extends React.Component {
|
||||
GlobalActions.emitPostFocusRightHandSideFromSearch(this.props.post, this.props.isMentionSearch);
|
||||
}
|
||||
|
||||
unflagPost(e) {
|
||||
e.preventDefault();
|
||||
unflagPost(this.props.post.id,
|
||||
() => getFlaggedPosts()
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
let channelName = null;
|
||||
const channel = this.props.channel;
|
||||
const timestamp = UserStore.getCurrentUser().update_at;
|
||||
const user = this.props.user || {};
|
||||
const post = this.props.post;
|
||||
const flagIcon = Constants.FLAG_ICON_SVG;
|
||||
|
||||
if (channel) {
|
||||
channelName = channel.display_name;
|
||||
@@ -77,11 +90,50 @@ export default class SearchResultsItem extends React.Component {
|
||||
}
|
||||
|
||||
let botIndicator;
|
||||
|
||||
if (post.props && post.props.from_webhook) {
|
||||
botIndicator = <li className='bot-indicator'>{Constants.BOT_NAME}</li>;
|
||||
}
|
||||
|
||||
let flag;
|
||||
let flagVisible = '';
|
||||
let flagTooltip = (
|
||||
<Tooltip id='flagTooltip'>
|
||||
<FormattedMessage
|
||||
id='flag_post.flag'
|
||||
defaultMessage='Flag for follow up'
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
if (this.props.isFlagged) {
|
||||
flagVisible = 'visible';
|
||||
flagTooltip = (
|
||||
<Tooltip id='flagTooltip'>
|
||||
<FormattedMessage
|
||||
id='flag_post.unflag'
|
||||
defaultMessage='Unflag'
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
flag = (
|
||||
<OverlayTrigger
|
||||
delayShow={Constants.OVERLAY_TIME_DELAY}
|
||||
placement='top'
|
||||
overlay={flagTooltip}
|
||||
>
|
||||
<a
|
||||
href='#'
|
||||
className={'flag-icon__container ' + flagVisible}
|
||||
onClick={this.unflagPost}
|
||||
>
|
||||
<span
|
||||
className='icon'
|
||||
dangerouslySetInnerHTML={{__html: flagIcon}}
|
||||
/>
|
||||
</a>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='search-item__container'>
|
||||
<div className='date-separator'>
|
||||
@@ -126,8 +178,19 @@ export default class SearchResultsItem extends React.Component {
|
||||
minute='2-digit'
|
||||
/>
|
||||
</time>
|
||||
{flag}
|
||||
</li>
|
||||
<li>
|
||||
<li className='col__controls'>
|
||||
<a
|
||||
href='#'
|
||||
className='comment-icon__container search-item__comment'
|
||||
onClick={this.handleFocusRHSClick}
|
||||
>
|
||||
<span
|
||||
className='comment-icon'
|
||||
dangerouslySetInnerHTML={{__html: Constants.REPLY_ICON}}
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
onClick={
|
||||
() => {
|
||||
@@ -163,18 +226,6 @@ export default class SearchResultsItem extends React.Component {
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href='#'
|
||||
className='comment-icon__container search-item__comment'
|
||||
onClick={this.handleFocusRHSClick}
|
||||
>
|
||||
<span
|
||||
className='comment-icon'
|
||||
dangerouslySetInnerHTML={{__html: Constants.REPLY_ICON}}
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div className='search-item-snippet'>
|
||||
<span
|
||||
@@ -197,5 +248,6 @@ SearchResultsItem.propTypes = {
|
||||
isMentionSearch: React.PropTypes.bool,
|
||||
term: React.PropTypes.string,
|
||||
useMilitaryTime: React.PropTypes.bool.isRequired,
|
||||
shrink: React.PropTypes.function
|
||||
shrink: React.PropTypes.function,
|
||||
isFlagged: React.PropTypes.bool
|
||||
};
|
||||
|
||||
@@ -5,12 +5,16 @@ import $ from 'jquery';
|
||||
|
||||
import SearchResults from './search_results.jsx';
|
||||
import RhsThread from './rhs_thread.jsx';
|
||||
|
||||
import SearchStore from 'stores/search_store.jsx';
|
||||
import PostStore from 'stores/post_store.jsx';
|
||||
import UserStore from 'stores/user_store.jsx';
|
||||
import PreferenceStore from 'stores/preference_store.jsx';
|
||||
import Constants from 'utils/constants.jsx';
|
||||
|
||||
import {getFlaggedPosts} from 'actions/post_actions.jsx';
|
||||
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
import Constants from 'utils/constants.jsx';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
@@ -101,15 +105,20 @@ export default class SidebarRight extends React.Component {
|
||||
}
|
||||
|
||||
onPreferenceChange() {
|
||||
if (this.state.isFlaggedPosts) {
|
||||
getFlaggedPosts();
|
||||
}
|
||||
|
||||
this.setState({
|
||||
useMilitaryTime: PreferenceStore.getBool(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, Constants.Preferences.USE_MILITARY_TIME, false)
|
||||
});
|
||||
}
|
||||
|
||||
onSelectedChange(fromSearch) {
|
||||
onSelectedChange(fromSearch, fromFlaggedPosts) {
|
||||
this.setState({
|
||||
postRightVisible: !!PostStore.getSelectedPost(),
|
||||
fromSearch
|
||||
fromSearch,
|
||||
fromFlaggedPosts
|
||||
});
|
||||
}
|
||||
|
||||
@@ -120,7 +129,8 @@ export default class SidebarRight extends React.Component {
|
||||
onSearchChange() {
|
||||
this.setState({
|
||||
searchVisible: SearchStore.getSearchResults() !== null,
|
||||
isMentionSearch: SearchStore.getIsMentionSearch()
|
||||
isMentionSearch: SearchStore.getIsMentionSearch(),
|
||||
isFlaggedPosts: SearchStore.getIsFlaggedPosts()
|
||||
});
|
||||
}
|
||||
|
||||
@@ -154,6 +164,7 @@ export default class SidebarRight extends React.Component {
|
||||
content = (
|
||||
<SearchResults
|
||||
isMentionSearch={this.state.isMentionSearch}
|
||||
isFlaggedPosts={this.state.isFlaggedPosts}
|
||||
useMilitaryTime={this.state.useMilitaryTime}
|
||||
toggleSize={this.toggleSize}
|
||||
shrink={this.onShrink}
|
||||
@@ -162,6 +173,7 @@ export default class SidebarRight extends React.Component {
|
||||
} else if (this.state.postRightVisible) {
|
||||
content = (
|
||||
<RhsThread
|
||||
fromFlaggedPosts={this.state.fromFlaggedPosts}
|
||||
fromSearch={this.state.fromSearch}
|
||||
isMentionSearch={this.state.isMentionSearch}
|
||||
currentUser={this.state.currentUser}
|
||||
|
||||
@@ -12,6 +12,7 @@ import TeamStore from 'stores/team_store.jsx';
|
||||
import PreferenceStore from 'stores/preference_store.jsx';
|
||||
|
||||
import * as GlobalActions from 'actions/global_actions.jsx';
|
||||
import {getFlaggedPosts} from 'actions/post_actions.jsx';
|
||||
import * as UserAgent from 'utils/user_agent.jsx';
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
import Constants from 'utils/constants.jsx';
|
||||
@@ -35,6 +36,7 @@ export default class SidebarRightMenu extends React.Component {
|
||||
this.handleAboutModal = this.handleAboutModal.bind(this);
|
||||
this.searchMentions = this.searchMentions.bind(this);
|
||||
this.aboutModalDismissed = this.aboutModalDismissed.bind(this);
|
||||
this.getFlagged = this.getFlagged.bind(this);
|
||||
|
||||
const state = this.getStateFromStores();
|
||||
state.showUserSettingsModal = false;
|
||||
@@ -53,6 +55,11 @@ export default class SidebarRightMenu extends React.Component {
|
||||
this.setState({showAboutModal: false});
|
||||
}
|
||||
|
||||
getFlagged(e) {
|
||||
e.preventDefault();
|
||||
getFlaggedPosts();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
PreferenceStore.addChangeListener(this.onPreferenceChange);
|
||||
}
|
||||
@@ -344,6 +351,18 @@ export default class SidebarRightMenu extends React.Component {
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href='#'
|
||||
onClick={this.getFlagged}
|
||||
>
|
||||
<i className='icon fa fa-flag'></i>
|
||||
<FormattedMessage
|
||||
id='sidebar_right_menu.flagged'
|
||||
defaultMessage='Flagged Posts'
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href='#'
|
||||
|
||||
@@ -1018,6 +1018,8 @@
|
||||
"create_comment.files": "Files uploading",
|
||||
"create_post.comment": "Comment",
|
||||
"create_post.post": "Post",
|
||||
"flag_post.flag": "Flag for follow up",
|
||||
"flag_post.unflag": "Unflag",
|
||||
"create_post.tutorialTip": "<h4>Sending Messages</h4><p>Type here to write a message and press <strong>Enter</strong> to post it.</p><p>Click the <strong>Attachment</strong> button to upload an image or a file.</p>",
|
||||
"create_post.write": "Write a message...",
|
||||
"create_team.agreement": "By proceeding to create your account and use {siteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}.",
|
||||
@@ -1370,6 +1372,8 @@
|
||||
"post_delete.someone": "Someone deleted the message on which you tried to post a comment.",
|
||||
"post_focus_view.beginning": "Beginning of Channel Archives",
|
||||
"post_info.del": "Delete",
|
||||
"post_info.mobile.flag": "Flag",
|
||||
"post_info.mobile.unflag": "Unflag",
|
||||
"post_info.edit": "Edit",
|
||||
"post_info.permalink": "Permalink",
|
||||
"post_info.reply": "Reply",
|
||||
@@ -1393,14 +1397,19 @@
|
||||
"rename_channel.title": "Rename Channel",
|
||||
"rhs_comment.comment": "Comment",
|
||||
"rhs_comment.del": "Delete",
|
||||
"rhs_comment.mobile.flag": "Flag",
|
||||
"rhs_comment.mobile.unflag": "Unflag",
|
||||
"rhs_comment.edit": "Edit",
|
||||
"rhs_comment.permalink": "Permalink",
|
||||
"rhs_header.backToResultsTooltip": "Back to Search Results",
|
||||
"rhs_header.backToFlaggedTooltip": "Back to Flagged Posts",
|
||||
"rhs_header.closeSidebarTooltip": "Close Sidebar",
|
||||
"rhs_header.details": "Message Details",
|
||||
"rhs_header.expandSidebarTooltip": "Expand Sidebar",
|
||||
"rhs_header.shrinkSidebarTooltip": "Shrink Sidebar",
|
||||
"rhs_root.del": "Delete",
|
||||
"rhs_root.mobile.flag": "Flag",
|
||||
"rhs_root.mobile.unflag": "Unflag",
|
||||
"rhs_root.direct": "Direct Message",
|
||||
"rhs_root.edit": "Edit",
|
||||
"rhs_root.permalink": "Permalink",
|
||||
@@ -1409,6 +1418,7 @@
|
||||
"search_bar.usage": "<h4>Search Options</h4><ul><li><span>Use </span><b>\"quotation marks\"</b><span> to search for phrases</span></li><li><span>Use </span><b>from:</b><span> to find posts from specific users and </span><b>in:</b><span> to find posts in specific channels</span></li></ul>",
|
||||
"search_header.results": "Search Results",
|
||||
"search_header.title2": "Recent Mentions",
|
||||
"search_header.title3": "Flagged Posts",
|
||||
"search_item.direct": "Direct Message",
|
||||
"search_item.jump": "Jump",
|
||||
"search_results.because": "<ul><li>If you're searching a partial phrase (ex. searching \"rea\", looking for \"reach\" or \"reaction\"), append a * to your search term.</li><li>Two letter searches and common words like \"this\", \"a\" and \"is\" won't appear in search results due to excessive results returned.</li></ul>",
|
||||
@@ -1440,6 +1450,7 @@
|
||||
"sidebar.unreadBelow": "Unread post(s) below",
|
||||
"sidebar_header.tutorial": "<h4>Main Menu</h4><p>The <strong>Main Menu</strong> is where you can <strong>Invite New Members</strong>, access your <strong>Account Settings</strong> and set your <strong>Theme Color</strong>.</p><p>Team administrators can also access their <strong>Team Settings</strong> from this menu.</p><p>System administrators will find a <strong>System Console</strong> option to administrate the entire system.</p>",
|
||||
"sidebar_right_menu.accountSettings": "Account Settings",
|
||||
"sidebar_right_menu.flagged": "Flagged Posts",
|
||||
"sidebar_right_menu.console": "System Console",
|
||||
"sidebar_right_menu.help": "Help",
|
||||
"sidebar_right_menu.inviteNew": "Invite New Member",
|
||||
|
||||
@@ -137,23 +137,31 @@
|
||||
}
|
||||
}
|
||||
|
||||
.search-item__jump {
|
||||
@include opacity(.8);
|
||||
.col__controls {
|
||||
font-size: 13px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
|
||||
&:hover {
|
||||
@include opacity(1);
|
||||
}
|
||||
}
|
||||
a {
|
||||
@include opacity(.8);
|
||||
vertical-align: top;
|
||||
|
||||
.search-item__comment {
|
||||
margin-right: 35px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
&:hover {
|
||||
@include opacity(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.search-item__jump {
|
||||
font-size: 13px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.search-item__comment {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.search-item-time {
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 8px;
|
||||
width: 8.9%;
|
||||
}
|
||||
}
|
||||
@@ -375,8 +376,17 @@
|
||||
font-size: 22px;
|
||||
height: 30px;
|
||||
line-height: 26px;
|
||||
margin-right: 9px;
|
||||
width: 24px;
|
||||
margin-right: 3px;
|
||||
text-align: center;
|
||||
width: 30px;
|
||||
|
||||
th {
|
||||
&:last-child {
|
||||
div {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.channel__wrap.move--left & {
|
||||
position: absolute;
|
||||
@@ -384,6 +394,15 @@
|
||||
top: 14px;
|
||||
}
|
||||
|
||||
.icon__flag {
|
||||
svg {
|
||||
height: 18px;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
width: 19px;
|
||||
}
|
||||
}
|
||||
|
||||
> a {
|
||||
@include opacity(.6);
|
||||
@include single-transition(all, .1s, ease-in);
|
||||
|
||||
@@ -486,6 +486,7 @@ body.ios {
|
||||
&:hover {
|
||||
.dropdown,
|
||||
.comment-icon__container,
|
||||
.flag-icon__container,
|
||||
.post__reply,
|
||||
.post__remove {
|
||||
visibility: visible;
|
||||
@@ -561,7 +562,7 @@ body.ios {
|
||||
.img-div {
|
||||
max-height: 150px;
|
||||
max-width: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: inherit;
|
||||
@@ -573,7 +574,7 @@ body.ios {
|
||||
|
||||
ol,
|
||||
ul {
|
||||
clear: both;
|
||||
clear: both;
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
@@ -694,6 +695,13 @@ body.ios {
|
||||
}
|
||||
}
|
||||
|
||||
.flag-icon__container {
|
||||
left: 36px;
|
||||
margin-left: 5px;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
.post__img {
|
||||
img {
|
||||
display: none;
|
||||
@@ -835,7 +843,9 @@ body.ios {
|
||||
}
|
||||
|
||||
.post__img {
|
||||
width: 42px;
|
||||
padding-right: 10px;
|
||||
text-align: right;
|
||||
width: 53px;
|
||||
|
||||
svg {
|
||||
height: 32px;
|
||||
@@ -1076,6 +1086,7 @@ body.ios {
|
||||
display: inline-block;
|
||||
margin-right: 6px;
|
||||
visibility: hidden;
|
||||
|
||||
svg {
|
||||
fill: inherit;
|
||||
position: relative;
|
||||
@@ -1115,6 +1126,36 @@ body.ios {
|
||||
}
|
||||
}
|
||||
|
||||
.flag-icon__container {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
margin-left: 7px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
visibility: hidden;
|
||||
|
||||
&.visible {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
path {
|
||||
fill: inherit;
|
||||
}
|
||||
|
||||
.fa-star-o {
|
||||
@include opacity(.8);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&.icon--visible {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.web-embed-data {
|
||||
@include border-radius(2px);
|
||||
@include alpha-property(background, $black, 0.05);
|
||||
|
||||
@@ -161,7 +161,7 @@
|
||||
|
||||
.sidebar--right__subheader {
|
||||
font-size: 1em;
|
||||
padding: 1em 1em 0;
|
||||
padding: 0.5em 1em 0;
|
||||
|
||||
h4 {
|
||||
font-size: 1em;
|
||||
@@ -176,6 +176,13 @@
|
||||
font-size: .95em;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.usage__icon {
|
||||
@include opacity(.6);
|
||||
margin: 0 3px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.suggestion-list__content {
|
||||
|
||||
@@ -94,6 +94,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.same--root {
|
||||
&.same--user {
|
||||
.flag-icon__container {
|
||||
left: auto;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.post__content {
|
||||
padding: 0 10px 0 0;
|
||||
}
|
||||
@@ -182,7 +192,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
.star-icon__container {
|
||||
left: auto;
|
||||
position: relative;
|
||||
top: auto;
|
||||
|
||||
&:not(.visible) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.same--root {
|
||||
.star-icon__container {
|
||||
left: auto;
|
||||
position: relative;
|
||||
top: auto;
|
||||
}
|
||||
|
||||
&.same--user {
|
||||
.post__header {
|
||||
height: auto;
|
||||
|
||||
@@ -116,7 +116,15 @@
|
||||
.channel__wrap & {
|
||||
.post__time {
|
||||
font-size: .85em;
|
||||
left: -70px;
|
||||
left: -79px;
|
||||
position: absolute;
|
||||
text-align: right;
|
||||
top: 6px;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.star-icon__container {
|
||||
left: -65px;
|
||||
position: absolute;
|
||||
text-align: right;
|
||||
top: 6px;
|
||||
@@ -125,7 +133,7 @@
|
||||
}
|
||||
|
||||
&:not(.post--thread) {
|
||||
padding: 5px .5em 0 70px;
|
||||
padding: 5px .5em 0 72px;
|
||||
|
||||
.post__link {
|
||||
margin: 4px 0 7px;
|
||||
@@ -197,11 +205,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
.flag-icon__container {
|
||||
left: -21px;
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
}
|
||||
|
||||
.sidebar--right & .flag-icon__container {
|
||||
left: auto;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
&.same--root {
|
||||
&.same--user {
|
||||
padding-left: 70px;
|
||||
padding-left: 72px;
|
||||
padding-top: 0;
|
||||
|
||||
.flag-icon__container {
|
||||
left: -19px;
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
}
|
||||
|
||||
.post__header {
|
||||
.col__reply {
|
||||
top: -1px;
|
||||
@@ -265,12 +291,16 @@
|
||||
|
||||
&:not(.post--compact) {
|
||||
.post__time {
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
left: -4px;
|
||||
line-height: 37px;
|
||||
left: -14px;
|
||||
line-height: 34px;
|
||||
position: absolute;
|
||||
text-align: right;
|
||||
text-rendering: auto;
|
||||
top: -2px;
|
||||
width: 51px;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,8 +441,8 @@ class PostStoreClass extends EventEmitter {
|
||||
return threadPosts;
|
||||
}
|
||||
|
||||
emitSelectedPostChange(fromSearch) {
|
||||
this.emit(SELECTED_POST_CHANGE_EVENT, fromSearch);
|
||||
emitSelectedPostChange(fromSearch, fromFlaggedPosts) {
|
||||
this.emit(SELECTED_POST_CHANGE_EVENT, fromSearch, fromFlaggedPosts);
|
||||
}
|
||||
|
||||
addSelectedPostChangeListener(callback) {
|
||||
@@ -599,7 +599,7 @@ PostStore.dispatchToken = AppDispatcher.register((payload) => {
|
||||
break;
|
||||
case ActionTypes.RECEIVED_POST_SELECTED:
|
||||
PostStore.storeSelectedPostId(action.postId);
|
||||
PostStore.emitSelectedPostChange(action.from_search);
|
||||
PostStore.emitSelectedPostChange(action.from_search, action.from_flagged_posts);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ class SearchStoreClass extends EventEmitter {
|
||||
|
||||
this.searchResults = null;
|
||||
this.isMentionSearch = false;
|
||||
this.isFlaggedPosts = false;
|
||||
this.searchTerm = '';
|
||||
}
|
||||
|
||||
@@ -77,6 +78,10 @@ class SearchStoreClass extends EventEmitter {
|
||||
return this.isMentionSearch;
|
||||
}
|
||||
|
||||
getIsFlaggedPosts() {
|
||||
return this.isFlaggedPosts;
|
||||
}
|
||||
|
||||
storeSearchTerm(term) {
|
||||
this.searchTerm = term;
|
||||
}
|
||||
@@ -85,9 +90,10 @@ class SearchStoreClass extends EventEmitter {
|
||||
return this.searchTerm;
|
||||
}
|
||||
|
||||
storeSearchResults(results, isMentionSearch) {
|
||||
storeSearchResults(results, isMentionSearch, isFlaggedPosts) {
|
||||
this.searchResults = results;
|
||||
this.isMentionSearch = isMentionSearch;
|
||||
this.isFlaggedPosts = isFlaggedPosts;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +104,7 @@ SearchStore.dispatchToken = AppDispatcher.register((payload) => {
|
||||
|
||||
switch (action.type) {
|
||||
case ActionTypes.RECEIVED_SEARCH:
|
||||
SearchStore.storeSearchResults(action.results, action.is_mention_search);
|
||||
SearchStore.storeSearchResults(action.results, action.is_mention_search, action.is_flagged_posts);
|
||||
SearchStore.emitSearchChange();
|
||||
break;
|
||||
case ActionTypes.RECEIVED_SEARCH_TERM:
|
||||
|
||||
@@ -197,5 +197,38 @@ describe('Client.Posts', function() {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('getFlaggedPosts', function(done) {
|
||||
TestHelper.initBasic(() => {
|
||||
var pref = {};
|
||||
pref.user_id = TestHelper.basicUser().id;
|
||||
pref.category = 'flagged_post';
|
||||
pref.name = TestHelper.basicPost().id;
|
||||
pref.value = 'true';
|
||||
|
||||
var prefs = [];
|
||||
prefs.push(pref);
|
||||
|
||||
TestHelper.basicClient().savePreferences(
|
||||
prefs,
|
||||
function() {
|
||||
TestHelper.basicClient().getFlaggedPosts(
|
||||
0,
|
||||
2,
|
||||
function(data) {
|
||||
assert.equal(data.order[0], TestHelper.basicPost().id);
|
||||
done();
|
||||
},
|
||||
function(err) {
|
||||
done(new Error(err.message));
|
||||
}
|
||||
);
|
||||
},
|
||||
function(err) {
|
||||
done(new Error(err.message));
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -51,7 +51,8 @@ export const Preferences = {
|
||||
COLLAPSE_DISPLAY: 'collapse_previews',
|
||||
COLLAPSE_DISPLAY_DEFAULT: 'false',
|
||||
USE_MILITARY_TIME: 'use_military_time',
|
||||
CATEGORY_THEME: 'theme'
|
||||
CATEGORY_THEME: 'theme',
|
||||
CATEGORY_FLAGGED_POST: 'flagged_post'
|
||||
};
|
||||
|
||||
export const ActionTypes = keyMirror({
|
||||
@@ -326,6 +327,8 @@ export const Constants = {
|
||||
OPEN_TEAM: 'O',
|
||||
MAX_POST_LEN: 4000,
|
||||
EMOJI_SIZE: 16,
|
||||
FLAG_ICON_OUTLINE_SVG: "<svg width='12px' height='12px' viewBox='0 0 48 48' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' xml:space='preserve' style='fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.41421;'> <g> <g transform='matrix(1,0,0,0.957537,-0.5,1.42123)'> <path d='M2.5,0.5C1.4,0.5 0.5,1.4 0.5,2.5L0.5,45.6C0.5,46.7 1.4,47.6 2.5,47.6C3.6,47.6 4.5,46.7 4.5,45.6L4.5,2.5C4.4,1.4 3.5,0.5 2.5,0.5Z'/> </g> <g transform='matrix(0.923469,0,0,0.870026,1.64285,2.0085)'> <path d='M46.4,3.5C43.3,2.1 40.1,1.4 36.5,1.4C32.1,1.4 27.8,2.4 23.6,3.4C19.4,4.4 15.5,5.3 11.6,5.3C10.5,5.3 9.4,5.2 8.4,5.1L8.4,37C9.4,37.1 10.5,37.2 11.6,37.2C16,37.2 20.3,36.2 24.5,35.2C28.7,34.2 32.6,33.3 36.5,33.3C39.5,33.3 42.3,33.9 44.8,35.1C45.4,35.4 46.1,35.3 46.7,35C47.3,34.6 47.6,34 47.6,33.3L47.6,5.3C47.5,4.6 47.1,3.9 46.4,3.5Z' style='stroke-width:3.23px; fill:none;'/> </g> </g> </svg>",
|
||||
FLAG_ICON_SVG: "<svg width='12px' height='12px' viewBox='0 0 48 48' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' xml:space='preserve' style='fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.41421;'> <g> <g transform='matrix(1,0,0,0.957537,-0.5,1.42123)'> <path d='M2.5,0.5C1.4,0.5 0.5,1.4 0.5,2.5L0.5,45.6C0.5,46.7 1.4,47.6 2.5,47.6C3.6,47.6 4.5,46.7 4.5,45.6L4.5,2.5C4.4,1.4 3.5,0.5 2.5,0.5Z'/> </g> <g transform='matrix(0.923469,0,0,0.870026,1.64285,2.0085)'> <path d='M46.4,3.5C43.3,2.1 40.1,1.4 36.5,1.4C32.1,1.4 27.8,2.4 23.6,3.4C19.4,4.4 15.5,5.3 11.6,5.3C10.5,5.3 9.4,5.2 8.4,5.1L8.4,37C9.4,37.1 10.5,37.2 11.6,37.2C16,37.2 20.3,36.2 24.5,35.2C28.7,34.2 32.6,33.3 36.5,33.3C39.5,33.3 42.3,33.9 44.8,35.1C45.4,35.4 46.1,35.3 46.7,35C47.3,34.6 47.6,34 47.6,33.3L47.6,5.3C47.5,4.6 47.1,3.9 46.4,3.5Z' style='stroke-width:3.23px;'/> </g> </g> </svg>",
|
||||
ATTACHMENT_ICON_SVG: "<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' x='0px' y='0px' viewBox='0 0 48 48' enable-background='new 0 0 48 48' xml:space='preserve'><g><path d='M43.922,6.653c-2.643-2.644-6.201-4.107-9.959-4.069c-3.774,0.019-7.32,1.497-9.983,4.161l-12.3,12.3l-8.523,8.521 c-4.143,4.144-4.217,10.812-0.167,14.862c1.996,1.996,4.626,2.989,7.277,2.989c2.73,0,5.482-1.055,7.583-3.156l15.547-15.545 c0.002-0.002,0.002-0.004,0.004-0.005l5.358-5.358c1.394-1.393,2.176-3.24,2.201-5.2c0.026-1.975-0.716-3.818-2.09-5.192 c-2.834-2.835-7.496-2.787-10.394,0.108L9.689,29.857c-0.563,0.563-0.563,1.474,0,2.036c0.281,0.28,0.649,0.421,1.018,0.421 c0.369,0,0.737-0.141,1.018-0.421l18.787-18.788c1.773-1.774,4.609-1.824,6.322-0.11c0.82,0.82,1.263,1.928,1.247,3.119 c-0.017,1.205-0.497,2.342-1.357,3.201l-5.55,5.551c-0.002,0.002-0.002,0.004-0.004,0.005L15.814,40.225 c-3.02,3.02-7.86,3.094-10.789,0.167c-2.928-2.929-2.854-7.77,0.167-10.791l0.958-0.958c0.001-0.002,0.004-0.002,0.005-0.004 L26.016,8.78c2.123-2.124,4.951-3.303,7.961-3.317c2.998,0.02,5.814,1.13,7.91,3.226c4.35,4.351,4.309,11.472-0.093,15.873 L25.459,40.895c-0.563,0.562-0.563,1.473,0,2.035c0.281,0.281,0.65,0.422,1.018,0.422c0.369,0,0.737-0.141,1.018-0.422 L43.83,26.596C49.354,21.073,49.395,12.126,43.922,6.653z'></path></g></svg>",
|
||||
MATTERMOST_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'viewBox='0 0 500 500' style='enable-background:new 0 0 500 500;' xml:space='preserve'> <style type='text/css'> .st0{fill-rule:evenodd;clip-rule:evenodd;fill:#222222;} </style> <g id='XMLID_1_'> <g id='XMLID_3_'> <path id='XMLID_4_' class='st0' d='M396.9,47.7l2.6,53.1c43,47.5,60,114.8,38.6,178.1c-32,94.4-137.4,144.1-235.4,110.9 S51.1,253.1,83,158.7C104.5,95.2,159.2,52,222.5,40.5l34.2-40.4C150-2.8,49.3,63.4,13.3,169.9C-31,300.6,39.1,442.5,169.9,486.7 s272.6-25.8,316.9-156.6C522.7,223.9,483.1,110.3,396.9,47.7z'/> </g> <path id='XMLID_2_' class='st0' d='M335.6,204.3l-1.8-74.2l-1.5-42.7l-1-37c0,0,0.2-17.8-0.4-22c-0.1-0.9-0.4-1.6-0.7-2.2 c0-0.1-0.1-0.2-0.1-0.3c0-0.1-0.1-0.2-0.1-0.2c-0.7-1.2-1.8-2.1-3.1-2.6c-1.4-0.5-2.9-0.4-4.2,0.2c0,0-0.1,0-0.1,0 c-0.2,0.1-0.3,0.1-0.4,0.2c-0.6,0.3-1.2,0.7-1.8,1.3c-3,3-13.7,17.2-13.7,17.2l-23.2,28.8l-27.1,33l-46.5,57.8 c0,0-21.3,26.6-16.6,59.4s29.1,48.7,48,55.1c18.9,6.4,48,8.5,71.6-14.7C336.4,238.4,335.6,204.3,335.6,204.3z'/> </g> </svg>",
|
||||
ONLINE_ICON_SVG: "<svg version='1.1'id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:cc='http://creativecommons.org/ns#' inkscape:version='0.48.4 r9939' sodipodi:docname='TRASH_1_4.svg'xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='-243 245 12 12'style='enable-background:new -243 245 12 12;' xml:space='preserve'> <sodipodi:namedview inkscape:cx='26.358185' inkscape:zoom='1.18' bordercolor='#666666' pagecolor='#ffffff' borderopacity='1' objecttolerance='10' inkscape:cy='139.7898' gridtolerance='10' guidetolerance='10' showgrid='false' showguides='true' id='namedview6' inkscape:pageopacity='0' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:window-width='1366' inkscape:current-layer='Layer_1' inkscape:window-height='705' inkscape:window-y='-8' inkscape:window-maximized='1' inkscape:window-x='-8'> <sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide> <sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide> </sodipodi:namedview> <g> <path class='online--icon' d='M-236,250.5C-236,250.5-236,250.5-236,250.5C-236,250.5-236,250.5-236,250.5C-236,250.5-236,250.5-236,250.5z'/> <ellipse class='online--icon' cx='-238.5' cy='248' rx='2.5' ry='2.5'/> </g> <path class='online--icon' d='M-238.9,253.8c0-0.4,0.1-0.9,0.2-1.3c-2.2-0.2-2.2-2-2.2-2s-1,0.1-1.2,0.5c-0.4,0.6-0.6,1.7-0.7,2.5c0,0.1-0.1,0.5,0,0.6 c0.2,1.3,2.2,2.3,4.4,2.4c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0C-238.7,255.7-238.9,254.8-238.9,253.8z'/> <g> <g> <path class='online--icon' d='M-232.3,250.1l1.3,1.3c0,0,0,0.1,0,0.1l-4.1,4.1c0,0,0,0-0.1,0c0,0,0,0,0,0l-2.7-2.7c0,0,0-0.1,0-0.1l1.2-1.2 c0,0,0.1,0,0.1,0l1.4,1.4l2.9-2.9C-232.4,250.1-232.3,250.1-232.3,250.1z'/> </g> </g> </svg>",
|
||||
|
||||
@@ -566,7 +566,8 @@ export function applyTheme(theme) {
|
||||
}
|
||||
|
||||
if (theme.centerChannelColor) {
|
||||
changeCss('.app__body .post-list__arrows', 'fill:' + changeOpacity(theme.centerChannelColor, 0.3), 1);
|
||||
changeCss('.app__body .post-list__arrows, .app__body .post .flag-icon__container', 'fill:' + changeOpacity(theme.centerChannelColor, 0.3), 1);
|
||||
changeCss('.app__body .channel-header__links .icon', 'stroke:' + theme.centerChannelColor, 1);
|
||||
changeCss('@media(min-width: 768px){.app__body .post:hover .post__header .col__reply, .app__body .post.post--hovered .post__header .col__reply', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 2);
|
||||
changeCss('.app__body .sidebar--left, .app__body .sidebar--right .sidebar--right__header, .app__body .suggestion-list__content .command', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
|
||||
changeCss('.app__body .post.post--system .post__body', 'color:' + changeOpacity(theme.centerChannelColor, 0.6), 1);
|
||||
@@ -638,7 +639,8 @@ export function applyTheme(theme) {
|
||||
if (theme.linkColor) {
|
||||
changeCss('.app__body a, .app__body a:focus, .app__body a:hover, .app__body .btn, .app__body .btn:focus, .app__body .btn:hover', 'color:' + theme.linkColor, 1);
|
||||
changeCss('.app__body .attachment .attachment__container', 'border-left-color:' + changeOpacity(theme.linkColor, 0.5), 1);
|
||||
changeCss('.app__body .post .comment-icon__container, .app__body .post .post__reply', 'fill:' + theme.linkColor, 1);
|
||||
changeCss('.app__body .channel-header__links .icon:hover, .app__body .post .flag-icon__container.visible, .app__body .post .comment-icon__container, .app__body .post .post__reply', 'fill:' + theme.linkColor, 1);
|
||||
changeCss('.app__body .channel-header__links .icon:hover', 'stroke:' + theme.linkColor, 1);
|
||||
}
|
||||
|
||||
if (theme.buttonBg) {
|
||||
|
||||
Ссылка в новой задаче
Block a user