Progress
This commit is contained in:
parent
d79133c72d
commit
416bc3d603
15
app/controllers/api/v1/gab_trends_controller.rb
Normal file
15
app/controllers/api/v1/gab_trends_controller.rb
Normal file
@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::GabTrendsController < Api::BaseController
|
||||
respond_to :json
|
||||
|
||||
skip_before_action :set_cache_headers
|
||||
|
||||
def index
|
||||
uri = URI('https://trends.gab.com/trend-feed/json')
|
||||
uri.query = URI.encode_www_form()
|
||||
|
||||
res = Net::HTTP.get_response(uri)
|
||||
render json: res.body if res.is_a?(Net::HTTPSuccess)
|
||||
end
|
||||
end
|
@ -1,220 +0,0 @@
|
||||
import { importFetchedStatus, importFetchedStatuses } from './importer';
|
||||
import api, { getLinks } from '../api';
|
||||
import { Map as ImmutableMap, List as ImmutableList, toJS } from 'immutable';
|
||||
|
||||
export const TIMELINE_UPDATE = 'TIMELINE_UPDATE';
|
||||
export const TIMELINE_DELETE = 'TIMELINE_DELETE';
|
||||
export const TIMELINE_CLEAR = 'TIMELINE_CLEAR';
|
||||
export const TIMELINE_UPDATE_QUEUE = 'TIMELINE_UPDATE_QUEUE';
|
||||
export const TIMELINE_DEQUEUE = 'TIMELINE_DEQUEUE';
|
||||
export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP';
|
||||
|
||||
export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST';
|
||||
export const TIMELINE_EXPAND_SUCCESS = 'TIMELINE_EXPAND_SUCCESS';
|
||||
export const TIMELINE_EXPAND_FAIL = 'TIMELINE_EXPAND_FAIL';
|
||||
|
||||
export const TIMELINE_CONNECT = 'TIMELINE_CONNECT';
|
||||
export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
|
||||
|
||||
export const MAX_QUEUED_ITEMS = 40;
|
||||
|
||||
export function updateTimeline(timeline, status, accept) {
|
||||
return dispatch => {
|
||||
if (typeof accept === 'function' && !accept(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(importFetchedStatus(status));
|
||||
|
||||
dispatch({
|
||||
type: TIMELINE_UPDATE,
|
||||
timeline,
|
||||
status,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export function updateTimelineQueue(timeline, status, accept) {
|
||||
return dispatch => {
|
||||
if (typeof accept === 'function' && !accept(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: TIMELINE_UPDATE_QUEUE,
|
||||
timeline,
|
||||
status,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export function dequeueTimeline(timeline, expandFunc, optionalExpandArgs) {
|
||||
return (dispatch, getState) => {
|
||||
const queuedItems = getState().getIn(['timelines', timeline, 'queuedItems'], ImmutableList());
|
||||
const totalQueuedItemsCount = getState().getIn(['timelines', timeline, 'totalQueuedItemsCount'], 0);
|
||||
|
||||
let shouldDispatchDequeue = true;
|
||||
|
||||
if (totalQueuedItemsCount == 0) {
|
||||
return;
|
||||
}
|
||||
else if (totalQueuedItemsCount > 0 && totalQueuedItemsCount <= MAX_QUEUED_ITEMS) {
|
||||
queuedItems.forEach(status => {
|
||||
dispatch(updateTimeline(timeline, status.toJS(), null));
|
||||
});
|
||||
}
|
||||
else {
|
||||
if (typeof expandFunc === 'function') {
|
||||
dispatch(clearTimeline(timeline));
|
||||
expandFunc();
|
||||
}
|
||||
else {
|
||||
if (timeline === 'home') {
|
||||
dispatch(clearTimeline(timeline));
|
||||
dispatch(expandHomeTimeline(optionalExpandArgs));
|
||||
}
|
||||
else if (timeline === 'community') {
|
||||
dispatch(clearTimeline(timeline));
|
||||
dispatch(expandCommunityTimeline(optionalExpandArgs));
|
||||
}
|
||||
else {
|
||||
shouldDispatchDequeue = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldDispatchDequeue) return;
|
||||
|
||||
dispatch({
|
||||
type: TIMELINE_DEQUEUE,
|
||||
timeline,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export function deleteFromTimelines(id) {
|
||||
return (dispatch, getState) => {
|
||||
const accountId = getState().getIn(['statuses', id, 'account']);
|
||||
const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => [status.get('id'), status.get('account')]);
|
||||
const reblogOf = getState().getIn(['statuses', id, 'reblog'], null);
|
||||
|
||||
dispatch({
|
||||
type: TIMELINE_DELETE,
|
||||
id,
|
||||
accountId,
|
||||
references,
|
||||
reblogOf,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export function clearTimeline(timeline) {
|
||||
return (dispatch) => {
|
||||
dispatch({ type: TIMELINE_CLEAR, timeline });
|
||||
};
|
||||
};
|
||||
|
||||
const noOp = () => { };
|
||||
|
||||
const parseTags = (tags = {}, mode) => {
|
||||
return (tags[mode] || []).map((tag) => {
|
||||
return tag.value;
|
||||
});
|
||||
};
|
||||
|
||||
export function expandTimeline(timelineId, path, params = {}, done = noOp) {
|
||||
return (dispatch, getState) => {
|
||||
const timeline = getState().getIn(['timelines', timelineId], ImmutableMap());
|
||||
const isLoadingMore = !!params.max_id;
|
||||
|
||||
if (timeline.get('isLoading')) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!params.max_id && !params.pinned && timeline.get('items', ImmutableList()).size > 0) {
|
||||
params.since_id = timeline.getIn(['items', 0]);
|
||||
}
|
||||
|
||||
const isLoadingRecent = !!params.since_id;
|
||||
|
||||
dispatch(expandTimelineRequest(timelineId, isLoadingMore));
|
||||
|
||||
api(getState).get(path, { params }).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedStatuses(response.data));
|
||||
dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.code === 206, isLoadingRecent, isLoadingMore));
|
||||
done();
|
||||
}).catch(error => {
|
||||
dispatch(expandTimelineFail(timelineId, error, isLoadingMore));
|
||||
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 expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, 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);
|
||||
export const expandGroupTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`group:${id}`, `/api/v1/timelines/group/${id}`, { max_id: maxId }, done);
|
||||
export const expandHashtagTimeline = (hashtag, { maxId, tags } = {}, done = noOp) => {
|
||||
return expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`, {
|
||||
max_id: maxId,
|
||||
any: parseTags(tags, 'any'),
|
||||
all: parseTags(tags, 'all'),
|
||||
none: parseTags(tags, 'none'),
|
||||
}, done);
|
||||
};
|
||||
|
||||
export function expandTimelineRequest(timeline, isLoadingMore) {
|
||||
return {
|
||||
type: TIMELINE_EXPAND_REQUEST,
|
||||
timeline,
|
||||
skipLoading: !isLoadingMore,
|
||||
};
|
||||
};
|
||||
|
||||
export function expandTimelineSuccess(timeline, statuses, next, partial, isLoadingRecent, isLoadingMore) {
|
||||
return {
|
||||
type: TIMELINE_EXPAND_SUCCESS,
|
||||
timeline,
|
||||
statuses,
|
||||
next,
|
||||
partial,
|
||||
isLoadingRecent,
|
||||
skipLoading: !isLoadingMore,
|
||||
};
|
||||
};
|
||||
|
||||
export function expandTimelineFail(timeline, error, isLoadingMore) {
|
||||
return {
|
||||
type: TIMELINE_EXPAND_FAIL,
|
||||
timeline,
|
||||
error,
|
||||
skipLoading: !isLoadingMore,
|
||||
};
|
||||
};
|
||||
|
||||
export function connectTimeline(timeline) {
|
||||
return {
|
||||
type: TIMELINE_CONNECT,
|
||||
timeline,
|
||||
};
|
||||
};
|
||||
|
||||
export function disconnectTimeline(timeline) {
|
||||
return {
|
||||
type: TIMELINE_DISCONNECT,
|
||||
timeline,
|
||||
};
|
||||
};
|
||||
|
||||
export function scrollTopTimeline(timeline, top) {
|
||||
return {
|
||||
type: TIMELINE_SCROLL_TOP,
|
||||
timeline,
|
||||
top,
|
||||
};
|
||||
};
|
26
app/javascript/gabsocial/assets/liked_icon.js
Normal file
26
app/javascript/gabsocial/assets/liked_icon.js
Normal file
@ -0,0 +1,26 @@
|
||||
const LikedIcon = ({
|
||||
className = '',
|
||||
width = '26px',
|
||||
height = '26px',
|
||||
viewBox = '0 0 48 48',
|
||||
title = 'Liked',
|
||||
}) => (
|
||||
<svg
|
||||
className={className}
|
||||
version='1.1'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
x='0px'
|
||||
y='0px'
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox={viewBox}
|
||||
xmlSpace='preserve'
|
||||
aria-label={title}
|
||||
>
|
||||
<g>
|
||||
<path d='M 25.398438 0.214844 C 24.757812 0.488281 24.449219 0.703125 23.945312 1.238281 C 22.875 2.34375 22.414062 3.507812 21.601562 7.117188 C 21.375 8.109375 21.09375 9.140625 20.972656 9.414062 C 20.851562 9.675781 20.382812 10.3125 19.921875 10.828125 C 19.472656 11.34375 18.195312 12.890625 17.101562 14.269531 C 15.992188 15.648438 14.746094 17.136719 14.316406 17.578125 L 13.527344 18.375 L 9.601562 18.375 C 7.445312 18.382812 5.382812 18.421875 5.035156 18.460938 C 3.46875 18.664062 2.210938 19.820312 1.882812 21.355469 C 1.742188 22.011719 1.734375 40.621094 1.875 41.351562 C 2.089844 42.476562 2.933594 43.539062 4.003906 44.042969 L 4.546875 44.296875 L 9.46875 44.34375 C 13.847656 44.390625 14.445312 44.417969 14.90625 44.570312 C 15.1875 44.652344 16.753906 45.167969 18.375 45.703125 C 24.777344 47.804688 25.867188 48 31.246094 48 C 34.367188 48 34.800781 47.980469 35.652344 47.804688 C 37.265625 47.484375 38.644531 46.800781 39.769531 45.757812 C 41.296875 44.34375 42.132812 42.523438 42.253906 40.3125 C 42.289062 39.523438 42.328125 39.375 42.5625 39.039062 C 42.964844 38.464844 43.453125 37.445312 43.679688 36.710938 C 43.941406 35.878906 44.101562 34.433594 44.015625 33.636719 C 43.96875 33.09375 43.988281 32.953125 44.183594 32.558594 C 44.710938 31.527344 44.925781 30.601562 44.980469 29.195312 C 45.007812 28.386719 44.980469 27.683594 44.914062 27.382812 C 44.8125 26.898438 44.8125 26.886719 45.28125 25.941406 C 45.964844 24.5625 46.144531 23.765625 46.152344 22.21875 C 46.164062 21.179688 46.125 20.820312 45.957031 20.222656 C 45.5625 18.835938 44.933594 17.839844 43.742188 16.707031 C 43.117188 16.117188 42.730469 15.84375 42.066406 15.515625 C 40.613281 14.804688 40.492188 14.785156 36.957031 14.738281 C 34.085938 14.710938 33.835938 14.691406 33.871094 14.550781 C 33.898438 14.464844 34.078125 13.996094 34.265625 13.519531 C 34.996094 11.617188 35.242188 9.945312 35.070312 7.902344 C 34.828125 5.070312 34.191406 3.421875 32.804688 2.054688 C 32.316406 1.566406 31.949219 1.320312 31.199219 0.957031 C 29.839844 0.28125 28.960938 0.0742188 27.28125 0.0273438 C 26.015625 -0.0078125 25.882812 0.0078125 25.398438 0.214844 Z M 7.820312 37.039062 C 8.324219 37.179688 8.972656 37.808594 9.101562 38.277344 C 9.320312 39.085938 8.960938 39.898438 8.222656 40.3125 C 7.105469 40.949219 5.738281 40.210938 5.644531 38.914062 C 5.578125 38.035156 6.09375 37.285156 6.9375 37.050781 C 7.378906 36.917969 7.398438 36.917969 7.820312 37.039062 Z M 7.820312 37.039062' />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default LikedIcon
|
@ -38,6 +38,7 @@ export default class Button extends PureComponent {
|
||||
underlineOnHover: PropTypes.bool,
|
||||
radiusSmall: PropTypes.bool,
|
||||
noClasses: PropTypes.bool,
|
||||
buttonRef: PropTypes.func,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -1,47 +0,0 @@
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import Button from './button';
|
||||
|
||||
const messages = defineMessages({
|
||||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
|
||||
});
|
||||
|
||||
export default
|
||||
@injectIntl
|
||||
class Domain extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
domain: PropTypes.string,
|
||||
onUnblockDomain: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handleDomainUnblock = () => {
|
||||
this.props.onUnblockDomain(this.props.domain);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { domain, intl } = this.props;
|
||||
|
||||
return (
|
||||
<div className='domain'>
|
||||
<div className='domain__wrapper'>
|
||||
<span className='domain__name'>
|
||||
<strong>{domain}</strong>
|
||||
</span>
|
||||
|
||||
<div className='domain__buttons'>
|
||||
<Button
|
||||
active
|
||||
icon='unlock'
|
||||
title={intl.formatMessage(messages.unblockDomain, {
|
||||
domain,
|
||||
})}
|
||||
onClick={this.handleDomainUnblock}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -26,6 +26,7 @@ import HomeIcon from '../assets/home_icon'
|
||||
import InvestorIcon from '../assets/investor_icon'
|
||||
import ItalicIcon from '../assets/italic_icon'
|
||||
import LikeIcon from '../assets/like_icon'
|
||||
import LikedIcon from '../assets/liked_icon'
|
||||
import LinkIcon from '../assets/link_icon'
|
||||
import ListIcon from '../assets/list_icon'
|
||||
import ListAddIcon from '../assets/list_add_icon'
|
||||
@ -84,6 +85,7 @@ const ICONS = {
|
||||
'investor': InvestorIcon,
|
||||
'italic': ItalicIcon,
|
||||
'like': LikeIcon,
|
||||
'liked': LikedIcon,
|
||||
'link': LinkIcon,
|
||||
'list': ListIcon,
|
||||
'list-add': ListAddIcon,
|
||||
|
@ -10,14 +10,25 @@ export default class List extends ImmutablePureComponent {
|
||||
scrollKey: PropTypes.string,
|
||||
emptyMessage: PropTypes.any,
|
||||
small: PropTypes.bool,
|
||||
onLoadMore: PropTypes.func,
|
||||
hasMore: PropTypes.bool,
|
||||
}
|
||||
|
||||
render() {
|
||||
const { items, scrollKey, emptyMessage, small } = this.props
|
||||
const {
|
||||
items,
|
||||
scrollKey,
|
||||
emptyMessage,
|
||||
hasMore,
|
||||
small,
|
||||
onLoadMore
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<Block>
|
||||
<ScrollableList
|
||||
onLoadMore={onLoadMore}
|
||||
hasMore={hasMore}
|
||||
scrollKey={scrollKey}
|
||||
emptyMessage={emptyMessage}
|
||||
>
|
||||
|
@ -1,70 +1,50 @@
|
||||
import { injectIntl, defineMessages } from 'react-intl'
|
||||
import { muteAccount } from '../../actions/accounts'
|
||||
import { blockDomain } from '../../actions/domain_blocks'
|
||||
import ConfirmationModal from './confirmation_modal'
|
||||
|
||||
const messages = defineMessages({
|
||||
muteMessage: { id: 'confirmations.mute.message', defaultMessage: 'Are you sure you want to mute {name}?' },
|
||||
blockDomain: { id: 'block_domain', defaultMessage: 'Block {domain}' },
|
||||
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
|
||||
blockDomainMessage: { id: 'confirmations.domain_block.message', defaultMessage: 'Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.' },
|
||||
cancel: { id: 'confirmation_modal.cancel', defaultMessage: 'Cancel' },
|
||||
confirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' },
|
||||
})
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
|
||||
account: state.getIn(['mutes', 'new', 'account']),
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
onConfirm(account, notifications) {
|
||||
dispatch(muteAccount(account.get('id'), notifications))
|
||||
onConfirm(domain) {
|
||||
dispatch(blockDomain(domain))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
@connect(null, mapDispatchToProps)
|
||||
@injectIntl
|
||||
class BlockDomainModal extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
isSubmitting: PropTypes.bool.isRequired,
|
||||
account: PropTypes.object.isRequired,
|
||||
domain: PropTypes.string.isRequired,
|
||||
onConfirm: PropTypes.func.isRequired,
|
||||
onClose: 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()
|
||||
this.props.onConfirm(this.props.domain)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { account, intl } = this.props
|
||||
const { onClose, domain, intl } = this.props
|
||||
|
||||
// dispatch(openModal('CONFIRM', {
|
||||
// message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.' values={{ domain: <strong>{domain}</strong> }} />,
|
||||
// confirm: intl.formatMessage(messages.blockDomainConfirm),
|
||||
// onConfirm: () => dispatch(blockDomain(domain)),
|
||||
// }));
|
||||
console.log("this.props: ", this.props)
|
||||
|
||||
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))
|
||||
}}
|
||||
title={intl.formatMessage(messages.blockDomain, { domain })}
|
||||
message={intl.formatMessage(messages.blockDomainMessage, { domain })}
|
||||
confirm={intl.formatMessage(messages.blockDomainConfirm)}
|
||||
onConfirm={this.handleClick}
|
||||
onClose={onClose}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -216,7 +216,7 @@ class ProfileOptionsPopover extends PureComponent {
|
||||
title: intl.formatMessage(isBlockingDomain ? messages.unblockDomain : messages.blockDomain, {
|
||||
domain,
|
||||
}),
|
||||
onClick: this.handleUnblockDomain
|
||||
onClick: isBlockingDomain ? this.handleUnblockDomain : this.handleBlockDomain,
|
||||
})
|
||||
}
|
||||
|
||||
@ -275,12 +275,14 @@ class ProfileOptionsPopover extends PureComponent {
|
||||
}
|
||||
|
||||
handleBlockDomain = () => {
|
||||
const domain = this.props.account.get('acct').split('@')[1];
|
||||
const domain = this.props.account.get('acct').split('@')[1]
|
||||
|
||||
console.log("handleBlockDomain:", domain)
|
||||
|
||||
// : todo : alert
|
||||
if (!domain) return;
|
||||
if (!domain) return
|
||||
|
||||
this.props.onBlockDomain(domain);
|
||||
this.props.onBlockDomain(domain)
|
||||
}
|
||||
|
||||
handleUnblockDomain = () => {
|
||||
|
@ -27,6 +27,9 @@ const messages = defineMessages({
|
||||
cannot_repost: { id: 'status.cannot_repost', defaultMessage: 'This post cannot be reposted' },
|
||||
cannot_quote: { id: 'status.cannot_quote', defaultMessage: 'This post cannot be quoted' },
|
||||
like: { id: 'status.like', defaultMessage: 'Like' },
|
||||
likesLabel: { id: 'likes.label', defaultMessage: '{number, plural, one {# like} other {# likes}}' },
|
||||
repostsLabel: { id: 'reposts.label', defaultMessage: '{number, plural, one {# repost} other {# reposts}}' },
|
||||
commentsLabel: { id: 'comments.label', defaultMessage: '{number, plural, one {# comment} other {# comments}}' },
|
||||
open: { id: 'status.open', defaultMessage: 'Expand this status' },
|
||||
report: { id: 'status.report', defaultMessage: 'Report @{name}' },
|
||||
muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
|
||||
@ -195,8 +198,9 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
favoriteCount > 0 &&
|
||||
<button className={interactionBtnClasses}>
|
||||
<Text color='secondary' size='small'>
|
||||
{favoriteCount}
|
||||
Likes
|
||||
{formatMessage(messages.likesLabel, {
|
||||
number: favoriteCount,
|
||||
})}
|
||||
</Text>
|
||||
</button>
|
||||
}
|
||||
@ -204,8 +208,9 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
replyCount > 0 &&
|
||||
<button className={interactionBtnClasses}>
|
||||
<Text color='secondary' size='small'>
|
||||
{replyCount}
|
||||
Comments
|
||||
{formatMessage(messages.commentsLabel, {
|
||||
number: replyCount,
|
||||
})}
|
||||
</Text>
|
||||
</button>
|
||||
}
|
||||
@ -213,8 +218,9 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
repostCount > 0 &&
|
||||
<button className={interactionBtnClasses}>
|
||||
<Text color='secondary' size='small'>
|
||||
{repostCount}
|
||||
Reposts
|
||||
{formatMessage(messages.repostsLabel, {
|
||||
number: repostCount,
|
||||
})}
|
||||
</Text>
|
||||
</button>
|
||||
}
|
||||
@ -224,8 +230,8 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
<div className={[_s.default, _s.flexRow, _s.py2, _s.width100PC].join(' ')}>
|
||||
<StatusActionBarItem
|
||||
title={formatMessage(messages.like)}
|
||||
icon='like'
|
||||
active={!!status.get('favorited')}
|
||||
icon={!!status.get('favourited') ? 'liked' : 'like'}
|
||||
active={!!status.get('favourited')}
|
||||
onClick={this.handleFavoriteClick}
|
||||
/>
|
||||
<StatusActionBarItem
|
||||
|
@ -1,5 +1,7 @@
|
||||
import classNames from 'classnames/bind'
|
||||
import Button from './button'
|
||||
import Icon from './icon'
|
||||
import Text from './text'
|
||||
|
||||
const cx = classNames.bind(_s)
|
||||
|
||||
@ -24,37 +26,36 @@ export default class StatusActionBarItem extends PureComponent {
|
||||
} = this.props
|
||||
|
||||
const btnClasses = cx({
|
||||
default: 1,
|
||||
text: 1,
|
||||
fontSize13PX: 1,
|
||||
fontWeightMedium: 1,
|
||||
cursorPointer: 1,
|
||||
displayFlex: 1,
|
||||
justifyContentCenter: 1,
|
||||
flexRow: 1,
|
||||
alignItemsCenter: 1,
|
||||
py10: 1,
|
||||
px10: 1,
|
||||
width100PC: 1,
|
||||
radiusSmall: 1,
|
||||
outlineNone: 1,
|
||||
backgroundTransparent: 1,
|
||||
backgroundSubtle_onHover: 1,
|
||||
colorSecondary: 1,
|
||||
})
|
||||
|
||||
const color = active ? 'brand' : 'secondary'
|
||||
const weight = active ? 'bold' : 'medium'
|
||||
|
||||
return (
|
||||
<div className={[_s.default, _s.flexGrow1, _s.px5].join(' ')}>
|
||||
<button
|
||||
ref={buttonRef}
|
||||
<div className={[_s.default, _s.flexNormal, _s.px5].join(' ')}>
|
||||
<Button
|
||||
block
|
||||
radiusSmall
|
||||
backgroundColor='none'
|
||||
color={color}
|
||||
buttonRef={buttonRef}
|
||||
className={btnClasses}
|
||||
onClick={onClick}
|
||||
active={active}
|
||||
disabled={disabled}
|
||||
icon={icon}
|
||||
iconWidth='16px'
|
||||
iconHeight='16px'
|
||||
iconClassName={[_s.default, _s.mr10, _s.inheritFill].join(' ')}
|
||||
>
|
||||
<Icon width='16px' height='16px' id={icon} className={[_s.default, _s.mr10, _s.fillColorSecondary].join(' ')} />
|
||||
{title}
|
||||
</button>
|
||||
<Text color='inherit' size='small' weight={weight}>
|
||||
{title}
|
||||
</Text>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,12 +1,8 @@
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'
|
||||
import { blockDomain, unblockDomain } from '../actions/domain_blocks'
|
||||
import { unblockDomain } from '../actions/domain_blocks'
|
||||
import { openModal } from '../actions/modal'
|
||||
import Domain from '../components/domain'
|
||||
|
||||
const messages = defineMessages({
|
||||
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
onBlockDomain (domain) {
|
||||
dispatch(openModal('BLOCK_DOMAIN', {
|
||||
|
@ -1,62 +1,86 @@
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { debounce } from 'lodash';
|
||||
import { fetchDomainBlocks, expandDomainBlocks } from '../actions/domain_blocks';
|
||||
import DomainContainer from '../containers/domain_container';
|
||||
import ColumnIndicator from '../components/column_indicator';
|
||||
import ScrollableList from '../components/scrollable_list';
|
||||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import { debounce } from 'lodash'
|
||||
import { unblockDomain, fetchDomainBlocks, expandDomainBlocks } from '../actions/domain_blocks'
|
||||
import ColumnIndicator from '../components/column_indicator'
|
||||
import List from '../components/list'
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.domain_blocks', defaultMessage: 'Hidden domains' },
|
||||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
|
||||
emptyMessage: { id: 'empty_column.domain_blocks', defaultMessage: 'There are no hidden domains yet.' },
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onExpandDomainBlocks() {
|
||||
dispatch(expandDomainBlocks())
|
||||
},
|
||||
|
||||
onFetchDomainBlocks() {
|
||||
dispatch(fetchDomainBlocks())
|
||||
},
|
||||
|
||||
onUnblockDomain (domain) {
|
||||
dispatch(unblockDomain(domain))
|
||||
},
|
||||
})
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
domains: state.getIn(['domain_lists', 'blocks', 'items']),
|
||||
hasMore: !!state.getIn(['domain_lists', 'blocks', 'next']),
|
||||
});
|
||||
})
|
||||
|
||||
export default
|
||||
@connect(mapStateToProps)
|
||||
@injectIntl
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
class Blocks extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
hasMore: PropTypes.bool,
|
||||
domains: ImmutablePropTypes.orderedSet,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
onUnblockDomain: PropTypes.func.isRequired,
|
||||
onFetchDomainBlocks: PropTypes.func.isRequired,
|
||||
onExpandDomainBlocks: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.props.dispatch(fetchDomainBlocks());
|
||||
this.props.onFetchDomainBlocks()
|
||||
}
|
||||
|
||||
handleUnblockDomain = (domain) => {
|
||||
this.props.onUnblockDomain(domain)
|
||||
}
|
||||
|
||||
handleLoadMore = debounce(() => {
|
||||
this.props.dispatch(expandDomainBlocks());
|
||||
}, 300, { leading: true });
|
||||
this.props.onExpandDomainBlocks()
|
||||
}, 300, { leading: true })
|
||||
|
||||
render() {
|
||||
const { intl, domains, hasMore } = this.props;
|
||||
const { intl, domains, hasMore } = this.props
|
||||
|
||||
if (!domains) {
|
||||
return (<ColumnIndicator type='loading' />);
|
||||
return <ColumnIndicator type='loading' />
|
||||
}
|
||||
|
||||
const items = domains.map((domain) => {
|
||||
return {
|
||||
title: intl.formatMessage(messages.unblockDomain, { domain }),
|
||||
onClick: () => this.handleUnblockDomain(domain),
|
||||
hideArrow: true,
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<ScrollableList
|
||||
<List
|
||||
scrollKey='domain_blocks'
|
||||
onLoadMore={this.handleLoadMore}
|
||||
hasMore={hasMore}
|
||||
emptyMessage={<FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no hidden domains yet.' />}
|
||||
>
|
||||
{domains.map(domain =>
|
||||
<DomainContainer key={domain} domain={domain} />
|
||||
)}
|
||||
</ScrollableList>
|
||||
);
|
||||
emptyMessage={intl.formatMessage(messages.emptyMessage)}
|
||||
items={items}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ export default class ComposeExtraButton extends PureComponent {
|
||||
fillColorWhite: active,
|
||||
})
|
||||
|
||||
const iconSize = !!small ? '12px' : '16px'
|
||||
const iconSize = !!small ? '14px' : '16px'
|
||||
|
||||
return (
|
||||
<div className={[_s.default, _s.mr2].join(' ')} ref={buttonRef}>
|
||||
|
@ -260,15 +260,6 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
px15: !shouldCondense,
|
||||
})
|
||||
|
||||
const contentWarningClasses = cx({
|
||||
default: 1,
|
||||
px15: 1,
|
||||
py10: 1,
|
||||
borderBottom1PX: 1,
|
||||
borderColorSecondary: 1,
|
||||
displayNone: !spoiler
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={parentContainerClasses}>
|
||||
<div className={[_s.default, _s.flexRow, _s.width100PC].join(' ')}>
|
||||
@ -285,23 +276,26 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
|
||||
<div className={contentWarningClasses}>
|
||||
<AutosuggestTextbox
|
||||
placeholder={intl.formatMessage(messages.spoiler_placeholder)}
|
||||
value={this.props.spoilerText}
|
||||
onChange={this.handleChangeSpoilerText}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
disabled={!this.props.spoiler}
|
||||
ref={this.setSpoilerText}
|
||||
suggestions={this.props.suggestions}
|
||||
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
|
||||
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
||||
onSuggestionSelected={this.onSpoilerSuggestionSelected}
|
||||
searchTokens={[':']}
|
||||
prependIcon='warning'
|
||||
id='cw-spoiler-input'
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
!!spoiler &&
|
||||
<div className={[_s.default, _s.px15, _s.py10, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
|
||||
<AutosuggestTextbox
|
||||
placeholder={intl.formatMessage(messages.spoiler_placeholder)}
|
||||
value={this.props.spoilerText}
|
||||
onChange={this.handleChangeSpoilerText}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
disabled={!this.props.spoiler}
|
||||
ref={this.setSpoilerText}
|
||||
suggestions={this.props.suggestions}
|
||||
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
|
||||
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
||||
onSuggestionSelected={this.onSpoilerSuggestionSelected}
|
||||
searchTokens={[':']}
|
||||
prependIcon='warning'
|
||||
id='cw-spoiler-input'
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
<AutosuggestTextbox
|
||||
ref={(isModalOpen && shouldCondense) ? null : this.setAutosuggestTextarea}
|
||||
@ -335,22 +329,35 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
|
||||
<div className={actionsContainerClasses}>
|
||||
<div className={[_s.default, _s.flexRow, _s.marginRightAuto].join(' ')}>
|
||||
<RichTextEditorButton small={shouldCondense} />
|
||||
{
|
||||
!shouldCondense &&
|
||||
<RichTextEditorButton />
|
||||
}
|
||||
<UploadButton small={shouldCondense} />
|
||||
{
|
||||
!edit && <PollButton small={shouldCondense} />
|
||||
!edit && !shouldCondense &&
|
||||
<PollButton />
|
||||
}
|
||||
{
|
||||
!shouldCondense &&
|
||||
<StatusVisibilityButton small={shouldCondense} />
|
||||
<StatusVisibilityButton />
|
||||
}
|
||||
{
|
||||
!shouldCondense &&
|
||||
<SpoilerButton />
|
||||
}
|
||||
{
|
||||
!shouldCondense &&
|
||||
<SchedulePostButton />
|
||||
}
|
||||
<SpoilerButton small={shouldCondense} />
|
||||
<SchedulePostButton small={shouldCondense} />
|
||||
<GifSelectorButton small={shouldCondense} />
|
||||
<EmojiPickerButton small={shouldCondense} />
|
||||
</div>
|
||||
|
||||
<CharacterCounter max={maxPostCharacterCount} text={text} small={shouldCondense} />
|
||||
{
|
||||
!shouldCondense &&
|
||||
<CharacterCounter max={maxPostCharacterCount} text={text} />
|
||||
}
|
||||
|
||||
{
|
||||
!shouldCondense &&
|
||||
|
@ -55,7 +55,7 @@ const mapDispatchToProps = dispatch => ({
|
||||
},
|
||||
|
||||
onFavorite (status) {
|
||||
if (status.get('favorited')) {
|
||||
if (status.get('favourited')) {
|
||||
dispatch(unfavorite(status))
|
||||
} else {
|
||||
dispatch(favorite(status))
|
||||
|
@ -80,7 +80,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
},
|
||||
|
||||
onFavorite (status) {
|
||||
if (status.get('favorited')) {
|
||||
if (status.get('favourited')) {
|
||||
dispatch(unfavorite(status));
|
||||
} else {
|
||||
dispatch(favorite(status));
|
||||
|
@ -85,7 +85,7 @@ export default class GroupLayout extends ImmutablePureComponent {
|
||||
className={_s.mr5}
|
||||
>
|
||||
<Text color='inherit' size='small'>
|
||||
Leave/Join
|
||||
Leave/Join
|
||||
</Text>
|
||||
</Button>
|
||||
<Button
|
||||
|
@ -1,55 +0,0 @@
|
||||
import {
|
||||
GIFS_CLEAR_RESULTS,
|
||||
GIF_SET_SELECTED,
|
||||
GIF_CHANGE_SEARCH_TEXT,
|
||||
GIF_RESULTS_FETCH_REQUEST,
|
||||
GIF_RESULTS_FETCH_SUCCESS,
|
||||
GIF_RESULTS_FETCH_FAIL,
|
||||
GIF_CATEGORIES_FETCH_REQUEST,
|
||||
GIF_CATEGORIES_FETCH_SUCCESS,
|
||||
GIF_CATEGORIES_FETCH_FAIL
|
||||
} from '../actions/tenor'
|
||||
import { Map as ImmutableMap } from 'immutable'
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
categories: [],
|
||||
results: [],
|
||||
chosenUrl: '',
|
||||
searchText: '',
|
||||
loading: false,
|
||||
error: false,
|
||||
})
|
||||
|
||||
export default function (state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case GIF_RESULTS_FETCH_REQUEST:
|
||||
case GIF_CATEGORIES_FETCH_REQUEST:
|
||||
return state.set('loading', true)
|
||||
case GIF_RESULTS_FETCH_SUCCESS:
|
||||
return state.withMutations(map => {
|
||||
map.set('results', action.results);
|
||||
map.set('error', false);
|
||||
map.set('loading', false);
|
||||
});
|
||||
case GIF_CATEGORIES_FETCH_SUCCESS:
|
||||
return state.withMutations(map => {
|
||||
map.set('categories', action.categories);
|
||||
map.set('error', false);
|
||||
map.set('loading', false);
|
||||
});
|
||||
case GIF_RESULTS_FETCH_FAIL:
|
||||
case GIF_CATEGORIES_FETCH_FAIL:
|
||||
return state.withMutations(map => {
|
||||
map.set('error', !!action.error);
|
||||
map.set('loading', false);
|
||||
});
|
||||
case GIFS_CLEAR_RESULTS:
|
||||
return state.set('results', [])
|
||||
case GIF_SET_SELECTED:
|
||||
return state.set('chosenUrl', action.url)
|
||||
case GIF_CHANGE_SEARCH_TEXT:
|
||||
return state.set('searchText', action.text.trim());
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user