From f41ec9af05d3e2145e62f705225dbabb7e04e242 Mon Sep 17 00:00:00 2001
From: Eugen Rochko <eugen@zeonfederated.com>
Date: Sun, 9 Oct 2022 06:08:37 +0200
Subject: [PATCH] Add dismissable hints to various timelines in web UI (#19315)
Co-authored-by: Yamagishi Kazutoshi <ykzts@desire.sh>
---
.../mastodon/components/dismissable_banner.js | 51 +++++++++++++++++++
.../features/community_timeline/index.js | 6 +++
.../mastodon/features/explore/links.js | 11 ++++
.../mastodon/features/explore/statuses.js | 29 +++++++----
.../mastodon/features/explore/tags.js | 11 ++++
.../features/public_timeline/index.js | 5 ++
app/javascript/mastodon/settings.js | 1 +
.../styles/mastodon/components.scss | 25 +++++++++
8 files changed, 128 insertions(+), 11 deletions(-)
create mode 100644 app/javascript/mastodon/components/dismissable_banner.js
diff --git a/app/javascript/mastodon/components/dismissable_banner.js b/app/javascript/mastodon/components/dismissable_banner.js
new file mode 100644
index 0000000000..1ee0320564
--- /dev/null
+++ b/app/javascript/mastodon/components/dismissable_banner.js
@@ -0,0 +1,51 @@
+import React from 'react';
+import IconButton from './icon_button';
+import PropTypes from 'prop-types';
+import { injectIntl, defineMessages } from 'react-intl';
+import { bannerSettings } from 'mastodon/settings';
+
+const messages = defineMessages({
+ dismiss: { id: 'dismissable_banner.dismiss', defaultMessage: 'Dismiss' },
+});
+
+export default @injectIntl
+class DismissableBanner extends React.PureComponent {
+
+ static propTypes = {
+ id: PropTypes.string.isRequired,
+ children: PropTypes.node,
+ intl: PropTypes.object.isRequired,
+ };
+
+ state = {
+ visible: !bannerSettings.get(this.props.id),
+ };
+
+ handleDismiss = () => {
+ const { id } = this.props;
+ this.setState({ visible: false }, () => bannerSettings.set(id, true));
+ }
+
+ render () {
+ const { visible } = this.state;
+
+ if (!visible) {
+ return null;
+ }
+
+ const { children, intl } = this.props;
+
+ return (
+ <div className='dismissable-banner'>
+ <div className='dismissable-banner__message'>
+ {children}
+ </div>
+
+ <div className='dismissable-banner__action'>
+ <IconButton icon='times' title={intl.formatMessage(messages.dismiss)} onClick={this.handleDismiss} />
+ </div>
+ </div>
+ );
+ }
+
+}
diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.js
index afa7b3ed47..7575218025 100644
--- a/app/javascript/mastodon/features/community_timeline/index.js
+++ b/app/javascript/mastodon/features/community_timeline/index.js
@@ -10,6 +10,8 @@ import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import ColumnSettingsContainer from './containers/column_settings_container';
import { connectCommunityStream } from '../../actions/streaming';
import { Helmet } from 'react-helmet';
+import { domain } from 'mastodon/initial_state';
+import DismissableBanner from 'mastodon/components/dismissable_banner';
const messages = defineMessages({
title: { id: 'column.community', defaultMessage: 'Local timeline' },
@@ -134,6 +136,10 @@ class CommunityTimeline extends React.PureComponent {
<ColumnSettingsContainer columnId={columnId} />
</ColumnHeader>
+ <DismissableBanner id='community_timeline'>
+ <FormattedMessage id='dismissable_banner.community_timeline' defaultMessage='These are the most recent public posts from people whose accounts are hosted by {domain}.' values={{ domain }} />
+ </DismissableBanner>
+
<StatusListContainer
trackScroll={!pinned}
scrollKey={`community_timeline-${columnId}`}
diff --git a/app/javascript/mastodon/features/explore/links.js b/app/javascript/mastodon/features/explore/links.js
index d3aaa9cddf..b47fc8fcf7 100644
--- a/app/javascript/mastodon/features/explore/links.js
+++ b/app/javascript/mastodon/features/explore/links.js
@@ -6,6 +6,7 @@ import LoadingIndicator from 'mastodon/components/loading_indicator';
import { connect } from 'react-redux';
import { fetchTrendingLinks } from 'mastodon/actions/trends';
import { FormattedMessage } from 'react-intl';
+import DismissableBanner from 'mastodon/components/dismissable_banner';
const mapStateToProps = state => ({
links: state.getIn(['trends', 'links', 'items']),
@@ -29,9 +30,17 @@ class Links extends React.PureComponent {
render () {
const { isLoading, links } = this.props;
+ const banner = (
+ <DismissableBanner id='explore/links'>
+ <FormattedMessage id='dismissable_banner.explore_links' defaultMessage='These news stories are being talked about by people on this and other servers of the decentralized network right now.' />
+ </DismissableBanner>
+ );
+
if (!isLoading && links.isEmpty()) {
return (
<div className='explore__links scrollable scrollable--flex'>
+ {banner}
+
<div className='empty-column-indicator'>
<FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' />
</div>
@@ -41,6 +50,8 @@ class Links extends React.PureComponent {
return (
<div className='explore__links'>
+ {banner}
+
{isLoading ? (<LoadingIndicator />) : links.map(link => (
<Story
key={link.get('id')}
diff --git a/app/javascript/mastodon/features/explore/statuses.js b/app/javascript/mastodon/features/explore/statuses.js
index 33e5b41796..791f11b9fa 100644
--- a/app/javascript/mastodon/features/explore/statuses.js
+++ b/app/javascript/mastodon/features/explore/statuses.js
@@ -6,6 +6,7 @@ import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { fetchTrendingStatuses, expandTrendingStatuses } from 'mastodon/actions/trends';
import { debounce } from 'lodash';
+import DismissableBanner from 'mastodon/components/dismissable_banner';
const mapStateToProps = state => ({
statusIds: state.getIn(['status_lists', 'trending', 'items']),
@@ -40,17 +41,23 @@ class Statuses extends React.PureComponent {
const emptyMessage = <FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' />;
return (
- <StatusList
- trackScroll
- statusIds={statusIds}
- scrollKey='explore-statuses'
- hasMore={hasMore}
- isLoading={isLoading}
- onLoadMore={this.handleLoadMore}
- emptyMessage={emptyMessage}
- bindToDocument={!multiColumn}
- withCounters
- />
+ <>
+ <DismissableBanner id='explore/statuses'>
+ <FormattedMessage id='dismissable_banner.explore_statuses' defaultMessage='These posts from this and other servers in the decentralized network are gaining traction on this server right now.' />
+ </DismissableBanner>
+
+ <StatusList
+ trackScroll
+ statusIds={statusIds}
+ scrollKey='explore-statuses'
+ hasMore={hasMore}
+ isLoading={isLoading}
+ onLoadMore={this.handleLoadMore}
+ emptyMessage={emptyMessage}
+ bindToDocument={!multiColumn}
+ withCounters
+ />
+ </>
);
}
diff --git a/app/javascript/mastodon/features/explore/tags.js b/app/javascript/mastodon/features/explore/tags.js
index 6cd3a6fb10..258dc392f2 100644
--- a/app/javascript/mastodon/features/explore/tags.js
+++ b/app/javascript/mastodon/features/explore/tags.js
@@ -6,6 +6,7 @@ import LoadingIndicator from 'mastodon/components/loading_indicator';
import { connect } from 'react-redux';
import { fetchTrendingHashtags } from 'mastodon/actions/trends';
import { FormattedMessage } from 'react-intl';
+import DismissableBanner from 'mastodon/components/dismissable_banner';
const mapStateToProps = state => ({
hashtags: state.getIn(['trends', 'tags', 'items']),
@@ -29,9 +30,17 @@ class Tags extends React.PureComponent {
render () {
const { isLoading, hashtags } = this.props;
+ const banner = (
+ <DismissableBanner id='explore/tags'>
+ <FormattedMessage id='dismissable_banner.explore_tags' defaultMessage='These hashtags are gaining traction among people on this and other servers of the decentralized network right now.' />
+ </DismissableBanner>
+ );
+
if (!isLoading && hashtags.isEmpty()) {
return (
<div className='explore__links scrollable scrollable--flex'>
+ {banner}
+
<div className='empty-column-indicator'>
<FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' />
</div>
@@ -41,6 +50,8 @@ class Tags extends React.PureComponent {
return (
<div className='explore__links'>
+ {banner}
+
{isLoading ? (<LoadingIndicator />) : hashtags.map(hashtag => (
<Hashtag key={hashtag.get('name')} hashtag={hashtag} />
))}
diff --git a/app/javascript/mastodon/features/public_timeline/index.js b/app/javascript/mastodon/features/public_timeline/index.js
index 5b1b7c650e..8dbef98c0b 100644
--- a/app/javascript/mastodon/features/public_timeline/index.js
+++ b/app/javascript/mastodon/features/public_timeline/index.js
@@ -10,6 +10,7 @@ import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import ColumnSettingsContainer from './containers/column_settings_container';
import { connectPublicStream } from '../../actions/streaming';
import { Helmet } from 'react-helmet';
+import DismissableBanner from 'mastodon/components/dismissable_banner';
const messages = defineMessages({
title: { id: 'column.public', defaultMessage: 'Federated timeline' },
@@ -137,6 +138,10 @@ class PublicTimeline extends React.PureComponent {
<ColumnSettingsContainer columnId={columnId} />
</ColumnHeader>
+ <DismissableBanner id='public_timeline'>
+ <FormattedMessage id='dismissable_banner.public_timeline' defaultMessage='These are the most recent public posts from people on this and other servers of the decentralized network that this server knows about.' />
+ </DismissableBanner>
+
<StatusListContainer
timelineId={`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`}
onLoadMore={this.handleLoadMore}
diff --git a/app/javascript/mastodon/settings.js b/app/javascript/mastodon/settings.js
index 7643a508ea..46cfadfa36 100644
--- a/app/javascript/mastodon/settings.js
+++ b/app/javascript/mastodon/settings.js
@@ -45,3 +45,4 @@ export default class Settings {
export const pushNotificationsSetting = new Settings('mastodon_push_notification_data');
export const tagHistory = new Settings('mastodon_tag_history');
+export const bannerSettings = new Settings('mastodon_banner_settings');
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index cc8455ce3c..02c2a14bff 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -8312,3 +8312,28 @@ noscript {
}
}
}
+
+.dismissable-banner {
+ background: $ui-base-color;
+ border-bottom: 1px solid lighten($ui-base-color, 8%);
+ display: flex;
+ align-items: center;
+ gap: 30px;
+
+ &__message {
+ flex: 1 1 auto;
+ padding: 20px 15px;
+ cursor: default;
+ font-size: 14px;
+ line-height: 18px;
+ color: $primary-text-color;
+ }
+
+ &__action {
+ padding: 15px;
+ flex: 0 0 auto;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+}
--
GitLab