diff options
author | Eugen Rochko <eugen@zeonfederated.com> | 2022-09-29 04:39:33 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-09-29 04:39:33 +0200 |
commit | 43b5d5e38d2b8ad8f1d1ad0911c3c1718159c912 (patch) | |
tree | edfad4e2db222aeba66c8c9e0d27f6a327ac9b98 /app | |
parent | 1a5150e9c364635b989cd0983dac259f94dbbea9 (diff) |
Add logged-out access to the web UI (#18961)
Diffstat (limited to 'app')
29 files changed, 393 insertions, 130 deletions
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index 7e443eb9e74..29478209dbf 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -2,8 +2,8 @@ class HomeController < ApplicationController before_action :redirect_unauthenticated_to_permalinks! - before_action :authenticate_user! before_action :set_referrer_policy_header + before_action :set_instance_presenter def index @body_classes = 'app-body' @@ -14,20 +14,16 @@ class HomeController < ApplicationController def redirect_unauthenticated_to_permalinks! return if user_signed_in? - redirect_to(PermalinkRedirector.new(request.path).redirect_path || default_redirect_path) - end + redirect_path = PermalinkRedirector.new(request.path).redirect_path - def default_redirect_path - if request.path.start_with?('/web') || whitelist_mode? - new_user_session_path - elsif single_user_mode? - short_account_path(Account.local.without_suspended.where('id > 0').first) - else - about_path - end + redirect_to(redirect_path) if redirect_path.present? end def set_referrer_policy_header response.headers['Referrer-Policy'] = 'origin' end + + def set_instance_presenter + @instance_presenter = InstancePresenter.new + end end diff --git a/app/javascript/mastodon/actions/accounts.js b/app/javascript/mastodon/actions/accounts.js index eedf61dc99e..f61f06e408f 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 16a3df8f638..b7f406cb86b 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 d1c7f08a91d..3570b3644b7 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 = () => ( - <svg viewBox='0 0 216.4144 232.00976' className='logo'> - <use xlinkHref='#mastodon-svg-logo' /> + <svg viewBox='0 0 261 66' className='logo'> + <use xlinkHref='#logo-symbol-wordmark' /> </svg> ); diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js index f4bef46863a..08241522cf6 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 8f2753c35ef..e407a0d55c6 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 = <Button className={classNames('logo-button', { 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />; } else if (!account.getIn(['relationship', 'blocking'])) { - actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']), 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />; + actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']), 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={signedIn ? this.props.onFollow : undefined} />; } else if (account.getIn(['relationship', 'blocking'])) { actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />; } @@ -183,7 +193,7 @@ class Header extends ImmutablePureComponent { lockedIcon = <Icon id='lock' title={intl.formatMessage(messages.account_locked)} />; } - 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 { </React.Fragment> )} - <DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' /> + <DropdownMenuContainer disabled={menu.length === 0} items={menu} icon='ellipsis-v' size={24} direction='right' /> </div> )} </div> @@ -327,7 +337,7 @@ class Header extends ImmutablePureComponent { </div> )} - {account.get('id') !== me && <AccountNoteContainer account={account} />} + {(account.get('id') !== me && signedIn) && <AccountNoteContainer account={account} />} {account.get('note').length > 0 && account.get('note') !== '<p></p>' && <div className='account__header__content translate' dangerouslySetInnerHTML={content} />} @@ -359,6 +369,10 @@ class Header extends ImmutablePureComponent { </div> )} </div> + + <Helmet> + <title>{titleFromAccount(account)} - {title}</title> + </Helmet> </div> ); } diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.js index 30f77604867..f9d50e64cec 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={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />} bindToDocument={!multiColumn} /> + + <Helmet> + <title>{intl.formatMessage(messages.title)} - {title}</title> + </Helmet> </Column> ); } diff --git a/app/javascript/mastodon/features/directory/index.js b/app/javascript/mastodon/features/directory/index.js index 94d7d1a9c54..36f46c510e0 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 ? <ScrollContainer scrollKey='directory'>{scrollableArea}</ScrollContainer> : scrollableArea} + + <Helmet> + <title>{intl.formatMessage(messages.title)} - {title}</title> + </Helmet> </Column> ); } diff --git a/app/javascript/mastodon/features/explore/index.js b/app/javascript/mastodon/features/explore/index.js index 8082f2d99cb..e1d1eb563f2 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 { <Route path='/explore/suggestions' component={Suggestions} /> <Route exact path={['/explore', '/explore/posts', '/search']} component={Statuses} componentParams={{ multiColumn }} /> </Switch> + + <Helmet> + <title>{intl.formatMessage(messages.title)} - {title}</title> + </Helmet> </React.Fragment> )} </div> diff --git a/app/javascript/mastodon/features/explore/links.js b/app/javascript/mastodon/features/explore/links.js index 6649fb6e471..d3aaa9cddf7 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 ( + <div className='explore__links scrollable scrollable--flex'> + <div className='empty-column-indicator'> + <FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' /> + </div> + </div> + ); + } + return ( <div className='explore__links'> {isLoading ? (<LoadingIndicator />) : links.map(link => ( diff --git a/app/javascript/mastodon/features/explore/results.js b/app/javascript/mastodon/features/explore/results.js index 1286020f54c..0dc108918d2 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 { <div className='explore__search-results'> {isLoading ? <LoadingIndicator /> : filteredResults} </div> + + <Helmet> + <title>{intl.formatMessage(messages.title, { q })} - {title}</title> + </Helmet> </React.Fragment> ); } diff --git a/app/javascript/mastodon/features/explore/suggestions.js b/app/javascript/mastodon/features/explore/suggestions.js index 0c6a7ef8a3b..e6ad09974c8 100644 --- a/app/javascript/mastodon/features/explore/suggestions.js +++ b/app/javascript/mastodon/features/explore/suggestio |