Этот коммит содержится в:
Gabin Aureche
2017-03-13 13:25:08 +01:00
коммит произвёл George Goldberg
родитель 482a0fb5fc
Коммит fe38d6d5bb
38 изменённых файлов: 885 добавлений и 52 удалений

Просмотреть файл

@@ -30,7 +30,7 @@ import * as Utils from 'utils/utils.jsx';
import * as ChannelUtils from 'utils/channel_utils.jsx';
import {getSiteURL} from 'utils/url.jsx';
import * as TextFormatting from 'utils/text_formatting.jsx';
import {getFlaggedPosts} from 'actions/post_actions.jsx';
import {getFlaggedPosts, getPinnedPosts} from 'actions/post_actions.jsx';
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
@@ -53,6 +53,7 @@ export default class ChannelHeader extends React.Component {
this.hideRenameChannelModal = this.hideRenameChannelModal.bind(this);
this.handleShortcut = this.handleShortcut.bind(this);
this.getFlagged = this.getFlagged.bind(this);
this.getPinnedPosts = this.getPinnedPosts.bind(this);
this.initWebrtc = this.initWebrtc.bind(this);
this.onBusy = this.onBusy.bind(this);
this.openDirectMessageModal = this.openDirectMessageModal.bind(this);
@@ -158,6 +159,15 @@ export default class ChannelHeader extends React.Component {
}
}
getPinnedPosts(e) {
e.preventDefault();
if (SearchStore.isPinnedPosts) {
GlobalActions.toggleSideBarAction(false);
} else {
getPinnedPosts(this.props.channelId);
}
}
getFlagged(e) {
e.preventDefault();
if (SearchStore.isFlaggedPosts) {
@@ -211,6 +221,7 @@ export default class ChannelHeader extends React.Component {
render() {
const flagIcon = Constants.FLAG_ICON_SVG;
const pinIcon = Constants.PIN_ICON;
if (!this.validState()) {
// Use an empty div to make sure the header's height stays constant
@@ -762,8 +773,20 @@ export default class ChannelHeader extends React.Component {
</OverlayTrigger>
</div>
</th>
<th className='header-list__members'>
<th className='header-list__right'>
{popoverListMembers}
<a
href='#'
type='button'
id='pinned-posts-button'
className='pinned-posts-button'
onClick={this.getPinnedPosts}
>
<span
dangerouslySetInnerHTML={{__html: pinIcon}}
aria-hidden='true'
/>
</a>
</th>
<th className='search-bar__container'>
<NavbarSearchBox

Просмотреть файл

@@ -19,12 +19,15 @@ import UserStore from 'stores/user_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import SearchStore from 'stores/search_store.jsx';
import ChannelSwitchModal from './channel_switch_modal.jsx';
import * as Utils from 'utils/utils.jsx';
import * as ChannelUtils from 'utils/channel_utils.jsx';
import * as ChannelActions from 'actions/channel_actions.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import {getPinnedPosts} from 'actions/post_actions.jsx';
import Constants from 'utils/constants.jsx';
const ActionTypes = Constants.ActionTypes;
@@ -62,6 +65,7 @@ export default class Navbar extends React.Component {
this.hideChannelSwitchModal = this.hideChannelSwitchModal.bind(this);
this.openDirectMessageModal = this.openDirectMessageModal.bind(this);
this.getPinnedPosts = this.getPinnedPosts.bind(this);
const state = this.getStateFromStores();
state.showEditChannelPurposeModal = false;
@@ -216,6 +220,15 @@ export default class Navbar extends React.Component {
});
}
getPinnedPosts(e) {
e.preventDefault();
if (SearchStore.isPinnedPosts) {
GlobalActions.toggleSideBarAction(false);
} else {
getPinnedPosts(this.state.channel.id);
}
}
toggleFavorite = (e) => {
e.preventDefault();
@@ -244,6 +257,7 @@ export default class Navbar extends React.Component {
}
let viewInfoOption;
let viewPinnedPostsOption;
let addMembersOption;
let manageMembersOption;
let setChannelHeaderOption;
@@ -335,6 +349,21 @@ export default class Navbar extends React.Component {
</li>
);
viewPinnedPostsOption = (
<li role='presentation'>
<a
role='menuitem'
href='#'
onClick={this.getPinnedPosts}
>
<FormattedMessage
id='navbar.viewPinnedPosts'
defaultMessage='View Pinned Posts'
/>
</a>
</li>
);
if (!ChannelStore.isDefault(channel)) {
addMembersOption = (
<li role='presentation'>
@@ -561,6 +590,7 @@ export default class Navbar extends React.Component {
role='menu'
>
{viewInfoOption}
{viewPinnedPostsOption}
{notificationPreferenceOption}
{addMembersOption}
{manageMembersOption}

Просмотреть файл

@@ -12,6 +12,7 @@ import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import PostStore from 'stores/post_store.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import {startPeriodicStatusUpdates, stopPeriodicStatusUpdates} from 'actions/status_actions.jsx';
import Constants from 'utils/constants.jsx';
@@ -173,12 +174,25 @@ export default class NeedsTeam extends React.Component {
</div>
);
}
let channel = ChannelStore.getByName(this.props.params.channel);
if (channel == null) {
// the permalink view is not really tied to a particular channel but still needs it
const postId = PostStore.getFocusedPostId();
const post = PostStore.getEarliestPostFromPage(postId);
// the post take some time before being available on page load
if (post != null) {
channel = ChannelStore.get(post.channel_id);
}
}
return (
<div className='channel-view'>
<ErrorBar/>
<WebrtcNotification/>
<div className='container-fluid'>
<SidebarRight/>
<SidebarRight channel={channel}/>
<SidebarRightMenu teamType={this.state.team.type}/>
<WebrtcSidebar/>
{content}

Просмотреть файл

@@ -233,7 +233,7 @@ export default class PopoverListMembers extends React.Component {
}
return (
<div>
<div className='member-popover__container'>
<div
id='member_popover'
className='member-popover__trigger'
@@ -243,13 +243,11 @@ export default class PopoverListMembers extends React.Component {
AsyncClient.getProfilesInChannel(this.props.channel.id, 0);
}}
>
<div>
{countText}
<span
className='fa fa-user'
aria-hidden='true'
/>
</div>
{countText}
<span
className='fa fa-user'
aria-hidden='true'
/>
</div>
<Overlay
rootClose={true}

Просмотреть файл

@@ -26,6 +26,8 @@ export default class PostInfo extends React.Component {
this.removePost = this.removePost.bind(this);
this.flagPost = this.flagPost.bind(this);
this.unflagPost = this.unflagPost.bind(this);
this.pinPost = this.pinPost.bind(this);
this.unpinPost = this.unpinPost.bind(this);
this.canEdit = false;
this.canDelete = false;
@@ -148,6 +150,42 @@ export default class PostInfo extends React.Component {
);
}
if (this.props.post.is_pinned) {
dropdownContents.push(
<li
key='unpinLink'
role='presentation'
>
<a
href='#'
onClick={this.unpinPost}
>
<FormattedMessage
id='post_info.unpin'
defaultMessage='Un-pin from channel'
/>
</a>
</li>
);
} else {
dropdownContents.push(
<li
key='pinLink'
role='presentation'
>
<a
href='#'
onClick={this.pinPost}
>
<FormattedMessage
id='post_info.pin'
defaultMessage='Pin to channel'
/>
</a>
</li>
);
}
if (this.canDelete) {
dropdownContents.push(
<li
@@ -250,6 +288,16 @@ export default class PostInfo extends React.Component {
);
}
pinPost(e) {
e.preventDefault();
PostActions.pinPost(this.props.post.channel_id, this.props.post.id);
}
unpinPost(e) {
e.preventDefault();
PostActions.unpinPost(this.props.post.channel_id, this.props.post.id);
}
flagPost(e) {
e.preventDefault();
PostActions.flagPost(this.props.post.id);
@@ -374,6 +422,18 @@ export default class PostInfo extends React.Component {
);
}
let pinnedBadge;
if (post.is_pinned) {
pinnedBadge = (
<span className='post__pinned-badge'>
<FormattedMessage
id='post_info.pinned'
defaultMessage='Pinned'
/>
</span>
);
}
return (
<ul className='post__header--info'>
<li className='col'>
@@ -384,6 +444,7 @@ export default class PostInfo extends React.Component {
useMilitaryTime={this.props.useMilitaryTime}
postId={post.id}
/>
{pinnedBadge}
{flagTrigger}
</li>
{options}

Просмотреть файл

@@ -10,7 +10,7 @@ import ReactionListContainer from 'components/post_view/components/reaction_list
import RhsDropdown from 'components/rhs_dropdown.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import {flagPost, unflagPost} from 'actions/post_actions.jsx';
import {flagPost, unflagPost, pinPost, unpinPost} from 'actions/post_actions.jsx';
import TeamStore from 'stores/team_store.jsx';
@@ -36,6 +36,8 @@ export default class RhsComment extends React.Component {
this.removePost = this.removePost.bind(this);
this.flagPost = this.flagPost.bind(this);
this.unflagPost = this.unflagPost.bind(this);
this.pinPost = this.pinPost.bind(this);
this.unpinPost = this.unpinPost.bind(this);
this.canEdit = false;
this.canDelete = false;
@@ -128,6 +130,16 @@ export default class RhsComment extends React.Component {
unflagPost(this.props.post.id);
}
pinPost(e) {
e.preventDefault();
pinPost(this.props.post.channel_id, this.props.post.id);
}
unpinPost(e) {
e.preventDefault();
unpinPost(this.props.post.channel_id, this.props.post.id);
}
createDropdown() {
const post = this.props.post;
@@ -195,6 +207,42 @@ export default class RhsComment extends React.Component {
</li>
);
if (post.is_pinned) {
dropdownContents.push(
<li
key='rhs-comment-unpin'
role='presentation'
>
<a
href='#'
onClick={this.unpinPost}
>
<FormattedMessage
id='rhs_root.unpin'
defaultMessage='Un-pin from channel'
/>
</a>
</li>
);
} else {
dropdownContents.push(
<li
key='rhs-comment-pin'
role='presentation'
>
<a
href='#'
onClick={this.pinPost}
>
<FormattedMessage
id='rhs_root.pin'
defaultMessage='Pin to channel'
/>
</a>
</li>
);
}
if (this.canDelete) {
dropdownContents.push(
<li
@@ -503,6 +551,18 @@ export default class RhsComment extends React.Component {
);
}
let pinnedBadge;
if (post.is_pinned) {
pinnedBadge = (
<span className='post__pinned-badge'>
<FormattedMessage
id='post_info.pinned'
defaultMessage='Pinned'
/>
</span>
);
}
const timeOptions = {
day: 'numeric',
month: 'short',
@@ -524,6 +584,7 @@ export default class RhsComment extends React.Component {
{botIndicator}
<li className='col'>
{this.renderTimeTag(post, timeOptions)}
{pinnedBadge}
{flagTrigger}
</li>
{options}

Просмотреть файл

@@ -14,7 +14,7 @@ import UserStore from 'stores/user_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import {flagPost, unflagPost} from 'actions/post_actions.jsx';
import {flagPost, unflagPost, pinPost, unpinPost} from 'actions/post_actions.jsx';
import * as Utils from 'utils/utils.jsx';
import * as PostUtils from 'utils/post_utils.jsx';
@@ -35,6 +35,8 @@ export default class RhsRootPost extends React.Component {
this.handlePermalink = this.handlePermalink.bind(this);
this.flagPost = this.flagPost.bind(this);
this.unflagPost = this.unflagPost.bind(this);
this.pinPost = this.pinPost.bind(this);
this.unpinPost = this.unpinPost.bind(this);
this.canEdit = false;
this.canDelete = false;
@@ -143,6 +145,16 @@ export default class RhsRootPost extends React.Component {
);
}
pinPost(e) {
e.preventDefault();
pinPost(this.props.post.channel_id, this.props.post.id);
}
unpinPost(e) {
e.preventDefault();
unpinPost(this.props.post.channel_id, this.props.post.id);
}
render() {
const post = this.props.post;
const user = this.props.user;
@@ -240,6 +252,42 @@ export default class RhsRootPost extends React.Component {
</li>
);
if (post.is_pinned) {
dropdownContents.push(
<li
key='rhs-root-unpin'
role='presentation'
>
<a
href='#'
onClick={this.unpinPost}
>
<FormattedMessage
id='rhs_root.unpin'
defaultMessage='Un-pin from channel'
/>
</a>
</li>
);
} else {
dropdownContents.push(
<li
key='rhs-root-pin'
role='presentation'
>
<a
href='#'
onClick={this.pinPost}
>
<FormattedMessage
id='rhs_root.pin'
defaultMessage='Pin to channel'
/>
</a>
</li>
);
}
if (this.canDelete) {
dropdownContents.push(
<li
@@ -450,6 +498,18 @@ export default class RhsRootPost extends React.Component {
flagFunc = this.flagPost;
}
let pinnedBadge;
if (post.is_pinned) {
pinnedBadge = (
<span className='post__pinned-badge'>
<FormattedMessage
id='post_info.pinned'
defaultMessage='Pinned'
/>
</span>
);
}
const timeOptions = {
day: 'numeric',
month: 'short',
@@ -470,6 +530,7 @@ export default class RhsRootPost extends React.Component {
{botIndicator}
<li className='col'>
{this.renderTimeTag(post, timeOptions)}
{pinnedBadge}
<OverlayTrigger
key={'rootpostflagtooltipkey' + flagVisible}
delayShow={Constants.OVERLAY_TIME_DELAY}

Просмотреть файл

@@ -213,6 +213,31 @@ export default class SearchResults extends React.Component {
</ul>
</div>
);
} else if (this.props.isPinnedPosts && noResults) {
ctls = (
<div className='sidebar--right__subheader'>
<ul>
<li>
<FormattedHTMLMessage
id='search_results.usagePin1'
defaultMessage='There are no pinned messages yet.'
/>
</li>
<li>
<FormattedHTMLMessage
id='search_results.usagePin2'
defaultMessage={'You can pin a message by clicking the "Pin to channel" option from the message\'s menu.'}
/>
</li>
<li>
<FormattedHTMLMessage
id='search_results.usagePin3'
defaultMessage='Pinned messages are accessible by all channel members and are a way to mark messages for future reference.'
/>
</li>
</ul>
</div>
);
} else if (!searchTerm && noResults) {
ctls = (
<div className='sidebar--right__subheader'>
@@ -289,6 +314,8 @@ export default class SearchResults extends React.Component {
toggleSize={this.props.toggleSize}
shrink={this.props.shrink}
isFlaggedPosts={this.props.isFlaggedPosts}
isPinnedPosts={this.props.isPinnedPosts}
channelDisplayName={this.props.channelDisplayName}
/>
<div
id='search-items-container'
@@ -307,5 +334,7 @@ SearchResults.propTypes = {
useMilitaryTime: React.PropTypes.bool.isRequired,
toggleSize: React.PropTypes.func,
shrink: React.PropTypes.func,
isFlaggedPosts: React.PropTypes.bool
isFlaggedPosts: React.PropTypes.bool,
isPinnedPosts: React.PropTypes.bool,
channelDisplayName: React.PropTypes.string.isRequired
};

Просмотреть файл

@@ -79,6 +79,16 @@ export default class SearchResultsHeader extends React.Component {
defaultMessage='Flagged Posts'
/>
);
} else if (this.props.isPinnedPosts) {
title = (
<FormattedMessage
id='search_header.title4'
defaultMessage='Pinned posts in {channelDisplayName}'
values={{
channelDisplayName: this.props.channelDisplayName
}}
/>
);
}
return (
@@ -131,5 +141,7 @@ SearchResultsHeader.propTypes = {
isMentionSearch: React.PropTypes.bool,
toggleSize: React.PropTypes.func,
shrink: React.PropTypes.func,
isFlaggedPosts: React.PropTypes.bool
isFlaggedPosts: React.PropTypes.bool,
isPinnedPosts: React.PropTypes.bool,
channelDisplayName: React.PropTypes.string.isRequired
};

Просмотреть файл

@@ -289,6 +289,18 @@ export default class SearchResultsItem extends React.Component {
);
}
let pinnedBadge;
if (post.is_pinned) {
pinnedBadge = (
<span className='post__pinned-badge'>
<FormattedMessage
id='post_info.pinned'
defaultMessage='Pinned'
/>
</span>
);
}
return (
<div className='search-item__container'>
<div className='date-separator'>
@@ -322,6 +334,7 @@ export default class SearchResultsItem extends React.Component {
{botIndicator}
<li className='col'>
{this.renderTimeTag(post)}
{pinnedBadge}
{flagContent}
</li>
{rhsControls}

Просмотреть файл

@@ -11,13 +11,13 @@ import UserStore from 'stores/user_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import WebrtcStore from 'stores/webrtc_store.jsx';
import {getFlaggedPosts} from 'actions/post_actions.jsx';
import {getFlaggedPosts, getPinnedPosts} from 'actions/post_actions.jsx';
import {trackEvent} from 'actions/diagnostics_actions.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
import React from 'react';
import React, {PropTypes} from 'react';
export default class SidebarRight extends React.Component {
constructor(props) {
@@ -27,6 +27,7 @@ export default class SidebarRight extends React.Component {
this.onPreferenceChange = this.onPreferenceChange.bind(this);
this.onSelectedChange = this.onSelectedChange.bind(this);
this.onPostPinnedChange = this.onPostPinnedChange.bind(this);
this.onSearchChange = this.onSearchChange.bind(this);
this.onUserChange = this.onUserChange.bind(this);
this.onShowSearch = this.onShowSearch.bind(this);
@@ -39,6 +40,7 @@ export default class SidebarRight extends React.Component {
searchVisible: SearchStore.getSearchResults() !== null,
isMentionSearch: SearchStore.getIsMentionSearch(),
isFlaggedPosts: SearchStore.getIsFlaggedPosts(),
isPinnedPosts: SearchStore.getIsPinnedPosts(),
postRightVisible: Boolean(PostStore.getSelectedPost()),
expanded: false,
fromSearch: false,
@@ -50,6 +52,7 @@ export default class SidebarRight extends React.Component {
componentDidMount() {
SearchStore.addSearchChangeListener(this.onSearchChange);
PostStore.addSelectedPostChangeListener(this.onSelectedChange);
PostStore.addPostPinnedChangeListener(this.onPostPinnedChange);
SearchStore.addShowSearchListener(this.onShowSearch);
UserStore.addChangeListener(this.onUserChange);
PreferenceStore.addChangeListener(this.onPreferenceChange);
@@ -59,6 +62,7 @@ export default class SidebarRight extends React.Component {
componentWillUnmount() {
SearchStore.removeSearchChangeListener(this.onSearchChange);
PostStore.removeSelectedPostChangeListener(this.onSelectedChange);
PostStore.removePostPinnedChangeListener(this.onPostPinnedChange);
SearchStore.removeShowSearchListener(this.onShowSearch);
UserStore.removeChangeListener(this.onUserChange);
PreferenceStore.removeChangeListener(this.onPreferenceChange);
@@ -137,6 +141,12 @@ export default class SidebarRight extends React.Component {
});
}
onPostPinnedChange() {
if (this.props.channel && this.state.isPinnedPosts) {
getPinnedPosts(this.props.channel.id);
}
}
onShrink() {
this.setState({
expanded: false
@@ -147,7 +157,8 @@ export default class SidebarRight extends React.Component {
this.setState({
searchVisible: SearchStore.getSearchResults() !== null,
isMentionSearch: SearchStore.getIsMentionSearch(),
isFlaggedPosts: SearchStore.getIsFlaggedPosts()
isFlaggedPosts: SearchStore.getIsFlaggedPosts(),
isPinnedPosts: SearchStore.getIsPinnedPosts()
});
}
@@ -182,9 +193,11 @@ export default class SidebarRight extends React.Component {
<SearchResults
isMentionSearch={this.state.isMentionSearch}
isFlaggedPosts={this.state.isFlaggedPosts}
isPinnedPosts={this.state.isPinnedPosts}
useMilitaryTime={this.state.useMilitaryTime}
toggleSize={this.toggleSize}
shrink={this.onShrink}
channelDisplayName={this.props.channel ? this.props.channel.display_name : ''}
/>
);
} else if (this.state.postRightVisible) {
@@ -222,3 +235,7 @@ export default class SidebarRight extends React.Component {
);
}
}
SidebarRight.propTypes = {
channel: PropTypes.object
};