This commit is contained in:
mgabdev 2020-03-04 17:26:01 -05:00
parent 143725b5bd
commit 567894f614
109 changed files with 976 additions and 1643 deletions

View File

@ -2,25 +2,25 @@ import api from '../api';
import { importFetchedAccounts, importFetchedStatus } from './importer';
import { me } from '../initial_state';
export const REBLOG_REQUEST = 'REBLOG_REQUEST';
export const REBLOG_SUCCESS = 'REBLOG_SUCCESS';
export const REBLOG_FAIL = 'REBLOG_FAIL';
export const REPOST_REQUEST = 'REPOST_REQUEST';
export const REPOST_SUCCESS = 'REPOST_SUCCESS';
export const REPOST_FAIL = 'REPOST_FAIL';
export const FAVORITE_REQUEST = 'FAVORITE_REQUEST';
export const FAVORITE_SUCCESS = 'FAVORITE_SUCCESS';
export const FAVORITE_FAIL = 'FAVORITE_FAIL';
export const UNREBLOG_REQUEST = 'UNREBLOG_REQUEST';
export const UNREBLOG_SUCCESS = 'UNREBLOG_SUCCESS';
export const UNREBLOG_FAIL = 'UNREBLOG_FAIL';
export const UNREPOST_REQUEST = 'UNREPOST_REQUEST';
export const UNREPOST_SUCCESS = 'UNREPOST_SUCCESS';
export const UNREPOST_FAIL = 'UNREPOST_FAIL';
export const UNFAVORITE_REQUEST = 'UNFAVORITE_REQUEST';
export const UNFAVORITE_SUCCESS = 'UNFAVORITE_SUCCESS';
export const UNFAVORITE_FAIL = 'UNFAVORITE_FAIL';
export const REBLOGS_FETCH_REQUEST = 'REBLOGS_FETCH_REQUEST';
export const REBLOGS_FETCH_SUCCESS = 'REBLOGS_FETCH_SUCCESS';
export const REBLOGS_FETCH_FAIL = 'REBLOGS_FETCH_FAIL';
export const REPOSTS_FETCH_REQUEST = 'REPOSTS_FETCH_REQUEST';
export const REPOSTS_FETCH_SUCCESS = 'REPOSTS_FETCH_SUCCESS';
export const REPOSTS_FETCH_FAIL = 'REPOSTS_FETCH_FAIL';
export const PIN_REQUEST = 'PIN_REQUEST';
export const PIN_SUCCESS = 'PIN_SUCCESS';
@ -30,119 +30,119 @@ export const UNPIN_REQUEST = 'UNPIN_REQUEST';
export const UNPIN_SUCCESS = 'UNPIN_SUCCESS';
export const UNPIN_FAIL = 'UNPIN_FAIL';
export function reblog(status) {
export function repost(status) {
return function (dispatch, getState) {
if (!me) return;
dispatch(reblogRequest(status));
dispatch(repostRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/reblog`).then(function (response) {
// The reblog API method returns a new status wrapped around the original. In this case we are only
// interested in how the original is modified, hence passing it skipping the wrapper
dispatch(importFetchedStatus(response.data.reblog));
dispatch(reblogSuccess(status));
dispatch(repostSuccess(status));
}).catch(function (error) {
dispatch(reblogFail(status, error));
dispatch(repostFail(status, error));
});
};
};
export function unreblog(status) {
export function unrepost(status) {
return (dispatch, getState) => {
if (!me) return;
dispatch(unreblogRequest(status));
dispatch(unrepostRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/unreblog`).then(response => {
dispatch(importFetchedStatus(response.data));
dispatch(unreblogSuccess(status));
dispatch(unrepostSuccess(status));
}).catch(error => {
dispatch(unreblogFail(status, error));
dispatch(unrepostFail(status, error));
});
};
};
export function reblogRequest(status) {
export function repostRequest(status) {
return {
type: REBLOG_REQUEST,
type: REPOST_REQUEST,
status: status,
skipLoading: true,
};
};
export function reblogSuccess(status) {
export function repostSuccess(status) {
return {
type: REBLOG_SUCCESS,
type: REPOST_SUCCESS,
status: status,
skipLoading: true,
};
};
export function reblogFail(status, error) {
export function repostFail(status, error) {
return {
type: REBLOG_FAIL,
type: REPOST_FAIL,
status: status,
error: error,
skipLoading: true,
};
};
export function unreblogRequest(status) {
export function unrepostRequest(status) {
return {
type: UNREBLOG_REQUEST,
type: UNREPOST_REQUEST,
status: status,
skipLoading: true,
};
};
export function unreblogSuccess(status) {
export function unrepostSuccess(status) {
return {
type: UNREBLOG_SUCCESS,
type: UNREPOST_SUCCESS,
status: status,
skipLoading: true,
};
};
export function unreblogFail(status, error) {
export function unrepostFail(status, error) {
return {
type: UNREBLOG_FAIL,
type: UNREPOST_FAIL,
status: status,
error: error,
skipLoading: true,
};
};
export function favourite(status) {
export function favorite(status) {
return function (dispatch, getState) {
if (!me) return;
dispatch(favouriteRequest(status));
dispatch(favoriteRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/favourite`).then(function (response) {
dispatch(importFetchedStatus(response.data));
dispatch(favouriteSuccess(status));
dispatch(favoriteSuccess(status));
}).catch(function (error) {
dispatch(favouriteFail(status, error));
dispatch(favoriteFail(status, error));
});
};
};
export function unfavourite(status) {
export function unfavorite(status) {
return (dispatch, getState) => {
if (!me) return;
dispatch(unfavouriteRequest(status));
dispatch(unfavoriteRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/unfavourite`).then(response => {
dispatch(importFetchedStatus(response.data));
dispatch(unfavouriteSuccess(status));
dispatch(unfavoriteSuccess(status));
}).catch(error => {
dispatch(unfavouriteFail(status, error));
dispatch(unfavoriteFail(status, error));
});
};
};
export function favouriteRequest(status) {
export function favoriteRequest(status) {
return {
type: FAVORITE_REQUEST,
status: status,
@ -150,7 +150,7 @@ export function favouriteRequest(status) {
};
};
export function favouriteSuccess(status) {
export function favoriteSuccess(status) {
return {
type: FAVORITE_SUCCESS,
status: status,
@ -158,7 +158,7 @@ export function favouriteSuccess(status) {
};
};
export function favouriteFail(status, error) {
export function favoriteFail(status, error) {
return {
type: FAVORITE_FAIL,
status: status,
@ -167,7 +167,7 @@ export function favouriteFail(status, error) {
};
};
export function unfavouriteRequest(status) {
export function unfavoriteRequest(status) {
return {
type: UNFAVORITE_REQUEST,
status: status,
@ -175,7 +175,7 @@ export function unfavouriteRequest(status) {
};
};
export function unfavouriteSuccess(status) {
export function unfavoriteSuccess(status) {
return {
type: UNFAVORITE_SUCCESS,
status: status,
@ -183,7 +183,7 @@ export function unfavouriteSuccess(status) {
};
};
export function unfavouriteFail(status, error) {
export function unfavoriteFail(status, error) {
return {
type: UNFAVORITE_FAIL,
status: status,
@ -192,39 +192,39 @@ export function unfavouriteFail(status, error) {
};
};
export function fetchReblogs(id) {
export function fetchReposts(id) {
return (dispatch, getState) => {
if (!me) return;
dispatch(fetchReblogsRequest(id));
dispatch(fetchRepostsRequest(id));
api(getState).get(`/api/v1/statuses/${id}/reblogged_by`).then(response => {
dispatch(importFetchedAccounts(response.data));
dispatch(fetchReblogsSuccess(id, response.data));
dispatch(fetchRepostsSuccess(id, response.data));
}).catch(error => {
dispatch(fetchReblogsFail(id, error));
dispatch(fetchRepostsFail(id, error));
});
};
};
export function fetchReblogsRequest(id) {
export function fetchRepostsRequest(id) {
return {
type: REBLOGS_FETCH_REQUEST,
type: REPOSTS_FETCH_REQUEST,
id,
};
};
export function fetchReblogsSuccess(id, accounts) {
export function fetchRepostsSuccess(id, accounts) {
return {
type: REBLOGS_FETCH_SUCCESS,
type: REPOSTS_FETCH_SUCCESS,
id,
accounts,
};
};
export function fetchReblogsFail(id, error) {
export function fetchRepostsFail(id, error) {
return {
type: REBLOGS_FETCH_FAIL,
type: REPOSTS_FETCH_FAIL,
error,
};
};

View File

@ -2,18 +2,18 @@ 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 = '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_EXPAND_FAIL = 'TIMELINE_EXPAND_FAIL';
export const TIMELINE_CONNECT = 'TIMELINE_CONNECT';
export const TIMELINE_CONNECT = 'TIMELINE_CONNECT';
export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
export const MAX_QUEUED_ITEMS = 40;
@ -94,9 +94,9 @@ export function dequeueTimeline(timeline, expandFunc, optionalExpandArgs) {
export function deleteFromTimelines(id) {
return (dispatch, getState) => {
const accountId = getState().getIn(['statuses', id, 'account']);
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);
const reblogOf = getState().getIn(['statuses', id, 'reblog'], null);
dispatch({
type: TIMELINE_DELETE,
@ -114,7 +114,7 @@ export function clearTimeline(timeline) {
};
};
const noOp = () => {};
const noOp = () => { };
const parseTags = (tags = {}, mode) => {
return (tags[mode] || []).map((tag) => {
@ -152,20 +152,20 @@ 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 expandPublicTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`public${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { 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 expandHomeTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done);
export const expandPublicTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`public${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { 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 expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: 40 });
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) => {
export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, 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'),
any: parseTags(tags, 'any'),
all: parseTags(tags, 'all'),
none: parseTags(tags, 'none'),
}, done);
};

View File

@ -0,0 +1,132 @@
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { NavLink } from 'react-router-dom'
import { decode } from 'blurhash'
import { autoPlayGif, displayMedia } from '../initial_state'
import Icon from './icon'
import Image from './image'
import Text from './text'
export default class MediaItem extends ImmutablePureComponent {
static propTypes = {
attachment: ImmutablePropTypes.map.isRequired,
}
state = {
visible: displayMedia !== 'hide_all' && !this.props.attachment.getIn(['status', 'sensitive']) || displayMedia === 'show_all',
loaded: false,
}
componentDidMount() {
if (this.props.attachment.get('blurhash')) {
this._decode()
}
}
componentDidUpdate(prevProps) {
if (prevProps.attachment.get('blurhash') !== this.props.attachment.get('blurhash') && this.props.attachment.get('blurhash')) {
this._decode()
}
}
_decode() {
const hash = this.props.attachment.get('blurhash')
const pixels = decode(hash, 160, 160)
if (pixels && this.canvas) {
const ctx = this.canvas.getContext('2d')
const imageData = new ImageData(pixels, 160, 160)
ctx.putImageData(imageData, 0, 0)
}
}
setCanvasRef = c => {
this.canvas = c
}
handleImageLoad = () => {
this.setState({ loaded: true })
}
hoverToPlay() {
return !autoPlayGif && ['gifv', 'video'].indexOf(this.props.attachment.get('type')) !== -1
}
render() {
const { attachment } = this.props
const { visible, loaded } = this.state
const status = attachment.get('status')
const title = status.get('spoiler_text') || attachment.get('description')
const attachmentType = attachment.get('type')
let badge = null
if (attachmentType === 'video') {
const duration = attachment.getIn(['meta', 'duration'])
badge = (duration / 60).toFixed(2)
} else if (attachmentType === 'gifv') {
badge = 'GIF'
}
return (
<div className={[_s.default, _s.width25PC, _s.paddingTop25PC].join(' ')}>
<div className={[_s.default, _s.positionAbsolute, _s.top0, _s.height100PC, _s.width100PC, _s.paddingVertical5PX, _s.paddingHorizontal5PX].join(' ')}>
<NavLink
to={status.get('url')} /* : todo : */
title={title}
className={[_s.default, _s.width100PC, _s.height100PC, _s.border1PX, _s.borderColorSecondary, _s.overflowHidden].join(' ')}
>
{
(!loaded || !visible) &&
<canvas
height='100%'
width='100%'
ref={this.setCanvasRef}
className={[_s.default, _s.width100PC, _s.height100PC, _s.z2].join(' ')}
/>
}
{
visible &&
<Image
height='100%'
src={attachment.get('preview_url')}
alt={attachment.get('description')}
title={attachment.get('description')}
onLoad={this.handleImageLoad}
className={_s.z1}
/>
}
<div className={[_s.default, _s.alignItemsCenter, _s.justifyContentCenter, _s.height100PC, _s.width100PC, _s.z3, _s.positionAbsolute].join(' ')}>
{
!visible &&
<Icon
id='hidden'
width='22px'
height='22px'
className={[_s.fillColorWhite].join('')}
/>
}
{
!!badge &&
<div className={[_s.default, _s.positionAbsolute, _s.radiusSmall, _s.backgroundColorOpaque, _s.paddingHorizontal5PX, _s.paddingVertical5PX, _s.marginRight5PX, _s.marginVertical5PX, _s.bottom0, _s.right0].join(' ')}>
<Text size='extraSmall' color='white'>
{badge}
</Text>
</div>
}
</div>
</NavLink>
</div>
</div>
)
}
}

View File

@ -24,7 +24,7 @@ class BoostModal extends ImmutablePureComponent {
static propTypes = {
status: ImmutablePropTypes.map.isRequired,
onReblog: PropTypes.func.isRequired,
onRepost: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
@ -33,8 +33,8 @@ class BoostModal extends ImmutablePureComponent {
this.button.focus();
}
handleReblog = () => {
this.props.onReblog(this.props.status);
handleRepost = () => {
this.props.onRepost(this.props.status);
this.props.onClose();
}
@ -96,7 +96,7 @@ class BoostModal extends ImmutablePureComponent {
}
})}
</div>
<Button text={intl.formatMessage(buttonText)} onClick={this.handleReblog} ref={this.setRef} />
<Button text={intl.formatMessage(buttonText)} onClick={this.handleRepost} ref={this.setRef} />
</div>
</div>
);

View File

@ -11,7 +11,7 @@ const messages = defineMessages({
reply: { id: 'keyboard_shortcuts.reply', defaultMessage: 'reply' },
mention: { id: 'keyboard_shortcuts.mention', defaultMessage: 'mention author' },
profile: { id: 'keyboard_shortcuts.profile', defaultMessage: 'open author\'s profile' },
favourite: { id: 'keyboard_shortcuts.favourite', defaultMessage: 'favorite' },
favorite: { id: 'keyboard_shortcuts.favorite', defaultMessage: 'favorite' },
boost: { id: 'keyboard_shortcuts.boost', defaultMessage: 'repost' },
enter: { id: 'keyboard_shortcuts.enter', defaultMessage: 'open status' },
toggle_hidden: { id: 'keyboard_shortcuts.toggle_hidden', defaultMessage: 'show/hide text behind CW' },
@ -28,7 +28,7 @@ const messages = defineMessages({
notifications: { id: 'keyboard_shortcuts.notifications', defaultMessage: 'open notifications column' },
direct: { id: 'keyboard_shortcuts.direct', defaultMessage: 'open direct messages column' },
start: { id: 'keyboard_shortcuts.start', defaultMessage: 'open "get started" column' },
favourites: { id: 'keyboard_shortcuts.favourites', defaultMessage: 'open favorites list' },
favorites: { id: 'keyboard_shortcuts.favorites', defaultMessage: 'open favorites list' },
pinned: { id: 'keyboard_shortcuts.pinned', defaultMessage: 'open pinned gabs list' },
my_profile: { id: 'keyboard_shortcuts.my_profile', defaultMessage: 'open your profile' },
blocked: { id: 'keyboard_shortcuts.blocked', defaultMessage: 'open blocked users list' },
@ -65,7 +65,7 @@ class HotkeysModal extends ImmutablePureComponent {
<HotKeysModalRow hotkey='r' action={intl.formatMessage(messages.reply)} />
<HotKeysModalRow hotkey='m' action={intl.formatMessage(messages.mention)} />
<HotKeysModalRow hotkey='p' action={intl.formatMessage(messages.profile)} />
<HotKeysModalRow hotkey='f' action={intl.formatMessage(messages.favourite)} />
<HotKeysModalRow hotkey='f' action={intl.formatMessage(messages.favorite)} />
<HotKeysModalRow hotkey='b' action={intl.formatMessage(messages.boost)} />
<HotKeysModalRow hotkey='enter, o' action={intl.formatMessage(messages.enter)} />
<HotKeysModalRow hotkey='x' action={intl.formatMessage(messages.toggle_hidden)} />
@ -108,7 +108,7 @@ class HotkeysModal extends ImmutablePureComponent {
</thead>
<tbody>
<HotKeysModalRow hotkey='g + s' action={intl.formatMessage(messages.start)} />
<HotKeysModalRow hotkey='g + f' action={intl.formatMessage(messages.favourites)} />
<HotKeysModalRow hotkey='g + f' action={intl.formatMessage(messages.favorites)} />
<HotKeysModalRow hotkey='g + p' action={intl.formatMessage(messages.pinned)} />
<HotKeysModalRow hotkey='g + u' action={intl.formatMessage(messages.my_profile)} />
<HotKeysModalRow hotkey='g + b' action={intl.formatMessage(messages.blocked)} />

View File

@ -51,7 +51,7 @@ class ProfileStatsPanel extends ImmutablePureComponent {
account.get('id') === me &&
<UserStat
title={intl.formatMessage(messages.favorites)}
value={shortNumberFormat(account.get('favourite_count'))}
value={shortNumberFormat(account.get('favourite_count'))} /* : todo : */
to={`/${account.get('acct')}/favorites`}
/>
}

View File

@ -12,7 +12,7 @@ export default class StatusOptionsPopover extends PureComponent {
// menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
// } else {
// if (status.get('visibility') === 'private') {
// menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancel_reblog_private : messages.reblog_private), action: this.handleReblogClick });
// menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancel_reblog_private : messages.reblog_private), action: this.handleRepostClick });
// }
// }

View File

@ -73,7 +73,7 @@ export default class ScrollableList extends PureComponent {
this.scrollToTopOnMouseIdle = false;
}
componentDidMount () {
componentDidMount() {
this.window = window;
this.documentElement = document.scrollingElement || document.documentElement;
@ -97,7 +97,7 @@ export default class ScrollableList extends PureComponent {
this.setScrollTop(newScrollTop);
}
componentDidUpdate (prevProps, prevState, snapshot) {
componentDidUpdate(prevProps, prevState, snapshot) {
// Reset the scroll position when a new child comes in in order not to
// jerk the scrollbar around if you're already scrolled down the page.
if (snapshot !== null) {
@ -105,12 +105,12 @@ export default class ScrollableList extends PureComponent {
}
}
attachScrollListener () {
attachScrollListener() {
this.window.addEventListener('scroll', this.handleScroll);
this.window.addEventListener('wheel', this.handleWheel);
}
detachScrollListener () {
detachScrollListener() {
this.window.removeEventListener('scroll', this.handleScroll);
this.window.removeEventListener('wheel', this.handleWheel);
}
@ -139,16 +139,16 @@ export default class ScrollableList extends PureComponent {
this.lastScrollWasSynthetic = false;
}
}, 150, {
trailing: true,
});
trailing: true,
});
handleWheel = throttle(() => {
this.scrollToTopOnMouseIdle = false;
}, 150, {
trailing: true,
});
trailing: true,
});
getSnapshotBeforeUpdate (prevProps) {
getSnapshotBeforeUpdate(prevProps) {
const someItemInserted = React.Children.count(prevProps.children) > 0 &&
React.Children.count(prevProps.children) < React.Children.count(this.props.children) &&
this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props);
@ -166,21 +166,21 @@ export default class ScrollableList extends PureComponent {
}
}
componentWillUnmount () {
componentWillUnmount() {
this.clearMouseIdleTimer();
this.detachScrollListener();
this.detachIntersectionObserver();
}
attachIntersectionObserver () {
attachIntersectionObserver() {
this.intersectionObserverWrapper.connect();
}
detachIntersectionObserver () {
detachIntersectionObserver() {
this.intersectionObserverWrapper.disconnect();
}
getFirstChildKey (props) {
getFirstChildKey(props) {
const { children } = props;
let firstChild = children;
@ -198,7 +198,7 @@ export default class ScrollableList extends PureComponent {
this.props.onLoadMore();
}
render () {
render() {
const { children, scrollKey, showLoading, isLoading, hasMore, emptyMessage, onLoadMore } = this.props;
const childrenCount = React.Children.count(children);
@ -207,28 +207,33 @@ export default class ScrollableList extends PureComponent {
const loadMore = (hasMore && onLoadMore) ? <LoadMore visible={!isLoading} onClick={this.handleLoadMore} /> : null;
if (showLoading) {
return ( <ColumnIndicator type='loading' /> );
return <ColumnIndicator type='loading' />
} else if (isLoading || childrenCount > 0 || hasMore || !emptyMessage) {
return (
<div className='scrollable-list' onMouseMove={this.handleMouseMove}>
<div onMouseMove={this.handleMouseMove}>
<div role='feed'>
{React.Children.map(this.props.children, (child, index) => (
<IntersectionObserverArticle
key={child.key}
id={child.key}
index={index}
listLength={childrenCount}
intersectionObserverWrapper={this.intersectionObserverWrapper}
saveHeightKey={trackScroll ? `${this.context.router.route.location.key}:${scrollKey}` : null}
>
{React.cloneElement(child, {
getScrollPosition: this.getScrollPosition,
updateScrollBottom: this.updateScrollBottom,
cachedMediaWidth: this.state.cachedMediaWidth,
cacheMediaWidth: this.cacheMediaWidth,
})}
</IntersectionObserverArticle>
))}
{
!!this.props.children &&
React.Children.map(this.props.children, (child, index) => (
<IntersectionObserverArticle
key={child.key}
id={child.key}
index={index}
listLength={childrenCount}
intersectionObserverWrapper={this.intersectionObserverWrapper}
saveHeightKey={trackScroll ? `${this.context.router.route.location.key}:${scrollKey}` : null}
>
{
React.cloneElement(child, {
getScrollPosition: this.getScrollPosition,
updateScrollBottom: this.updateScrollBottom,
cachedMediaWidth: this.state.cachedMediaWidth,
cacheMediaWidth: this.cacheMediaWidth,
})
}
</IntersectionObserverArticle>
))
}
{loadMore}
</div>
@ -236,7 +241,7 @@ export default class ScrollableList extends PureComponent {
);
}
return ( <ColumnIndicator type='error' message={emptyMessage} /> );
return <ColumnIndicator type='error' message={emptyMessage} />
}
}

View File

@ -76,8 +76,8 @@ class Status extends ImmutablePureComponent {
onReply: PropTypes.func,
onShowRevisions: PropTypes.func,
onQuote: PropTypes.func,
onFavourite: PropTypes.func,
onReblog: PropTypes.func,
onFavorite: PropTypes.func,
onRepost: PropTypes.func,
onDelete: PropTypes.func,
onEdit: PropTypes.func,
onDirect: PropTypes.func,
@ -212,12 +212,12 @@ class Status extends ImmutablePureComponent {
this.props.onReply(this._properStatus(), this.context.router.history);
};
handleHotkeyFavourite = () => {
this.props.onFavourite(this._properStatus());
handleHotkeyFavorite = () => {
this.props.onFavorite(this._properStatus());
};
handleHotkeyBoost = e => {
this.props.onReblog(this._properStatus(), e);
this.props.onRepost(this._properStatus(), e);
};
handleHotkeyMention = e => {
@ -424,7 +424,7 @@ class Status extends ImmutablePureComponent {
const handlers = this.props.muted ? {} : {
reply: this.handleHotkeyReply,
favourite: this.handleHotkeyFavourite,
favorite: this.handleHotkeyFavorite,
boost: this.handleHotkeyBoost,
mention: this.handleHotkeyMention,
open: this.handleHotkeyOpen,

View File

@ -63,8 +63,8 @@ class StatusActionBar extends ImmutablePureComponent {
onOpenUnauthorizedModal: PropTypes.func.isRequired,
onReply: PropTypes.func,
onQuote: PropTypes.func,
onFavourite: PropTypes.func,
onReblog: PropTypes.func,
onFavorite: PropTypes.func,
onRepost: PropTypes.func,
onDelete: PropTypes.func,
onMention: PropTypes.func,
onMute: PropTypes.func,
@ -101,17 +101,17 @@ class StatusActionBar extends ImmutablePureComponent {
}
}
handleFavouriteClick = () => {
handleFavoriteClick = () => {
if (me) {
this.props.onFavourite(this.props.status)
this.props.onFavorite(this.props.status)
} else {
this.props.onOpenUnauthorizedModal()
}
}
handleReblogClick = e => {
handleRepostClick = e => {
if (me) {
this.props.onReblog(this.props.status, e)
this.props.onRepost(this.props.status, e)
} else {
this.props.onOpenUnauthorizedModal()
}
@ -129,7 +129,7 @@ class StatusActionBar extends ImmutablePureComponent {
const reblogCount = status.get('reblogs_count')
const reblogTitle = !publicStatus ? formatMessage(messages.cannot_reblog) : formatMessage(messages.reblog)
const favoriteCount = status.get('favourites_count')
const favoriteCount = status.get('favorites_count') // : todo :
const shareButton = ('share' in navigator) && status.get('visibility') === 'public' && (
<IconButton className='status-action-bar-button' title={formatMessage(messages.share)} icon='share-alt' onClick={this.handleShareClick} />
@ -139,8 +139,8 @@ class StatusActionBar extends ImmutablePureComponent {
{
title: formatMessage(messages.like),
icon: 'like',
active: !!status.get('favourited'),
onClick: this.handleFavouriteClick,
active: !!status.get('favorited'),
onClick: this.handleFavoriteClick,
},
{
title: formatMessage(messages.comment),
@ -153,13 +153,13 @@ class StatusActionBar extends ImmutablePureComponent {
icon: (status.get('visibility') === 'private') ? 'lock' : 'repost',
disabled: !publicStatus,
active: !!status.get('reblogged'),
onClick: this.handleReblogClick,
onClick: this.handleRepostClick,
},
{
title: formatMessage(messages.share),
icon: 'share',
active: false,
onClick: this.handleFavouriteClick,
onClick: this.handleFavoriteClick,
},
]

View File

@ -122,7 +122,7 @@ export default class StatusHeader extends ImmutablePureComponent {
menu.push({ text: formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
} else {
if (status.get('visibility') === 'private') {
menu.push({ text: formatMessage(status.get('reblogged') ? messages.cancel_reblog_private : messages.reblog_private), action: this.handleReblogClick });
menu.push({ text: formatMessage(status.get('reblogged') ? messages.cancel_reblog_private : messages.reblog_private), action: this.handleRepostClick });
}
}
menu.push({ text: formatMessage(messages.delete), action: this.handleDeleteClick });

View File

@ -7,9 +7,9 @@ import {
} from '../actions/compose';
import {
reblog,
favourite,
favorite,
unreblog,
unfavourite,
unfavorite,
pin,
unpin,
} from '../actions/interactions';
@ -88,19 +88,19 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
});
},
onModalReblog (status) {
onModalRepost (status) {
if (status.get('reblogged')) {
dispatch(unreblog(status));
dispatch(unrepost(status));
} else {
dispatch(reblog(status));
dispatch(repost(status));
}
},
onReblog (status, e) {
onRepost (status, e) {
if (e.shiftKey || !boostModal) {
this.onModalReblog(status);
this.onModalRepost(status);
} else {
dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
dispatch(openModal('BOOST', { status, onRepost: this.onModalRepost }));
}
},
@ -108,11 +108,11 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
dispatch(openModal('STATUS_REVISION', { status }));
},
onFavourite (status) {
onFavorite (status) {
if (status.get('favourited')) {
dispatch(unfavourite(status));
dispatch(unfavorite(status));
} else {
dispatch(favourite(status));
dispatch(favorite(status));
}
},

View File

@ -0,0 +1,125 @@
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { injectIntl, defineMessages } from 'react-intl'
import { expandAccountMediaTimeline } from '../actions/timelines'
import { getAccountGallery } from '../selectors'
import ColumnIndicator from '../components/column_indicator'
import MediaItem from '../components/media_item'
import LoadMore from '../components/load_more'
import Block from '../components/block'
const messages = defineMessages({
none: { id: 'account_gallery.none', defaultMessage: 'No media to show.' },
})
const mapStateToProps = (state, { account }) => {
const accountId = !!account ? account.get('id') : -1
return {
accountId,
attachments: getAccountGallery(state, accountId),
isLoading: state.getIn(['timelines', `account:${accountId}:media`, 'isLoading']),
hasMore: state.getIn(['timelines', `account:${accountId}:media`, 'hasMore']),
}
}
export default
@connect(mapStateToProps)
@injectIntl
class AccountGallery extends ImmutablePureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
account: ImmutablePropTypes.map,
accountId: PropTypes.string,
attachments: ImmutablePropTypes.list.isRequired,
isLoading: PropTypes.bool,
hasMore: PropTypes.bool,
intl: PropTypes.object.isRequired,
}
componentDidMount() {
const { accountId } = this.props
if (accountId) {
this.props.dispatch(expandAccountMediaTimeline(accountId))
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.accountId && nextProps.accountId !== this.props.accountId) {
this.props.dispatch(expandAccountMediaTimeline(nextProps.accountId))
}
}
handleScrollToBottom = () => {
if (this.props.hasMore) {
this.handleLoadMore(this.props.attachments.size > 0 ? this.props.attachments.last().getIn(['status', 'id']) : undefined)
}
}
handleScroll = e => {
const { scrollTop, scrollHeight, clientHeight } = e.target
const offset = scrollHeight - scrollTop - clientHeight
if (150 > offset && !this.props.isLoading) {
this.handleScrollToBottom()
}
}
handleLoadMore = maxId => {
if (this.props.accountId && this.props.accountId !== -1) {
this.props.dispatch(expandAccountMediaTimeline(this.props.accountId, { maxId }))
}
}
handleLoadOlder = e => {
e.preventDefault()
this.handleScrollToBottom()
}
render() {
const {
attachments,
isLoading,
hasMore,
intl,
account
} = this.props
if (!account) return null
return (
<Block>
<div
role='feed'
onScroll={this.handleScroll}
className={[_s.default, _s.flexRow, _s.flexWrap, _s.heightMin50VH, _s.paddingVertical5PX, _s.paddingHorizontal5PX].join(' ')}
>
{
attachments.map((attachment) => (
<MediaItem key={attachment.get('id')} attachment={attachment} />
))
}
{
isLoading && attachments.size === 0 &&
<ColumnIndicator type='loading' />
}
{ /*
attachments.size == 0 &&
<ColumnIndicator type='empty' message={intl.formatMessage(messages.none)} />
*/ }
{
hasMore && !(isLoading && attachments.size === 0) &&
<LoadMore visible={!isLoading} onClick={this.handleLoadOlder} />
}
</div>
</Block>
)
}
}

View File

@ -1,205 +0,0 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
import {
fetchAccount,
fetchAccountByUsername,
} from '../../actions/accounts';
import { openModal } from '../../actions/modal';
import { expandAccountMediaTimeline } from '../../actions/timelines';
import { me } from '../../initial_state';
import { getAccountGallery } from '../../selectors';
import ColumnIndicator from '../../components/column_indicator';
import MediaItem from './components/media_item';
import LoadMore from '../../components/load_more';
const messages = defineMessages({
posts: { id: 'account.posts', defaultMessage: 'Gabs' },
postsWithReplies: { id: 'account.posts_with_replies', defaultMessage: 'Gabs and replies' },
media: { id: 'account.media', defaultMessage: 'Media' },
error: { id: 'empty_column.account_unavailable', defaultMessage: 'Profile unavailable' },
});
const mapStateToProps = (state, { mediaType, params: { username } }) => {
const accounts = state.getIn(['accounts']);
const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() == username.toLowerCase());
let accountId = -1;
let accountUsername = username;
if (accountFetchError) {
accountId = null;
} else {
let account = accounts.find(acct => username.toLowerCase() == acct.getIn(['acct'], '').toLowerCase());
accountId = account ? account.getIn(['id'], null) : -1;
accountUsername = account ? account.getIn(['acct'], '') : '';
}
const isBlocked = state.getIn(['relationships', accountId, 'blocked_by'], false);
const isLocked = state.getIn(['accounts', accountId, 'locked'], false);
const isFollowing = state.getIn(['relationships', accountId, 'following'], false);
const unavailable = (me === accountId) ? false : (isBlocked || (isLocked && !isFollowing));
return {
accountId,
unavailable,
accountUsername,
isAccount: !!state.getIn(['accounts', accountId]),
attachments: getAccountGallery(state, accountId),
isLoading: state.getIn(['timelines', `account:${accountId}:media`, 'isLoading']),
hasMore: state.getIn(['timelines', `account:${accountId}:media`, 'hasMore']),
};
};
class LoadMoreMedia extends ImmutablePureComponent {
static propTypes = {
maxId: PropTypes.string,
onLoadMore: PropTypes.func.isRequired,
};
handleLoadMore = () => {
this.props.onLoadMore(this.props.maxId);
}
render() {
return (
<LoadMore
disabled={this.props.disabled}
onClick={this.handleLoadMore}
/>
)
}
}
export default
@connect(mapStateToProps)
@injectIntl
class AccountGallery extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
attachments: ImmutablePropTypes.list.isRequired,
isLoading: PropTypes.bool,
hasMore: PropTypes.bool,
isAccount: PropTypes.bool,
unavailable: PropTypes.bool,
intl: PropTypes.object.isRequired,
}
state = {
width: 323,
}
componentDidMount() {
const { params: { username }, accountId } = this.props;
if (accountId && accountId !== -1) {
this.props.dispatch(fetchAccount(accountId));
this.props.dispatch(expandAccountMediaTimeline(accountId));
} else {
this.props.dispatch(fetchAccountByUsername(username));
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.accountId && nextProps.accountId !== -1 && (nextProps.accountId !== this.props.accountId && nextProps.accountId)) {
this.props.dispatch(fetchAccount(nextProps.params.accountId));
this.props.dispatch(expandAccountMediaTimeline(nextProps.accountId));
}
}
handleScrollToBottom = () => {
if (this.props.hasMore) {
this.handleLoadMore(this.props.attachments.size > 0 ? this.props.attachments.last().getIn(['status', 'id']) : undefined);
}
}
handleScroll = e => {
const { scrollTop, scrollHeight, clientHeight } = e.target;
const offset = scrollHeight - scrollTop - clientHeight;
if (150 > offset && !this.props.isLoading) {
this.handleScrollToBottom();
}
}
handleLoadMore = maxId => {
if (this.props.accountId && this.props.accountId !== -1) {
this.props.dispatch(expandAccountMediaTimeline(this.props.accountId, { maxId }));
}
};
handleLoadOlder = e => {
e.preventDefault();
this.handleScrollToBottom();
}
handleOpenMedia = attachment => {
if (attachment.get('type') === 'video') {
this.props.dispatch(openModal('VIDEO', { media: attachment, status: attachment.get('status') }));
} else {
const media = attachment.getIn(['status', 'media_attachments']);
const index = media.findIndex(x => x.get('id') === attachment.get('id'));
this.props.dispatch(openModal('MEDIA', { media, index, status: attachment.get('status') }));
}
}
handleRef = c => {
if (c) {
this.setState({ width: c.offsetWidth });
}
}
render() {
const { attachments, isLoading, hasMore, isAccount, accountId, unavailable, accountUsername, intl } = this.props;
const { width } = this.state;
if (!isAccount && accountId !== -1) {
return <ColumnIndicator type='missing' />
} else if (accountId === -1 || (!attachments && isLoading)) {
return <ColumnIndicator type='loading' />
} else if (unavailable) {
return <ColumnIndicator type='error' message={intl.formatMessage(messages.error)} />
}
let loadOlder = null
if (hasMore && !(isLoading && attachments.size === 0)) {
loadOlder = <LoadMore visible={!isLoading} onClick={this.handleLoadOlder} />
}
return (
<div className='scrollable-list scrollable-list--flex' onScroll={this.handleScroll}>
<div role='feed' className='account-gallery__container' ref={this.handleRef}>
{
attachments.map((attachment, index) => attachment === null ? (
<LoadMoreMedia key={'more:' + attachments.getIn(index + 1, 'id')} maxId={index > 0 ? attachments.getIn(index - 1, 'id') : null} onLoadMore={this.handleLoadMore} />
) : (
<MediaItem key={attachment.get('id')} attachment={attachment} displayWidth={width} onOpenMedia={this.handleOpenMedia} />
))
}
{
attachments.size == 0 &&
<div className='empty-column-indicator'>
<FormattedMessage id='account_gallery.none' defaultMessage='No media to show.' />
</div>
}
{loadOlder}
</div>
{
isLoading && attachments.size === 0 &&
<div className='slist__append'>
<ColumnIndicator type='loading' />
</div>
}
</div>
);
}
}

View File

@ -1,5 +0,0 @@
.account-gallery__container {
display: flex;
flex-wrap: wrap;
padding: 4px 2px;
}

View File

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

View File

@ -1,156 +0,0 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import classNames from 'classnames';
import { decode } from 'blurhash';
import Icon from '../../../../components/icon';
import { autoPlayGif, displayMedia } from '../../../../initial_state';
import { isIOS } from '../../../../utils/is_mobile';
export default class MediaItem extends ImmutablePureComponent {
static propTypes = {
attachment: ImmutablePropTypes.map.isRequired,
displayWidth: PropTypes.number.isRequired,
onOpenMedia: PropTypes.func.isRequired,
};
state = {
visible: displayMedia !== 'hide_all' && !this.props.attachment.getIn(['status', 'sensitive']) || displayMedia === 'show_all',
loaded: false,
};
componentDidMount () {
if (this.props.attachment.get('blurhash')) {
this._decode();
}
}
componentDidUpdate (prevProps) {
if (prevProps.attachment.get('blurhash') !== this.props.attachment.get('blurhash') && this.props.attachment.get('blurhash')) {
this._decode();
}
}
_decode () {
const hash = this.props.attachment.get('blurhash');
const pixels = decode(hash, 32, 32);
if (pixels) {
const ctx = this.canvas.getContext('2d');
const imageData = new ImageData(pixels, 32, 32);
ctx.putImageData(imageData, 0, 0);
}
}
setCanvasRef = c => {
this.canvas = c;
}
handleImageLoad = () => {
this.setState({ loaded: true });
}
handleMouseEnter = e => {
if (this.hoverToPlay()) {
e.target.play();
}
}
handleMouseLeave = e => {
if (this.hoverToPlay()) {
e.target.pause();
e.target.currentTime = 0;
}
}
hoverToPlay () {
return !autoPlayGif && ['gifv', 'video'].indexOf(this.props.attachment.get('type')) !== -1;
}
handleClick = e => {
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
if (this.state.visible) {
this.props.onOpenMedia(this.props.attachment);
} else {
this.setState({ visible: true });
}
}
}
render () {
const { attachment, displayWidth } = this.props;
const { visible, loaded } = this.state;
const width = `${Math.floor((displayWidth - 4) / 3) - 4}px`;
const height = width;
const status = attachment.get('status');
const title = status.get('spoiler_text') || attachment.get('description');
let thumbnail = '';
let icon;
if (attachment.get('type') === 'unknown') {
// Skip
} else if (attachment.get('type') === 'image') {
const focusX = attachment.getIn(['meta', 'focus', 'x']) || 0;
const focusY = attachment.getIn(['meta', 'focus', 'y']) || 0;
const x = ((focusX / 2) + .5) * 100;
const y = ((focusY / -2) + .5) * 100;
thumbnail = (
<img
src={attachment.get('preview_url')}
alt={attachment.get('description')}
title={attachment.get('description')}
style={{ objectPosition: `${x}% ${y}%` }}
onLoad={this.handleImageLoad}
/>
);
} else if (['gifv', 'video'].indexOf(attachment.get('type')) !== -1) {
const autoPlay = !isIOS() && autoPlayGif;
thumbnail = (
<div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}>
<video
className='media-gallery__item-gifv-thumbnail'
aria-label={attachment.get('description')}
title={attachment.get('description')}
role='application'
src={attachment.get('url')}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
autoPlay={autoPlay}
preload='auto'
loop
muted
playsInline
/>
<span className='media-gallery__gifv__label'>GIF</span>
</div>
);
}
if (!visible) {
icon = (
<span className='account-gallery__item__icons'>
<Icon id='eye-slash' />
</span>
);
}
return (
<div className='account-gallery__item' style={{ width, height }}>
<a href={status.get('url')} target='_blank' onClick={this.handleClick} title={title}>
<canvas width={32} height={32} ref={this.setCanvasRef} className={classNames('media-gallery__preview', { 'media-gallery__preview--hidden': visible && loaded })} />
{visible && thumbnail}
{!visible && icon}
</a>
</div>
);
}
}

View File

@ -1,17 +0,0 @@
.account-gallery__item {
border: none;
box-sizing: border-box;
display: block;
position: relative;
border-radius: 4px;
overflow: hidden;
margin: 2px;
&__icons {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
}
}

View File

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

View File

@ -1,55 +1,30 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { List as ImmutableList } from 'immutable';
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
import { fetchAccount, fetchAccountByUsername } from '../../actions/accounts';
import { expandAccountFeaturedTimeline, expandAccountTimeline } from '../../actions/timelines';
import { fetchAccountIdentityProofs } from '../../actions/identity_proofs';
import { me } from '../../initial_state';
import StatusList from '../../components/status_list/status_list';
import ColumnIndicator from '../../components/column_indicator';
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { List as ImmutableList } from 'immutable'
import { injectIntl, defineMessages } from 'react-intl'
import { expandAccountFeaturedTimeline, expandAccountTimeline } from '../../actions/timelines'
import { fetchAccountIdentityProofs } from '../../actions/identity_proofs'
import StatusList from '../../components/status_list/status_list'
const messages = defineMessages({
posts: { id: 'account.posts', defaultMessage: 'Gabs' },
postsWithReplies: { id: 'account.posts_with_replies', defaultMessage: 'Gabs and replies' },
media: { id: 'account.media', defaultMessage: 'Media' },
error: { id: 'empty_column.account_unavailable', defaultMessage: 'Profile unavailable' },
});
empty: { id: 'empty_column.account_timeline', defaultMessage: 'No gabs here!' },
})
const emptyList = ImmutableList();
const emptyList = ImmutableList()
const mapStateToProps = (state, { params: { username }, withReplies = false }) => {
const accounts = state.getIn(['accounts']);
const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() == username.toLowerCase());
const mapStateToProps = (state, { account, withReplies = false }) => {
const accountId = !!account ? account.getIn(['id'], null) : -1
let accountId = -1;
let accountUsername = username;
if (accountFetchError) {
accountId = null;
} else {
let account = accounts.find(acct => username.toLowerCase() == acct.getIn(['acct'], '').toLowerCase());
accountId = account ? account.getIn(['id'], null) : -1;
accountUsername = account ? account.getIn(['acct'], '') : '';
}
const path = withReplies ? `${accountId}:with_replies` : accountId;
const isBlocked = state.getIn(['relationships', accountId, 'blocked_by'], false);
const isLocked = state.getIn(['accounts', accountId, 'locked'], false);
const isFollowing = state.getIn(['relationships', accountId, 'following'], false);
const unavailable = (me == accountId) ? false : (isBlocked || (isLocked && !isFollowing));
const path = withReplies ? `${accountId}:with_replies` : accountId
return {
accountId,
unavailable,
accountUsername,
isAccount: !!state.getIn(['accounts', accountId]),
statusIds: state.getIn(['timelines', `account:${path}`, 'items'], emptyList),
featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], emptyList),
isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']),
hasMore: state.getIn(['timelines', `account:${path}`, 'hasMore']),
};
};
}
}
export default
@connect(mapStateToProps)
@ -64,57 +39,56 @@ class AccountTimeline extends ImmutablePureComponent {
isLoading: PropTypes.bool,
hasMore: PropTypes.bool,
withReplies: PropTypes.bool,
isAccount: PropTypes.bool,
unavailable: PropTypes.bool,
intl: PropTypes.object.isRequired,
};
}
componentWillMount() {
const { params: { username }, accountId, withReplies } = this.props;
const { accountId, withReplies } = this.props
if (accountId && accountId !== -1) {
this.props.dispatch(fetchAccount(accountId));
this.props.dispatch(fetchAccountIdentityProofs(accountId));
this.props.dispatch(fetchAccountIdentityProofs(accountId))
if (!withReplies) {
this.props.dispatch(expandAccountFeaturedTimeline(accountId));
this.props.dispatch(expandAccountFeaturedTimeline(accountId))
}
this.props.dispatch(expandAccountTimeline(accountId, { withReplies }));
} else {
this.props.dispatch(fetchAccountByUsername(username));
this.props.dispatch(expandAccountTimeline(accountId, { withReplies }))
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.accountId && nextProps.accountId !== -1 && (nextProps.accountId !== this.props.accountId && nextProps.accountId) || nextProps.withReplies !== this.props.withReplies) {
this.props.dispatch(fetchAccount(nextProps.accountId));
this.props.dispatch(fetchAccountIdentityProofs(nextProps.accountId));
this.props.dispatch(fetchAccountIdentityProofs(nextProps.accountId))
if (!nextProps.withReplies) {
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, { withReplies: nextProps.withReplies }))
}
}
handleLoadMore = maxId => {
if (this.props.accountId && this.props.accountId !== -1) {
this.props.dispatch(expandAccountTimeline(this.props.accountId, { maxId, withReplies: this.props.withReplies }));
this.props.dispatch(expandAccountTimeline(this.props.accountId, {
maxId,
withReplies: this.props.withReplies
}))
}
}
render() {
const { statusIds, featuredStatusIds, isLoading, hasMore, isAccount, accountId, unavailable, accountUsername, intl } = this.props;
const {
account,
statusIds,
featuredStatusIds,
isLoading,
hasMore,
intl
} = this.props
if (!account) return null
if (!isAccount && accountId !== -1) {
return <ColumnIndicator type='missing' />
} else if (accountId === -1 || (!statusIds && isLoading)) {
return <ColumnIndicator type='loading' />
} else if (unavailable) {
return <ColumnIndicator type='error' message={intl.formatMessage(messages.error)} />
}
return (
<StatusList
scrollKey='account_timeline'
@ -123,9 +97,9 @@ class AccountTimeline extends ImmutablePureComponent {
isLoading={isLoading}
hasMore={hasMore}
onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.account_timeline' defaultMessage='No gabs here!' />}
emptyMessage={intl.formatMessage(messages.empty)}
/>
);
)
}
}

View File

@ -12,7 +12,7 @@ export default class Header extends ImmutablePureComponent {
onBlock: PropTypes.func.isRequired,
onMention: PropTypes.func.isRequired,
onDirect: PropTypes.func.isRequired,
onReblogToggle: PropTypes.func.isRequired,
onRepostToggle: PropTypes.func.isRequired,
onReport: PropTypes.func.isRequired,
onMute: PropTypes.func.isRequired,
onBlockDomain: PropTypes.func.isRequired,
@ -47,8 +47,8 @@ export default class Header extends ImmutablePureComponent {
this.props.onReport(this.props.account);
}
handleReblogToggle = () => {
this.props.onReblogToggle(this.props.account);
handleRepostToggle = () => {
this.props.onRepostToggle(this.props.account);
}
handleMute = () => {
@ -94,7 +94,7 @@ export default class Header extends ImmutablePureComponent {
onBlock={this.handleBlock}
onMention={this.handleMention}
onDirect={this.handleDirect}
onReblogToggle={this.handleReblogToggle}
onRepostToggle={this.handleRepostToggle}
onReport={this.handleReport}
onMute={this.handleMute}
onBlockDomain={this.handleBlockDomain}

View File

@ -114,9 +114,9 @@ class Header extends ImmutablePureComponent {
} else {
if (account.getIn(['relationship', 'following'])) {
if (account.getIn(['relationship', 'showing_reblogs'])) {
menu.push({ text: intl.formatMessage(messages.hideReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
menu.push({ text: intl.formatMessage(messages.hideReblogs, { name: account.get('username') }), action: this.props.onRepostToggle });
} else {
menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onRepostToggle });
}
menu.push({ text: intl.formatMessage(messages.add_or_remove_from_list), action: this.props.onAddToList });
@ -279,7 +279,7 @@ class Header extends ImmutablePureComponent {
<NavLink exact activeClassName='active' to={`/${account.get('acct')}/favorites`} title={intl.formatNumber(account.get('favourite_count'))}>
{ /* : TODO : shortNumberFormat(account.get('favourite_count')) */ }
<span></span>
<FormattedMessage id='navigation_bar.favourites' defaultMessage='Favorites' />
<FormattedMessage id='navigation_bar.favorites' defaultMessage='Favorites' />
</NavLink>
<NavLink exact activeClassName='active' to={`/${account.get('acct')}/pins`} title={intl.formatNumber(account.get('pinned_count'))}>
{ /* : TODO : shortNumberFormat(account.get('pinned_count')) */ }

View File

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

View File

@ -1,135 +0,0 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { List as ImmutableList } from 'immutable';
import Icon from '../../../../components/icon';
import VerifiedIcon from '../../../../components/verified_icon';
import Badge from '../../../../components/badge';
const messages = defineMessages({
linkVerifiedOn: { id: 'account.link_verified_on', defaultMessage: 'Ownership of this link was checked on {date}' },
account_locked: { id: 'account.locked_info', defaultMessage: 'This account privacy status is set to locked. The owner manually reviews who can follow them.' },
bot: { id: 'account.badges.bot', defaultMessage: 'Bot' },
memberSince: { id: 'account.member_since', defaultMessage:'Member since {date}'},
});
const dateFormatOptions = {
month: 'short',
day: 'numeric',
year: 'numeric',
hour12: false,
hour: '2-digit',
minute: '2-digit',
};
const mapStateToProps = (state, { account }) => {
const identity_proofs = account ? state.getIn(['identity_proofs', account.get('id')], ImmutableList()) : ImmutableList();
return {
identity_proofs,
domain: state.getIn(['meta', 'domain']),
};
};
export default
@connect(mapStateToProps)
@injectIntl
class ProfileInfoPanel extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map,
identity_proofs: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired,
username: PropTypes.string,
};
render () {
const { account, intl, identity_proofs, username } = this.props;
if (!account) {
return (
<div className='profile-info-panel'>
<div className='profile-info-panel__content'>
<div className='profile-info-panel-content__name'>
<h1>
<span/>
<small>@{username}</small>
</h1>
</div>
</div>
</div>
);
}
const lockedIcon = account.get('locked') ? (<Icon id='lock' title={intl.formatMessage(messages.account_locked)} />) : '';
const badge = account.get('bot') ? (<div className='account-role bot'>{intl.formatMessage(messages.bot)}</div>) : null;
const content = { __html: account.get('note_emojified') };
const fields = account.get('fields');
const acct = account.get('acct');
const displayNameHtml = { __html: account.get('display_name_html') };
const memberSinceDate = intl.formatDate(account.get('created_at'), { month: 'long', year: 'numeric' });
return (
<div className='profile-info-panel'>
<div className='profile-info-panel__content'>
<div className='profile-info-panel-content__name'>
<h1>
<span dangerouslySetInnerHTML={displayNameHtml} />
{account.get('is_verified') && <VerifiedIcon />}
{badge}
<small>@{acct} {lockedIcon}</small>
</h1>
</div>
<div className='profile-info-panel-content__badges'>
{account.get('is_pro') && <Badge type='pro' />}
{account.get('is_donor') && <Badge type='donor' />}
{account.get('is_investor') && <Badge type='investor' />}
<div className='profile-info-panel-content__badges__join-date'>
<Icon id="calendar"/>
{intl.formatMessage(messages.memberSince, {
date: memberSinceDate
})}
</div>
</div>
{
(account.get('note').length > 0 && account.get('note') !== '<p></p>') &&
<div className='profile-info-panel-content__bio' dangerouslySetInnerHTML={content} />
}
{(fields.size > 0 || identity_proofs.size > 0) && (
<div className='profile-info-panel-content__fields'>
{identity_proofs.map((proof, i) => (
<dl className='test' key={i}>
<dt dangerouslySetInnerHTML={{ __html: proof.get('provider') }} />
<dd className='verified'>
<a href={proof.get('proof_url')} target='_blank' rel='noopener'>
<span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(proof.get('updated_at'), dateFormatOptions) })}>
<Icon id='check' className='verified__mark' />
</span>
</a>
<a href={proof.get('profile_url')} target='_blank' rel='noopener'>
<span dangerouslySetInnerHTML={{ __html: ' ' + proof.get('provider_username') }} />
</a>
</dd>
</dl>
))}
{fields.map((pair, i) => (
<dl className='profile-info-panel-content__fields__item' key={i}>
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} />
<dd className={pair.get('verified_at') && 'verified'} title={pair.get('value_plain')}>
{pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><Icon id='check' className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} />
</dd>
</dl>
))}
</div>
)}
</div>
</div>
);
}
}

View File

@ -1,133 +0,0 @@
.profile-info-panel {
display: flex;
position: relative;
&__content {
display: flex;
flex-direction: column;
flex: 1 1;
@media (min-width:895px) {
padding-top: 60px;
}
}
}
.profile-info-panel-content {
display: flex;
&__badges {
display: flex;
margin: 5px 0;
flex-direction: row;
flex-wrap: wrap;
&__join-date {
display: block;
margin-top: 5px;
.fa {
margin-right: 8px;
}
span {
color: $primary-text-color;
@include text-sizing(15px, 400, 1.25);
body.theme-gabsocial-light & {
color: $gab-default-text-light;
}
}
}
}
&__name {
display: block;
.account-role {
vertical-align: top;
}
.emojione {
@include size(22px);
}
h1 {
span:first-of-type {
color: #ffffff;
@include text-overflow(nowrap);
@include text-sizing(20px, 600, 1.25);
body.theme-gabsocial-light & {
color: $gab-default-text-light;
}
}
small {
display: block;
color: $secondary-text-color;
@include text-sizing(16px, 400, 1.5);
@include text-overflow;
}
}
}
&__bio {
display: block;
flex: 1 1;
color: $primary-text-color;
margin: 15px 0;
@include text-sizing(15px, 400, 1.25);
a {
color: lighten($ui-highlight-color, 8%);
}
}
&__fields {
display: flex;
flex-direction: column;
border-top: 1px solid lighten($ui-base-color, 12%);
padding: 10px 0;
margin: 5px 0;
@media screen and (max-width:895px) {
border-bottom: 1px solid lighten($ui-base-color, 12%);
}
a {
color: lighten($ui-highlight-color, 8%);
}
dl:first-child .verified {
border-radius: 0 4px 0 0;
}
.verified a {
color: $valid-value-color;
}
&__item {
display: flex;
padding: 2px 0;
margin: 2px 0;
flex: 1 1;
* {
@include text-sizing(15px, 400, 24px);
}
dt {
min-width: 26px;
}
dd {
padding-left: 4px;
}
}
}
}

View File

@ -83,7 +83,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
dispatch(directCompose(account, router));
},
onReblogToggle (account) {
onRepostToggle (account) {
if (account.getIn(['relationship', 'showing_reblogs'])) {
dispatch(followAccount(account.get('id'), false));
} else {

View File

@ -1,11 +1,11 @@
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 ColumnIndicator from '../../components/column_indicator';
import AccountContainer from '../../containers/account_container';
import { fetchBlocks, expandBlocks } from '../../actions/blocks';
import ScrollableList from '../../components/scrollable_list';
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 ColumnIndicator from '../components/column_indicator'
import AccountContainer from '../containers/account_container'
import { fetchBlocks, expandBlocks } from '../actions/blocks'
import ScrollableList from '../components/scrollable_list'
const messages = defineMessages({
heading: { id: 'column.blocks', defaultMessage: 'Blocked users' },

View File

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

View File

@ -2,10 +2,10 @@ 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 { fetchDomainBlocks, expandDomainBlocks } from '../actions/domain_blocks';
import DomainContainer from '../containers/domain_container';
import ColumnIndicator from '../components/column_indicator';
import ScrollableList from '../components/scrollable_list';
const messages = defineMessages({
heading: { id: 'column.domain_blocks', defaultMessage: 'Hidden domains' },

View File

@ -1 +1 @@
export { default } from './domain_blocks'
export { default } from '../domain_blocks'

View File

@ -1,7 +1,7 @@
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { FormattedMessage } from 'react-intl';
import { fetchReblogs } from '../../actions/interactions';
import { fetchReposts } from '../../actions/interactions';
import { fetchStatus } from '../../actions/statuses';
import { makeGetStatus } from '../../selectors';
import AccountContainer from '../../containers/account_container';
@ -33,13 +33,13 @@ class Favorites extends ImmutablePureComponent {
};
componentWillMount() {
this.props.dispatch(fetchReblogs(this.props.params.statusId));
this.props.dispatch(fetchReposts(this.props.params.statusId));
this.props.dispatch(fetchStatus(this.props.params.statusId));
}
componentWillReceiveProps(nextProps) {
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
this.props.dispatch(fetchReblogs(nextProps.params.statusId));
this.props.dispatch(fetchReposts(nextProps.params.statusId));
this.props.dispatch(fetchStatus(nextProps.params.statusId));
}
}

View File

@ -0,0 +1,100 @@
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { debounce } from 'lodash'
import { defineMessages, injectIntl } from 'react-intl'
import {
fetchFollowers,
expandFollowers,
} from '../actions/accounts'
import AccountContainer from '../containers/account_container'
import ScrollableList from '../components/scrollable_list'
import Block from '../components/block'
import Heading from '../components/heading'
const mapStateToProps = (state, { account }) => {
const accountId = !!account ? account.get('id') : -1
return {
accountId,
accountIds: state.getIn(['user_lists', 'followers', accountId, 'items']),
hasMore: !!state.getIn(['user_lists', 'followers', accountId, 'next']),
}
}
const messages = defineMessages({
followers: { id: 'account.followers', defaultMessage: 'Followers' },
empty: { id: 'account.followers.empty', defaultMessage: 'No one follows this user yet.' },
})
export default
@connect(mapStateToProps)
@injectIntl
class Followers extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map,
accountId: PropTypes.string,
intl: PropTypes.object.isRequired,
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool,
}
componentWillMount() {
const { accountId } = this.props
if (accountId && accountId !== -1) {
this.props.dispatch(fetchFollowers(accountId))
}
}
componentWillReceiveProps(nextProps) {
if (!!nextProps.accountId && nextProps.accountId !== -1 && nextProps.accountId !== this.props.accountId) {
this.props.dispatch(fetchFollowers(nextProps.accountId))
}
}
handleLoadMore = debounce(() => {
const { accountId } = this.props
if (!!accountId && accountId !== -1) {
this.props.dispatch(expandFollowers(accountId))
}
}, 300, { leading: true })
render() {
const {
account,
accountIds,
hasMore,
intl
} = this.props
if (!account) return null
return (
<Block>
<div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX, _s.justifyContentCenter, _s.borderColorSecondary, _s.borderBottom1PX].join(' ')}>
<Heading size='h3'>
{intl.formatMessage(messages.followers)}
</Heading>
</div>
<div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX].join(' ')}>
<ScrollableList
scrollKey='followers'
hasMore={hasMore}
onLoadMore={this.handleLoadMore}
emptyMessage={intl.formatMessage(messages.empty)}
>
{
!!accountIds && accountIds.map((id) => (
<AccountContainer key={`follower-${id}`} id={id} withNote={false} compact />
))
}
</ScrollableList>
</div>
</Block>
)
}
}

View File

@ -1,132 +0,0 @@
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { debounce } from 'lodash'
import { defineMessages, injectIntl } from 'react-intl'
import ColumnIndicator from '../../components/column_indicator'
import {
fetchAccount,
fetchFollowers,
expandFollowers,
fetchAccountByUsername,
} from '../../actions/accounts'
import { me } from '../../initial_state'
import AccountContainer from '../../containers/account_container'
import ScrollableList from '../../components/scrollable_list'
import Block from '../../components/block'
import Heading from '../../components/heading'
const mapStateToProps = (state, { params: { username } }) => {
const accounts = state.getIn(['accounts'])
const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() == username.toLowerCase())
let accountId = -1
if (accountFetchError) {
accountId = null
} else {
let account = accounts.find(acct => username.toLowerCase() == acct.getIn(['acct'], '').toLowerCase())
accountId = account ? account.getIn(['id'], null) : -1
}
const isBlocked = state.getIn(['relationships', accountId, 'blocked_by'], false)
const isLocked = state.getIn(['accounts', accountId, 'locked'], false)
const isFollowing = state.getIn(['relationships', accountId, 'following'], false)
const unavailable = (me == accountId) ? false : (isBlocked || (isLocked && !isFollowing))
return {
accountId,
unavailable,
isAccount: !!state.getIn(['accounts', accountId]),
accountIds: state.getIn(['user_lists', 'followers', accountId, 'items']),
hasMore: !!state.getIn(['user_lists', 'followers', accountId, 'next']),
}
}
const messages = defineMessages({
followers: { id: 'account.followers', defaultMessage: 'Followers' },
empty: { id: 'account.followers.empty', defaultMessage: 'No one follows this user yet.' },
unavailable: { id: 'empty_column.account_unavailable', defaultMessage: 'Profile unavailable' },
})
export default
@connect(mapStateToProps)
@injectIntl
class Followers extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool,
isAccount: PropTypes.bool,
unavailable: PropTypes.bool,
}
componentWillMount() {
const { params: { username }, accountId } = this.props
if (accountId && accountId !== -1) {
this.props.dispatch(fetchAccount(accountId))
this.props.dispatch(fetchFollowers(accountId))
} else {
this.props.dispatch(fetchAccountByUsername(username))
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.accountId && nextProps.accountId !== -1 && (nextProps.accountId !== this.props.accountId && nextProps.accountId)) {
this.props.dispatch(fetchAccount(nextProps.accountId))
this.props.dispatch(fetchFollowers(nextProps.accountId))
}
}
handleLoadMore = debounce(() => {
if (this.props.accountId && this.props.accountId !== -1) {
this.props.dispatch(expandFollowers(this.props.accountId))
}
}, 300, { leading: true })
render() {
const {
accountIds,
hasMore,
isAccount,
accountId,
unavailable,
intl
} = this.props
if (!isAccount && accountId !== -1) {
return <ColumnIndicator type='missing' />
} else if (accountId === -1 || (!accountIds)) {
return <ColumnIndicator type='loading' />
} else if (unavailable) {
return <ColumnIndicator type='error' message={intl.formatMessage(messages.unavailable)} />
}
return (
<Block>
<div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX, _s.justifyContentCenter, _s.borderColorSecondary, _s.borderBottom1PX].join(' ')}>
<Heading size='h3'>
{intl.formatMessage(messages.followers)}
</Heading>
</div>
<div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX].join(' ')}>
<ScrollableList
scrollKey='followers'
hasMore={hasMore}
onLoadMore={this.handleLoadMore}
emptyMessage={intl.formatMessage(messages.empty)}
>
{
accountIds.map((id, i) => (
<AccountContainer key={id} id={id} withNote={false} compact />
))
}
</ScrollableList>
</div>
</Block>
)
}
}

View File

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

View File

@ -0,0 +1,100 @@
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { debounce } from 'lodash'
import { defineMessages, injectIntl } from 'react-intl'
import {
fetchFollowing,
expandFollowing,
} from '../actions/accounts'
import AccountContainer from '../containers/account_container'
import ScrollableList from '../components/scrollable_list'
import Block from '../components/block'
import Heading from '../components/heading'
const mapStateToProps = (state, { account }) => {
const accountId = !!account ? account.get('id') : -1
return {
accountId,
accountIds: state.getIn(['user_lists', 'following', accountId, 'items']),
hasMore: !!state.getIn(['user_lists', 'following', accountId, 'next']),
}
}
const messages = defineMessages({
follows: { id: 'account.follows', defaultMessage: 'Follows' },
empty: { id: 'account.follows.empty', defaultMessage: 'This user doesn\'t follow anyone yet.' },
})
export default
@connect(mapStateToProps)
@injectIntl
class Following extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list,
account: ImmutablePropTypes.map,
accountId: PropTypes.string,
hasMore: PropTypes.bool,
}
componentWillMount() {
const { accountId } = this.props
if (!!accountId && accountId !== -1) {
this.props.dispatch(fetchFollowing(accountId))
}
}
componentWillReceiveProps(nextProps) {
if (!!nextProps.accountId && nextProps.accountId !== -1 && nextProps.accountId !== this.props.accountId) {
this.props.dispatch(fetchFollowing(nextProps.accountId))
}
}
handleLoadMore = debounce(() => {
const { accountId } = this.props
if (!!accountId && accountId !== -1) {
this.props.dispatch(expandFollowing(accountId))
}
}, 300, { leading: true })
render() {
const {
account,
accountIds,
hasMore,
intl
} = this.props
if (!account) return null
return (
<Block>
<div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX, _s.justifyContentCenter, _s.borderColorSecondary, _s.borderBottom1PX].join(' ')}>
<Heading size='h3'>
{intl.formatMessage(messages.follows)}
</Heading>
</div>
<div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX].join(' ')}>
<ScrollableList
scrollKey='following'
hasMore={hasMore}
onLoadMore={this.handleLoadMore}
emptyMessage={intl.formatMessage(messages.empty)}
>
{
!!accountIds && accountIds.map((id) => (
<AccountContainer key={`following-${id}`} id={id} withNote={false} compact />
))
}
</ScrollableList>
</div>
</Block>
)
}
}

View File

@ -1,134 +0,0 @@
import { Fragment } from 'react'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { debounce } from 'lodash'
import { defineMessages, injectIntl } from 'react-intl'
import {
fetchAccount,
fetchFollowing,
expandFollowing,
fetchAccountByUsername,
} from '../../actions/accounts'
import { me } from '../../initial_state'
import AccountContainer from '../../containers/account_container'
import ColumnIndicator from '../../components/column_indicator'
import ScrollableList from '../../components/scrollable_list'
import Block from '../../components/block'
import Divider from '../../components/divider'
import Heading from '../../components/heading'
const mapStateToProps = (state, { params: { username } }) => {
const accounts = state.getIn(['accounts'])
const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() == username.toLowerCase())
let accountId = -1
if (accountFetchError) {
accountId = null
} else {
let account = accounts.find(acct => username.toLowerCase() == acct.getIn(['acct'], '').toLowerCase())
accountId = account ? account.getIn(['id'], null) : -1
}
const isBlocked = state.getIn(['relationships', accountId, 'blocked_by'], false)
const isLocked = state.getIn(['accounts', accountId, 'locked'], false)
const isFollowing = state.getIn(['relationships', accountId, 'following'], false)
const unavailable = (me == accountId) ? false : (isBlocked || (isLocked && !isFollowing))
return {
accountId,
unavailable,
isAccount: !!state.getIn(['accounts', accountId]),
accountIds: state.getIn(['user_lists', 'following', accountId, 'items']),
hasMore: !!state.getIn(['user_lists', 'following', accountId, 'next']),
}
}
const messages = defineMessages({
follows: { id: 'account.follows', defaultMessage: 'Follows' },
empty: { id: 'account.follows.empty', defaultMessage: 'This user doesn\'t follow anyone yet.' },
unavailable: { id: 'empty_column.account_unavailable', defaultMessage: 'Profile unavailable' },
})
export default
@connect(mapStateToProps)
@injectIntl
class Following extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool,
isAccount: PropTypes.bool,
unavailable: PropTypes.bool,
}
componentWillMount() {
const { params: { username }, accountId } = this.props
if (accountId && accountId !== -1) {
this.props.dispatch(fetchAccount(accountId))
this.props.dispatch(fetchFollowing(accountId))
} else {
this.props.dispatch(fetchAccountByUsername(username))
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.accountId && nextProps.accountId !== -1 && (nextProps.accountId !== this.props.accountId && nextProps.accountId)) {
this.props.dispatch(fetchAccount(nextProps.accountId))
this.props.dispatch(fetchFollowing(nextProps.accountId))
}
}
handleLoadMore = debounce(() => {
if (this.props.accountId && this.props.accountId !== -1) {
this.props.dispatch(expandFollowing(this.props.accountId))
}
}, 300, { leading: true })
render() {
const {
accountIds,
hasMore,
isAccount,
accountId,
unavailable,
intl
} = this.props
if (!isAccount && accountId !== -1) {
return <ColumnIndicator type='missing' />
} else if (accountId === -1 || (!accountIds)) {
return <ColumnIndicator type='loading' />
} else if (unavailable) {
return <ColumnIndicator type='error' message={intl.formatMessage(messages.unavailable)} />
}
return (
<Block>
<div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX, _s.justifyContentCenter, _s.borderColorSecondary, _s.borderBottom1PX].join(' ')}>
<Heading size='h3'>
{intl.formatMessage(messages.follows)}
</Heading>
</div>
<div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX].join(' ')}>
<ScrollableList
scrollKey='following'
hasMore={hasMore}
onLoadMore={this.handleLoadMore}
emptyMessage={intl.formatMessage(messages.empty)}
>
{
accountIds.map((id) => (
<AccountContainer key={id} id={id} withNote={false} compact />
))
}
</ScrollableList>
</div>
</Block>
)
}
}

View File

@ -1 +1 @@
export { default } from './following'
export { default } from '../following'

View File

@ -1,4 +1,4 @@
import ColumnIndicator from '../../components/column_indicator';
import ColumnIndicator from '../components/column_indicator';
export default class GenericNotFound extends PureComponent {

View File

@ -1 +1 @@
export { default } from './generic_not_found'
export { default } from '../generic_not_found'

View File

@ -3,11 +3,11 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
import { injectIntl, defineMessages } from 'react-intl'
import { Link } from 'react-router-dom'
import classNames from 'classnames'
import { connectGroupStream } from '../../actions/streaming'
import { expandGroupTimeline } from '../../actions/timelines'
import StatusListContainer from '../../containers/status_list_container'
import { connectGroupStream } from '../actions/streaming'
import { expandGroupTimeline } from '../actions/timelines'
import StatusListContainer from '../containers/status_list_container'
// import ColumnSettingsContainer from './containers/column_settings_container'
import ColumnIndicator from '../../components/column_indicator'
import ColumnIndicator from '../components/column_indicator'
const messages = defineMessages({
tabLatest: { id: 'group.timeline.tab_latest', defaultMessage: 'Latest' },

View File

@ -1 +1 @@
export { default } from './group_timeline'
export { default } from '../group_timeline'

View File

@ -1,8 +1,8 @@
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { fetchGroups } from '../../actions/groups'
import Block from '../../components/block'
import GroupCollectionItem from '../../components/group_collection_item'
import { fetchGroups } from '../actions/groups'
import Block from '../components/block'
import GroupCollectionItem from '../components/group_collection_item'
const mapStateToProps = (state, { activeTab }) => ({
groupIds: state.getIn(['group_lists', activeTab]),

View File

@ -1 +1 @@
export { default } from './groups_collection'
export { default } from '../groups_collection'

View File

@ -1,8 +1,8 @@
import { FormattedMessage } from 'react-intl'
import { isEqual } from 'lodash'
import { expandHashtagTimeline, clearTimeline } from '../../actions/timelines'
import { connectHashtagStream } from '../../actions/streaming'
import StatusListContainer from '../../containers/status_list_container'
import { expandHashtagTimeline, clearTimeline } from '../actions/timelines'
import { connectHashtagStream } from '../actions/streaming'
import StatusListContainer from '../containers/status_list_container'
const mapStateToProps = (state, props) => ({
hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}`, 'unread']) > 0,

View File

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

View File

@ -115,10 +115,10 @@ class FrameInteractions extends Component {
<div>
<h3>
<FormattedMessage id='introduction.interactions.favourite.headline' defaultMessage='Favorite' />
<FormattedMessage id='introduction.interactions.favorite.headline' defaultMessage='Favorite' />
</h3>
<p>
<FormattedMessage id='introduction.interactions.favourite.text' defaultMessage='You can save a gab for later, and let the author know that you liked it, by favouriting it.' />
<FormattedMessage id='introduction.interactions.favorite.text' defaultMessage='You can save a gab for later, and let the author know that you liked it, by favoriting it.' />
</p>
</div>
</div>

View File

@ -1,13 +1,13 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import { connectListStream } from '../../actions/streaming';
import { expandListTimeline } from '../../actions/timelines';
import { fetchList, deleteList } from '../../actions/lists';
import { openModal } from '../../actions/modal';
import StatusListContainer from '../../containers/status_list_container';
import ColumnIndicator from '../../components/column_indicator';
import Button from '../../components/button';
import { connectListStream } from '../actions/streaming';
import { expandListTimeline } from '../actions/timelines';
import { fetchList, deleteList } from '../actions/lists';
import { openModal } from '../actions/modal';
import StatusListContainer from '../containers/status_list_container';
import ColumnIndicator from '../components/column_indicator';
import Button from '../components/button';
const messages = defineMessages({
deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' },

View File

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

View File

@ -2,10 +2,10 @@ 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 { fetchMutes, expandMutes } from '../../actions/mutes';
import AccountContainer from '../../containers/account_container';
import ColumnIndicator from '../../components/column_indicator';
import ScrollableList from '../../components/scrollable_list';
import { fetchMutes, expandMutes } from '../actions/mutes';
import AccountContainer from '../containers/account_container';
import ColumnIndicator from '../components/column_indicator';
import ScrollableList from '../components/scrollable_list';
const messages = defineMessages({
heading: { id: 'column.mutes', defaultMessage: 'Muted users' },

View File

@ -1 +1 @@
export { default } from './mutes'
export { default } from '../mutes'

View File

@ -51,12 +51,12 @@ export default class ColumnSettings extends ImmutablePureComponent {
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'follow']} onChange={onChange} label={soundStr} />
</div>
<div role='group' aria-labelledby='notifications-favourite'>
<FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favorites:' />
<SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'favourite']} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'favourite']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'favourite']} onChange={onChange} label={soundStr} />
<div role='group' aria-labelledby='notifications-favorite'>
<FormattedMessage id='notifications.column_settings.favorite' defaultMessage='Favorites:' />
<SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'favorite']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'favorite']} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'favorite']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'favorite']} onChange={onChange} label={soundStr} />
</div>
<div role='group' aria-labelledby='notifications-mention'>

View File

@ -29,8 +29,8 @@ class Notification extends ImmutablePureComponent {
onMoveUp: PropTypes.func.isRequired,
onMoveDown: PropTypes.func.isRequired,
onMention: PropTypes.func.isRequired,
onFavourite: PropTypes.func.isRequired,
onReblog: PropTypes.func.isRequired,
onFavorite: PropTypes.func.isRequired,
onRepost: PropTypes.func.isRequired,
onToggleHidden: PropTypes.func.isRequired,
status: ImmutablePropTypes.map,
intl: PropTypes.object.isRequired,
@ -72,14 +72,14 @@ class Notification extends ImmutablePureComponent {
onMention(notification.get('account'), this.context.router.history);
}
handleHotkeyFavourite = () => {
handleHotkeyFavorite = () => {
const { status } = this.props;
if (status) this.props.onFavourite(status);
if (status) this.props.onFavorite(status);
}
handleHotkeyBoost = e => {
const { status } = this.props;
if (status) this.props.onReblog(status, e);
if (status) this.props.onRepost(status, e);
}
handleHotkeyToggleHidden = () => {
@ -90,7 +90,7 @@ class Notification extends ImmutablePureComponent {
getHandlers() {
return {
reply: this.handleMention,
favourite: this.handleHotkeyFavourite,
favorite: this.handleHotkeyFavorite,
boost: this.handleHotkeyBoost,
mention: this.handleMention,
open: this.handleOpen,
@ -108,7 +108,7 @@ class Notification extends ImmutablePureComponent {
<HotKeys handlers={this.getHandlers()}>
<div className='notification notification--follow focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.follow', defaultMessage: '{name} followed you' }, { name: account.get('acct') }), notification.get('created_at'))}>
<div className='notification__message'>
<div className='notification__favourite-icon-wrapper'>
<div className='notification__favorite-icon-wrapper'>
<Icon id='user-plus' fixedWidth />
</div>
@ -140,19 +140,19 @@ class Notification extends ImmutablePureComponent {
);
}
renderFavourite(notification, link) {
renderFavorite(notification, link) {
const { intl } = this.props;
return (
<HotKeys handlers={this.getHandlers()}>
<div className='notification notification--favourite focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.favourite', defaultMessage: '{name} favorited your status' }, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
<div className='notification notification--favorite focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.favorite', defaultMessage: '{name} favorited your status' }, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
<div className='notification__message'>
<div className='notification__favourite-icon-wrapper'>
<div className='notification__favorite-icon-wrapper'>
<Icon id='star' className='star-icon' fixedWidth />
</div>
<span title={notification.get('created_at')}>
<FormattedMessage id='notification.favourite' defaultMessage='{name} favorited your status' values={{ name: link }} />
<FormattedMessage id='notification.favorite' defaultMessage='{name} favorited your status' values={{ name: link }} />
</span>
</div>
@ -173,14 +173,14 @@ class Notification extends ImmutablePureComponent {
);
}
renderReblog(notification, link) {
renderRepost(notification, link) {
const { intl } = this.props;
return (
<HotKeys handlers={this.getHandlers()}>
<div className='notification notification--reblog focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.reblog', defaultMessage: '{name} reposted your status' }, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
<div className='notification__message'>
<div className='notification__favourite-icon-wrapper'>
<div className='notification__favorite-icon-wrapper'>
<Icon id='retweet' fixedWidth />
</div>
@ -212,7 +212,7 @@ class Notification extends ImmutablePureComponent {
<HotKeys handlers={this.getHandlers()}>
<div className='notification notification--poll focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.poll', defaultMessage: 'A poll you have voted in has ended' }), notification.get('created_at'))}>
<div className='notification__message'>
<div className='notification__favourite-icon-wrapper'>
<div className='notification__favorite-icon-wrapper'>
<Icon id='tasks' fixedWidth />
</div>
@ -261,9 +261,9 @@ class Notification extends ImmutablePureComponent {
// case 'mention':
// return this.renderMention(notification);
case 'favourite':
return this.renderFavourite(notification, link);
return this.renderFavorite(notification, link);
// case 'reblog':
// return this.renderReblog(notification, link);
// return this.renderRepost(notification, link);
// case 'poll':
// return this.renderPoll(notification);
}

View File

@ -1,59 +0,0 @@
.notification {
&--favourite {
.status.status-direct {
background: transparent;
.icon-button.disabled {
color: lighten($action-button-color, 13%);
}
}
}
&--follow {}
&--reblog {}
&--poll {}
&__message {
margin: 0 10px 0 68px;
padding: 8px 0 0;
cursor: default;
color: $gab-secondary-text;
position: relative;
@include text-sizing(15px, 400, 22px);
.fa {
color: $highlight-text-color;
}
>span {
display: inline;
@include text-overflow(nowrap);
}
}
&__display-name {
color: inherit;
font-weight: 500;
text-decoration: none;
&:hover {
color: $primary-text-color;
text-decoration: underline;
}
}
&__favourite-icon-wrapper {
left: -26px;
position: absolute;
.star-icon {
color: $gold-star;
}
}
}

View File

@ -2,7 +2,7 @@ import { defineMessages, injectIntl } from 'react-intl';
const messages = defineMessages({
mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Favorites' },
favorites: { id: 'notifications.filter.favorites', defaultMessage: 'Favorites' },
boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Reposts' },
polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' },
follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
@ -65,9 +65,9 @@ class NotificationFilterBar extends PureComponent {
icon: 'at',
},
{
className: selectedFilter === 'favourite' ? 'active' : '',
onClick: this.onClick('favourite'),
title: intl.formatMessage(messages.favourites),
className: selectedFilter === 'favorite' ? 'active' : '',
onClick: this.onClick('favorite'),
title: intl.formatMessage(messages.favorites),
icon: 'star',
},
{

View File

@ -2,9 +2,9 @@ import { openModal } from '../../../actions/modal'
import { mentionCompose } from '../../../actions/compose'
import {
reblog,
favourite,
favorite,
unreblog,
unfavourite,
unfavorite,
} from '../../../actions/interactions'
import {
hideStatus,
@ -38,27 +38,27 @@ const mapDispatchToProps = dispatch => ({
dispatch(mentionCompose(account, router))
},
onModalReblog (status) {
dispatch(reblog(status))
onModalRepost (status) {
dispatch(repost(status))
},
onReblog (status, e) {
onRepost (status, e) {
if (status.get('reblogged')) {
dispatch(unreblog(status))
dispatch(unrepost(status))
} else {
if (e.shiftKey || !boostModal) {
this.onModalReblog(status)
this.onModalRepost(status)
} else {
dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }))
dispatch(openModal('BOOST', { status, onRepost: this.onModalRepost }))
}
}
},
onFavourite (status) {
if (status.get('favourited')) {
dispatch(unfavourite(status))
onFavorite (status) {
if (status.get('favorited')) {
dispatch(unfavorite(status))
} else {
dispatch(favourite(status))
dispatch(favorite(status))
}
},

View File

@ -1 +1 @@
export { default } from './reblogs'
export { default } from '../reblogs'

View File

@ -1,12 +1,12 @@
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { FormattedMessage } from 'react-intl';
import { fetchReblogs } from '../../actions/interactions';
import { fetchStatus } from '../../actions/statuses';
import { makeGetStatus } from '../../selectors';
import AccountContainer from '../../containers/account_container';
import ColumnIndicator from '../../components/column_indicator';
import ScrollableList from '../../components/scrollable_list';
import { fetchReposts } from '../actions/interactions';
import { fetchStatus } from '../actions/statuses';
import { makeGetStatus } from '../selectors';
import AccountContainer from '../containers/account_container';
import ColumnIndicator from '../components/column_indicator';
import ScrollableList from '../components/scrollable_list';
const mapStateToProps = (state, props) => {
const getStatus = makeGetStatus();
@ -23,7 +23,7 @@ const mapStateToProps = (state, props) => {
export default
@connect(mapStateToProps)
class Reblogs extends ImmutablePureComponent {
class Reposts extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
@ -33,13 +33,13 @@ class Reblogs extends ImmutablePureComponent {
};
componentWillMount () {
this.props.dispatch(fetchReblogs(this.props.params.statusId));
this.props.dispatch(fetchReposts(this.props.params.statusId));
this.props.dispatch(fetchStatus(this.props.params.statusId));
}
componentWillReceiveProps(nextProps) {
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
this.props.dispatch(fetchReblogs(nextProps.params.statusId));
this.props.dispatch(fetchReposts(nextProps.params.statusId));
this.props.dispatch(fetchStatus(nextProps.params.statusId));
}
}
@ -48,19 +48,21 @@ class Reblogs extends ImmutablePureComponent {
const { accountIds, status } = this.props;
if (!accountIds) {
return (<ColumnIndicator type='loading' />);
return <ColumnIndicator type='loading' />
} else if (!status) {
return (<ColumnIndicator type='missing' />);
return <ColumnIndicator type='missing' />
}
return (
<ScrollableList
scrollKey='reblogs'
scrollKey='reposts'
emptyMessage={<FormattedMessage id='status.reblogs.empty' defaultMessage='No one has reposted this gab yet. When someone does, they will show up here.' />}
>
{accountIds.map(id =>
<AccountContainer key={id} id={id} withNote={false} />
)}
{
accountIds.map(id =>
<AccountContainer key={id} id={id} withNote={false} />
)
}
</ScrollableList>
);
}

View File

@ -1,5 +1,5 @@
// import SearchContainer from '../compose/containers/search_container';
import SearchResultsContainer from '../compose/containers/search_results_container';
import SearchResultsContainer from './compose/containers/search_results_container';
export default class Search extends PureComponent {

View File

@ -1,57 +0,0 @@
import { FormattedMessage } from 'react-intl';
const mapStateToProps = state => ({
value: state.getIn(['search', 'value']),
submitted: state.getIn(['search', 'submitted']),
});
export default
@connect(mapStateToProps)
class Header extends PureComponent {
static propTypes = {
value: PropTypes.string,
submitted: PropTypes.bool,
};
state = {
submittedValue: '',
};
componentWillReceiveProps (nextProps) {
if (nextProps.submitted) {
const submittedValue = nextProps.value;
this.setState({submittedValue})
}
}
render () {
const { submittedValue } = this.state;
if (!submittedValue) {
return null;
}
return (
<div className='search-header'>
<div className='search-header__text-container'>
<h1 className='search-header__title-text'>
{submittedValue}
</h1>
</div>
<div className='search-header__type-filters'>
<div className='search-header__type-filters-tabs'>
{ /* <SectionHeadlineBar
items={[
{
to: '/search',
title: <FormattedMessage id='search_results.top' defaultMessage='Top' />
}
]}
/> */ }
</div>
</div>
</div>
);
}
}

View File

@ -1,44 +0,0 @@
.search-header {
display: block;
width: 100%;
&__text-container {
display: none;
padding: 25px 0;
background-color: lighten($ui-base-color, 6%);
@media (min-width:895px) {
display: block;
}
}
&__title-text {
color: $primary-text-color;
padding-left: 20px;
max-width: 1200px;
@include text-sizing(27px, bold, 32px);
@include text-overflow(nowrap);
@include margin-center;
@media (min-width:895px) and (max-width:1190px) {
max-width: 900px;
}
}
&__type-filters-tabs {
display: flex;
width: 100%;
max-width: 1200px;
@include margin-center;
@media screen and (max-width:895px) {
max-width: 580px;
}
@media (min-width:895px) and (max-width:1190px) {
max-width: 900px;
}
}
}

View File

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

View File

@ -1 +1 @@
export { default } from './search'
export { default } from '../search'

View File

@ -6,9 +6,9 @@ import {
} from '../../../actions/compose';
import {
reblog,
favourite,
favorite,
unreblog,
unfavourite,
unfavorite,
pin,
unpin,
} from '../../../actions/interactions';
@ -65,27 +65,27 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
});
},
onModalReblog (status) {
dispatch(reblog(status));
onModalRepost (status) {
dispatch(repost(status));
},
onReblog (status, e) {
onRepost (status, e) {
if (status.get('reblogged')) {
dispatch(unreblog(status));
dispatch(unrepost(status));
} else {
if (e.shiftKey || !boostModal) {
this.onModalReblog(status);
this.onModalRepost(status);
} else {
dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
dispatch(openModal('BOOST', { status, onRepost: this.onModalRepost }));
}
}
},
onFavourite (status) {
if (status.get('favourited')) {
dispatch(unfavourite(status));
onFavorite (status) {
if (status.get('favorited')) {
dispatch(unfavorite(status));
} else {
dispatch(favourite(status));
dispatch(favorite(status));
}
},

View File

@ -6,8 +6,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { HotKeys } from 'react-hotkeys';
import { fetchStatus } from '../../actions/statuses';
import {
favourite,
unfavourite,
favorite,
unfavorite,
reblog,
unreblog,
pin,
@ -155,11 +155,11 @@ class Status extends ImmutablePureComponent {
this.setState({ showMedia: !this.state.showMedia });
}
handleFavouriteClick = (status) => {
handleFavoriteClick = (status) => {
if (status.get('favourited')) {
this.props.dispatch(unfavourite(status));
this.props.dispatch(unfavorite(status));
} else {
this.props.dispatch(favourite(status));
this.props.dispatch(favorite(status));
}
}
@ -184,18 +184,18 @@ class Status extends ImmutablePureComponent {
}
}
handleModalReblog = (status) => {
this.props.dispatch(reblog(status));
handleModalRepost = (status) => {
this.props.dispatch(repost(status));
}
handleReblogClick = (status, e) => {
handleRepostClick = (status, e) => {
if (status.get('reblogged')) {
this.props.dispatch(unreblog(status));
this.props.dispatch(unrepost(status));
} else {
if ((e && e.shiftKey) || !boostModal) {
this.handleModalReblog(status);
this.handleModalRepost(status);
} else {
this.props.dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog }));
this.props.dispatch(openModal('BOOST', { status, onRepost: this.handleModalRepost }));
}
}
}
@ -298,12 +298,12 @@ class Status extends ImmutablePureComponent {
this.handleReplyClick(this.props.status);
}
handleHotkeyFavourite = () => {
this.handleFavouriteClick(this.props.status);
handleHotkeyFavorite = () => {
this.handleFavoriteClick(this.props.status);
}
handleHotkeyBoost = () => {
this.handleReblogClick(this.props.status);
this.handleRepostClick(this.props.status);
}
handleHotkeyMention = e => {
@ -432,7 +432,7 @@ class Status extends ImmutablePureComponent {
moveUp: this.handleHotkeyMoveUp,
moveDown: this.handleHotkeyMoveDown,
reply: this.handleHotkeyReply,
favourite: this.handleHotkeyFavourite,
favorite: this.handleHotkeyFavorite,
boost: this.handleHotkeyBoost,
mention: this.handleHotkeyMention,
openProfile: this.handleHotkeyOpenProfile,
@ -481,11 +481,11 @@ class Status extends ImmutablePureComponent {
showThread
/>
<ActionBar
{/*<ActionBar
status={status}
onReply={this.handleReplyClick}
onFavourite={this.handleFavouriteClick}
onReblog={this.handleReblogClick}
onFavorite={this.handleFavoriteClick}
onRepost={this.handleRepostClick}
onDelete={this.handleDeleteClick}
onDirect={this.handleDirectClick}
onMention={this.handleMentionClick}
@ -495,7 +495,7 @@ class Status extends ImmutablePureComponent {
onReport={this.handleReport}
onPin={this.handlePin}
onEmbed={this.handleEmbed}
/>
/>*/}
</div>
</HotKeys>

View File

@ -182,8 +182,10 @@ class SwitchingArea extends PureComponent {
<Redirect from='/@:username' to='/:username' exact />
<WrappedRoute path='/:username' publicRoute exact page={ProfilePage} component={AccountTimeline} content={children} />
{ /*
<Redirect from='/@:username/comments' to='/:username/comments' />
<WrappedRoute path='/:username/comments' page={ProfilePage} component={AccountTimeline} content={children} componentParams={{ commentsOnly: true }} />
*/ }
<Redirect from='/@:username/followers' to='/:username/followers' />
<WrappedRoute path='/:username/followers' page={ProfilePage} component={Followers} content={children} />
@ -191,11 +193,8 @@ class SwitchingArea extends PureComponent {
<Redirect from='/@:username/following' to='/:username/following' />
<WrappedRoute path='/:username/following' page={ProfilePage} component={Following} content={children} />
<Redirect from='/@:username/photos' to='/:username/photos' />
<WrappedRoute path='/:username/photos' page={ProfilePage} component={AccountGallery} content={children} componentParams={{ mediaType: 'photo' }} />
<Redirect from='/@:username/videos' to='/:username/videos' />
<WrappedRoute path='/:username/videos' page={ProfilePage} component={AccountGallery} content={children} componentParams={{ mediaType: 'video' }} />
<Redirect from='/@:username/media' to='/:username/media' />
<WrappedRoute path='/:username/media' page={ProfilePage} component={AccountGallery} content={children} />
<Redirect from='/@:username/favorites' to='/:username/favorites' />
<WrappedRoute path='/:username/favorites' page={ProfilePage} component={FavoritedStatuses} content={children} />
@ -203,8 +202,8 @@ class SwitchingArea extends PureComponent {
<Redirect from='/@:username/posts/:statusId' to='/:username/posts/:statusId' exact />
<WrappedRoute path='/:username/posts/:statusId' publicRoute exact page={BasicPage} component={Status} content={children} componentParams={{ title: 'Status' }} />
<Redirect from='/@:username/posts/:statusId/reblogs' to='/:username/posts/:statusId/reblogs' />
<WrappedRoute path='/:username/posts/:statusId/reblogs' page={BasicPage} component={Reblogs} content={children} componentParams={{ title: 'Reblogs' }} />
<Redirect from='/@:username/posts/:statusId/reposts' to='/:username/posts/:statusId/reposts' />
<WrappedRoute path='/:username/posts/:statusId/reposts' page={BasicPage} component={Reposts} content={children} componentParams={{ title: 'Reposts' }} />
<Redirect from='/@:username/posts/:statusId/favorites' to='/:username/posts/:statusId/favorites' />
<WrappedRoute path='/:username/posts/:statusId/favorites' page={BasicPage} component={Favorites} content={children} componentParams={{ title: 'Favorites' }} />

View File

@ -1,47 +0,0 @@
.ui {
display: block;
width: 100%;
padding: 0 0 100px 0;
}
@media screen and (max-width: 630px) and (max-height: 400px) {
.is-composing {
.tabs-bar,
.search {
margin-top: -50px;
}
.navigation-bar {
padding-bottom: 0;
&>a:first-child {
margin: -100px 10px 0 -50px;
}
&__profile {
padding-top: 2px;
}
&__profile-edit {
position: absolute;
margin-top: -60px;
}
&__actions {
.icon-button.close {
pointer-events: auto;
opacity: 1;
transform: scale(1.0, 1.0) translate(0, 0);
bottom: 5px;
}
.compose__action-bar .icon-button {
pointer-events: none;
opacity: 0;
transform: scale(0.0, 1.0) translate(100%, 0);
}
}
}
}
}

View File

@ -114,8 +114,8 @@ export function Notifications() {
return import(/* webpackChunkName: "features/notifications" */'../../notifications')
}
export function Reblogs() {
return import(/* webpackChunkName: "features/reblogs" */'../../reblogs')
export function Reposts() {
return import(/* webpackChunkName: "features/reblogs" */'../../reposts')
}
export function ReportModal() {

View File

@ -29,12 +29,8 @@ export default class ProfileLayout extends ImmutablePureComponent {
title: 'Comments',
},
{
to: `/${account.get('acct')}/photos`,
title: 'Photos',
},
{
to: `/${account.get('acct')}/videos`,
title: 'Videos',
to: `/${account.get('acct')}/media`,
title: 'Media',
},
{
to: '',

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a gab for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a gab for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's gabs with your followers by reposting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by reposting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Gorffen tiwtorial!",
"introduction.interactions.favourite.headline": "Ffefryn",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Hwb",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by reposting them.",
"introduction.interactions.reply.headline": "Ateb",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Slut tutorial!",
"introduction.interactions.favourite.headline": "Favorisere",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by reposting them.",
"introduction.interactions.reply.headline": "Svar",

View File

@ -1507,7 +1507,7 @@
"id": "introduction.interactions.favourite.headline"
},
{
"defaultMessage": "You can save a gab for later, and let the author know that you liked it, by favouriting it.",
"defaultMessage": "You can save a gab for later, and let the author know that you liked it, by favoriting it.",
"id": "introduction.interactions.favourite.text"
},
{

View File

@ -170,7 +170,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favorite",
"introduction.interactions.favourite.text": "You can save a gab for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a gab for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's gabs with your followers by reposting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by reposting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a gab for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a gab for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's gabs with your followers by reposting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Boost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish toot-orial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by reposting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by reposting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by reposting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by reposting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by reposting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by reposting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a gab for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a gab for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's gabs with your followers by reposting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by reposting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by reposting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by reposting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by reposting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by reposting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by reposting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a gab for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a gab for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's gabs with your followers by reposting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by reposting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by reposting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by reposting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by reposting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -169,7 +169,7 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favoriting it.",
"introduction.interactions.reblog.headline": "Repost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by reposting them.",
"introduction.interactions.reply.headline": "Reply",

View File

@ -3,25 +3,30 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { fetchAccountByUsername } from '../actions/accounts'
import { makeGetAccount } from '../selectors'
import { me } from '../initial_state'
import LinkFooter from '../components/link_footer'
import ProfileStatsPanel from '../components/panel/profile_stats_panel'
import ProfileInfoPanel from '../components/panel/profile_info_panel'
import MediaGalleryPanel from '../components/panel/media_gallery_panel'
import ColumnIndicator from '../components/column_indicator'
import ProfileLayout from '../layouts/profile_layout'
const mapStateToProps = (state, { params: { username }, withReplies = false }) => {
const mapStateToProps = (state, { params: { username } }) => {
const accounts = state.getIn(['accounts'])
const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() == username.toLowerCase())
const account = accounts.find(acct => username.toLowerCase() == acct.getIn(['acct'], '').toLowerCase())
let account = null
if (!accountFetchError) {
account = accounts.find(acct => username.toLowerCase() == acct.getIn(['acct'], '').toLowerCase())
}
const accountId = !!account ? account.get('id') : -1
const isBlocked = state.getIn(['relationships', accountId, 'blocked_by'], false)
const isLocked = state.getIn(['accounts', accountId, 'locked'], false)
const isFollowing = state.getIn(['relationships', accountId, 'following'], false)
const unavailable = (me === accountId) ? false : (isBlocked || (isLocked && !isFollowing))
const getAccount = makeGetAccount()
return {
account: account ? getAccount(state, account.get('id')) : null,
unavailable,
account: accountId !== -1 ? getAccount(state, accountId) : null,
}
}
@ -29,10 +34,11 @@ export default
@connect(mapStateToProps)
class ProfilePage extends ImmutablePureComponent {
static propTypes = {
children: PropTypes.node,
params: PropTypes.object.isRequired,
account: ImmutablePropTypes.map,
dispatch: PropTypes.func.isRequired,
children: PropTypes.node,
unavailable: PropTypes.bool.isRequired,
}
componentWillMount() {
@ -41,7 +47,11 @@ class ProfilePage extends ImmutablePureComponent {
}
render() {
const { account } = this.props
const {
account,
children,
unavailable
} = this.props
return (
<ProfileLayout
@ -55,7 +65,15 @@ class ProfilePage extends ImmutablePureComponent {
</Fragment>
)}
>
{ this.props.children }
{
!account && <ColumnIndicator type='loading' />
}
{
!!account && !unavailable &&
React.cloneElement(children, {
account,
})
}
</ProfileLayout>
)
}

View File

@ -9,7 +9,7 @@ import {
FOLLOW_REQUEST_REJECT_SUCCESS,
} from '../actions/accounts';
import {
REBLOGS_FETCH_SUCCESS,
REPOSTS_FETCH_SUCCESS,
} from '../actions/interactions';
import {
BLOCKS_FETCH_SUCCESS,
@ -63,7 +63,7 @@ export default function userLists(state = initialState, action) {
return normalizeList(state, 'following', action.id, action.accounts, action.next);
case FOLLOWING_EXPAND_SUCCESS:
return appendToList(state, 'following', action.id, action.accounts, action.next);
case REBLOGS_FETCH_SUCCESS:
case REPOSTS_FETCH_SUCCESS:
return state.setIn(['reblogged_by', action.id], ImmutableList(action.accounts.map(item => item.id)));
case FOLLOW_REQUESTS_FETCH_SUCCESS:
return state.setIn(['follow_requests', 'items'], ImmutableList(action.accounts.map(item => item.id))).setIn(['follow_requests', 'next'], action.next);

View File

@ -103,7 +103,7 @@ const handlePush = (event) => {
options.image = undefined;
options.actions = [actionExpand(preferred_locale)];
} else if (notification.type === 'mention') {
options.actions = [actionReblog(preferred_locale), actionFavourite(preferred_locale)];
options.actions = [actionRepost(preferred_locale), actionFavorite(preferred_locale)];
}
return notify(options);
@ -127,13 +127,13 @@ const actionExpand = preferred_locale => ({
title: formatMessage('status.show_more', preferred_locale),
});
const actionReblog = preferred_locale => ({
const actionRepost = preferred_locale => ({
action: 'reblog',
icon: '/web-push-icon_reblog.png',
title: formatMessage('status.reblog', preferred_locale),
});
const actionFavourite = preferred_locale => ({
const actionFavorite = preferred_locale => ({
action: 'favourite',
icon: '/web-push-icon_favourite.png',
title: formatMessage('status.favourite', preferred_locale),
@ -151,7 +151,7 @@ const expandNotification = notification => {
newNotification.body = newNotification.data.hiddenBody;
newNotification.image = newNotification.data.hiddenImage;
newNotification.actions = [actionReblog(notification.data.preferred_locale), actionFavourite(notification.data.preferred_locale)];
newNotification.actions = [actionRepost(notification.data.preferred_locale), actionFavorite(notification.data.preferred_locale)];
return self.registration.showNotification(newNotification.title, newNotification);
};

View File

@ -409,6 +409,10 @@ body {
min-height: 100vh;
}
.heightMin50VH {
min-height: 50vh;
}
.height100VH {
height: 100vh;
}
@ -437,6 +441,10 @@ body {
height: 122px;
}
.height158PX {
height: 158px;
}
.height220PX {
height: 220px;
}
@ -469,18 +477,22 @@ body {
width: 240px;
}
.width100PC {
width: 100%;
}
.width72PX {
width: 72px;
}
.width100PC {
width: 100%;
}
.width50PC {
width: 50%;
}
.width25PC {
width: 25%;
}
.maxWidth100PC {
max-width: 100%;
}
@ -743,6 +755,10 @@ body {
padding-top: 56.25%;
}
.paddingTop25PC {
padding-top: 25%;
}
.paddingTop10PX {
padding-top: 10px;
}

Some files were not shown because too many files have changed in this diff Show More