- {!showResults &&
}
+ {!showResults &&
}
{showResults && !this.props.disabled &&
· }
{votesCount}
{poll.get('expires_at') &&
· {timeRemaining}}
diff --git a/app/javascript/flavours/glitch/components/server_banner.js b/app/javascript/flavours/glitch/components/server_banner.js
new file mode 100644
index 0000000000..e29876d4b4
--- /dev/null
+++ b/app/javascript/flavours/glitch/components/server_banner.js
@@ -0,0 +1,91 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { domain } from 'flavours/glitch/util/initial_state';
+import { fetchServer } from 'flavours/glitch/actions/server';
+import { connect } from 'react-redux';
+import Account from 'flavours/glitch/containers/account_container';
+import ShortNumber from 'flavours/glitch/components/short_number';
+import Skeleton from 'flavours/glitch/components/skeleton';
+import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
+
+const messages = defineMessages({
+ aboutActiveUsers: { id: 'server_banner.about_active_users', defaultMessage: 'People using this server during the last 30 days (Monthly Active Users)' },
+});
+
+const mapStateToProps = state => ({
+ server: state.get('server'),
+});
+
+export default @connect(mapStateToProps)
+@injectIntl
+class ServerBanner extends React.PureComponent {
+
+ static propTypes = {
+ server: PropTypes.object,
+ dispatch: PropTypes.func,
+ intl: PropTypes.object,
+ };
+
+ componentDidMount () {
+ const { dispatch } = this.props;
+ dispatch(fetchServer());
+ }
+
+ render () {
+ const { server, intl } = this.props;
+ const isLoading = server.get('isLoading');
+
+ return (
+
+
+
+
+
+
+ {isLoading ? (
+ <>
+
+
+
+
+
+ >
+ ) : server.get('description')}
+
+
+
+
+
+
+
+
+ {isLoading ? (
+ <>
+
+
+
+ >
+ ) : (
+ <>
+
+
+
+ >
+ )}
+
+
+
+
+
+
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js
index d3b6bd7e98..9b656b2b3e 100644
--- a/app/javascript/flavours/glitch/components/status.js
+++ b/app/javascript/flavours/glitch/components/status.js
@@ -83,6 +83,7 @@ class Status extends ImmutablePureComponent {
onEmbed: PropTypes.func,
onHeightChange: PropTypes.func,
onToggleHidden: PropTypes.func,
+ onInteractionModal: PropTypes.func,
muted: PropTypes.bool,
hidden: PropTypes.bool,
unread: PropTypes.bool,
diff --git a/app/javascript/flavours/glitch/components/status_action_bar.js b/app/javascript/flavours/glitch/components/status_action_bar.js
index 78964e882c..4e5e5121ae 100644
--- a/app/javascript/flavours/glitch/components/status_action_bar.js
+++ b/app/javascript/flavours/glitch/components/status_action_bar.js
@@ -69,6 +69,7 @@ class StatusActionBar extends ImmutablePureComponent {
onBookmark: PropTypes.func,
onFilter: PropTypes.func,
onAddFilter: PropTypes.func,
+ onInteractionModal: PropTypes.func,
withDismiss: PropTypes.bool,
withCounters: PropTypes.bool,
showReplyCount: PropTypes.bool,
@@ -86,10 +87,12 @@ class StatusActionBar extends ImmutablePureComponent {
]
handleReplyClick = () => {
- if (me) {
+ const { signedIn } = this.context.identity;
+
+ if (signedIn) {
this.props.onReply(this.props.status, this.context.router.history);
} else {
- this._openInteractionDialog('reply');
+ this.props.onInteractionModal('reply', this.props.status);
}
}
@@ -101,28 +104,28 @@ class StatusActionBar extends ImmutablePureComponent {
}
handleFavouriteClick = (e) => {
- if (me) {
+ const { signedIn } = this.context.identity;
+
+ if (signedIn) {
this.props.onFavourite(this.props.status, e);
} else {
- this._openInteractionDialog('favourite');
+ this.props.onInteractionModal('favourite', this.props.status);
}
}
- handleBookmarkClick = (e) => {
- this.props.onBookmark(this.props.status, e);
- }
-
handleReblogClick = e => {
- if (me) {
+ const { signedIn } = this.context.identity;
+
+ if (signedIn) {
this.props.onReblog(this.props.status, e);
} else {
- this._openInteractionDialog('reblog');
+ this.props.onInteractionModal('reblog', this.props.status);
}
}
- _openInteractionDialog = type => {
- window.open(`/interact/${this.props.status.get('id')}?type=${type}`, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
- }
+ handleBookmarkClick = (e) => {
+ this.props.onBookmark(this.props.status, e);
+ }
handleDeleteClick = () => {
this.props.onDelete(this.props.status, this.context.router.history);
diff --git a/app/javascript/flavours/glitch/containers/mastodon.js b/app/javascript/flavours/glitch/containers/mastodon.js
index d07b2b3d0e..a6ec5845da 100644
--- a/app/javascript/flavours/glitch/containers/mastodon.js
+++ b/app/javascript/flavours/glitch/containers/mastodon.js
@@ -31,7 +31,7 @@ const createIdentityContext = state => ({
signedIn: !!state.meta.me,
accountId: state.meta.me,
accessToken: state.meta.access_token,
- permissions: state.role.permissions,
+ permissions: state.role ? state.role.permissions : 0,
});
export default class Mastodon extends React.PureComponent {
diff --git a/app/javascript/flavours/glitch/containers/status_container.js b/app/javascript/flavours/glitch/containers/status_container.js
index 3703080435..a1b4c57c6e 100644
--- a/app/javascript/flavours/glitch/containers/status_container.js
+++ b/app/javascript/flavours/glitch/containers/status_container.js
@@ -244,6 +244,14 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
});
},
+ onInteractionModal (type, status) {
+ dispatch(openModal('INTERACTION', {
+ type,
+ accountId: status.getIn(['account', 'id']),
+ url: status.get('url'),
+ }));
+ },
+
});
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status));
diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js
index cc2a7d4d45..fc80446b11 100644
--- a/app/javascript/flavours/glitch/features/account/components/header.js
+++ b/app/javascript/flavours/glitch/features/account/components/header.js
@@ -3,7 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
-import { autoPlayGif, me } from 'flavours/glitch/util/initial_state';
+import { autoPlayGif, me, title, domain } from 'flavours/glitch/util/initial_state';
import { preferencesLink, profileLink, accountAdminLink } from 'flavours/glitch/util/backend_links';
import classNames from 'classnames';
import Icon from 'flavours/glitch/components/icon';
@@ -14,6 +14,7 @@ import { NavLink } from 'react-router-dom';
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
import AccountNoteContainer from '../containers/account_note_container';
import { PERMISSION_MANAGE_USERS } from 'flavours/glitch/permissions';
+import { Helmet } from 'react-helmet';
const messages = defineMessages({
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
@@ -54,6 +55,14 @@ const messages = defineMessages({
languages: { id: 'account.languages', defaultMessage: 'Change subscribed languages' },
});
+const titleFromAccount = account => {
+ const displayName = account.get('display_name');
+ const acct = account.get('acct') === account.get('username') ? `${account.get('username')}@${domain}` : account.get('acct');
+ const prefix = displayName.trim().length === 0 ? account.get('username') : displayName;
+
+ return `${prefix} (@${acct})`;
+};
+
const dateFormatOptions = {
month: 'short',
day: 'numeric',
@@ -87,6 +96,7 @@ class Header extends ImmutablePureComponent {
onAddToList: PropTypes.func.isRequired,
onEditAccountNote: PropTypes.func.isRequired,
onChangeLanguages: PropTypes.func.isRequired,
+ onInteractionModal: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
domain: PropTypes.string.isRequired,
hidden: PropTypes.bool,
@@ -124,6 +134,7 @@ class Header extends ImmutablePureComponent {
render () {
const { account, hidden, intl, domain } = this.props;
+ const { signedIn } = this.context.identity;
if (!account) {
return null;
@@ -157,12 +168,12 @@ class Header extends ImmutablePureComponent {
}
if (me !== account.get('id')) {
- if (!account.get('relationship')) { // Wait until the relationship is loaded
+ if (signedIn && !account.get('relationship')) { // Wait until the relationship is loaded
actionBtn = '';
} else if (account.getIn(['relationship', 'requested'])) {
actionBtn =
;
} else if (!account.getIn(['relationship', 'blocking'])) {
- actionBtn =
;
+ actionBtn =
;
} else if (account.getIn(['relationship', 'blocking'])) {
actionBtn =
;
}
@@ -182,7 +193,7 @@ class Header extends ImmutablePureComponent {
lockedIcon =
;
}
- if (account.get('id') !== me && !suspended) {
+ if (signedIn && account.get('id') !== me && !suspended) {
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect });
menu.push(null);
@@ -209,7 +220,7 @@ class Header extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });
menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
- } else {
+ } else if (signedIn) {
if (account.getIn(['relationship', 'following'])) {
if (!account.getIn(['relationship', 'muting'])) {
if (account.getIn(['relationship', 'showing_reblogs'])) {
@@ -242,7 +253,7 @@ class Header extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport });
}
- if (account.get('acct') !== account.get('username')) {
+ if (signedIn && account.get('acct') !== account.get('username')) {
const domain = account.get('acct').split('@')[1];
menu.push(null);
@@ -301,7 +312,7 @@ class Header extends ImmutablePureComponent {
)}
-
+
)}
@@ -313,7 +324,7 @@ class Header extends ImmutablePureComponent {
-
@@ -339,6 +350,10 @@ class Header extends ImmutablePureComponent {
)}
+
+
- {layout === 'mobile' ? (
-
-
-
- ) : (
-
- )}
+
+
+
+
+
{isSearching ? (
@@ -73,7 +74,7 @@ class Explore extends React.PureComponent {
-
+ {signedIn && }
@@ -82,6 +83,10 @@ class Explore extends React.PureComponent {
+
+
+ {intl.formatMessage(messages.title)} - {title}
+
)}
diff --git a/app/javascript/flavours/glitch/features/explore/links.js b/app/javascript/flavours/glitch/features/explore/links.js
index 1d8cd8efc1..6adc2f6fba 100644
--- a/app/javascript/flavours/glitch/features/explore/links.js
+++ b/app/javascript/flavours/glitch/features/explore/links.js
@@ -5,6 +5,7 @@ import Story from './components/story';
import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
import { connect } from 'react-redux';
import { fetchTrendingLinks } from 'flavours/glitch/actions/trends';
+import { FormattedMessage } from 'react-intl';
const mapStateToProps = state => ({
links: state.getIn(['trends', 'links', 'items']),
@@ -28,6 +29,16 @@ class Links extends React.PureComponent {
render () {
const { isLoading, links } = this.props;
+ if (!isLoading && links.isEmpty()) {
+ return (
+
+ );
+ }
+
return (
{isLoading ? (
) : links.map(link => (
diff --git a/app/javascript/flavours/glitch/features/explore/results.js b/app/javascript/flavours/glitch/features/explore/results.js
index 8eac63c2c5..3f43b9ee1f 100644
--- a/app/javascript/flavours/glitch/features/explore/results.js
+++ b/app/javascript/flavours/glitch/features/explore/results.js
@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import { FormattedMessage } from 'react-intl';
+import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { expandSearch } from 'flavours/glitch/actions/search';
import Account from 'flavours/glitch/containers/account_container';
@@ -10,10 +10,17 @@ import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag'
import { List as ImmutableList } from 'immutable';
import LoadMore from 'flavours/glitch/components/load_more';
import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
+import { title } from 'flavours/glitch/util/initial_state';
+import { Helmet } from 'react-helmet';
+
+const messages = defineMessages({
+ title: { id: 'search_results.title', defaultMessage: 'Search for {q}' },
+});
const mapStateToProps = state => ({
isLoading: state.getIn(['search', 'isLoading']),
results: state.getIn(['search', 'results']),
+ q: state.getIn(['search', 'searchTerm']),
});
const appendLoadMore = (id, list, onLoadMore) => {
@@ -37,6 +44,7 @@ const renderStatuses = (results, onLoadMore) => appendLoadMore('statuses', resul
)), onLoadMore);
export default @connect(mapStateToProps)
+@injectIntl
class Results extends React.PureComponent {
static propTypes = {
@@ -44,6 +52,8 @@ class Results extends React.PureComponent {
isLoading: PropTypes.bool,
multiColumn: PropTypes.bool,
dispatch: PropTypes.func.isRequired,
+ q: PropTypes.string,
+ intl: PropTypes.object,
};
state = {
@@ -64,7 +74,7 @@ class Results extends React.PureComponent {
}
render () {
- const { isLoading, results } = this.props;
+ const { intl, isLoading, q, results } = this.props;
const { type } = this.state;
let filteredResults = ImmutableList();
@@ -106,6 +116,10 @@ class Results extends React.PureComponent {
{isLoading ? : filteredResults}
+
+
+ {intl.formatMessage(messages.title, { q })} - {title}
+
);
}
diff --git a/app/javascript/flavours/glitch/features/explore/suggestions.js b/app/javascript/flavours/glitch/features/explore/suggestions.js
index ccd2c950a5..52e5ce62bd 100644
--- a/app/javascript/flavours/glitch/features/explore/suggestions.js
+++ b/app/javascript/flavours/glitch/features/explore/suggestions.js
@@ -5,6 +5,7 @@ import AccountCard from 'flavours/glitch/features/directory/components/account_c
import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
import { connect } from 'react-redux';
import { fetchSuggestions, dismissSuggestion } from 'flavours/glitch/actions/suggestions';
+import { FormattedMessage } from 'react-intl';
const mapStateToProps = state => ({
suggestions: state.getIn(['suggestions', 'items']),
@@ -33,6 +34,16 @@ class Suggestions extends React.PureComponent {
render () {
const { isLoading, suggestions } = this.props;
+ if (!isLoading && suggestions.isEmpty()) {
+ return (
+
+ );
+ }
+
return (
{isLoading ?
: suggestions.map(suggestion => (
diff --git a/app/javascript/flavours/glitch/features/explore/tags.js b/app/javascript/flavours/glitch/features/explore/tags.js
index 0ec1eb88be..465fad0df9 100644
--- a/app/javascript/flavours/glitch/features/explore/tags.js
+++ b/app/javascript/flavours/glitch/features/explore/tags.js
@@ -5,6 +5,7 @@ import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag'
import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
import { connect } from 'react-redux';
import { fetchTrendingHashtags } from 'flavours/glitch/actions/trends';
+import { FormattedMessage } from 'react-intl';
const mapStateToProps = state => ({
hashtags: state.getIn(['trends', 'tags', 'items']),
@@ -28,6 +29,16 @@ class Tags extends React.PureComponent {
render () {
const { isLoading, hashtags } = this.props;
+ if (!isLoading && hashtags.isEmpty()) {
+ return (
+
+ );
+ }
+
return (
{isLoading ? (
) : hashtags.map(hashtag => (
diff --git a/app/javascript/flavours/glitch/features/follow_recommendations/index.js b/app/javascript/flavours/glitch/features/follow_recommendations/index.js
index d050e3cc75..f934aeb350 100644
--- a/app/javascript/flavours/glitch/features/follow_recommendations/index.js
+++ b/app/javascript/flavours/glitch/features/follow_recommendations/index.js
@@ -10,7 +10,6 @@ import { requestBrowserPermission } from 'flavours/glitch/actions/notifications'
import { markAsPartial } from 'flavours/glitch/actions/timelines';
import Column from 'flavours/glitch/features/ui/components/column';
import Account from './components/account';
-import Logo from 'flavours/glitch/components/logo';
import imageGreeting from 'mastodon/../images/elephant_ui_greeting.svg';
import Button from 'flavours/glitch/components/button';
@@ -78,7 +77,10 @@ class FollowRecommendations extends ImmutablePureComponent {
diff --git a/app/javascript/flavours/glitch/features/hashtag_timeline/index.js b/app/javascript/flavours/glitch/features/hashtag_timeline/index.js
index 87a52b269e..38dda85b2b 100644
--- a/app/javascript/flavours/glitch/features/hashtag_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/hashtag_timeline/index.js
@@ -14,6 +14,8 @@ import { isEqual } from 'lodash';
import { fetchHashtag, followHashtag, unfollowHashtag } from 'flavours/glitch/actions/tags';
import Icon from 'flavours/glitch/components/icon';
import classNames from 'classnames';
+import { title } from 'flavours/glitch/util/initial_state';
+import { Helmet } from 'react-helmet';
const messages = defineMessages({
followHashtag: { id: 'hashtag.follow', defaultMessage: 'Follow hashtag' },
@@ -31,6 +33,10 @@ class HashtagTimeline extends React.PureComponent {
disconnects = [];
+ static contextTypes = {
+ identity: PropTypes.object,
+ };
+
static propTypes = {
params: PropTypes.object.isRequired,
columnId: PropTypes.string,
@@ -90,6 +96,12 @@ class HashtagTimeline extends React.PureComponent {
}
_subscribe (dispatch, id, tags = {}, local) {
+ const { signedIn } = this.context.identity;
+
+ if (!signedIn) {
+ return;
+ }
+
let any = (tags.any || []).map(tag => tag.value);
let all = (tags.all || []).map(tag => tag.value);
let none = (tags.none || []).map(tag => tag.value);
@@ -158,6 +170,11 @@ class HashtagTimeline extends React.PureComponent {
handleFollow = () => {
const { dispatch, params, tag } = this.props;
const { id } = params;
+ const { signedIn } = this.context.identity;
+
+ if (!signedIn) {
+ return;
+ }
if (tag.get('following')) {
dispatch(unfollowHashtag(id));
@@ -170,6 +187,7 @@ class HashtagTimeline extends React.PureComponent {
const { hasUnread, columnId, multiColumn, tag, intl } = this.props;
const { id, local } = this.props.params;
const pinned = !!columnId;
+ const { signedIn } = this.context.identity;
let followButton;
@@ -177,7 +195,7 @@ class HashtagTimeline extends React.PureComponent {
const following = tag.get('following');
followButton = (
-