diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js
index df6a36379..858a12b15 100644
--- a/app/javascript/mastodon/actions/timelines.js
+++ b/app/javascript/mastodon/actions/timelines.js
@@ -120,7 +120,7 @@ export function refreshTimeline(timelineId, path, params = {}) {
export const refreshHomeTimeline = () => refreshTimeline('home', '/api/v1/timelines/home');
export const refreshPublicTimeline = () => refreshTimeline('public', '/api/v1/timelines/public');
export const refreshCommunityTimeline = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true });
-export const refreshAccountTimeline = accountId => refreshTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`);
+export const refreshAccountTimeline = (accountId, withReplies) => refreshTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies });
export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
export const refreshHashtagTimeline = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
export const refreshListTimeline = id => refreshTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`);
@@ -161,7 +161,7 @@ export function expandTimeline(timelineId, path, params = {}) {
export const expandHomeTimeline = () => expandTimeline('home', '/api/v1/timelines/home');
export const expandPublicTimeline = () => expandTimeline('public', '/api/v1/timelines/public');
export const expandCommunityTimeline = () => expandTimeline('community', '/api/v1/timelines/public', { local: true });
-export const expandAccountTimeline = accountId => expandTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`);
+export const expandAccountTimeline = (accountId, withReplies) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies });
export const expandAccountMediaTimeline = accountId => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
export const expandHashtagTimeline = hashtag => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
export const expandListTimeline = id => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`);
diff --git a/app/javascript/mastodon/features/account/components/action_bar.js b/app/javascript/mastodon/features/account/components/action_bar.js
index b4f812c9a..b538fa5fc 100644
--- a/app/javascript/mastodon/features/account/components/action_bar.js
+++ b/app/javascript/mastodon/features/account/components/action_bar.js
@@ -53,11 +53,11 @@ export default class ActionBar extends React.PureComponent {
let extraInfo = '';
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
+
if ('share' in navigator) {
menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
}
- menu.push(null);
- menu.push({ text: intl.formatMessage(messages.media), to: `/accounts/${account.get('id')}/media` });
+
menu.push(null);
if (account.get('id') === me) {
diff --git a/app/javascript/mastodon/features/account_gallery/components/media_item.js b/app/javascript/mastodon/features/account_gallery/components/media_item.js
index dda3d4e37..59c805c38 100644
--- a/app/javascript/mastodon/features/account_gallery/components/media_item.js
+++ b/app/javascript/mastodon/features/account_gallery/components/media_item.js
@@ -12,24 +12,26 @@ export default class MediaItem extends ImmutablePureComponent {
render () {
const { media } = this.props;
const status = media.get('status');
+ const focusX = media.getIn(['meta', 'focus', 'x']);
+ const focusY = media.getIn(['meta', 'focus', 'y']);
+ const x = ((focusX / 2) + .5) * 100;
+ const y = ((focusY / -2) + .5) * 100;
+ const style = {};
- let content, style;
+ let content;
if (media.get('type') === 'gifv') {
content = GIF;
}
if (!status.get('sensitive')) {
- style = { backgroundImage: `url(${media.get('preview_url')})` };
+ style.backgroundImage = `url(${media.get('preview_url')})`;
+ style.backgroundPosition = `${x}% ${y}%`;
}
return (
diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js
index ece219a3d..4b408256a 100644
--- a/app/javascript/mastodon/features/account_gallery/index.js
+++ b/app/javascript/mastodon/features/account_gallery/index.js
@@ -11,7 +11,6 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { getAccountGallery } from '../../selectors';
import MediaItem from './components/media_item';
import HeaderContainer from '../account_timeline/containers/header_container';
-import { FormattedMessage } from 'react-intl';
import { ScrollContainer } from 'react-router-scroll-4';
import LoadMore from '../../components/load_more';
@@ -89,10 +88,6 @@ export default class AccountGallery extends ImmutablePureComponent {
-
-
-
-
{medias.map(media => (
+
+
+
+
+
+
);
}
diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js
index f8c85c296..aed009ef0 100644
--- a/app/javascript/mastodon/features/account_timeline/index.js
+++ b/app/javascript/mastodon/features/account_timeline/index.js
@@ -12,11 +12,15 @@ import ColumnBackButton from '../../components/column_back_button';
import { List as ImmutableList } from 'immutable';
import ImmutablePureComponent from 'react-immutable-pure-component';
-const mapStateToProps = (state, props) => ({
- statusIds: state.getIn(['timelines', `account:${props.params.accountId}`, 'items'], ImmutableList()),
- isLoading: state.getIn(['timelines', `account:${props.params.accountId}`, 'isLoading']),
- hasMore: !!state.getIn(['timelines', `account:${props.params.accountId}`, 'next']),
-});
+const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => {
+ const path = withReplies ? `${accountId}:with_replies` : accountId;
+
+ return {
+ statusIds: state.getIn(['timelines', `account:${path}`, 'items'], ImmutableList()),
+ isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']),
+ hasMore: !!state.getIn(['timelines', `account:${path}`, 'next']),
+ };
+};
@connect(mapStateToProps)
export default class AccountTimeline extends ImmutablePureComponent {
@@ -27,23 +31,24 @@ export default class AccountTimeline extends ImmutablePureComponent {
statusIds: ImmutablePropTypes.list,
isLoading: PropTypes.bool,
hasMore: PropTypes.bool,
+ withReplies: PropTypes.bool,
};
componentWillMount () {
this.props.dispatch(fetchAccount(this.props.params.accountId));
- this.props.dispatch(refreshAccountTimeline(this.props.params.accountId));
+ this.props.dispatch(refreshAccountTimeline(this.props.params.accountId, this.props.withReplies));
}
componentWillReceiveProps (nextProps) {
- if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
+ if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) {
this.props.dispatch(fetchAccount(nextProps.params.accountId));
- this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId));
+ this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId, nextProps.params.withReplies));
}
}
handleScrollToBottom = () => {
if (!this.props.isLoading && this.props.hasMore) {
- this.props.dispatch(expandAccountTimeline(this.props.params.accountId));
+ this.props.dispatch(expandAccountTimeline(this.props.params.accountId, this.props.withReplies));
}
}
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
index 5b0d7246a..ef909136f 100644
--- a/app/javascript/mastodon/features/ui/index.js
+++ b/app/javascript/mastodon/features/ui/index.js
@@ -398,6 +398,7 @@ export default class UI extends React.Component {
+
diff --git a/app/javascript/mastodon/features/ui/util/react_router_helpers.js b/app/javascript/mastodon/features/ui/util/react_router_helpers.js
index 43007ddc3..32dfe320b 100644
--- a/app/javascript/mastodon/features/ui/util/react_router_helpers.js
+++ b/app/javascript/mastodon/features/ui/util/react_router_helpers.js
@@ -35,14 +35,19 @@ export class WrappedRoute extends React.Component {
component: PropTypes.func.isRequired,
content: PropTypes.node,
multiColumn: PropTypes.bool,
- }
+ componentParams: PropTypes.object,
+ };
+
+ static defaultProps = {
+ componentParams: {},
+ };
renderComponent = ({ match }) => {
- const { component, content, multiColumn } = this.props;
+ const { component, content, multiColumn, componentParams } = this.props;
return (
- {Component => {content}}
+ {Component => {content}}
);
}
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 213f4df47..fe9e03fc9 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -4230,6 +4230,7 @@ a.status-card {
&.standalone {
.media-gallery__item-gifv-thumbnail {
transform: none;
+ top: 0;
}
}
}
@@ -4579,64 +4580,94 @@ a.status-card {
/* End Video Player */
.account-gallery__container {
- margin: -2px;
- padding: 4px;
display: flex;
+ justify-content: center;
flex-wrap: wrap;
+ padding: 2px;
}
.account-gallery__item {
- flex: 1 1 auto;
- width: calc(100% / 3 - 4px);
- height: 95px;
- margin: 2px;
+ flex-grow: 1;
+ width: 50%;
+ overflow: hidden;
+ position: relative;
+
+ &::before {
+ content: "";
+ display: block;
+ padding-top: 100%;
+ }
a {
display: block;
- width: 100%;
- height: 100%;
+ width: calc(100% - 4px);
+ height: calc(100% - 4px);
+ margin: 2px;
+ top: 0;
+ left: 0;
background-color: $base-overlay-background;
background-size: cover;
background-position: center;
- position: relative;
+ position: absolute;
color: inherit;
text-decoration: none;
+ border-radius: 4px;
&:hover,
&:active,
&:focus {
outline: 0;
+
+ &::before {
+ content: "";
+ display: block;
+ width: 100%;
+ height: 100%;
+ background: rgba($base-overlay-background, 0.3);
+ border-radius: 4px;
+ }
}
}
}
-.account-section-headline {
- color: $ui-base-lighter-color;
+.account__section-headline {
background: lighten($ui-base-color, 2%);
border-bottom: 1px solid lighten($ui-base-color, 4%);
- padding: 15px 10px;
- font-size: 14px;
- font-weight: 500;
- position: relative;
cursor: default;
+ display: flex;
- &::before,
- &::after {
+ a {
display: block;
- content: "";
- position: absolute;
- bottom: 0;
- left: 18px;
- width: 0;
- height: 0;
- border-style: solid;
- border-width: 0 10px 10px;
- border-color: transparent transparent lighten($ui-base-color, 4%);
- }
+ color: $ui-base-lighter-color;
+ padding: 15px 10px;
+ font-size: 14px;
+ font-weight: 500;
+ text-decoration: none;
+ position: relative;
- &::after {
- bottom: -1px;
- border-color: transparent transparent $ui-base-color;
+ &.active {
+ color: $ui-highlight-color;
+
+ &::before,
+ &::after {
+ display: block;
+ content: "";
+ position: absolute;
+ bottom: 0;
+ left: 50%;
+ width: 0;
+ height: 0;
+ transform: translateX(-50%);
+ border-style: solid;
+ border-width: 0 10px 10px;
+ border-color: transparent transparent lighten($ui-base-color, 4%);
+ }
+
+ &::after {
+ bottom: -1px;
+ border-color: transparent transparent $ui-base-color;
+ }
+ }
}
}