This commit is contained in:
mgabdev 2020-04-17 01:35:46 -04:00
parent 35852e7fee
commit 4d7aee59c9
37 changed files with 568 additions and 319 deletions

View File

@ -49,6 +49,7 @@ class AccountsController < ReactController
statuses.merge!(hashtag_scope) if tag_requested? statuses.merge!(hashtag_scope) if tag_requested?
statuses.merge!(only_media_scope) if media_requested? statuses.merge!(only_media_scope) if media_requested?
statuses.merge!(no_replies_scope) unless replies_requested? statuses.merge!(no_replies_scope) unless replies_requested?
statuses.merge!(only_replies_scope) unless comments_only_requested?
end end
end end
@ -68,6 +69,10 @@ class AccountsController < ReactController
Status.without_replies Status.without_replies
end end
def only_replies_scope
Status.only_replies
end
def hashtag_scope def hashtag_scope
tag = Tag.find_normalized(params[:tag]) tag = Tag.find_normalized(params[:tag])
@ -97,6 +102,8 @@ class AccountsController < ReactController
short_account_media_url(@account, max_id: max_id, min_id: min_id) short_account_media_url(@account, max_id: max_id, min_id: min_id)
elsif replies_requested? elsif replies_requested?
short_account_with_replies_url(@account, max_id: max_id, min_id: min_id) short_account_with_replies_url(@account, max_id: max_id, min_id: min_id)
elsif comments_only_requested?
short_account_comments_only_url(@account, max_id: max_id, min_id: min_id)
else else
short_account_url(@account, max_id: max_id, min_id: min_id) short_account_url(@account, max_id: max_id, min_id: min_id)
end end
@ -110,6 +117,10 @@ class AccountsController < ReactController
request.path.ends_with?('/with_replies') request.path.ends_with?('/with_replies')
end end
def comments_only_requested?
request.path.ends_with?('/comments_only')
end
def tag_requested? def tag_requested?
request.path.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize) request.path.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
end end

View File

@ -3,6 +3,7 @@
class Api::V1::AccountByUsernameController < Api::BaseController class Api::V1::AccountByUsernameController < Api::BaseController
before_action :set_account before_action :set_account
before_action :check_account_suspension before_action :check_account_suspension
before_action :check_account_local
respond_to :json respond_to :json
@ -17,4 +18,9 @@ class Api::V1::AccountByUsernameController < Api::BaseController
def check_account_suspension def check_account_suspension
gone if @account.suspended? gone if @account.suspended?
end end
# if not our domain don't display
def check_account_local
gone unless @account.local?
end
end end

View File

@ -32,6 +32,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
statuses.merge!(only_media_scope) if truthy_param?(:only_media) statuses.merge!(only_media_scope) if truthy_param?(:only_media)
statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies) statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies)
statuses.merge!(only_replies_scope) if truthy_param?(:only_comments)
statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs) statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs)
statuses.merge!(hashtag_scope) if params[:tagged].present? statuses.merge!(hashtag_scope) if params[:tagged].present?
@ -64,6 +65,10 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
Status.without_replies Status.without_replies
end end
def only_replies_scope
Status.only_replies
end
def no_reblogs_scope def no_reblogs_scope
Status.without_reblogs Status.without_reblogs
end end
@ -79,7 +84,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
end end
def pagination_params(core_params) def pagination_params(core_params)
params.slice(:limit, :only_media, :exclude_replies).permit(:limit, :only_media, :exclude_replies).merge(core_params) params.slice(:limit, :only_media, :exclude_replies, :only_comments).permit(:limit, :only_media, :exclude_replies, :only_comments).merge(core_params)
end end
def insert_pagination_headers def insert_pagination_headers

View File

@ -1,14 +1,31 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::GifsController < Api::BaseController class Api::V1::GifsController < Api::BaseController
before_action :require_user!
respond_to :json respond_to :json
skip_before_action :set_cache_headers skip_before_action :set_cache_headers
def index def categories
uri = URI('https://api.tenor.com/v1/categories') uri = URI('https://api.tenor.com/v1/categories')
params = { :key => "QHFJ0C5EWGBH" } theOptions = { :key => "QHFJ0C5EWGBH" }
uri.query = URI.encode_www_form(params) uri.query = URI.encode_www_form(theOptions)
res = Net::HTTP.get_response(uri)
render json: res.body if res.is_a?(Net::HTTPSuccess)
end
def search
uri = URI('https://api.tenor.com/v1/search')
theOptions = {
:key => "QHFJ0C5EWGBH",
:media_filter => "minimal",
:limit => 30,
:q => params[:search],
:pos => params[:next] || 0
}
uri.query = URI.encode_www_form(theOptions)
res = Net::HTTP.get_response(uri) res = Net::HTTP.get_response(uri)
render json: res.body if res.is_a?(Net::HTTPSuccess) render json: res.body if res.is_a?(Net::HTTPSuccess)

View File

@ -4,6 +4,7 @@ class Api::V1::NotificationsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:notifications' }, except: [:clear, :dismiss, :mark_read] before_action -> { doorkeeper_authorize! :read, :'read:notifications' }, except: [:clear, :dismiss, :mark_read]
before_action -> { doorkeeper_authorize! :write, :'write:notifications' }, only: [:clear, :dismiss, :mark_read] before_action -> { doorkeeper_authorize! :write, :'write:notifications' }, only: [:clear, :dismiss, :mark_read]
before_action :require_user! before_action :require_user!
before_action :set_filter_params
after_action :insert_pagination_headers, only: :index after_action :insert_pagination_headers, only: :index
respond_to :json respond_to :json
@ -49,7 +50,7 @@ class Api::V1::NotificationsController < Api::BaseController
end end
def browserable_account_notifications def browserable_account_notifications
current_account.notifications.browserable(exclude_types, from_account) current_account.notifications.browserable(exclude_types, from_account, params[:only_verified], params[:only_following])
end end
def target_statuses_from_notifications def target_statuses_from_notifications
@ -86,6 +87,13 @@ class Api::V1::NotificationsController < Api::BaseController
val val
end end
def set_filter_params
params.permit(
:only_verified,
:only_following
)
end
def from_account def from_account
params[:account_id] params[:account_id]
end end

View File

