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!(only_media_scope) if media_requested?
statuses.merge!(no_replies_scope) unless replies_requested?
statuses.merge!(only_replies_scope) unless comments_only_requested?
end
end
@ -68,6 +69,10 @@ class AccountsController < ReactController
Status.without_replies
end
def only_replies_scope
Status.only_replies
end
def hashtag_scope
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)
elsif replies_requested?
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
short_account_url(@account, max_id: max_id, min_id: min_id)
end
@ -110,6 +117,10 @@ class AccountsController < ReactController
request.path.ends_with?('/with_replies')
end
def comments_only_requested?
request.path.ends_with?('/comments_only')
end
def tag_requested?
request.path.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
end

View File

@ -3,6 +3,7 @@
class Api::V1::AccountByUsernameController < Api::BaseController
before_action :set_account
before_action :check_account_suspension
before_action :check_account_local
respond_to :json
@ -17,4 +18,9 @@ class Api::V1::AccountByUsernameController < Api::BaseController
def check_account_suspension
gone if @account.suspended?
end
# if not our domain don't display
def check_account_local
gone unless @account.local?
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!(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!(hashtag_scope) if params[:tagged].present?
@ -64,6 +65,10 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
Status.without_replies
end
def only_replies_scope
Status.only_replies
end
def no_reblogs_scope
Status.without_reblogs
end
@ -79,7 +84,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
end
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
def insert_pagination_headers

View File

@ -1,14 +1,31 @@
# frozen_string_literal: true
class Api::V1::GifsController < Api::BaseController
before_action :require_user!
respond_to :json
skip_before_action :set_cache_headers
def index
def categories
uri = URI('https://api.tenor.com/v1/categories')
params = { :key => "QHFJ0C5EWGBH" }
uri.query = URI.encode_www_form(params)
theOptions = { :key => "QHFJ0C5EWGBH" }
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)
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! :write, :'write:notifications' }, only: [:clear, :dismiss, :mark_read]
before_action :require_user!
before_action :set_filter_params
after_action :insert_pagination_headers, only: :index
respond_to :json
@ -49,7 +50,7 @@ class Api::V1::NotificationsController < Api::BaseController
end
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
def target_statuses_from_notifications
@ -86,6 +87,13 @@ class Api::V1::NotificationsController < Api::BaseController
val
end
def set_filter_params
params.permit(
:only_verified,
:only_following
)
end
def from_account
params[:account_id]
end

View File

