From 43b5d5e38d2b8ad8f1d1ad0911c3c1718159c912 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 29 Sep 2022 04:39:33 +0200 Subject: Add logged-out access to the web UI (#18961) --- app/javascript/mastodon/actions/accounts.js | 6 +- app/javascript/mastodon/actions/markers.js | 10 +-- app/javascript/mastodon/components/logo.js | 4 +- app/javascript/mastodon/containers/mastodon.js | 2 +- .../mastodon/features/account/components/header.js | 30 +++++--- .../mastodon/features/community_timeline/index.js | 6 ++ .../mastodon/features/directory/index.js | 6 ++ app/javascript/mastodon/features/explore/index.js | 6 ++ app/javascript/mastodon/features/explore/links.js | 11 +++ .../mastodon/features/explore/results.js | 18 ++++- .../mastodon/features/explore/suggestions.js | 11 +++ app/javascript/mastodon/features/explore/tags.js | 11 +++ .../mastodon/features/hashtag_timeline/index.js | 18 ++++- .../mastodon/features/public_timeline/index.js | 6 ++ app/javascript/mastodon/features/status/index.js | 24 ++++++- .../features/ui/components/columns_area.js | 4 +- .../features/ui/components/compose_panel.js | 22 +++++- .../features/ui/components/document_title.js | 41 ----------- .../mastodon/features/ui/components/link_footer.js | 46 +++++++++--- .../features/ui/components/navigation_panel.js | 83 ++++++++++++++++------ .../features/ui/components/sign_in_banner.js | 11 +++ app/javascript/mastodon/features/ui/index.js | 43 ++++++++--- app/javascript/mastodon/initial_state.js | 2 + app/javascript/styles/mastodon/_mixins.scss | 1 + app/javascript/styles/mastodon/components.scss | 56 +++++++++++++-- 25 files changed, 368 insertions(+), 110 deletions(-) delete mode 100644 app/javascript/mastodon/features/ui/components/document_title.js create mode 100644 app/javascript/mastodon/features/ui/components/sign_in_banner.js (limited to 'app/javascript') diff --git a/app/javascript/mastodon/actions/accounts.js b/app/javascript/mastodon/actions/accounts.js index eedf61dc9..f61f06e40 100644 --- a/app/javascript/mastodon/actions/accounts.js +++ b/app/javascript/mastodon/actions/accounts.js @@ -536,10 +536,12 @@ export function expandFollowingFail(id, error) { export function fetchRelationships(accountIds) { return (dispatch, getState) => { - const loadedRelationships = getState().get('relationships'); + const state = getState(); + const loadedRelationships = state.get('relationships'); const newAccountIds = accountIds.filter(id => loadedRelationships.get(id, null) === null); + const signedIn = !!state.getIn(['meta', 'me']); - if (newAccountIds.length === 0) { + if (!signedIn || newAccountIds.length === 0) { return; } diff --git a/app/javascript/mastodon/actions/markers.js b/app/javascript/mastodon/actions/markers.js index 16a3df8f6..b7f406cb8 100644 --- a/app/javascript/mastodon/actions/markers.js +++ b/app/javascript/mastodon/actions/markers.js @@ -1,6 +1,7 @@ import api from '../api'; import { debounce } from 'lodash'; import compareId from '../compare_id'; +import { List as ImmutableList } from 'immutable'; export const MARKERS_FETCH_REQUEST = 'MARKERS_FETCH_REQUEST'; export const MARKERS_FETCH_SUCCESS = 'MARKERS_FETCH_SUCCESS'; @@ -11,7 +12,7 @@ export const synchronouslySubmitMarkers = () => (dispatch, getState) => { const accessToken = getState().getIn(['meta', 'access_token'], ''); const params = _buildParams(getState()); - if (Object.keys(params).length === 0) { + if (Object.keys(params).length === 0 || accessToken === '') { return; } @@ -63,7 +64,7 @@ export const synchronouslySubmitMarkers = () => (dispatch, getState) => { const _buildParams = (state) => { const params = {}; - const lastHomeId = state.getIn(['timelines', 'home', 'items']).find(item => item !== null); + const lastHomeId = state.getIn(['timelines', 'home', 'items'], ImmutableList()).find(item => item !== null); const lastNotificationId = state.getIn(['notifications', 'lastReadId']); if (lastHomeId && compareId(lastHomeId, state.getIn(['markers', 'home'])) > 0) { @@ -82,9 +83,10 @@ const _buildParams = (state) => { }; const debouncedSubmitMarkers = debounce((dispatch, getState) => { - const params = _buildParams(getState()); + const accessToken = getState().getIn(['meta', 'access_token'], ''); + const params = _buildParams(getState()); - if (Object.keys(params).length === 0) { + if (Object.keys(params).length === 0 || accessToken === '') { return; } diff --git a/app/javascript/mastodon/components/logo.js b/app/javascript/mastodon/components/logo.js index d1c7f08a9..3570b3644 100644 --- a/app/javascript/mastodon/components/logo.js +++ b/app/javascript/mastodon/components/logo.js @@ -1,8 +1,8 @@ import React from 'react'; const Logo = () => ( - - + + ); diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js index f4bef4686..08241522c 100644 --- a/app/javascript/mastodon/containers/mastodon.js +++ b/app/javascript/mastodon/containers/mastodon.js @@ -26,7 +26,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/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index 8f2753c35..e407a0d55 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import Button from 'mastodon/components/button'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { autoPlayGif, me } from 'mastodon/initial_state'; +import { autoPlayGif, me, title, domain } from 'mastodon/initial_state'; import classNames from 'classnames'; import Icon from 'mastodon/components/icon'; import IconButton from 'mastodon/components/icon_button'; @@ -15,6 +15,7 @@ import { NavLink } from 'react-router-dom'; import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; import AccountNoteContainer from '../containers/account_note_container'; import { PERMISSION_MANAGE_USERS } from 'mastodon/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', @@ -132,6 +141,7 @@ class Header extends ImmutablePureComponent { render () { const { account, hidden, intl, domain } = this.props; + const { signedIn } = this.context.identity; if (!account) { return null; @@ -162,12 +172,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 = ; } @@ -183,7 +193,7 @@ class Header extends ImmutablePureComponent { lockedIcon = ; } - if (account.get('id') !== me) { + if (signedIn && account.get('id') !== me) { 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); @@ -206,7 +216,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'])) { @@ -239,7 +249,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); @@ -298,7 +308,7 @@ class Header extends ImmutablePureComponent { )} - + )} @@ -327,7 +337,7 @@ class Header extends ImmutablePureComponent { )} - {account.get('id') !== me && } + {(account.get('id') !== me && signedIn) && } {account.get('note').length > 0 && account.get('note') !== '

' &&
} @@ -359,6 +369,10 @@ class Header extends ImmutablePureComponent {
)} + + + {titleFromAccount(account)} - {title} + ); } diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.js index 30f776048..f9d50e64c 100644 --- a/app/javascript/mastodon/features/community_timeline/index.js +++ b/app/javascript/mastodon/features/community_timeline/index.js @@ -9,6 +9,8 @@ import { expandCommunityTimeline } from '../../actions/timelines'; 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 { title } from 'mastodon/initial_state'; const messages = defineMessages({ title: { id: 'column.community', defaultMessage: 'Local timeline' }, @@ -128,6 +130,10 @@ class CommunityTimeline extends React.PureComponent { emptyMessage={} bindToDocument={!multiColumn} /> + + + {intl.formatMessage(messages.title)} - {title} + ); } diff --git a/app/javascript/mastodon/features/directory/index.js b/app/javascript/mastodon/features/directory/index.js index 94d7d1a9c..36f46c510 100644 --- a/app/javascript/mastodon/features/directory/index.js +++ b/app/javascript/mastodon/features/directory/index.js @@ -13,6 +13,8 @@ import RadioButton from 'mastodon/components/radio_button'; import LoadMore from 'mastodon/components/load_more'; import ScrollContainer from 'mastodon/containers/scroll_container'; import LoadingIndicator from 'mastodon/components/loading_indicator'; +import { title } from 'mastodon/initial_state'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ title: { id: 'column.directory', defaultMessage: 'Browse profiles' }, @@ -165,6 +167,10 @@ class Directory extends React.PureComponent { /> {multiColumn && !pinned ? {scrollableArea} : scrollableArea} + + + {intl.formatMessage(messages.title)} - {title} + ); } diff --git a/app/javascript/mastodon/features/explore/index.js b/app/javascript/mastodon/features/explore/index.js index 8082f2d99..e1d1eb563 100644 --- a/app/javascript/mastodon/features/explore/index.js +++ b/app/javascript/mastodon/features/explore/index.js @@ -11,6 +11,8 @@ import Statuses from './statuses'; import Suggestions from './suggestions'; import Search from 'mastodon/features/compose/containers/search_container'; import SearchResults from './results'; +import { Helmet } from 'react-helmet'; +import { title } from 'mastodon/initial_state'; const messages = defineMessages({ title: { id: 'explore.title', defaultMessage: 'Explore' }, @@ -81,6 +83,10 @@ class Explore extends React.PureComponent { + + + {intl.formatMessage(messages.title)} - {title} + )} diff --git a/app/javascript/mastodon/features/explore/links.js b/app/javascript/mastodon/features/explore/links.js index 6649fb6e4..d3aaa9cdd 100644 --- a/app/javascript/mastodon/features/explore/links.js +++ b/app/javascript/mastodon/features/explore/links.js @@ -5,6 +5,7 @@ import Story from './components/story'; import LoadingIndicator from 'mastodon/components/loading_indicator'; import { connect } from 'react-redux'; import { fetchTrendingLinks } from 'mastodon/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/mastodon/features/explore/results.js b/app/javascript/mastodon/features/explore/results.js index 1286020f5..0dc108918 100644 --- a/app/javascript/mastodon/features/explore/results.js +++ b/app/javascript/mastodon/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 'mastodon/actions/search'; import Account from 'mastodon/containers/account_container'; @@ -10,10 +10,17 @@ import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag'; import { List as ImmutableList } from 'immutable'; import LoadMore from 'mastodon/components/load_more'; import LoadingIndicator from 'mastodon/components/loading_indicator'; +import { title } from 'mastodon/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/mastodon/features/explore/suggestions.js b/app/javascript/mastodon/features/explore/suggestions.js index 0c6a7ef8a..e6ad09974 100644 --- a/app/javascript/mastodon/features/explore/suggestions.js +++ b/app/javascript/mastodon/features/explore/suggestions.js @@ -5,6 +5,7 @@ import AccountCard from 'mastodon/features/directory/components/account_card'; import LoadingIndicator from 'mastodon/components/loading_indicator'; import { connect } from 'react-redux'; import { fetchSuggestions } from 'mastodon/actions/suggestions'; +import { FormattedMessage } from 'react-intl'; const mapStateToProps = state => ({ suggestions: state.getIn(['suggestions', 'items']), @@ -28,6 +29,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/mastodon/features/explore/tags.js b/app/javascript/mastodon/features/explore/tags.js index c0ad9fc6e..6cd3a6fb1 100644 --- a/app/javascript/mastodon/features/explore/tags.js +++ b/app/javascript/mastodon/features/explore/tags.js @@ -5,6 +5,7 @@ import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag'; import LoadingIndicator from 'mastodon/components/loading_indicator'; import { connect } from 'react-redux'; import { fetchTrendingHashtags } from 'mastodon/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/mastodon/features/hashtag_timeline/index.js b/app/javascript/mastodon/features/hashtag_timeline/index.js index dc8a61640..7069e0341 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/index.js +++ b/app/javascript/mastodon/features/hashtag_timeline/index.js @@ -14,6 +14,8 @@ import { isEqual } from 'lodash'; import { fetchHashtag, followHashtag, unfollowHashtag } from 'mastodon/actions/tags'; import Icon from 'mastodon/components/icon'; import classNames from 'classnames'; +import { title } from 'mastodon/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, @@ -158,6 +164,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 +181,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 +189,7 @@ class HashtagTimeline extends React.PureComponent { const following = tag.get('following'); followButton = ( - ); @@ -208,6 +220,10 @@ class HashtagTimeline extends React.PureComponent { emptyMessage={} bindToDocument={!multiColumn} /> + + + {`#${id}`} - {title} + ); } diff --git a/app/javascript/mastodon/features/public_timeline/index.js b/app/javascript/mastodon/features/public_timeline/index.js index b1d5518af..2f926678c 100644 --- a/app/javascript/mastodon/features/public_timeline/index.js +++ b/app/javascript/mastodon/features/public_timeline/index.js @@ -9,6 +9,8 @@ import { expandPublicTimeline } from '../../actions/timelines'; 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 { title } from 'mastodon/initial_state'; const messages = defineMessages({ title: { id: 'column.public', defaultMessage: 'Federated timeline' }, @@ -131,6 +133,10 @@ class PublicTimeline extends React.PureComponent { emptyMessage={} bindToDocument={!multiColumn} /> + + + {intl.formatMessage(messages.title)} - {title} + ); } diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index 5ff7e060e..748dc7a92 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -56,10 +56,11 @@ import { openModal } from '../../actions/modal'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { HotKeys } from 'react-hotkeys'; -import { boostModal, deleteModal } from '../../initial_state'; +import { boostModal, deleteModal, title } from '../../initial_state'; import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen'; import { textForScreenReader, defaultMediaVisibility } from '../../components/status'; import Icon from 'mastodon/components/icon'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, @@ -156,6 +157,23 @@ const makeMapStateToProps = () => { return mapStateToProps; }; +const truncate = (str, num) => { + if (str.length > num) { + return str.slice(0, num) + '…'; + } else { + return str; + } +}; + +const titleFromStatus = status => { + const displayName = status.getIn(['account', 'display_name']); + const username = status.getIn(['account', 'username']); + const prefix = displayName.trim().length === 0 ? username : displayName; + const text = status.get('search_index'); + + return `${prefix}: "${truncate(text, 30)}"`; +}; + export default @injectIntl @connect(makeMapStateToProps) class Status extends ImmutablePureComponent { @@ -605,6 +623,10 @@ class Status extends ImmutablePureComponent { {descendants}
+ + + {titleFromStatus(status)} - {title} + ); } diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js index 68017a5f1..83e10e003 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.js +++ b/app/javascript/mastodon/features/ui/components/columns_area.js @@ -60,6 +60,7 @@ class ColumnsArea extends ImmutablePureComponent { static contextTypes = { router: PropTypes.object.isRequired, + identity: PropTypes.object.isRequired, }; static propTypes = { @@ -212,11 +213,12 @@ class ColumnsArea extends ImmutablePureComponent { render () { const { columns, children, singleColumn, isModalOpen, intl } = this.props; const { shouldAnimate, renderComposePanel } = this.state; + const { signedIn } = this.context.identity; const columnIndex = getIndex(this.context.router.history.location.pathname); if (singleColumn) { - const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : ; + const floatingActionButton = (!signedIn || shouldHideFAB(this.context.router.history.location.pathname)) ? null : ; const content = columnIndex !== -1 ? ( diff --git a/app/javascript/mastodon/features/ui/components/compose_panel.js b/app/javascript/mastodon/features/ui/components/compose_panel.js index 3d0c48c7a..1c128188f 100644 --- a/app/javascript/mastodon/features/ui/components/compose_panel.js +++ b/app/javascript/mastodon/features/ui/components/compose_panel.js @@ -10,6 +10,10 @@ import { changeComposing } from 'mastodon/actions/compose'; export default @connect() class ComposePanel extends React.PureComponent { + static contextTypes = { + identity: PropTypes.object.isRequired, + }; + static propTypes = { dispatch: PropTypes.func.isRequired, }; @@ -23,11 +27,25 @@ class ComposePanel extends React.PureComponent { } render() { + const { signedIn } = this.context.identity; + return (
- - + + {!signedIn && ( + +
+ + )} + + {signedIn && ( + + + + + )} +
); diff --git a/app/javascript/mastodon/features/ui/components/document_title.js b/app/javascript/mastodon/features/ui/components/document_title.js deleted file mode 100644 index cd081b20c..000000000 --- a/app/javascript/mastodon/features/ui/components/document_title.js +++ /dev/null @@ -1,41 +0,0 @@ -import { PureComponent } from 'react'; -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import { title } from 'mastodon/initial_state'; - -const mapStateToProps = state => ({ - unread: state.getIn(['missed_updates', 'unread']), -}); - -export default @connect(mapStateToProps) -class DocumentTitle extends PureComponent { - - static propTypes = { - unread: PropTypes.number.isRequired, - }; - - componentDidMount () { - this._sideEffects(); - } - - componentDidUpdate() { - this._sideEffects(); - } - - _sideEffects () { - const { unread } = this.props; - - if (unread > 99) { - document.title = `(*) ${title}`; - } else if (unread > 0) { - document.title = `(${unread}) ${title}`; - } else { - document.title = title; - } - } - - render () { - return null; - } - -} diff --git a/app/javascript/mastodon/features/ui/components/link_footer.js b/app/javascript/mastodon/features/ui/components/link_footer.js index bbb9b122a..95cd6cf8e 100644 --- a/app/javascript/mastodon/features/ui/components/link_footer.js +++ b/app/javascript/mastodon/features/ui/components/link_footer.js @@ -49,20 +49,46 @@ class LinkFooter extends React.PureComponent { render () { const { withHotkeys } = this.props; + const { signedIn, permissions } = this.context.identity; + const items = []; + + if ((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS) { + items.push(); + } + + if (withHotkeys) { + items.push(); + } + + if (signedIn) { + items.push(); + } + + if (!limitedFederationMode) { + items.push(); + } + + if (profileDirectory) { + items.push(); + } + + items.push(); + items.push(); + + if (signedIn) { + items.push(); + } + + items.push(); + + if (signedIn) { + items.push(); + } return (
    - {((this.context.identity.permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS) &&
  • ·
  • } - {withHotkeys &&
  • ·
  • } -
  • ·
  • - {!limitedFederationMode &&
  • ·
  • } - {profileDirectory &&
  • ·
  • } -
  • ·
  • -
  • ·
  • -
  • ·
  • -
  • ·
  • -
  • +
  • {items.reduce((prev, curr) => [prev, ' · ', curr])}

diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.js b/app/javascript/mastodon/features/ui/components/navigation_panel.js index fe4ed5d77..00ae04761 100644 --- a/app/javascript/mastodon/features/ui/components/navigation_panel.js +++ b/app/javascript/mastodon/features/ui/components/navigation_panel.js @@ -1,5 +1,6 @@ import React from 'react'; -import { NavLink, withRouter } from 'react-router-dom'; +import PropTypes from 'prop-types'; +import { NavLink, Link } from 'react-router-dom'; import { FormattedMessage } from 'react-intl'; import Icon from 'mastodon/components/icon'; import { showTrends } from 'mastodon/initial_state'; @@ -7,30 +8,68 @@ import NotificationsCounterIcon from './notifications_counter_icon'; import FollowRequestsNavLink from './follow_requests_nav_link'; import ListPanel from './list_panel'; import TrendsContainer from 'mastodon/features/getting_started/containers/trends_container'; +import Logo from 'mastodon/components/logo'; +import SignInBanner from './sign_in_banner'; -const NavigationPanel = () => ( -

- - - - - - - - - - +export default class NavigationPanel extends React.Component { - + static contextTypes = { + router: PropTypes.object.isRequired, + identity: PropTypes.object.isRequired, + }; -
+ render () { + const { signedIn } = this.context.identity; - - + return ( +
+ - {showTrends &&
} - {showTrends && } -
-); +
-export default withRouter(NavigationPanel); + {signedIn && ( + + + + + + )} + + + + + + {!signedIn && ( + +
+ +
+ )} + + {signedIn && ( + + + + + + + + +
+ + + +
+ )} + + {showTrends && ( + +
+ + + )} +
+ ); + } + +} diff --git a/app/javascript/mastodon/features/ui/components/sign_in_banner.js b/app/javascript/mastodon/features/ui/components/sign_in_banner.js new file mode 100644 index 000000000..c8403a8ad --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/sign_in_banner.js @@ -0,0 +1,11 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; + +const SignInBanner = () => ( +
+

+ +
+); + +export default SignInBanner; diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 9a901f12a..5825db1e4 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -20,7 +20,6 @@ import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodo import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers'; import UploadArea from './components/upload_area'; import ColumnsAreaContainer from './containers/columns_area_container'; -import DocumentTitle from './components/document_title'; import PictureInPicture from 'mastodon/features/picture_in_picture'; import { Compose, @@ -53,8 +52,9 @@ import { Explore, FollowRecommendations, } from './util/async-components'; -import { me } from '../../initial_state'; +import { me, title } from '../../initial_state'; import { closeOnboarding, INTRODUCTION_VERSION } from 'mastodon/actions/onboarding'; +import { Helmet } from 'react-helmet'; // Dummy import, to make sure that ends up in the application bundle. // Without this it ends up in ~8 very commonly used bundles. @@ -110,6 +110,10 @@ const keyMap = { class SwitchingColumnsArea extends React.PureComponent { + static contextTypes = { + identity: PropTypes.object, + }; + static propTypes = { children: PropTypes.node, location: PropTypes.object, @@ -145,12 +149,25 @@ class SwitchingColumnsArea extends React.PureComponent { render () { const { children, mobile } = this.props; - const redirect = mobile ? : ; + const { signedIn } = this.context.identity; + + let redirect; + + if (signedIn) { + if (mobile) { + redirect = ; + } else { + redirect = ; + } + } else { + redirect = ; + } return ( {redirect} + @@ -208,6 +225,7 @@ class UI extends React.PureComponent { static contextTypes = { router: PropTypes.object.isRequired, + identity: PropTypes.object.isRequired, }; static propTypes = { @@ -343,6 +361,8 @@ class UI extends React.PureComponent { } componentDidMount () { + const { signedIn } = this.context.identity; + window.addEventListener('focus', this.handleWindowFocus, false); window.addEventListener('blur', this.handleWindowBlur, false); window.addEventListener('beforeunload', this.handleBeforeUnload, false); @@ -359,16 +379,18 @@ class UI extends React.PureComponent { } // On first launch, redirect to the follow recommendations page - if (this.props.firstLaunch) { + if (signedIn && this.props.firstLaunch) { this.context.router.history.replace('/start'); this.props.dispatch(closeOnboarding()); } - this.props.dispatch(fetchMarkers()); - this.props.dispatch(expandHomeTimeline()); - this.props.dispatch(expandNotifications()); + if (signedIn) { + this.props.dispatch(fetchMarkers()); + this.props.dispatch(expandHomeTimeline()); + this.props.dispatch(expandNotifications()); - setTimeout(() => this.props.dispatch(fetchRules()), 3000); + setTimeout(() => this.props.dispatch(fetchRules()), 3000); + } this.hotkeys.__mousetrap__.stopCallback = (e, element) => { return ['TEXTAREA', 'SELECT', 'INPUT'].includes(element.tagName); @@ -546,7 +568,10 @@ class UI extends React.PureComponent { - + + + {title} +
); diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js index 709975270..9cc75b6cb 100644 --- a/app/javascript/mastodon/initial_state.js +++ b/app/javascript/mastodon/initial_state.js @@ -3,6 +3,7 @@ const initialState = element && JSON.parse(element.textContent); const getMeta = (prop) => initialState && initialState.meta && initialState.meta[prop]; +export const domain = getMeta('domain'); export const reduceMotion = getMeta('reduce_motion'); export const autoPlayGif = getMeta('auto_play_gif'); export const displayMedia = getMeta('display_media'); @@ -26,5 +27,6 @@ export const title = getMeta('title'); export const cropImages = getMeta('crop_images'); export const disableSwiping = getMeta('disable_swiping'); export const languages = initialState && initialState.languages; +export const server = initialState && initialState.server; export default initialState; diff --git a/app/javascript/styles/mastodon/_mixins.scss b/app/javascript/styles/mastodon/_mixins.scss index 68cad0fde..dcfab6bd0 100644 --- a/app/javascript/styles/mastodon/_mixins.scss +++ b/app/javascript/styles/mastodon/_mixins.scss @@ -20,6 +20,7 @@ font-family: inherit; background: $ui-base-color; color: $darker-text-color; + border-radius: 4px; font-size: 14px; margin: 0; } diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index f5377a858..1f1a5a5ca 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -126,6 +126,7 @@ &:hover { border-color: lighten($ui-primary-color, 4%); color: lighten($darker-text-color, 4%); + text-decoration: none; } &:disabled { @@ -700,6 +701,15 @@ transition: height 0.4s ease, opacity 0.4s ease; } +.sign-in-banner { + padding: 10px; + + p { + color: $darker-text-color; + margin-bottom: 20px; + } +} + .emojione { font-size: inherit; vertical-align: middle; @@ -2214,6 +2224,7 @@ a.account__display-name { > .scrollable { background: $ui-base-color; + border-radius: 0 0 4px 4px; } } @@ -2660,6 +2671,26 @@ a.account__display-name { height: calc(100% - 10px); overflow-y: hidden; + .hero-widget { + box-shadow: none; + + &__text, + &__img, + &__img img { + border-radius: 0; + } + + &__text { + padding: 15px; + color: $secondary-text-color; + + strong { + font-weight: 700; + color: $primary-text-color; + } + } + } + .navigation-bar { padding-top: 20px; padding-bottom: 20px; @@ -2667,10 +2698,6 @@ a.account__display-name { min-height: 20px; } - .flex-spacer { - background: transparent; - } - .compose-form { flex: 1; overflow-y: hidden; @@ -2709,6 +2736,14 @@ a.account__display-name { flex: 0 0 auto; } + .logo { + height: 30px; + width: auto; + } +} + +.navigation-panel, +.compose-panel { hr { flex: 0 0 auto; border: 0; @@ -2836,6 +2871,7 @@ a.account__display-name { box-sizing: border-box; width: 100%; background: lighten($ui-base-color, 4%); + border-radius: 4px 4px 0 0; color: $highlight-text-color; cursor: pointer; flex: 0 0 auto; @@ -3031,6 +3067,17 @@ a.account__display-name { color: $highlight-text-color; } } + + &--logo { + background: transparent; + padding: 10px; + + &:hover, + &:focus, + &:active { + background: transparent; + } + } } .column-link__icon { @@ -3551,6 +3598,7 @@ a.status-card.compact:hover { display: flex; font-size: 16px; background: lighten($ui-base-color, 4%); + border-radius: 4px 4px 0 0; flex: 0 0 auto; cursor: pointer; position: relative; -- cgit