@ -100,12 +100,11 @@ function getFromDB(dispatch, getState, index, id) {
export function fetchAccount(id) { export function fetchAccount(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
dispatch(fetchRelationships([id]));
if (id === -1 || getState().getIn(['accounts', id], null) !== null) { if (id === -1 || getState().getIn(['accounts', id], null) !== null) {
return; return;
} }
dispatch(fetchRelationships([id]));
dispatch(fetchAccountRequest(id)); dispatch(fetchAccountRequest(id));
openDB().then(db => getFromDB( openDB().then(db => getFromDB(
@ -128,8 +127,13 @@ export function fetchAccount(id) {
export function fetchAccountByUsername(username) { export function fetchAccountByUsername(username) {
return (dispatch, getState) => { return (dispatch, getState) => {
if (!username) {
return;
}
api(getState).get(`/api/v1/account_by_username/${username}`).then(response => { api(getState).get(`/api/v1/account_by_username/${username}`).then(response => {
dispatch(importFetchedAccount(response.data)); dispatch(importFetchedAccount(response.data))
dispatch(fetchRelationships([response.data.id]))
}).then(() => { }).then(() => {
dispatch(fetchAccountSuccess()); dispatch(fetchAccountSuccess());
}).catch(error => { }).catch(error => {

View File

@ -165,14 +165,15 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
// filter verified and following here too // filter verified and following here too
const params = { const params = {
max_id: maxId, max_id: maxId,
// only_verified: onlyVerified,
// only_following: onlyFollowing,
exclude_types: activeFilter === 'all' ? null : excludeTypesFromFilter(activeFilter), exclude_types: activeFilter === 'all' ? null : excludeTypesFromFilter(activeFilter),
// exclude_types: activeFilter === 'all' // exclude_types: activeFilter === 'all'
// ? excludeTypesFromSettings(getState()) // ? excludeTypesFromSettings(getState())
// : excludeTypesFromFilter(activeFilter), // : excludeTypesFromFilter(activeFilter),
}; };
if (!!onlyVerified) params.only_verified = onlyVerified
if (!!onlyFollowing) params.only_following = onlyFollowing
if (!maxId && notifications.get('items').size > 0) { if (!maxId && notifications.get('items').size > 0) {
params.since_id = notifications.getIn(['items', 0, 'id']); params.since_id = notifications.getIn(['items', 0, 'id']);
} }

View File

@ -21,7 +21,7 @@ export const fetchGifCategories = () => {
dispatch(fetchGifCategoriesRequest()) dispatch(fetchGifCategoriesRequest())
api(getState).get('/api/v1/gifs').then(response => { api(getState).get('/api/v1/gifs/categories').then(response => {
dispatch(fetchGifCategoriesSuccess(response.data.tags)) dispatch(fetchGifCategoriesSuccess(response.data.tags))
}).catch(function (error) { }).catch(function (error) {
dispatch(fetchGifCategoriesFail(error)) dispatch(fetchGifCategoriesFail(error))
@ -29,24 +29,25 @@ export const fetchGifCategories = () => {
} }
} }
export const fetchGifResults = (maxId) => { export const fetchGifResults = (expand) => {
return function (dispatch, getState) { return function (dispatch, getState) {
if (!me) return if (!me) return
dispatch(fetchGifResultsRequest()) dispatch(fetchGifResultsRequest())
const searchText = getState().getIn(['tenor', 'searchText'], ''); const search = getState().getIn(['tenor', 'searchText'], '');
const pos = 0 //expand ? getState().getIn(['tenor', 'results'], []).length
axios.get(`https://api.tenor.com/v1/search?q=${searchText}&media_filter=minimal&limit=30&key=${tenorkey}`) api(getState).get('/api/v1/gifs/search', { search, pos }).then((response) => {
.then((response) => { console.log("response.data:", response.data)
console.log('response:', response) dispatch(fetchGifResultsSuccess(response.data))
dispatch(fetchGifResultsSuccess(response.data.results)) }).catch(function (error) {
}).catch(function (error) { dispatch(fetchGifResultsFail(error))
dispatch(fetchGifResultsFail(error)) })
})
} }
} }
export const clearGifResults = () => ({ export const clearGifResults = () => ({
type: GIFS_CLEAR_RESULTS, type: GIFS_CLEAR_RESULTS,
}) })
@ -69,10 +70,10 @@ function fetchGifResultsRequest() {
} }
} }
function fetchGifResultsSuccess(results) { function fetchGifResultsSuccess(data) {
return { return {
type: GIF_RESULTS_FETCH_SUCCESS, type: GIF_RESULTS_FETCH_SUCCESS,
results, data,
} }
} }

View File

@ -149,7 +149,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done); export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done);
export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done); export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done);
export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId }); export const expandAccountTimeline = (accountId, { maxId, withReplies, commentsOnly } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}${commentsOnly ? ':comments_only' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { only_comments: commentsOnly, exclude_replies: (!withReplies && !commentsOnly), max_id: maxId });
export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true }); export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
export const expandAccountMediaTimeline = (accountId, { maxId, limit } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: limit || 20 }); export const expandAccountMediaTimeline = (accountId, { maxId, limit } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: limit || 20 });
export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done); export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done);

View File

@ -136,6 +136,7 @@ export default class Button extends PureComponent {
backgroundSubtle2Dark_onHover: backgroundColor === COLORS.tertiary || backgroundColor === COLORS.secondary, backgroundSubtle2Dark_onHover: backgroundColor === COLORS.tertiary || backgroundColor === COLORS.secondary,
backgroundColorBlackOpaque_onHover: backgroundColor === COLORS.black, backgroundColorBlackOpaque_onHover: backgroundColor === COLORS.black,
backgroundColorBrandDark_onHover: backgroundColor === COLORS.brand, backgroundColorBrandDark_onHover: backgroundColor === COLORS.brand,
backgroundColorDangerDark_onHover: backgroundColor === COLORS.danger,
backgroundColorBrand_onHover: color === COLORS.brand && outline, backgroundColorBrand_onHover: color === COLORS.brand && outline,
colorWhite_onHover: !!children && color === COLORS.brand && outline, colorWhite_onHover: !!children && color === COLORS.brand && outline,

View File

@ -52,7 +52,7 @@ class Comment extends ImmutablePureComponent {
// : todo : add media // : todo : add media
return ( return (
<div className={[_s.default, _s.px10, _s.mb10, _s.py5].join(' ')} data-comment={status.get('id')}> <div className={[_s.default, _s.px15, _s.mb10, _s.py5].join(' ')} data-comment={status.get('id')}>
<div className={[_s.default].join(' ')} style={style}> <div className={[_s.default].join(' ')} style={style}>
<div className={[_s.default, _s.flexRow].join(' ')}> <div className={[_s.default, _s.flexRow].join(' ')}>

View File

@ -1,20 +1,30 @@
import ImmutablePropTypes from 'react-immutable-proptypes' import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component' import ImmutablePureComponent from 'react-immutable-pure-component'
import Button from './button'
import Comment from './comment' import Comment from './comment'
import Text from './text'
export default class CommentList extends ImmutablePureComponent { export default class CommentList extends ImmutablePureComponent {
static propTypes = { static propTypes = {
commentsLimited: PropTypes.bool,
descendants: ImmutablePropTypes.list, descendants: ImmutablePropTypes.list,
} }
render() { render() {
const { descendants } = this.props const {
descendants,
commentsLimited,
} = this.props
const size = descendants.size
const max = Math.min(commentsLimited ? 2 : 6, size)
console.log("max:", size, max)
return ( return (
<div> <div>
{ {
descendants.map((descendant, i) => ( descendants.slice(0, max).map((descendant, i) => (
<Comment <Comment
key={`comment-${descendant.get('statusId')}-${i}`} key={`comment-${descendant.get('statusId')}-${i}`}
id={descendant.get('statusId')} id={descendant.get('statusId')}
@ -22,6 +32,27 @@ export default class CommentList extends ImmutablePureComponent {
/> />
)) ))
} }
{
size > 0 && size > max &&
<div className={[_s.default, _s.flexRow, _s.px15, _s.pb5, _s.mb10, _s.alignItemsCenter].join(' ')}>
<Button
text
backgroundColor='none'
color='tertiary'
>
<Text weight='bold' color='inherit'>
View more comments
</Text>
</Button>
<div className={[_s.default, _s.marginLeftAuto].join(' ')}>
<Text color='tertiary'>
{max}
&nbsp;of&nbsp;
{size}
</Text>
</div>
</div>
}
</div> </div>
) )
} }

View File

@ -33,29 +33,31 @@ class LoadMore extends PureComponent {
const { disabled, visible, gap, intl } = this.props const { disabled, visible, gap, intl } = this.props
return ( return (
<Button <div className={[_s.default, _s.py10, _s.px10].join(' ')}>
block <Button
radiusSmall block
backgroundColor='tertiary' radiusSmall
color='primary' backgroundColor='tertiary'
disabled={disabled || !visible} color='primary'
style={{ visibility: visible ? 'visible' : 'hidden' }} disabled={disabled || !visible}
onClick={this.handleClick} style={{ visibility: visible ? 'visible' : 'hidden' }}
aria-label={intl.formatMessage(messages.load_more)} onClick={this.handleClick}
> aria-label={intl.formatMessage(messages.load_more)}
{ >
!gap && {
<Text color='inherit'> !gap &&
{intl.formatMessage(messages.load_more)} <Text color='inherit'>
</Text> {intl.formatMessage(messages.load_more)}
} </Text>
{ }
gap && {
<Text align='center'> gap &&
<Icon id='ellipsis' /> <Text align='center'>
</Text> <Icon id='ellipsis' />
} </Text>
</Button> }
</Button>
</div>
) )
} }

View File

@ -99,7 +99,7 @@ class GifPickerModal extends PureComponent {
} }
handleSelectGifResult = (resultId) => { handleSelectGifResult = (resultId) => {
console.log("handleSelectGifResult:", resultId)
} }
render() { render() {

View File

@ -0,0 +1,68 @@
import { injectIntl, defineMessages } from 'react-intl'
import { muteAccount } from '../../actions/accounts'
const messages = defineMessages({
muteMessage: { id: 'confirmations.mute.message', defaultMessage: 'Are you sure you want to mute {name}?' },
cancel: { id: 'confirmation_modal.cancel', defaultMessage: 'Cancel' },
confirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' },
})
const mapStateToProps = (state) => ({
isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
account: state.getIn(['mutes', 'new', 'account']),
})
const mapDispatchToProps = (dispatch) => ({
onConfirm(account, notifications) {
dispatch(muteAccount(account.get('id'), notifications))
},
})
export default
@connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class UnfollowModal extends PureComponent {
static propTypes = {
isSubmitting: PropTypes.bool.isRequired,
account: PropTypes.object.isRequired,
onConfirm: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
}
componentDidMount() {
this.button.focus()
}
handleClick = () => {
this.props.onClose()
this.props.onConfirm(this.props.account, this.props.notifications)
}
handleCancel = () => {
this.props.onClose()
}
render() {
const { account, intl } = this.props
// , {
// message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
// confirm: intl.formatMessage(messages.unfollowConfirm),
// onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
// }));
return (
<ConfirmationModal
title={`Mute @${account.get('acct')}`}
message={<FormattedMessage id='confirmations.mute.message' defaultMessage='Are you sure you want to mute @{name}?' values={{ name: account.get('acct') }} />}
confirm={<FormattedMessage id='mute' defaultMessage='Mute' />}
onConfirm={() => {
// dispatch(blockDomain(domain))
// dispatch(blockDomain(domain))
}}
/>
)
}
}

View File

@ -1,7 +1,7 @@
import axios from 'axios'
import ImmutablePropTypes from 'react-immutable-proptypes' import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component' import ImmutablePureComponent from 'react-immutable-pure-component'
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl' import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'
import Sticky from 'react-stickynode'
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
import { import {
followAccount, followAccount,
@ -26,9 +26,11 @@ const cx = classNames.bind(_s)
const messages = defineMessages({ const messages = defineMessages({
follow: { id: 'follow', defaultMessage: 'Follow' }, follow: { id: 'follow', defaultMessage: 'Follow' },
following: { id: 'following', defaultMessage: 'Following' },
unfollow: { id: 'unfollow', defaultMessage: 'Unfollow' }, unfollow: { id: 'unfollow', defaultMessage: 'Unfollow' },
requested: { id: 'requested', defaultMessage: 'Requested' }, requested: { id: 'requested', defaultMessage: 'Requested' },
unblock: { id: 'unblock', defaultMessage: 'Unblock' }, unblock: { id: 'unblock', defaultMessage: 'Unblock' },
blocked: { id: 'account.blocked', defaultMessage: 'Blocked' },
followers: { id: 'account.followers', defaultMessage: 'Followers' }, followers: { id: 'account.followers', defaultMessage: 'Followers' },
follows: { id: 'account.follows', defaultMessage: 'Follows' }, follows: { id: 'account.follows', defaultMessage: 'Follows' },
profile: { id: 'account.profile', defaultMessage: 'Profile' }, profile: { id: 'account.profile', defaultMessage: 'Profile' },
@ -92,6 +94,10 @@ class ProfileHeader extends ImmutablePureComponent {
openProfileOptionsPopover: PropTypes.func.isRequired, openProfileOptionsPopover: PropTypes.func.isRequired,
} }
state = {
stickied: false,
}
handleOpenMore = () => { handleOpenMore = () => {
const { openProfileOptionsPopover, account } = this.props const { openProfileOptionsPopover, account } = this.props
openProfileOptionsPopover({ openProfileOptionsPopover({
@ -110,7 +116,7 @@ class ProfileHeader extends ImmutablePureComponent {
} }
// : todo : // : todo :
makeInfo() { makeInfo = () => {
const { account, intl } = this.props const { account, intl } = this.props
const info = [] const info = []
@ -132,12 +138,24 @@ class ProfileHeader extends ImmutablePureComponent {
return info return info
} }
onStickyStateChange = (status) => {
switch (status.status) {
case Sticky.STATUS_FIXED:
this.setState({ stickied: true })
break;
default:
this.setState({ stickied: false })
break;
}
}
setOpenMoreNodeRef = (n) => { setOpenMoreNodeRef = (n) => {
this.openMoreNode = n this.openMoreNode = n
} }
render() { render() {
const { account, intl } = this.props const { account, intl } = this.props
const { stickied } = this.state
const tabs = !account ? null : [ const tabs = !account ? null : [
{ {
@ -160,6 +178,60 @@ class ProfileHeader extends ImmutablePureComponent {
const headerSrc = !!account ? account.get('header') : '' const headerSrc = !!account ? account.get('header') : ''
const headerMissing = headerSrc.indexOf('/headers/original/missing.png') > -1 || !headerSrc const headerMissing = headerSrc.indexOf('/headers/original/missing.png') > -1 || !headerSrc
const avatarSize = headerMissing ? '75' : '150'
let buttonText = ''
let buttonOptions = {}
if (!account) {
//
} else {
if (!account.get('relationship')) {
// Wait until the relationship is loaded
} else {
const isRequested = account.getIn(['relationship', 'requested'])
const isBlocking = account.getIn(['relationship', 'blocking'])
const isFollowing = account.getIn(['relationship', 'following'])
const isBlockedBy = account.getIn(['relationship', 'blocked_by'])
if (isRequested) {
buttonText = intl.formatMessage(messages.requested)
buttonOptions = {
onClick: this.handleFollow,
color: 'primary',
backgroundColor: 'tertiary',
}
} else if (isBlocking) {
buttonText = intl.formatMessage(messages.blocked)
buttonOptions = {
onClick: this.handleBlock,
color: 'white',
backgroundColor: 'danger',
}
} else if (isFollowing) {
buttonText = intl.formatMessage(messages.following)
buttonOptions = {
onClick: this.handleFollow,
color: 'white',
backgroundColor: 'brand',
}
} else if (isBlockedBy) {
//Don't show
}
else {
buttonText = intl.formatMessage(messages.follow)
buttonOptions = {
onClick: this.handleFollow,
color: 'brand',
backgroundColor: 'none',
outline: true,
}
}
}
}
console.log('buttonOptions:', buttonText, buttonOptions)
console.log('account: ', account)
const avatarContainerClasses = cx({ const avatarContainerClasses = cx({
circle: 1, circle: 1,
@ -168,78 +240,18 @@ class ProfileHeader extends ImmutablePureComponent {
border2PX: 1, border2PX: 1,
}) })
const avatarSize = headerMissing ? '75' : '150' const stickyBarContainerClasses = cx({
default: 1,
flexRow: 1,
px15: 1,
alignItemsCenter: 1,
displayNone: !stickied,
})
let buttonText = '' const tabBarContainerClasses = cx({
let buttonOptions = {} default: 1,
displayNone: stickied,
if (!account) { })
console.log('no account')
} else {
if (!account.get('relationship')) {
console.log('no relationship')
// Wait until the relationship is loaded
} else if (account.getIn(['relationship', 'requested'])) {
buttonText = intl.formatMessage(messages.requested)
buttonOptions = {
narrow: true,
onClick: this.handleFollow,
color: 'primary',
backgroundColor: 'tertiary',
}
} else if (!account.getIn(['relationship', 'blocking'])) {
const isFollowing = account.getIn(['relationship', 'following'])
const isBlockedBy = account.getIn(['relationship', 'blocked_by'])
buttonText = intl.formatMessage(isFollowing ? messages.unfollow : messages.follow)
buttonOptions = {
narrow: true,
onClick: this.handleFollow,
color: 'primary',
backgroundColor: 'tertiary',
disabled: isBlockedBy,
}
} else if (account.getIn(['relationship', 'blocking'])) {
buttonText = intl.formatMessage(messages.unblock)
buttonOptions = {
narrow: true,
onClick: this.handleBlock,
color: 'primary',
backgroundColor: 'tertiary',
}
} else {
console.log('no nothin')
}
// if (account.get('id') !== me && account.get('relationship', null) !== null) {
// const following = account.getIn(['relationship', 'following'])
// const requested = account.getIn(['relationship', 'requested'])
// const blocking = account.getIn(['relationship', 'blocking'])
// if (requested || blocking) {
// buttonText = intl.formatMessage(requested ? messages.requested : messages.unblock)
// buttonOptions = {
// narrow: true,
// onClick: requested ? this.handleFollow : this.handleBlock,
// color: 'primary',
// backgroundColor: 'tertiary',
// }
// } else if (!account.get('moved') || following) {
// buttonOptions = {
// narrow: true,
// outline: !following,
// color: !following ? 'brand' : 'white',
// backgroundColor: !following ? 'none' : 'brand',
// onClick: this.handleFollow,
// }
// buttonText = intl.formatMessage(following ? messages.unfollow : messages.follow)
// } else {
// console.log("SHOW ELSE")
// }
// }
}
console.log('buttonOptions:', buttonText, buttonOptions)
console.log('account: ', account)
// : todo : "follows you", "mutual follow" // : todo : "follows you", "mutual follow"
@ -260,7 +272,7 @@ class ProfileHeader extends ImmutablePureComponent {
<div className={[_s.default, _s.borderBottom1PX, _s.borderColorSecondary, _s.width100PC].join(' ')}> <div className={[_s.default, _s.borderBottom1PX, _s.borderColorSecondary, _s.width100PC].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.px15].join(' ')}> <div className={[_s.default, _s.flexRow, _s.px15, _s.mb5].join(' ')}>
<div className={avatarContainerClasses}> <div className={avatarContainerClasses}>
<Avatar size={avatarSize} account={account} /> <Avatar size={avatarSize} account={account} />
</div> </div>
@ -271,87 +283,103 @@ class ProfileHeader extends ImmutablePureComponent {
account && account.get('locked') && account && account.get('locked') &&
<Icon id='lock-filled' height='14px' width='14px' className={[_s.mt10, _s.ml10].join(' ')} /> <Icon id='lock-filled' height='14px' width='14px' className={[_s.mt10, _s.ml10].join(' ')} />
} }
{
/* : todo :
account.getIn(['relationship', 'muting'])
*/
}
</div> </div>
</div> </div>
<div className={[_s.default, _s.flexRow, _s.borderBottom1PX, _s.borderColorSecondary, _s.mt5, _s.height53PX].join(' ')}> <Sticky enabled onStateChange={this.onStickyStateChange}>
<div className={[_s.default].join(' ')}> <div className={[_s.default, _s.flexRow, _s.backgroundColorSecondary3, _s.borderBottom1PX, _s.borderColorSecondary, _s.height53PX].join(' ')}>
<TabBar tabs={tabs} large /> <div className={tabBarContainerClasses}>
</div> <TabBar tabs={tabs} large />
{
account && account.get('id') === me &&
<div className={[_s.default, _s.flexRow, _s.marginLeftAuto, _s.py5].join(' ')}>
<Button
outline
backgroundColor='none'
color='brand'
className={[_s.justifyContentCenter, _s.alignItemsCenter].join(' ')}
href=''
>
<Text
color='inherit'
weight='bold'
size='medium'
className={[_s.px15].join(' ')}
>
Edit Profile
</Text>
</Button>
</div> </div>
}
{ <div className={stickyBarContainerClasses}>
account && account.get('id') !== me && <Avatar size={36} account={account} />
<div className={[_s.default, _s.flexRow, _s.marginLeftAuto, _s.py5].join(' ')}> <div className={[_s.default, _s.ml10].join(' ')}>
<div ref={this.setOpenMoreNodeRef}> <DisplayName account={account} noUsername large />
<Button
outline
icon='ellipsis'
iconWidth='18px'
iconHeight='18px'
iconClassName={_s.inheritFill}
color='brand'
backgroundColor='none'
className={[_s.justifyContentCenter, _s.alignItemsCenter, _s.mr10, _s.px10].join(' ')}
onClick={this.handleOpenMore}
/>
</div> </div>
</div>
<form action='https://chat.gab.com/private-message' method='POST'> {
account && account.get('id') === me &&
<div className={[_s.default, _s.flexRow, _s.marginLeftAuto, _s.py5].join(' ')}>
<Button <Button
type='submit'
outline outline
icon='chat'
iconWidth='18px'
iconHeight='18px'
iconClassName={_s.inheritFill}
color='brand'
backgroundColor='none' backgroundColor='none'
className={[_s.justifyContentCenter, _s.alignItemsCenter, _s.mr10, _s.px10].join(' ')} color='brand'
/> className={[_s.justifyContentCenter, _s.alignItemsCenter].join(' ')}
<input type='hidden' value={account.get('username')} name='username' /> href=''
</form> >
<Button
{...buttonOptions}
className={[_s.justifyContentCenter, _s.alignItemsCenter].join(' ')}
>
<span className={[_s.px15].join(' ')}>
<Text <Text
color='inherit' color='inherit'
weight='bold' weight='bold'
size='medium' size='medium'
className={[_s.px15].join(' ')} className={[_s.px15].join(' ')}
> >
{buttonText} Edit Profile
</Text> </Text>
</span> </Button>
</Button> </div>
}
</div> {
} account && account.get('id') !== me &&
</div> <div className={[_s.default, _s.flexRow, _s.marginLeftAuto, _s.py5].join(' ')}>
<div ref={this.setOpenMoreNodeRef}>
<Button
outline
icon='ellipsis'
iconWidth='18px'
iconHeight='18px'
iconClassName={_s.inheritFill}
color='brand'
backgroundColor='none'
className={[_s.justifyContentCenter, _s.alignItemsCenter, _s.mr10, _s.px10].join(' ')}
onClick={this.handleOpenMore}
/>
</div>
<form action='https://chat.gab.com/private-message' method='POST'>
<Button
type='submit'
outline
icon='chat'
iconWidth='18px'
iconHeight='18px'
iconClassName={_s.inheritFill}
color='brand'
backgroundColor='none'
className={[_s.justifyContentCenter, _s.alignItemsCenter, _s.mr10, _s.px10].join(' ')}
/>
<input type='hidden' value={account.get('username')} name='username' />
</form>
{
!!buttonText &&
<Button
{...buttonOptions}
narrow
className={[_s.justifyContentCenter, _s.alignItemsCenter].join(' ')}
>
<Text
color='inherit'
weight='bold'
size='medium'
className={_s.px10}
>
{buttonText}
</Text>
</Button>
}
</div>
}
</div>
</Sticky>
</div> </div>
</div> </div>
) )

View File

@ -106,8 +106,6 @@ class Sidebar extends ImmutablePureComponent {
const acct = account.get('acct') const acct = account.get('acct')
const isPro = account.get('is_pro') const isPro = account.get('is_pro')
console.log('showCommunityTimeline:', showCommunityTimeline)
const menuItems = [ const menuItems = [
{ {
title: 'Home', title: 'Home',

View File

@ -84,7 +84,6 @@ class Status extends ImmutablePureComponent {
hidden: PropTypes.bool, hidden: PropTypes.bool,
onMoveUp: PropTypes.func, onMoveUp: PropTypes.func,
onMoveDown: PropTypes.func, onMoveDown: PropTypes.func,
showThread: PropTypes.bool,
getScrollPosition: PropTypes.func, getScrollPosition: PropTypes.func,
updateScrollBottom: PropTypes.func, updateScrollBottom: PropTypes.func,
cacheMediaWidth: PropTypes.func, cacheMediaWidth: PropTypes.func,
@ -93,7 +92,6 @@ class Status extends ImmutablePureComponent {
promoted: PropTypes.bool, promoted: PropTypes.bool,
onOpenProUpgradeModal: PropTypes.func, onOpenProUpgradeModal: PropTypes.func,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
borderless: PropTypes.bool,
isChild: PropTypes.bool, isChild: PropTypes.bool,
} }
@ -267,10 +265,8 @@ class Status extends ImmutablePureComponent {
intl, intl,
hidden, hidden,
featured, featured,
showThread,
group, group,
promoted, promoted,
borderless,
isChild, isChild,
} = this.props } = this.props
@ -386,8 +382,8 @@ class Status extends ImmutablePureComponent {
const containerClasses = cx({ const containerClasses = cx({
default: 1, default: 1,
radiusSmall: !borderless && !isChild, radiusSmall: !isChild,
// mb15: !borderless && !isChild, // mb15: !isChild,
// backgroundColorPrimary: 1, // backgroundColorPrimary: 1,
pb15: featured, pb15: featured,
borderBottom1PX: featured && !isChild, borderBottom1PX: featured && !isChild,
@ -397,9 +393,9 @@ class Status extends ImmutablePureComponent {
const innerContainerClasses = cx({ const innerContainerClasses = cx({
default: 1, default: 1,
overflowHidden: 1, overflowHidden: 1,
radiusSmall: !borderless, radiusSmall: isChild,
borderColorSecondary: !borderless, borderColorSecondary: isChild,
border1PX: !borderless, border1PX: isChild,
pb10: isChild && status.get('media_attachments').size === 0, pb10: isChild && status.get('media_attachments').size === 0,
pb5: isChild && status.get('media_attachments').size > 1, pb5: isChild && status.get('media_attachments').size > 1,
cursorPointer: isChild, cursorPointer: isChild,
@ -409,7 +405,7 @@ class Status extends ImmutablePureComponent {
return ( return (
<HotKeys handlers={handlers}> <HotKeys handlers={handlers}>
<div <div
className={containerClasses} className={containerClasses}
tabIndex={this.props.muted ? null : 0} tabIndex={this.props.muted ? null : 0}
data-featured={featured ? 'true' : null} data-featured={featured ? 'true' : null}
aria-label={textForScreenReader(intl, status, rebloggedByText)} aria-label={textForScreenReader(intl, status, rebloggedByText)}

View File

@ -105,14 +105,6 @@ class StatusActionBar extends ImmutablePureComponent {
} }
} }
handleQuoteClick = () => {
if (me) {
this.props.onQuote(this.props.status, this.context.router.history)
} else {
this.props.onOpenUnauthorizedModal()
}
}
handleFavoriteClick = () => { handleFavoriteClick = () => {
if (me) { if (me) {
this.props.onFavorite(this.props.status) this.props.onFavorite(this.props.status)
@ -123,17 +115,29 @@ class StatusActionBar extends ImmutablePureComponent {
handleRepostClick = e => { handleRepostClick = e => {
if (me) { if (me) {
this.props.onRepost(this.props.status, e) // this.props.onRepost(this.props.status, e)
this.props.onQuote(this.props.status, this.context.router.history)
} else { } else {
this.props.onOpenUnauthorizedModal() this.props.onOpenUnauthorizedModal()
} }
} }
handleShareClick = () => { handleShareClick = () => {
console.log("handleShareClick:", this.shareButton, this.props.status)
this.props.onOpenStatusSharePopover(this.shareButton, this.props.status) this.props.onOpenStatusSharePopover(this.shareButton, this.props.status)
} }
openLikesList = () => {
}
toggleCommentsVisible = () => {
}
openRepostsList = () => {
}
setShareButton = (n) => { setShareButton = (n) => {
this.shareButton = n this.shareButton = n
} }
@ -176,6 +180,7 @@ class StatusActionBar extends ImmutablePureComponent {
text: 1, text: 1,
cursorPointer: 1, cursorPointer: 1,
fontWeightNormal: 1, fontWeightNormal: 1,
underline_onHover: 1,
mr10: 1, mr10: 1,
py5: 1, py5: 1,
}) })
@ -187,7 +192,7 @@ class StatusActionBar extends ImmutablePureComponent {
<div className={[_s.default, _s.flexRow, _s.px5].join(' ')}> <div className={[_s.default, _s.flexRow, _s.px5].join(' ')}>
{ {
favoriteCount > 0 && favoriteCount > 0 &&
<button className={interactionBtnClasses}> <button className={interactionBtnClasses} onClick={this.openLikesList}>
<Text color='secondary' size='small'> <Text color='secondary' size='small'>
{formatMessage(messages.likesLabel, { {formatMessage(messages.likesLabel, {
number: favoriteCount, number: favoriteCount,
@ -197,7 +202,7 @@ class StatusActionBar extends ImmutablePureComponent {
} }
{ {
replyCount > 0 && replyCount > 0 &&
<button className={interactionBtnClasses}> <button className={interactionBtnClasses} onClick={this.toggleCommentsVisible}>
<Text color='secondary' size='small'> <Text color='secondary' size='small'>
{formatMessage(messages.commentsLabel, { {formatMessage(messages.commentsLabel, {
number: replyCount, number: replyCount,
@ -207,7 +212,7 @@ class StatusActionBar extends ImmutablePureComponent {
} }
{ {
repostCount > 0 && repostCount > 0 &&
<button className={interactionBtnClasses}> <button className={interactionBtnClasses} onClick={this.openRepostsList}>
<Text color='secondary' size='small'> <Text color='secondary' size='small'>
{formatMessage(messages.repostsLabel, { {formatMessage(messages.repostsLabel, {
number: repostCount, number: repostCount,

View File

@ -38,7 +38,9 @@ const makeGetStatusIds = () => createSelector([
}); });
}); });
const mapStateToProps = (state, {timelineId}) => { const mapStateToProps = (state, { timelineId }) => {
if (!timelineId) return {}
const getStatusIds = makeGetStatusIds(); const getStatusIds = makeGetStatusIds();
const promotion = promotions.length > 0 && sample(promotions.filter(p => p.timeline_id === timelineId)); const promotion = promotions.length > 0 && sample(promotions.filter(p => p.timeline_id === timelineId));
@ -181,15 +183,15 @@ class StatusList extends ImmutablePureComponent {
contextType={timelineId} contextType={timelineId}
group={group} group={group}
withGroupAdmin={withGroupAdmin} withGroupAdmin={withGroupAdmin}
showThread commentsLimited
/> />
{ { /* : todo : */
promotedStatus && index === promotion.position && promotedStatus && index === promotion.position &&
<Status <Status
id={promotion.status_id} id={promotion.status_id}
contextType={timelineId} contextType={timelineId}
promoted promoted
showThread commentsLimited
/> />
} }
</Fragment> </Fragment>
@ -205,7 +207,7 @@ class StatusList extends ImmutablePureComponent {
onMoveUp={this.handleMoveUp} onMoveUp={this.handleMoveUp}
onMoveDown={this.handleMoveDown} onMoveDown={this.handleMoveDown}
contextType={timelineId} contextType={timelineId}
showThread commentsLimited
/> />
)).concat(scrollableContent) )).concat(scrollableContent)
} }

View File

@ -5,6 +5,7 @@ const cx = classNames.bind(_s)
const COLORS = { const COLORS = {
primary: 'primary', primary: 'primary',
secondary: 'secondary', secondary: 'secondary',
tertiary: 'tertiary',
brand: 'brand', brand: 'brand',
error: 'error', error: 'error',
white: 'white', white: 'white',
@ -78,6 +79,7 @@ export default class Text extends PureComponent {
colorPrimary: color === COLORS.primary, colorPrimary: color === COLORS.primary,
colorSecondary: color === COLORS.secondary, colorSecondary: color === COLORS.secondary,
colorTertiary: color === COLORS.tertiary,
colorBrand: color === COLORS.brand, colorBrand: color === COLORS.brand,
colorWhite: color === COLORS.white, colorWhite: color === COLORS.white,
colorGabPro: color === COLORS.pro, colorGabPro: color === COLORS.pro,

View File

@ -12,15 +12,17 @@ const messages = defineMessages({
const emptyList = ImmutableList() const emptyList = ImmutableList()
const mapStateToProps = (state, { account, withReplies = false }) => { const mapStateToProps = (state, { account, commentsOnly = false }) => {
const accountId = !!account ? account.getIn(['id'], null) : -1 const accountId = !!account ? account.getIn(['id'], null) : -1
const path = withReplies ? `${accountId}:with_replies` : accountId const path = commentsOnly ? `${accountId}:comments_only` : accountId
console.log("commentsOnly, path:", commentsOnly, path)
return { return {
accountId, accountId,
statusIds: state.getIn(['timelines', `account:${path}`, 'items'], emptyList), statusIds: state.getIn(['timelines', `account:${path}`, 'items'], emptyList),
featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], emptyList), featuredStatusIds: commentsOnly ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], emptyList),
isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']), isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']),
hasMore: state.getIn(['timelines', `account:${path}`, 'hasMore']), hasMore: state.getIn(['timelines', `account:${path}`, 'hasMore']),
} }
@ -38,33 +40,33 @@ class AccountTimeline extends ImmutablePureComponent {
featuredStatusIds: ImmutablePropTypes.list, featuredStatusIds: ImmutablePropTypes.list,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
withReplies: PropTypes.bool, commentsOnly: PropTypes.bool,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
} }
componentWillMount() { componentWillMount() {
const { accountId, withReplies } = this.props const { accountId, commentsOnly } = this.props
if (accountId && accountId !== -1) { if (accountId && accountId !== -1) {
this.props.dispatch(fetchAccountIdentityProofs(accountId)) this.props.dispatch(fetchAccountIdentityProofs(accountId))
if (!withReplies) { if (!commentsOnly) {
this.props.dispatch(expandAccountFeaturedTimeline(accountId)) this.props.dispatch(expandAccountFeaturedTimeline(accountId))
} }
this.props.dispatch(expandAccountTimeline(accountId, { withReplies })) this.props.dispatch(expandAccountTimeline(accountId, { commentsOnly }))
} }
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (nextProps.accountId && nextProps.accountId !== -1 && (nextProps.accountId !== this.props.accountId && nextProps.accountId) || nextProps.withReplies !== this.props.withReplies) { if (nextProps.accountId && nextProps.accountId !== -1 && (nextProps.accountId !== this.props.accountId && nextProps.accountId) || nextProps.commentsOnly !== this.props.commentsOnly) {
this.props.dispatch(fetchAccountIdentityProofs(nextProps.accountId)) this.props.dispatch(fetchAccountIdentityProofs(nextProps.accountId))
if (!nextProps.withReplies) { if (!nextProps.commentsOnly) {
this.props.dispatch(expandAccountFeaturedTimeline(nextProps.accountId)) this.props.dispatch(expandAccountFeaturedTimeline(nextProps.accountId))
} }
this.props.dispatch(expandAccountTimeline(nextProps.accountId, { withReplies: nextProps.withReplies })) this.props.dispatch(expandAccountTimeline(nextProps.accountId, { commentsOnly: nextProps.commentsOnly }))
} }
} }
@ -72,7 +74,7 @@ class AccountTimeline extends ImmutablePureComponent {
if (this.props.accountId && this.props.accountId !== -1) { if (this.props.accountId && this.props.accountId !== -1) {
this.props.dispatch(expandAccountTimeline(this.props.accountId, { this.props.dispatch(expandAccountTimeline(this.props.accountId, {
maxId, maxId,
withReplies: this.props.withReplies commentsOnly: this.props.commentsOnly
})) }))
} }
} }
@ -89,6 +91,8 @@ class AccountTimeline extends ImmutablePureComponent {
if (!account) return null if (!account) return null
console.log("statusIds:", statusIds)
return ( return (
<StatusList <StatusList
scrollKey='account_timeline' scrollKey='account_timeline'

View File

@ -193,8 +193,8 @@ class ComposeForm extends ImmutablePureComponent {
selectionStart = selectionEnd; selectionStart = selectionEnd;
} }
this.autosuggestTextarea.textbox.setSelectionRange(selectionStart, selectionEnd); // this.autosuggestTextarea.textbox.setSelectionRange(selectionStart, selectionEnd);
this.autosuggestTextarea.textbox.focus(); // this.autosuggestTextarea.textbox.focus();
} }
} }
@ -332,6 +332,14 @@ class ComposeForm extends ImmutablePureComponent {
</div> </div>
} }
{ /*
(isUploading || hasGif) &&
<div className={[_s.default, _s.px15].join(' ')}>
<UploadForm replyToId={replyToId} />
</div>
*/
}
{ {
!edit && hasPoll && !edit && hasPoll &&
<div className={[_s.default, _s.px15].join(' ')}> <div className={[_s.default, _s.px15].join(' ')}>

View File

@ -0,0 +1,39 @@
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ProgressBar from '../../../../components/progress_bar'
import Upload from '../media_upload_item'
import SensitiveMediaButton from '../sensitive_media_button'
const mapStateToProps = (state) => ({
mediaIds: state.getIn(['compose', 'media_attachments']).map(item => item.get('id')),
isUploading: state.getIn(['compose', 'is_uploading']),
uploadProgress: state.getIn(['compose', 'progress']),
});
export default
@connect(mapStateToProps)
class GifForm extends ImmutablePureComponent {
static propTypes = {
mediaIds: ImmutablePropTypes.list.isRequired,
isUploading: PropTypes.bool,
uploadProgress: PropTypes.number,
};
render () {
const {
mediaIds,
isUploading,
uploadProgress,
} = this.props
return (
<div className={_s.default}>
<div className={[_s.default, _s.flexRow, _s.flexWrap].join(' ')}>
<Upload id={id} key={id} />
</div>
</div>
)
}
}

View File

@ -1,8 +1,8 @@
import ImmutablePropTypes from 'react-immutable-proptypes' import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component' import ImmutablePureComponent from 'react-immutable-pure-component'
import ProgressBar from '../../../../components/progress_bar' import ProgressBar from '../../../components/progress_bar'
import Upload from '../media_upload_item' import Upload from './media_upload_item'
import SensitiveMediaButton from '../sensitive_media_button' import SensitiveMediaButton from './sensitive_media_button'
const mapStateToProps = (state) => ({ const mapStateToProps = (state) => ({
mediaIds: state.getIn(['compose', 'media_attachments']).map(item => item.get('id')), mediaIds: state.getIn(['compose', 'media_attachments']).map(item => item.get('id')),

View File

@ -1 +0,0 @@
export { default } from './upload_form'

View File

@ -1,10 +0,0 @@
.compose-form-upload-wrapper {
overflow: hidden;
}
.compose-form-uploads-wrapper {
display: flex;
flex-direction: row;
padding: 5px;
flex-wrap: wrap;
}

View File

@ -111,7 +111,6 @@ const makeMapStateToProps = () => {
ancestorsIds, ancestorsIds,
descendantsIds, descendantsIds,
askReplyConfirmation: state.getIn(['compose', 'text']).trim().length !== 0, askReplyConfirmation: state.getIn(['compose', 'text']).trim().length !== 0,
domain: state.getIn(['meta', 'domain']),
}; };
}; };
@ -135,7 +134,7 @@ class Status extends ImmutablePureComponent {
descendantsIds: ImmutablePropTypes.list, descendantsIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
askReplyConfirmation: PropTypes.bool, askReplyConfirmation: PropTypes.bool,
domain: PropTypes.string.isRequired, contextType: PropTypes.string,
}; };
state = { state = {
@ -412,7 +411,8 @@ class Status extends ImmutablePureComponent {
ancestorsIds, ancestorsIds,
descendantsIds, descendantsIds,
intl, intl,
domain contextType,
commentsLimited,
} = this.props } = this.props
let ancestors, descendants let ancestors, descendants
@ -441,11 +441,13 @@ class Status extends ImmutablePureComponent {
toggleSensitive: this.handleHotkeyToggleSensitive, toggleSensitive: this.handleHotkeyToggleSensitive,
}; };
console.log("descendantsIds.size > 0:", descendantsIds.size > 0)
return ( return (
<div ref={this.setRef} className={_s.mb15}> <div ref={this.setRef} className={_s.mb15}>
<Block> <Block>
{ {
/* ancestors */ /* : todo : ancestors if is comment */
} }
<HotKeys handlers={handlers}> <HotKeys handlers={handlers}>
@ -453,13 +455,10 @@ class Status extends ImmutablePureComponent {
<StatusContainer <StatusContainer
id={status.get('id')} id={status.get('id')}
contextType={'timelineId'} contextType={contextType}
showThread
borderless={descendantsIds && descendantsIds.size > 0}
// onOpenVideo={this.handleOpenVideo} // onOpenVideo={this.handleOpenVideo}
// onOpenMedia={this.handleOpenMedia} // onOpenMedia={this.handleOpenMedia}
// onToggleHidden={this.handleToggleHidden} // onToggleHidden={this.handleToggleHidden}
// domain={domain}
// showMedia={this.state.showMedia} // showMedia={this.state.showMedia}
// onToggleMediaVisibility={this.handleToggleMediaVisibility} // onToggleMediaVisibility={this.handleToggleMediaVisibility}
/> />
@ -472,7 +471,10 @@ class Status extends ImmutablePureComponent {
<div className={[_s.default, _s.mr10, _s.ml10, _s.mb10, _s.borderColorSecondary, _s.borderBottom1PX].join(' ')}/> <div className={[_s.default, _s.mr10, _s.ml10, _s.mb10, _s.borderColorSecondary, _s.borderBottom1PX].join(' ')}/>
} }
<CommentList descendants={descendantsIds} /> <CommentList
commentsLimited={commentsLimited}
descendants={descendantsIds}
/>
</Block> </Block>
</div> </div>
) )

View File

@ -26,13 +26,12 @@ export default class ProfileLayout extends ImmutablePureComponent {
noHeader noHeader
noComposeButton noComposeButton
> >
<div className={[_s.default, _s.width1015PX, _s.flexRow, _s.justifyContentSpaceBetween, _s.pr15].join(' ')}>
<div className={[_s.default, _s.width1015PX, _s.flexRow, _s.justifyContentSpaceBetween, _s.pl15, _s.py15].join(' ')}>
<div className={[_s.default, _s.z1, _s.width100PC].join(' ')}> <div className={[_s.default, _s.z1, _s.width100PC].join(' ')}>
<ProfileHeader account={account} /> <ProfileHeader account={account} />
<div className={[_s.default, _s.width1015PX, _s.flexRow, _s.justifyContentSpaceBetween, _s.pr15, _s.py15].join(' ')}> <div className={[_s.default, _s.width100PC, _s.flexRow, _s.justifyContentSpaceBetween, _s.py15].join(' ')}>
<div className={[_s.default, _s.width645PX, _s.z1].join(' ')}> <div className={[_s.default, _s.width645PX, _s.z1].join(' ')}>
<div className={_s.default}> <div className={_s.default}>
{children} {children}
@ -40,7 +39,7 @@ export default class ProfileLayout extends ImmutablePureComponent {
</div> </div>
<div className={[_s.default, _s.width340PX].join(' ')}> <div className={[_s.default, _s.width340PX].join(' ')}>
<Sticky top={15} enabled> <Sticky top={63} enabled>
<div className={[_s.default, _s.width340PX].join(' ')}> <div className={[_s.default, _s.width340PX].join(' ')}>
{layout} {layout}
</div> </div>

View File

@ -1,5 +1,6 @@
import { Fragment } from 'react' import { Fragment } from 'react'
import { defineMessages, injectIntl } from 'react-intl' import { defineMessages, injectIntl } from 'react-intl'
import queryString from 'query-string'
import { setFilter } from '../actions/notifications' import { setFilter } from '../actions/notifications'
import PageTitle from '../features/ui/util/page_title' import PageTitle from '../features/ui/util/page_title'
import LinkFooter from '../components/link_footer' import LinkFooter from '../components/link_footer'
@ -8,13 +9,22 @@ import NotificationFilterPanel from '../components/panel/notification_filter_pan
import TrendsPanel from '../components/panel/trends_panel' import TrendsPanel from '../components/panel/trends_panel'
import DefaultLayout from '../layouts/default_layout' import DefaultLayout from '../layouts/default_layout'
const filters = [
'all',
'mention',
'favourite',
'reblog',
'poll',
'follow',
]
const messages = defineMessages({ const messages = defineMessages({
notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' }, notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' },
mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' }, mention: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
likes: { id: 'likes', defaultMessage: 'Likes' }, favourite: { id: 'likes', defaultMessage: 'Likes' },
reposts: { id: 'reposts', defaultMessage: 'Reposts' }, reblog: { id: 'reposts', defaultMessage: 'Reposts' },
polls: { id: 'polls', defaultMessage: 'Poll' }, poll: { id: 'polls', defaultMessage: 'Poll' },
follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' }, follow: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
all: { id: 'notifications.filter.all', defaultMessage: 'All' }, all: { id: 'notifications.filter.all', defaultMessage: 'All' },
}) })
@ -39,16 +49,31 @@ class NotificationsPage extends PureComponent {
} }
static propTypes = { static propTypes = {
setFilter: PropTypes.func.isRequired, children: PropTypes.node.isRequired,
selectedFilter: PropTypes.string.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
notificationCount: PropTypes.number.isRequired, notificationCount: PropTypes.number.isRequired,
setFilter: PropTypes.func.isRequired,
selectedFilter: PropTypes.string.isRequired,
} }
// : todo : on pop change filter active type componentDidMount() {
this.checkForQueryStringChange(this.context.router.route.location)
}
onClick(notificationType) { checkForQueryStringChange = (location) => {
this.props.setFilter('active', notificationType) try {
const qp = queryString.parse(location.search)
const view = `${qp.view}`.toLowerCase()
if (filters.indexOf(view) > -1) {
this.onChangeActiveFilter(view)
}
} catch (error) {
//
}
}
onChangeActiveFilter(notificationType) {
this.props.setFilter(notificationType)
if (notificationType === 'all') { if (notificationType === 'all') {
this.context.router.history.push('/notifications') this.context.router.history.push('/notifications')
@ -59,44 +84,17 @@ class NotificationsPage extends PureComponent {
render() { render() {
const { const {
intl,
children, children,
intl,
notificationCount,
selectedFilter, selectedFilter,
notificationCount
} = this.props } = this.props
const tabs = [ const tabs = filters.map((filter) => ({
{ title: intl.formatMessage(messages[filter]),
title: intl.formatMessage(messages.all), onClick: () => this.onChangeActiveFilter(filter),
onClick: () => this.onClick('all'), active: selectedFilter === filter,
active: selectedFilter === 'all', }))
},
{
title: intl.formatMessage(messages.mentions),
onClick: () => this.onClick('mention'),
active: selectedFilter === 'mention',
},
{
title: intl.formatMessage(messages.likes),
onClick: () => this.onClick('favourite'),
active: selectedFilter === 'favourite',
},
{
title: intl.formatMessage(messages.reposts),
onClick: () => this.onClick('reblog'),
active: selectedFilter === 'reblog',
},
{
title: intl.formatMessage(messages.polls),
onClick: () => this.onClick('poll'),
active: selectedFilter === 'poll',
},
{
title: intl.formatMessage(messages.follows),
onClick: () => this.onClick('follow'),
active: selectedFilter === 'follow',
},
]
return ( return (
<DefaultLayout <DefaultLayout

View File

@ -61,7 +61,8 @@ class ProfilePage extends ImmutablePureComponent {
params: { username }, params: { username },
} = this.props } = this.props
const title = !!account ? account.get('display_name') : username const name = !!account ? account.get('display_name_html') : ''
console.log("name:", name, account)
return ( return (
<ProfileLayout <ProfileLayout
@ -75,7 +76,7 @@ class ProfilePage extends ImmutablePureComponent {
</Fragment> </Fragment>
)} )}
> >
<PageTitle path={title} /> <PageTitle path={`${name} (@${username})`} />
{ {
!account && <ColumnIndicator type='loading' /> !account && <ColumnIndicator type='loading' />
} }

View File

@ -224,6 +224,7 @@ export default function notifications(state = initialState, action) {
case NOTIFICATIONS_FILTER_SET: case NOTIFICATIONS_FILTER_SET:
return state.withMutations(mutable => { return state.withMutations(mutable => {
mutable.set('items', ImmutableList()).set('hasMore', true) mutable.set('items', ImmutableList()).set('hasMore', true)
console.log("NOTIFICATIONS_FILTER_SET:", action.path, action.value)
mutable.setIn(['filter', action.path], action.value) mutable.setIn(['filter', action.path], action.value)
}) })
case NOTIFICATIONS_SCROLL_TOP: case NOTIFICATIONS_SCROLL_TOP:

View File

@ -16,6 +16,7 @@ const initialState = ImmutableMap({
results: [], results: [],
chosenUrl: '', chosenUrl: '',
searchText: '', searchText: '',
next: 0,
loading: false, loading: false,
error: false, error: false,
}) })
@ -27,7 +28,8 @@ export default function (state = initialState, action) {
return state.set('loading', true) return state.set('loading', true)
case GIF_RESULTS_FETCH_SUCCESS: case GIF_RESULTS_FETCH_SUCCESS:
return state.withMutations(map => { return state.withMutations(map => {
map.set('results', action.results); map.set('results', action.data.results);
map.set('next', action.data.next);
map.set('error', false); map.set('error', false);
map.set('loading', false); map.set('loading', false);
}); });

View File

@ -356,6 +356,11 @@ body {
background-color: #DE2960; background-color: #DE2960;
} }
.backgroundColorDangerDark_onHover:hover {
background-color: #c72c5b;
}
.colorPrimary { .colorPrimary {
color: #2d3436; color: #2d3436;
} }

View File

@ -41,19 +41,28 @@ class Notification < ApplicationRecord
validates :account_id, uniqueness: { scope: [:activity_type, :activity_id] } validates :account_id, uniqueness: { scope: [:activity_type, :activity_id] }
validates :activity_type, inclusion: { in: TYPE_CLASS_MAP.values } validates :activity_type, inclusion: { in: TYPE_CLASS_MAP.values }
scope :browserable, ->(exclude_types = [], account_id = nil) { scope :browserable, ->(exclude_types = [], account_id = nil, only_verified = false, only_following = false) {
types = TYPE_CLASS_MAP.values - activity_types_from_types(exclude_types + [:follow_request]) types = TYPE_CLASS_MAP.values - activity_types_from_types(exclude_types + [:follow_request])
# if account_id.nil?
puts "-----VERTS------" # Notification.includes(:from_account).where(activity_type: types, accounts: {
Notification.includes(:from_account).where(activity_type: types, accounts: { # is_verified: true
is_verified: true # })
})
# joins(:account).where({ 'from_account.id' => 6 }) theOptions = { :activity_type => types }
# is_verified: false
# ) if !account_id.nil?
# els theOptions.from_account_id = account_id
# where(activity_type: types, from_account_id: account_id) end
# end
if only_verified
theOptions[:accounts] = {
:is_verified => true
}
Notification.includes(:from_account).where(theOptions)
else
where(theOptions)
end
} }
cache_associated :from_account, status: STATUS_INCLUDES, mention: [status: STATUS_INCLUDES], favourite: [:account, status: STATUS_INCLUDES], follow: :account, poll: [status: STATUS_INCLUDES] cache_associated :from_account, status: STATUS_INCLUDES, mention: [status: STATUS_INCLUDES], favourite: [:account, status: STATUS_INCLUDES], follow: :account, poll: [status: STATUS_INCLUDES]

View File

@ -87,7 +87,8 @@ class Status < ApplicationRecord
scope :remote, -> { where(local: false).or(where.not(uri: nil)) } scope :remote, -> { where(local: false).or(where.not(uri: nil)) }
scope :local, -> { where(local: true).or(where(uri: nil)) } scope :local, -> { where(local: true).or(where(uri: nil)) }
scope :without_replies, -> { where('statuses.reply = FALSE OR statuses.in_reply_to_account_id = statuses.account_id') } scope :only_replies, -> { where('statuses.reply = TRUE') }
scope :without_replies, -> { where('statuses.reply = FALSE') }
scope :without_reblogs, -> { where('statuses.reblog_of_id IS NULL') } scope :without_reblogs, -> { where('statuses.reblog_of_id IS NULL') }
scope :with_public_visibility, -> { where(visibility: :public) } scope :with_public_visibility, -> { where(visibility: :public) }
scope :tagged_with, ->(tag) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag }) } scope :tagged_with, ->(tag) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag }) }

View File

@ -329,12 +329,16 @@ Rails.application.routes.draw do
resources :gab_trends, only: [:index] resources :gab_trends, only: [:index]
resources :streaming, only: [:index] resources :streaming, only: [:index]
resources :custom_emojis, only: [:index] resources :custom_emojis, only: [:index]
resources :gifs, only: [:index]
resources :suggestions, only: [:index, :destroy] resources :suggestions, only: [:index, :destroy]
resources :scheduled_statuses, only: [:index, :show, :update, :destroy] resources :scheduled_statuses, only: [:index, :show, :update, :destroy]
resources :preferences, only: [:index] resources :preferences, only: [:index]
resources :trends, only: [:index] resources :trends, only: [:index]
namespace :gifs do
get :categories
get :search
end
resources :conversations, only: [:index, :destroy] do resources :conversations, only: [:index, :destroy] do
member do member do
post :read post :read
@ -462,6 +466,7 @@ Rails.application.routes.draw do
get '/tags/:tag', to: 'react#react' get '/tags/:tag', to: 'react#react'
get '/:username/with_replies', to: 'accounts#show', username: username_regex, as: :short_account_with_replies get '/:username/with_replies', to: 'accounts#show', username: username_regex, as: :short_account_with_replies
get '/:username/comments_only', to: 'accounts#show', username: username_regex, as: :short_account_comments_only
get '/:username/media', to: 'accounts#show', username: username_regex, as: :short_account_media get '/:username/media', to: 'accounts#show', username: username_regex, as: :short_account_media
get '/:username/tagged/:tag', to: 'accounts#show', username: username_regex, as: :short_account_tag get '/:username/tagged/:tag', to: 'accounts#show', username: username_regex, as: :short_account_tag
get '/:username/posts/:statusId/reblogs', to: 'statuses#show', username: username_regex get '/:username/posts/:statusId/reblogs', to: 'statuses#show', username: username_regex