@ -100,12 +100,11 @@ function getFromDB(dispatch, getState, index, id) {
export function fetchAccount(id) {
return (dispatch, getState) => {
dispatch(fetchRelationships([id]));
if (id === -1 || getState().getIn(['accounts', id], null) !== null) {
return;
}
dispatch(fetchRelationships([id]));
dispatch(fetchAccountRequest(id));
openDB().then(db => getFromDB(
@ -128,8 +127,13 @@ export function fetchAccount(id) {
export function fetchAccountByUsername(username) {
return (dispatch, getState) => {
if (!username) {
return;
}
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(() => {
dispatch(fetchAccountSuccess());
}).catch(error => {

View File

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

View File

@ -21,7 +21,7 @@ export const fetchGifCategories = () => {
dispatch(fetchGifCategoriesRequest())
api(getState).get('/api/v1/gifs').then(response => {
api(getState).get('/api/v1/gifs/categories').then(response => {
dispatch(fetchGifCategoriesSuccess(response.data.tags))
}).catch(function (error) {
dispatch(fetchGifCategoriesFail(error))
@ -29,24 +29,25 @@ export const fetchGifCategories = () => {
}
}
export const fetchGifResults = (maxId) => {
export const fetchGifResults = (expand) => {
return function (dispatch, getState) {
if (!me) return
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}`)
.then((response) => {
console.log('response:', response)
dispatch(fetchGifResultsSuccess(response.data.results))
api(getState).get('/api/v1/gifs/search', { search, pos }).then((response) => {
console.log("response.data:", response.data)
dispatch(fetchGifResultsSuccess(response.data))
}).catch(function (error) {
dispatch(fetchGifResultsFail(error))
})
}
}
export const clearGifResults = () => ({
type: GIFS_CLEAR_RESULTS,
})
@ -69,10 +70,10 @@ function fetchGifResultsRequest() {
}
}
function fetchGifResultsSuccess(results) {
function fetchGifResultsSuccess(data) {
return {
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 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 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);

View File

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

View File

@ -52,7 +52,7 @@ class Comment extends ImmutablePureComponent {
// : todo : add media
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, _s.flexRow].join(' ')}>

View File

@ -1,20 +1,30 @@
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import Button from './button'
import Comment from './comment'
import Text from './text'
export default class CommentList extends ImmutablePureComponent {
static propTypes = {
commentsLimited: PropTypes.bool,
descendants: ImmutablePropTypes.list,
}
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 (
<div>
{
descendants.map((descendant, i) => (
descendants.slice(0, max).map((descendant, i) => (
<Comment
key={`comment-${descendant.get('statusId')}-${i}`}
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>
)
}

View File

@ -33,6 +33,7 @@ class LoadMore extends PureComponent {
const { disabled, visible, gap, intl } = this.props
return (
<div className={[_s.default, _s.py10, _s.px10].join(' ')}>
<Button
block
radiusSmall
@ -56,6 +57,7 @@ class LoadMore extends PureComponent {
</Text>
}
</Button>
</div>
)
}

View File

@ -99,7 +99,7 @@ class GifPickerModal extends PureComponent {
}
handleSelectGifResult = (resultId) => {
console.log("handleSelectGifResult:", resultId)
}
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 ImmutablePureComponent from 'react-immutable-pure-component'
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'
import Sticky from 'react-stickynode'
import classNames from 'classnames/bind'
import {
followAccount,
@ -26,9 +26,11 @@ const cx = classNames.bind(_s)
const messages = defineMessages({
follow: { id: 'follow', defaultMessage: 'Follow' },
following: { id: 'following', defaultMessage: 'Following' },
unfollow: { id: 'unfollow', defaultMessage: 'Unfollow' },
requested: { id: 'requested', defaultMessage: 'Requested' },
unblock: { id: 'unblock', defaultMessage: 'Unblock' },
blocked: { id: 'account.blocked', defaultMessage: 'Blocked' },
followers: { id: 'account.followers', defaultMessage: 'Followers' },
follows: { id: 'account.follows', defaultMessage: 'Follows' },
profile: { id: 'account.profile', defaultMessage: 'Profile' },
@ -92,6 +94,10 @@ class ProfileHeader extends ImmutablePureComponent {
openProfileOptionsPopover: PropTypes.func.isRequired,
}
state = {
stickied: false,
}
handleOpenMore = () => {
const { openProfileOptionsPopover, account } = this.props
openProfileOptionsPopover({
@ -110,7 +116,7 @@ class ProfileHeader extends ImmutablePureComponent {
}
// : todo :
makeInfo() {
makeInfo = () => {
const { account, intl } = this.props
const info = []
@ -132,12 +138,24 @@ class ProfileHeader extends ImmutablePureComponent {
return info
}
onStickyStateChange = (status) => {
switch (status.status) {
case Sticky.STATUS_FIXED:
this.setState({ stickied: true })
break;
default:
this.setState({ stickied: false })
break;
}
}
setOpenMoreNodeRef = (n) => {
this.openMoreNode = n
}
render() {
const { account, intl } = this.props
const { stickied } = this.state
const tabs = !account ? null : [
{
@ -160,6 +178,60 @@ class ProfileHeader extends ImmutablePureComponent {
const headerSrc = !!account ? account.get('header') : ''
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({
circle: 1,
@ -168,78 +240,18 @@ class ProfileHeader extends ImmutablePureComponent {
border2PX: 1,
})
const avatarSize = headerMissing ? '75' : '150'
const stickyBarContainerClasses = cx({
default: 1,
flexRow: 1,
px15: 1,
alignItemsCenter: 1,
displayNone: !stickied,
})
let buttonText = ''
let buttonOptions = {}
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)
const tabBarContainerClasses = cx({
default: 1,
displayNone: stickied,
})
// : 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.flexRow, _s.px15].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.px15, _s.mb5].join(' ')}>
<div className={avatarContainerClasses}>
<Avatar size={avatarSize} account={account} />
</div>
@ -271,14 +283,27 @@ class ProfileHeader extends ImmutablePureComponent {
account && account.get('locked') &&
<Icon id='lock-filled' height='14px' width='14px' className={[_s.mt10, _s.ml10].join(' ')} />
}
{
/* : todo :
account.getIn(['relationship', 'muting'])
*/
}
</div>
</div>
<div className={[_s.default, _s.flexRow, _s.borderBottom1PX, _s.borderColorSecondary, _s.mt5, _s.height53PX].join(' ')}>
<div className={[_s.default].join(' ')}>
<Sticky enabled onStateChange={this.onStickyStateChange}>
<div className={[_s.default, _s.flexRow, _s.backgroundColorSecondary3, _s.borderBottom1PX, _s.borderColorSecondary, _s.height53PX].join(' ')}>
<div className={tabBarContainerClasses}>
<TabBar tabs={tabs} large />
</div>
<div className={stickyBarContainerClasses}>
<Avatar size={36} account={account} />
<div className={[_s.default, _s.ml10].join(' ')}>
<DisplayName account={account} noUsername large />
</div>
</div>
{
account && account.get('id') === me &&
<div className={[_s.default, _s.flexRow, _s.marginLeftAuto, _s.py5].join(' ')}>
@ -333,25 +358,28 @@ class ProfileHeader extends ImmutablePureComponent {
<input type='hidden' value={account.get('username')} name='username' />
</form>
{
!!buttonText &&
<Button
{...buttonOptions}
narrow
className={[_s.justifyContentCenter, _s.alignItemsCenter].join(' ')}
>
<span className={[_s.px15].join(' ')}>
<Text
color='inherit'
weight='bold'
size='medium'
className={[_s.px15].join(' ')}
className={_s.px10}
>
{buttonText}
</Text>
</span>
</Button>
}
</div>
}
</div>
</Sticky>
</div>
</div>
)

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@ -193,8 +193,8 @@ class ComposeForm extends ImmutablePureComponent {
selectionStart = selectionEnd;
}
this.autosuggestTextarea.textbox.setSelectionRange(selectionStart, selectionEnd);
this.autosuggestTextarea.textbox.focus();
// this.autosuggestTextarea.textbox.setSelectionRange(selectionStart, selectionEnd);
// this.autosuggestTextarea.textbox.focus();
}
}
@ -332,6 +332,14 @@ class ComposeForm extends ImmutablePureComponent {
</div>
}
{ /*
(isUploading || hasGif) &&
<div className={[_s.default, _s.px15].join(' ')}>
<UploadForm replyToId={replyToId} />
</div>
*/
}
{
!edit && hasPoll &&
<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 ImmutablePureComponent from 'react-immutable-pure-component'
import ProgressBar from '../../../../components/progress_bar'
import Upload from '../media_upload_item'
import SensitiveMediaButton from '../sensitive_media_button'
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')),

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

View File

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

View File

@ -1,5 +1,6 @@
import { Fragment } from 'react'
import { defineMessages, injectIntl } from 'react-intl'
import queryString from 'query-string'
import { setFilter } from '../actions/notifications'
import PageTitle from '../features/ui/util/page_title'
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 DefaultLayout from '../layouts/default_layout'
const filters = [
'all',
'mention',
'favourite',
'reblog',
'poll',
'follow',
]
const messages = defineMessages({
notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' },
mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
likes: { id: 'likes', defaultMessage: 'Likes' },
reposts: { id: 'reposts', defaultMessage: 'Reposts' },
polls: { id: 'polls', defaultMessage: 'Poll' },
follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
mention: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
favourite: { id: 'likes', defaultMessage: 'Likes' },
reblog: { id: 'reposts', defaultMessage: 'Reposts' },
poll: { id: 'polls', defaultMessage: 'Poll' },
follow: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
all: { id: 'notifications.filter.all', defaultMessage: 'All' },
})
@ -39,16 +49,31 @@ class NotificationsPage extends PureComponent {
}
static propTypes = {
setFilter: PropTypes.func.isRequired,
selectedFilter: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
intl: PropTypes.object.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) {
this.props.setFilter('active', notificationType)
checkForQueryStringChange = (location) => {
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') {
this.context.router.history.push('/notifications')
@ -59,44 +84,17 @@ class NotificationsPage extends PureComponent {
render() {
const {
intl,
children,
intl,
notificationCount,
selectedFilter,
notificationCount
} = this.props
const tabs = [
{
title: intl.formatMessage(messages.all),
onClick: () => this.onClick('all'),
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',
},
]
const tabs = filters.map((filter) => ({
title: intl.formatMessage(messages[filter]),
onClick: () => this.onChangeActiveFilter(filter),
active: selectedFilter === filter,
}))
return (
<DefaultLayout

View File

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

View File

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

View File

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

View File

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

View File

@ -41,19 +41,28 @@ class Notification < ApplicationRecord
validates :account_id, uniqueness: { scope: [:activity_type, :activity_id] }
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])
# if account_id.nil?
puts "-----VERTS------"
Notification.includes(:from_account).where(activity_type: types, accounts: {
is_verified: true
})
# joins(:account).where({ 'from_account.id' => 6 })
# is_verified: false
# )
# els
# where(activity_type: types, from_account_id: account_id)
# end
# Notification.includes(:from_account).where(activity_type: types, accounts: {
# is_verified: true
# })
theOptions = { :activity_type => types }
if !account_id.nil?
theOptions.from_account_id = account_id
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]

View File

@ -87,7 +87,8 @@ class Status < ApplicationRecord
scope :remote, -> { where(local: false).or(where.not(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 :with_public_visibility, -> { where(visibility: :public) }
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 :streaming, only: [:index]
resources :custom_emojis, only: [:index]
resources :gifs, only: [:index]
resources :suggestions, only: [:index, :destroy]
resources :scheduled_statuses, only: [:index, :show, :update, :destroy]
resources :preferences, only: [:index]
resources :trends, only: [:index]
namespace :gifs do
get :categories
get :search
end
resources :conversations, only: [:index, :destroy] do
member do
post :read
@ -462,6 +466,7 @@ Rails.application.routes.draw do
get '/tags/:tag', to: 'react#react'
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/tagged/:tag', to: 'accounts#show', username: username_regex, as: :short_account_tag
get '/:username/posts/:statusId/reblogs', to: 'statuses#show', username: username_regex