From 5cdb4c483f732235d3b0b07eeed34757b33b0f83 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 24 May 2019 15:57:31 +0200 Subject: [PATCH 01/12] Forward port version bumps to 2.8.3 and 2.8.4 (#10819) * Bump version to 2.8.3 * Bump version to 2.8.4 --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ lib/mastodon/version.rb | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 222b7411d..f183b6f5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,39 @@ Changelog All notable changes to this project will be documented in this file. +## [2.8.4] - 2019-05-24 +### Fixed + +- Fix delivery not retrying on some inbox errors that should be retriable ([ThibG](https://github.com/tootsuite/mastodon/pull/10812)) +- Fix unnecessary 5 minute cooldowns on signature verifications in some cases ([ThibG](https://github.com/tootsuite/mastodon/pull/10813)) +- Fix possible race condition when processing statuses ([ThibG](https://github.com/tootsuite/mastodon/pull/10815)) + +### Security + +- Require specific OAuth scopes for specific endpoints of the streaming API, instead of merely requiring a token for all endpoints, and allow using WebSockets protocol negotiation to specify the access token instead of using a query string ([ThibG](https://github.com/tootsuite/mastodon/pull/10818)) + +## [2.8.3] - 2019-05-19 +### Added + +- Add `og:image:alt` OpenGraph tag ([BenLubar](https://github.com/tootsuite/mastodon/pull/10779)) +- Add clickable area below avatar in statuses in web UI ([Dar13](https://github.com/tootsuite/mastodon/pull/10766)) +- Add crossed-out eye icon on account gallery in web UI ([Kjwon15](https://github.com/tootsuite/mastodon/pull/10715)) +- Add media description tooltip to thumbnails in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10713)) + +### Changed + +- Change "mark as sensitive" button into a checkbox for clarity ([ThibG](https://github.com/tootsuite/mastodon/pull/10748)) + +### Fixed + +- Fix bug allowing users to publicly boost their private statuses ([ThibG](https://github.com/tootsuite/mastodon/pull/10775), [ThibG](https://github.com/tootsuite/mastodon/pull/10783)) +- Fix performance in formatter by a little ([ThibG](https://github.com/tootsuite/mastodon/pull/10765)) +- Fix some colors in the light theme ([yuzulabo](https://github.com/tootsuite/mastodon/pull/10754)) +- Fix some colors of the high contrast theme ([yuzulabo](https://github.com/tootsuite/mastodon/pull/10711)) +- Fix ambivalent active state of poll refresh button in web UI ([MaciekBaron](https://github.com/tootsuite/mastodon/pull/10720)) +- Fix duplicate posting being possible from web UI ([hinaloe](https://github.com/tootsuite/mastodon/pull/10785)) +- Fix "invited by" not showing up in admin UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10791)) + ## [2.8.2] - 2019-05-05 ### Added diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 91f45e45d..59ded05f7 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -13,7 +13,7 @@ module Mastodon end def patch - 2 + 4 end def pre From 1e5532e693d9533ee37f553aeb191e284178fa52 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 25 May 2019 21:27:00 +0200 Subject: [PATCH 02/12] Add responsive panels to the single-column layout (#10820) * Add responsive panels to the single-column layout * Fixes * Fix not being able to save the preference * Fix code style issues * Set max-height on the compose textarea and add a link to relationship manager --- .../settings/preferences_controller.rb | 1 + app/javascript/mastodon/actions/compose.js | 20 +- app/javascript/mastodon/actions/statuses.js | 8 +- .../mastodon/components/autosuggest_input.js | 2 +- .../features/compose/components/action_bar.js | 2 +- .../compose/components/navigation_bar.js | 2 +- .../features/compose/components/search.js | 10 + .../features/getting_started/index.js | 17 +- .../mastodon/features/search/index.js | 17 ++ .../features/ui/components/columns_area.js | 14 +- .../features/ui/components/compose_panel.js | 41 ++++ .../features/ui/components/list_panel.js | 55 +++++ .../ui/components/navigation_panel.js | 27 +++ .../components/notifications_counter_icon.js | 5 +- .../features/ui/components/tabs_bar.js | 14 +- app/javascript/mastodon/features/ui/index.js | 14 +- .../features/ui/util/async-components.js | 4 + app/javascript/mastodon/initial_state.js | 1 + app/javascript/mastodon/reducers/settings.js | 2 - .../styles/mastodon/components.scss | 214 ++++++++++++++---- app/lib/user_settings_decorator.rb | 5 + app/models/user.rb | 3 +- app/serializers/initial_state_serializer.rb | 1 + app/views/settings/preferences/show.html.haml | 3 + config/locales/simple_form.en.yml | 2 + config/settings.yml | 1 + 26 files changed, 389 insertions(+), 96 deletions(-) create mode 100644 app/javascript/mastodon/features/search/index.js create mode 100644 app/javascript/mastodon/features/ui/components/compose_panel.js create mode 100644 app/javascript/mastodon/features/ui/components/list_panel.js create mode 100644 app/javascript/mastodon/features/ui/components/navigation_panel.js diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb index 5afdf0eec..930ff70e7 100644 --- a/app/controllers/settings/preferences_controller.rb +++ b/app/controllers/settings/preferences_controller.rb @@ -49,6 +49,7 @@ class Settings::PreferencesController < Settings::BaseController :setting_hide_network, :setting_aggregate_reblogs, :setting_show_application, + :setting_advanced_layout, notification_emails: %i(follow follow_request reblog favourite mention digest report pending_account), interactions: %i(must_be_follower must_be_following) ) diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 94062f2be..33e631364 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -63,6 +63,14 @@ const messages = defineMessages({ uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' }, }); +const COMPOSE_PANEL_BREAKPOINT = 600 + (285 * 1) + (10 * 3); + +export const ensureComposeIsVisible = (getState, routerHistory) => { + if (!getState().getIn(['compose', 'mounted']) && window.innerWidth < COMPOSE_PANEL_BREAKPOINT) { + routerHistory.push('/statuses/new'); + } +}; + export function changeCompose(text) { return { type: COMPOSE_CHANGE, @@ -77,9 +85,7 @@ export function replyCompose(status, routerHistory) { status: status, }); - if (!getState().getIn(['compose', 'mounted'])) { - routerHistory.push('/statuses/new'); - } + ensureComposeIsVisible(getState, routerHistory); }; }; @@ -102,9 +108,7 @@ export function mentionCompose(account, routerHistory) { account: account, }); - if (!getState().getIn(['compose', 'mounted'])) { - routerHistory.push('/statuses/new'); - } + ensureComposeIsVisible(getState, routerHistory); }; }; @@ -115,9 +119,7 @@ export function directCompose(account, routerHistory) { account: account, }); - if (!getState().getIn(['compose', 'mounted'])) { - routerHistory.push('/statuses/new'); - } + ensureComposeIsVisible(getState, routerHistory); }; }; diff --git a/app/javascript/mastodon/actions/statuses.js b/app/javascript/mastodon/actions/statuses.js index 3916b9ac1..06a19afc3 100644 --- a/app/javascript/mastodon/actions/statuses.js +++ b/app/javascript/mastodon/actions/statuses.js @@ -4,6 +4,7 @@ import { evictStatus } from '../storage/modifier'; import { deleteFromTimelines } from './timelines'; import { importFetchedStatus, importFetchedStatuses, importAccount, importStatus } from './importer'; +import { ensureComposeIsVisible } from './compose'; export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST'; export const STATUS_FETCH_SUCCESS = 'STATUS_FETCH_SUCCESS'; @@ -139,7 +140,7 @@ export function redraft(status, raw_text) { }; }; -export function deleteStatus(id, router, withRedraft = false) { +export function deleteStatus(id, routerHistory, withRedraft = false) { return (dispatch, getState) => { let status = getState().getIn(['statuses', id]); @@ -156,10 +157,7 @@ export function deleteStatus(id, router, withRedraft = false) { if (withRedraft) { dispatch(redraft(status, response.data.text)); - - if (!getState().getIn(['compose', 'mounted'])) { - router.push('/statuses/new'); - } + ensureComposeIsVisible(getState, routerHistory); } }).catch(error => { dispatch(deleteStatusFail(id, error)); diff --git a/app/javascript/mastodon/components/autosuggest_input.js b/app/javascript/mastodon/components/autosuggest_input.js index 4b4aa8f0e..c7d965b53 100644 --- a/app/javascript/mastodon/components/autosuggest_input.js +++ b/app/javascript/mastodon/components/autosuggest_input.js @@ -49,7 +49,7 @@ export default class AutosuggestInput extends ImmutablePureComponent { autoFocus: PropTypes.bool, className: PropTypes.string, id: PropTypes.string, - searchTokens: ImmutablePropTypes.list, + searchTokens: PropTypes.arrayOf(PropTypes.string), maxLength: PropTypes.number, }; diff --git a/app/javascript/mastodon/features/compose/components/action_bar.js b/app/javascript/mastodon/features/compose/components/action_bar.js index 95d6eeb06..077226d70 100644 --- a/app/javascript/mastodon/features/compose/components/action_bar.js +++ b/app/javascript/mastodon/features/compose/components/action_bar.js @@ -46,7 +46,7 @@ class ActionBar extends React.PureComponent { return (
- +
); diff --git a/app/javascript/mastodon/features/compose/components/navigation_bar.js b/app/javascript/mastodon/features/compose/components/navigation_bar.js index 9910eb4f9..d8d49cb95 100644 --- a/app/javascript/mastodon/features/compose/components/navigation_bar.js +++ b/app/javascript/mastodon/features/compose/components/navigation_bar.js @@ -20,7 +20,7 @@ export default class NavigationBar extends ImmutablePureComponent {
{this.props.account.get('acct')} - +
diff --git a/app/javascript/mastodon/features/compose/components/search.js b/app/javascript/mastodon/features/compose/components/search.js index 774658b1b..6833c43ef 100644 --- a/app/javascript/mastodon/features/compose/components/search.js +++ b/app/javascript/mastodon/features/compose/components/search.js @@ -47,6 +47,10 @@ class SearchPopout extends React.PureComponent { export default @injectIntl class Search extends React.PureComponent { + static contextTypes = { + router: PropTypes.object.isRequired, + }; + static propTypes = { value: PropTypes.string.isRequired, submitted: PropTypes.bool, @@ -54,6 +58,7 @@ class Search extends React.PureComponent { onSubmit: PropTypes.func.isRequired, onClear: PropTypes.func.isRequired, onShow: PropTypes.func.isRequired, + openInRoute: PropTypes.bool, intl: PropTypes.object.isRequired, }; @@ -76,7 +81,12 @@ class Search extends React.PureComponent { handleKeyUp = (e) => { if (e.key === 'Enter') { e.preventDefault(); + this.props.onSubmit(); + + if (this.props.openInRoute) { + this.context.router.history.push('/search'); + } } else if (e.key === 'Escape') { document.querySelector('.ui').parentElement.focus(); } diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index a671578a0..cb3efb57b 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -9,12 +9,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { me, invitesEnabled, version, profile_directory, repository, source_url } from '../../initial_state'; import { fetchFollowRequests } from 'mastodon/actions/accounts'; -import { changeSetting } from 'mastodon/actions/settings'; import { List as ImmutableList } from 'immutable'; import { Link } from 'react-router-dom'; import NavigationBar from '../compose/components/navigation_bar'; import Icon from 'mastodon/components/icon'; -import Toggle from 'react-toggle'; const messages = defineMessages({ home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' }, @@ -41,12 +39,10 @@ const messages = defineMessages({ const mapStateToProps = state => ({ myAccount: state.getIn(['accounts', me]), unreadFollowRequests: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size, - forceSingleColumn: state.getIn(['settings', 'forceSingleColumn'], false), }); const mapDispatchToProps = dispatch => ({ fetchFollowRequests: () => dispatch(fetchFollowRequests()), - changeForceSingleColumn: checked => dispatch(changeSetting(['forceSingleColumn'], checked)), }); const badgeDisplay = (number, limit) => { @@ -71,8 +67,6 @@ class GettingStarted extends ImmutablePureComponent { fetchFollowRequests: PropTypes.func.isRequired, unreadFollowRequests: PropTypes.number, unreadNotifications: PropTypes.number, - forceSingleColumn: PropTypes.bool, - changeForceSingleColumn: PropTypes.func.isRequired, }; componentDidMount () { @@ -83,12 +77,8 @@ class GettingStarted extends ImmutablePureComponent { } } - handleForceSingleColumnChange = ({ target }) => { - this.props.changeForceSingleColumn(target.checked); - } - render () { - const { intl, myAccount, multiColumn, unreadFollowRequests, forceSingleColumn } = this.props; + const { intl, myAccount, multiColumn, unreadFollowRequests } = this.props; const navItems = []; let i = 1; @@ -187,11 +177,6 @@ class GettingStarted extends ImmutablePureComponent {

- - ); } diff --git a/app/javascript/mastodon/features/search/index.js b/app/javascript/mastodon/features/search/index.js new file mode 100644 index 000000000..76bf70d4b --- /dev/null +++ b/app/javascript/mastodon/features/search/index.js @@ -0,0 +1,17 @@ +import React from 'react'; +import SearchContainer from 'mastodon/features/compose/containers/search_container'; +import SearchResultsContainer from 'mastodon/features/compose/containers/search_results_container'; + +const Search = () => ( +
+ + +
+
+ +
+
+
+); + +export default Search; diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js index ae07b8907..756db3c61 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.js +++ b/app/javascript/mastodon/features/ui/components/columns_area.js @@ -14,6 +14,8 @@ import DrawerLoading from './drawer_loading'; import BundleColumnError from './bundle_column_error'; import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses, ListTimeline } from '../../ui/util/async-components'; import Icon from 'mastodon/components/icon'; +import ComposePanel from './compose_panel'; +import NavigationPanel from './navigation_panel'; import detectPassiveEvents from 'detect-passive-events'; import { scrollRight } from '../../../scroll'; @@ -173,14 +175,22 @@ class ColumnsArea extends ImmutablePureComponent { return (
-
+
+
+ +
+
{content}
-
+
+
+ +
+
{floatingActionButton}
diff --git a/app/javascript/mastodon/features/ui/components/compose_panel.js b/app/javascript/mastodon/features/ui/components/compose_panel.js new file mode 100644 index 000000000..7c1158e5d --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/compose_panel.js @@ -0,0 +1,41 @@ +import React from 'react'; +import SearchContainer from 'mastodon/features/compose/containers/search_container'; +import ComposeFormContainer from 'mastodon/features/compose/containers/compose_form_container'; +import NavigationContainer from 'mastodon/features/compose/containers/navigation_container'; +import { invitesEnabled, version, repository, source_url } from 'mastodon/initial_state'; +import { Link } from 'react-router-dom'; +import { FormattedMessage } from 'react-intl'; + +const ComposePanel = () => ( +
+ + + + +
+ +
+
    + {invitesEnabled &&
  • ·
  • } +
  • ·
  • +
  • ·
  • +
  • ·
  • +
  • ·
  • +
  • ·
  • +
  • ·
  • +
  • ·
  • +
  • +
+ +

+ {repository} (v{version}) }} + /> +

+
+
+); + +export default ComposePanel; diff --git a/app/javascript/mastodon/features/ui/components/list_panel.js b/app/javascript/mastodon/features/ui/components/list_panel.js new file mode 100644 index 000000000..9a52c1b10 --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/list_panel.js @@ -0,0 +1,55 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { fetchLists } from 'mastodon/actions/lists'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import { NavLink, withRouter } from 'react-router-dom'; +import Icon from 'mastodon/components/icon'; + +const getOrderedLists = createSelector([state => state.get('lists')], lists => { + if (!lists) { + return lists; + } + + return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title'))); +}); + +const mapStateToProps = state => ({ + lists: getOrderedLists(state), +}); + +export default @withRouter +@connect(mapStateToProps) +class ListPanel extends ImmutablePureComponent { + + static propTypes = { + dispatch: PropTypes.func.isRequired, + lists: ImmutablePropTypes.list, + }; + + componentDidMount () { + const { dispatch } = this.props; + dispatch(fetchLists()); + } + + render () { + const { lists } = this.props; + + if (!lists) { + return null; + } + + return ( +
+
+ + {lists.map(list => ( + {list.get('title')} + ))} +
+ ); + } + +} diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.js b/app/javascript/mastodon/features/ui/components/navigation_panel.js new file mode 100644 index 000000000..e2d962c63 --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/navigation_panel.js @@ -0,0 +1,27 @@ +import React from 'react'; +import { NavLink, withRouter } from 'react-router-dom'; +import { FormattedMessage } from 'react-intl'; +import Icon from 'mastodon/components/icon'; +import NotificationsCounterIcon from './notifications_counter_icon'; +import ListPanel from './list_panel'; + +const NavigationPanel = () => ( +
+ + + + + + + + + + +
+ + + +
+); + +export default withRouter(NavigationPanel); diff --git a/app/javascript/mastodon/features/ui/components/notifications_counter_icon.js b/app/javascript/mastodon/features/ui/components/notifications_counter_icon.js index deb907866..9673c57fe 100644 --- a/app/javascript/mastodon/features/ui/components/notifications_counter_icon.js +++ b/app/javascript/mastodon/features/ui/components/notifications_counter_icon.js @@ -9,15 +9,16 @@ const mapStateToProps = state => ({ const formatNumber = num => num > 99 ? '99+' : num; -const NotificationsCounterIcon = ({ count }) => ( +const NotificationsCounterIcon = ({ count, className }) => ( - + {count > 0 && {formatNumber(count)}} ); NotificationsCounterIcon.propTypes = { count: PropTypes.number.isRequired, + className: PropTypes.string, }; export default connect(mapStateToProps)(NotificationsCounterIcon); diff --git a/app/javascript/mastodon/features/ui/components/tabs_bar.js b/app/javascript/mastodon/features/ui/components/tabs_bar.js index 979b782bb..29583d3d7 100644 --- a/app/javascript/mastodon/features/ui/components/tabs_bar.js +++ b/app/javascript/mastodon/features/ui/components/tabs_bar.js @@ -8,14 +8,12 @@ import Icon from 'mastodon/components/icon'; import NotificationsCounterIcon from './notifications_counter_icon'; export const links = [ - , - , - - , - , - , - - , + , + , + , + , + , + , ]; export function getIndex (path) { diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 6d5279157..61fd77af7 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -44,8 +44,9 @@ import { Mutes, PinnedStatuses, Lists, + Search, } from './util/async-components'; -import { me } from '../../initial_state'; +import { me, forceSingleColumn } from '../../initial_state'; import { previewState as previewMediaState } from './components/media_modal'; import { previewState as previewVideoState } from './components/video_modal'; @@ -62,7 +63,6 @@ const mapStateToProps = state => ({ hasComposingText: state.getIn(['compose', 'text']).trim().length !== 0, hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0, dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null, - forceSingleColumn: state.getIn(['settings', 'forceSingleColumn'], false), }); const keyMap = { @@ -101,7 +101,6 @@ class SwitchingColumnsArea extends React.PureComponent { children: PropTypes.node, location: PropTypes.object, onLayoutChange: PropTypes.func.isRequired, - forceSingleColumn: PropTypes.bool, }; state = { @@ -140,7 +139,7 @@ class SwitchingColumnsArea extends React.PureComponent { } render () { - const { children, forceSingleColumn } = this.props; + const { children } = this.props; const { mobile } = this.state; const singleColumn = forceSingleColumn || mobile; const redirect = singleColumn ? : ; @@ -162,7 +161,7 @@ class SwitchingColumnsArea extends React.PureComponent { - + @@ -207,7 +206,6 @@ class UI extends React.PureComponent { location: PropTypes.object, intl: PropTypes.object.isRequired, dropdownMenuIsOpen: PropTypes.bool, - forceSingleColumn: PropTypes.bool, }; state = { @@ -456,7 +454,7 @@ class UI extends React.PureComponent { render () { const { draggingOver } = this.state; - const { children, isComposing, location, dropdownMenuIsOpen, forceSingleColumn } = this.props; + const { children, isComposing, location, dropdownMenuIsOpen } = this.props; const handlers = { help: this.handleHotkeyToggleHelp, @@ -482,7 +480,7 @@ class UI extends React.PureComponent { return (
- + {children} diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index 235fd2a07..6e8ed163a 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -129,3 +129,7 @@ export function ListEditor () { export function ListAdder () { return import(/*webpackChunkName: "features/list_adder" */'../../list_adder'); } + +export function Search () { + return import(/*webpackChunkName: "features/search" */'../../search'); +} diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js index 74bcfee58..125508c23 100644 --- a/app/javascript/mastodon/initial_state.js +++ b/app/javascript/mastodon/initial_state.js @@ -19,5 +19,6 @@ export const version = getMeta('version'); export const mascot = getMeta('mascot'); export const profile_directory = getMeta('profile_directory'); export const isStaff = getMeta('is_staff'); +export const forceSingleColumn = !getMeta('advanced_layout'); export default initialState; diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js index 419c313af..a0eea137f 100644 --- a/app/javascript/mastodon/reducers/settings.js +++ b/app/javascript/mastodon/reducers/settings.js @@ -14,8 +14,6 @@ const initialState = ImmutableMap({ skinTone: 1, - forceSingleColumn: false, - home: ImmutableMap({ shows: ImmutableMap({ reblog: true, diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 351a635bc..4fbbe67c7 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1801,7 +1801,12 @@ a.account__display-name { display: flex; justify-content: flex-end; + &--start { + justify-content: flex-start; + } + &__inner { + width: 285px; pointer-events: auto; height: 100%; } @@ -1925,6 +1930,7 @@ a.account__display-name { display: block; flex: 1 1 auto; padding: 15px 10px; + padding-bottom: 13px; color: $primary-text-color; text-decoration: none; text-align: center; @@ -1949,6 +1955,7 @@ a.account__display-name { &:active { @media screen and (min-width: 631px) { background: lighten($ui-base-color, 14%); + border-bottom-color: lighten($ui-base-color, 14%); } } @@ -1978,11 +1985,21 @@ a.account__display-name { padding: 0; } - .search__input, .autosuggest-textarea__textarea { font-size: 16px; } + .search__input { + line-height: 18px; + font-size: 16px; + padding: 15px; + padding-right: 30px; + } + + .search__icon .fa { + top: 15px; + } + @media screen and (min-width: 360px) { padding: 10px 0; } @@ -2038,6 +2055,58 @@ a.account__display-name { margin-top: 10px; } } + + .account { + padding: 15px 10px; + } + + .notification { + &__message { + margin-left: 48px + 15px * 2; + padding-top: 15px; + } + + &__favourite-icon-wrapper { + left: -32px; + } + + .status { + padding-top: 8px; + } + + .account { + padding-top: 8px; + } + + .account__avatar-wrapper { + margin-left: 17px; + margin-right: 15px; + } + } + } +} + +.floating-action-button { + position: fixed; + display: flex; + justify-content: center; + align-items: center; + width: 3.9375rem; + height: 3.9375rem; + bottom: 1.3125rem; + right: 1.3125rem; + background: darken($ui-highlight-color, 3%); + color: $white; + border-radius: 50%; + font-size: 21px; + line-height: 21px; + text-decoration: none; + box-shadow: 2px 3px 9px rgba($base-shadow-color, 0.4); + + &:hover, + &:focus, + &:active { + background: lighten($ui-highlight-color, 7%); } } @@ -2059,12 +2128,41 @@ a.account__display-name { } } +@media screen and (max-width: 600px + (285px * 1) + (10px * 1)) { + .columns-area__panels__pane--compositional { + display: none; + } +} + +@media screen and (min-width: 600px + (285px * 1) + (10px * 1)) { + .floating-action-button, + .tabs-bar__link.optional { + display: none; + } + + .search-page .search { + display: none; + } +} + +@media screen and (max-width: 600px + (285px * 2) + (10px * 2)) { + .columns-area__panels__pane--navigational { + display: none; + } +} + +@media screen and (min-width: 600px + (285px * 2) + (10px * 2)) { + .tabs-bar { + display: none; + } +} + .icon-with-badge { position: relative; &__badge { position: absolute; - right: -13px; + left: 9px; top: -13px; background: $ui-highlight-color; border: 2px solid lighten($ui-base-color, 8%); @@ -2077,6 +2175,57 @@ a.account__display-name { } } +.column-link--transparent .icon-with-badge__badge { + border-color: darken($ui-base-color, 8%); +} + +.compose-panel { + width: 285px; + margin-top: 10px; + display: flex; + flex-direction: column; + height: 100%; + + .search__input { + line-height: 18px; + font-size: 16px; + padding: 15px; + padding-right: 30px; + } + + .search__icon .fa { + top: 15px; + } + + .navigation-bar { + padding-top: 20px; + padding-bottom: 20px; + } + + .flex-spacer { + background: transparent; + } + + .autosuggest-textarea__textarea { + max-height: 200px; + } + + .compose-form__upload-thumbnail { + height: 80px; + } +} + +.navigation-panel { + margin-top: 10px; + + hr { + border: 0; + background: transparent; + border-top: 1px solid lighten($ui-base-color, 4%); + margin: 10px 0; + } +} + .drawer__pager { box-sizing: border-box; padding: 0; @@ -2127,15 +2276,6 @@ a.account__display-name { } } -.navigational-toggle { - padding: 10px; - display: flex; - align-items: center; - justify-content: space-between; - font-size: 14px; - color: $dark-text-color; -} - .pseudo-drawer { background: lighten($ui-base-color, 13%); font-size: 13px; @@ -2365,9 +2505,31 @@ a.account__display-name { padding: 15px; text-decoration: none; - &:hover { + &:hover, + &:focus, + &:active { background: lighten($ui-base-color, 11%); } + + &:focus { + outline: 0; + } + + &--transparent { + background: transparent; + color: $ui-secondary-color; + + &:hover, + &:focus, + &:active { + background: transparent; + color: $primary-text-color; + } + + &.active { + color: $ui-highlight-color; + } + } } .column-link__icon { @@ -5436,34 +5598,6 @@ noscript { } } -.floating-action-button { - position: fixed; - display: flex; - justify-content: center; - align-items: center; - width: 3.9375rem; - height: 3.9375rem; - bottom: 1.3125rem; - right: 1.3125rem; - background: darken($ui-highlight-color, 3%); - color: $white; - border-radius: 50%; - font-size: 21px; - line-height: 21px; - text-decoration: none; - box-shadow: 2px 3px 9px rgba($base-shadow-color, 0.4); - - &:hover, - &:focus, - &:active { - background: lighten($ui-highlight-color, 7%); - } - - @media screen and (min-width: 630px) { - display: none; - } -} - .account__header__content { color: $darker-text-color; font-size: 14px; diff --git a/app/lib/user_settings_decorator.rb b/app/lib/user_settings_decorator.rb index daeb3d936..bf2e5a962 100644 --- a/app/lib/user_settings_decorator.rb +++ b/app/lib/user_settings_decorator.rb @@ -33,6 +33,7 @@ class UserSettingsDecorator user.settings['hide_network'] = hide_network_preference if change?('setting_hide_network') user.settings['aggregate_reblogs'] = aggregate_reblogs_preference if change?('setting_aggregate_reblogs') user.settings['show_application'] = show_application_preference if change?('setting_show_application') + user.settings['advanced_layout'] = advanced_layout_preference if change?('setting_advanced_layout') end def merged_notification_emails @@ -107,6 +108,10 @@ class UserSettingsDecorator boolean_cast_setting 'setting_aggregate_reblogs' end + def advanced_layout_preference + boolean_cast_setting 'setting_advanced_layout' + end + def boolean_cast_setting(key) ActiveModel::Type::Boolean.new.cast(settings[key]) end diff --git a/app/models/user.rb b/app/models/user.rb index 3d1eb5f20..eb1a2fece 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -104,7 +104,8 @@ class User < ApplicationRecord delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :delete_modal, :reduce_motion, :system_font_ui, :noindex, :theme, :display_media, :hide_network, - :expand_spoilers, :default_language, :aggregate_reblogs, :show_application, to: :settings, prefix: :setting, allow_nil: false + :expand_spoilers, :default_language, :aggregate_reblogs, :show_application, + :advanced_layout, to: :settings, prefix: :setting, allow_nil: false attr_reader :invite_code attr_writer :external diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index 0c9fc625f..184ed867c 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -31,6 +31,7 @@ class InitialStateSerializer < ActiveModel::Serializer store[:display_media] = object.current_account.user.setting_display_media store[:expand_spoilers] = object.current_account.user.setting_expand_spoilers store[:reduce_motion] = object.current_account.user.setting_reduce_motion + store[:advanced_layout] = object.current_account.user.setting_advanced_layout store[:is_staff] = object.current_account.user.staff? end diff --git a/app/views/settings/preferences/show.html.haml b/app/views/settings/preferences/show.html.haml index d81ee61ad..35b660a68 100644 --- a/app/views/settings/preferences/show.html.haml +++ b/app/views/settings/preferences/show.html.haml @@ -46,6 +46,9 @@ .fields-group.fields-row__column.fields-row__column-6 = f.input :setting_display_media, collection: ['default', 'show_all', 'hide_all'], wrapper: :with_label, include_blank: false, label_method: lambda { |item| t("simple_form.hints.defaults.setting_display_media_#{item}") }, hint: false + .fields-group + = f.input :setting_advanced_layout, as: :boolean, wrapper: :with_label + .fields-group = f.input :setting_unfollow_modal, as: :boolean, wrapper: :with_label = f.input :setting_boost_modal, as: :boolean, wrapper: :with_label diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 1a43e19e2..2f75efdbd 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -26,6 +26,7 @@ en: password: Use at least 8 characters phrase: Will be matched regardless of casing in text or content warning of a toot scopes: Which APIs the application will be allowed to access. If you select a top-level scope, you don't need to select individual ones. + setting_advanced_layout: The advanced UI consists of multiple customizable columns setting_aggregate_reblogs: Do not show new boosts for toots that have been recently boosted (only affects newly-received boosts) setting_default_language: The language of your toots can be detected automatically, but it's not always accurate setting_display_media_default: Hide media marked as sensitive @@ -90,6 +91,7 @@ en: otp_attempt: Two-factor code password: Password phrase: Keyword or phrase + setting_advanced_layout: Enable advanced web interface setting_aggregate_reblogs: Group boosts in timelines setting_auto_play_gif: Auto-play animated GIFs setting_boost_modal: Show confirmation dialog before boosting diff --git a/config/settings.yml b/config/settings.yml index 63f7c3380..0c3510699 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -31,6 +31,7 @@ defaults: &defaults noindex: false theme: 'default' aggregate_reblogs: true + advanced_layout: true notification_emails: follow: false reblog: false From d55d8a611b5d0d9ea91058cc7d78c7155d0e68cf Mon Sep 17 00:00:00 2001 From: Aditoo17 <42938951+Aditoo17@users.noreply.github.com> Date: Sat, 25 May 2019 22:34:40 +0200 Subject: [PATCH 03/12] I18n: Update Czech translation (#10832) --- config/locales/simple_form.cs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/locales/simple_form.cs.yml b/config/locales/simple_form.cs.yml index 2b4888424..fa45fecd5 100644 --- a/config/locales/simple_form.cs.yml +++ b/config/locales/simple_form.cs.yml @@ -26,6 +26,7 @@ cs: password: Použijte alespoň 8 znaků phrase: Shoda bude nalezena bez ohledu na velikost písmen v těle tootu či varování o obsahu scopes: Která API bude aplikaci povoleno používat. Pokud vyberete rozsah nejvyššího stupně, nebudete je muset vybírat jednotlivě. + setting_advanced_layout: Pokročilé rozhraní se skládá z několika přizpůsobitelných sloupců setting_aggregate_reblogs: Nezobrazovat nové boosty pro tooty, které byly nedávno boostnuty (ovlivňuje pouze nově přijaté boosty) setting_default_language: Jazyk vašich tootů může být detekován automaticky, není to však vždy přesné setting_display_media_default: Skrývat média označená jako citlivá @@ -90,6 +91,7 @@ cs: otp_attempt: Dvoufázový kód password: Heslo phrase: Klíčové slovo či fráze + setting_advanced_layout: Povolit pokročilé webové rozhraní setting_aggregate_reblogs: Seskupovat boosty v časových osách setting_auto_play_gif: Automaticky přehrávat animace GIF setting_boost_modal: Zobrazovat před boostnutím potvrzovací okno From 4168aeb234fb1cb01162aa586775d52d52c6d02c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9lanie=20Chauvel=20=28ariasuni=29?= Date: Sat, 25 May 2019 23:19:39 +0200 Subject: [PATCH 04/12] Avoid cutting bottom of letters of last paragraph of statuses (#10821) --- app/javascript/styles/mastodon/components.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 4fbbe67c7..61f595362 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -710,7 +710,7 @@ white-space: pre-wrap; &:last-child { - margin-bottom: 0; + margin-bottom: 2px; } } From 0852e643b7d5ff0f97e2a443a7427fceed5d1421 Mon Sep 17 00:00:00 2001 From: Alfie John <33c6c91f3bb4a391082e8a29642cafaf@alfie.wtf> Date: Sun, 26 May 2019 07:20:18 +1000 Subject: [PATCH 05/12] Expand abbreviation to minimise confusion (#10826) --- Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6373172fc..32af8ddb5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -86,7 +86,7 @@ RUN apt update && \ useradd -m -u $UID -g $GID -d /opt/mastodon mastodon && \ echo "mastodon:`head /dev/urandom | tr -dc A-Za-z0-9 | head -c 24 | mkpasswd -s -m sha-256`" | chpasswd -# Install masto runtime deps +# Install mastodon runtime deps RUN apt -y --no-install-recommends install \ libssl1.1 libpq5 imagemagick ffmpeg \ libicu60 libprotobuf10 libidn11 libyaml-0-2 \ @@ -104,11 +104,11 @@ ADD https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini /tin RUN echo "$TINI_SUM tini" | sha256sum -c - RUN chmod +x /tini -# Copy over masto source, and dependencies from building, and set permissions +# Copy over mastodon source, and dependencies from building, and set permissions COPY --chown=mastodon:mastodon . /opt/mastodon COPY --from=build-dep --chown=mastodon:mastodon /opt/mastodon /opt/mastodon -# Run masto services in prod mode +# Run mastodon services in prod mode ENV RAILS_ENV="production" ENV NODE_ENV="production" From c90f3b9865cec8386b3258169c38dbc6128a696e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Sat, 25 May 2019 23:20:28 +0200 Subject: [PATCH 06/12] Docker: Keep /var/lib/apt/ to keep apt working (#10830) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 32af8ddb5..46631cde4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -95,7 +95,7 @@ RUN apt -y --no-install-recommends install \ ln -s /opt/mastodon /mastodon && \ gem install bundler && \ rm -rf /var/cache && \ - rm -rf /var/lib/apt + rm -rf /var/lib/apt/lists/* # Add tini ENV TINI_VERSION="0.18.0" From a472190729782f31731674c626c07af483fe9c7f Mon Sep 17 00:00:00 2001 From: ThibG Date: Sat, 25 May 2019 23:20:51 +0200 Subject: [PATCH 07/12] Add a keyboard shortcut to hide/show media (#10647) * Move control of media visibility to parent component * Add keyboard shortcut to toggle media visibility --- .../mastodon/components/media_gallery.js | 16 +++++++++--- app/javascript/mastodon/components/status.js | 18 +++++++++++++ .../features/keyboard_shortcuts/index.js | 4 +++ .../status/components/detailed_status.js | 6 +++++ .../mastodon/features/status/index.js | 17 ++++++++++++- app/javascript/mastodon/features/ui/index.js | 1 + .../mastodon/features/video/index.js | 25 +++++++++++++------ 7 files changed, 75 insertions(+), 12 deletions(-) diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js index abd17647e..56618462b 100644 --- a/app/javascript/mastodon/components/media_gallery.js +++ b/app/javascript/mastodon/components/media_gallery.js @@ -244,6 +244,8 @@ class MediaGallery extends React.PureComponent { intl: PropTypes.object.isRequired, defaultWidth: PropTypes.number, cacheWidth: PropTypes.func, + visible: PropTypes.bool, + onToggleVisibility: PropTypes.func, }; static defaultProps = { @@ -251,18 +253,24 @@ class MediaGallery extends React.PureComponent { }; state = { - visible: displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all', + visible: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'), width: this.props.defaultWidth, }; componentWillReceiveProps (nextProps) { - if (!is(nextProps.media, this.props.media)) { - this.setState({ visible: !nextProps.sensitive }); + if (!is(nextProps.media, this.props.media) && nextProps.visible === undefined) { + this.setState({ visible: displayMedia !== 'hide_all' && !nextProps.sensitive || displayMedia === 'show_all' }); + } else if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) { + this.setState({ visible: nextProps.visible }); } } handleOpen = () => { - this.setState({ visible: !this.state.visible }); + if (this.props.onToggleVisibility) { + this.props.onToggleVisibility(); + } else { + this.setState({ visible: !this.state.visible }); + } } handleClick = (index) => { diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 6f66a4260..5722d3778 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -17,6 +17,7 @@ import { HotKeys } from 'react-hotkeys'; import classNames from 'classnames'; import Icon from 'mastodon/components/icon'; import PollContainer from 'mastodon/containers/poll_container'; +import { displayMedia } from '../initial_state'; // We use the component (and not the container) since we do not want // to use the progress bar to show download progress @@ -85,6 +86,10 @@ class Status extends ImmutablePureComponent { 'hidden', ]; + state = { + showMedia: displayMedia !== 'hide_all' && !this.props.status.get('sensitive') || displayMedia === 'show_all', + }; + // Track height changes we know about to compensate scrolling componentDidMount () { this.didShowCard = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card'); @@ -122,6 +127,10 @@ class Status extends ImmutablePureComponent { } } + handleToggleMediaVisibility = () => { + this.setState({ showMedia: !this.state.showMedia }); + } + handleClick = () => { if (this.props.onClick) { this.props.onClick(); @@ -198,6 +207,10 @@ class Status extends ImmutablePureComponent { this.props.onToggleHidden(this._properStatus()); } + handleHotkeyToggleSensitive = () => { + this.handleToggleMediaVisibility(); + } + _properStatus () { const { status } = this.props; @@ -298,6 +311,8 @@ class Status extends ImmutablePureComponent { sensitive={status.get('sensitive')} onOpenVideo={this.handleOpenVideo} cacheWidth={this.props.cacheMediaWidth} + visible={this.state.showMedia} + onToggleVisibility={this.handleToggleMediaVisibility} /> )} @@ -313,6 +328,8 @@ class Status extends ImmutablePureComponent { onOpenMedia={this.props.onOpenMedia} cacheWidth={this.props.cacheMediaWidth} defaultWidth={this.props.cachedMediaWidth} + visible={this.state.showMedia} + onToggleVisibility={this.handleToggleMediaVisibility} /> )} @@ -348,6 +365,7 @@ class Status extends ImmutablePureComponent { moveUp: this.handleHotkeyMoveUp, moveDown: this.handleHotkeyMoveDown, toggleHidden: this.handleHotkeyToggleHidden, + toggleSensitive: this.handleHotkeyToggleSensitive, }; return ( diff --git a/app/javascript/mastodon/features/keyboard_shortcuts/index.js b/app/javascript/mastodon/features/keyboard_shortcuts/index.js index ab1ac511e..01b45652c 100644 --- a/app/javascript/mastodon/features/keyboard_shortcuts/index.js +++ b/app/javascript/mastodon/features/keyboard_shortcuts/index.js @@ -60,6 +60,10 @@ class KeyboardShortcuts extends ImmutablePureComponent { x + + h + + up, k diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index 059ecd979..22821af0c 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.js @@ -30,6 +30,8 @@ export default class DetailedStatus extends ImmutablePureComponent { onHeightChange: PropTypes.func, domain: PropTypes.string.isRequired, compact: PropTypes.bool, + showMedia: PropTypes.bool, + onToggleMediaVisibility: PropTypes.func, }; state = { @@ -122,6 +124,8 @@ export default class DetailedStatus extends ImmutablePureComponent { inline onOpenVideo={this.handleOpenVideo} sensitive={status.get('sensitive')} + visible={this.props.showMedia} + onToggleVisibility={this.props.onToggleMediaVisibility} /> ); } else { @@ -132,6 +136,8 @@ export default class DetailedStatus extends ImmutablePureComponent { media={status.get('media_attachments')} height={300} onOpenMedia={this.props.onOpenMedia} + visible={this.props.showMedia} + onToggleVisibility={this.props.onToggleMediaVisibility} /> ); } diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index 6279bb468..499afe6f7 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -41,7 +41,7 @@ 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, displayMedia } from '../../initial_state'; import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen'; import { textForScreenReader } from '../../components/status'; import Icon from 'mastodon/components/icon'; @@ -131,6 +131,7 @@ class Status extends ImmutablePureComponent { state = { fullscreen: false, + showMedia: !this.props.status ? undefined : (displayMedia !== 'hide_all' && !this.props.status.get('sensitive') || displayMedia === 'show_all'), }; componentWillMount () { @@ -146,6 +147,13 @@ class Status extends ImmutablePureComponent { this._scrolledIntoView = false; this.props.dispatch(fetchStatus(nextProps.params.statusId)); } + if (!Immutable.is(nextProps.status, this.props.status) && nextProps.status) { + this.setState({ showMedia: displayMedia !== 'hide_all' && !nextProps.status.get('sensitive') || displayMedia === 'show_all' }); + } + } + + handleToggleMediaVisibility = () => { + this.setState({ showMedia: !this.state.showMedia }); } handleFavouriteClick = (status) => { @@ -312,6 +320,10 @@ class Status extends ImmutablePureComponent { this.handleToggleHidden(this.props.status); } + handleHotkeyToggleSensitive = () => { + this.handleToggleMediaVisibility(); + } + handleMoveUp = id => { const { status, ancestorsIds, descendantsIds } = this.props; @@ -432,6 +444,7 @@ class Status extends ImmutablePureComponent { mention: this.handleHotkeyMention, openProfile: this.handleHotkeyOpenProfile, toggleHidden: this.handleHotkeyToggleHidden, + toggleSensitive: this.handleHotkeyToggleSensitive, }; return ( @@ -455,6 +468,8 @@ class Status extends ImmutablePureComponent { onOpenMedia={this.handleOpenMedia} onToggleHidden={this.handleToggleHidden} domain={domain} + showMedia={this.state.showMedia} + onToggleMediaVisibility={this.handleToggleMediaVisibility} /> { - if (this.state.revealed) { - this.video.pause(); + if (this.props.onToggleVisibility) { + this.props.onToggleVisibility(); + } else { + this.setState({ revealed: !this.state.revealed }); } - - this.setState({ revealed: !this.state.revealed }); } handleLoadedData = () => { From 0e445ebb1392c8dbce320509d219f16c7c221406 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 26 May 2019 02:55:37 +0200 Subject: [PATCH 08/12] Improvements to the single column layout (#10835) * Improvements to the single column layout - Add follows and followers link to the right panel - Increase margins around separators in right panel - Add follow requests link with counter when account is locked to right panel * Redirect from getting started to home when navigation panel is visible --- app/javascript/mastodon/actions/compose.js | 2 +- .../mastodon/components/icon_with_badge.js | 20 +++++++++ .../features/follow_requests/index.js | 2 +- .../features/getting_started/index.js | 13 +++++- .../ui/components/follow_requests_nav_link.js | 44 +++++++++++++++++++ .../features/ui/components/list_panel.js | 4 +- .../ui/components/navigation_panel.js | 2 + .../components/notifications_counter_icon.js | 21 ++------- 8 files changed, 85 insertions(+), 23 deletions(-) create mode 100644 app/javascript/mastodon/components/icon_with_badge.js create mode 100644 app/javascript/mastodon/features/ui/components/follow_requests_nav_link.js diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 33e631364..300fb48a9 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -63,7 +63,7 @@ const messages = defineMessages({ uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' }, }); -const COMPOSE_PANEL_BREAKPOINT = 600 + (285 * 1) + (10 * 3); +const COMPOSE_PANEL_BREAKPOINT = 600 + (285 * 1) + (10 * 1); export const ensureComposeIsVisible = (getState, routerHistory) => { if (!getState().getIn(['compose', 'mounted']) && window.innerWidth < COMPOSE_PANEL_BREAKPOINT) { diff --git a/app/javascript/mastodon/components/icon_with_badge.js b/app/javascript/mastodon/components/icon_with_badge.js new file mode 100644 index 000000000..7851eb4be --- /dev/null +++ b/app/javascript/mastodon/components/icon_with_badge.js @@ -0,0 +1,20 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Icon from 'mastodon/components/icon'; + +const formatNumber = num => num > 40 ? '40+' : num; + +const IconWithBadge = ({ id, count, className }) => ( + + + {count > 0 && {formatNumber(count)}} + +); + +IconWithBadge.propTypes = { + id: PropTypes.string.isRequired, + count: PropTypes.number.isRequired, + className: PropTypes.string, +}; + +export default IconWithBadge; diff --git a/app/javascript/mastodon/features/follow_requests/index.js b/app/javascript/mastodon/features/follow_requests/index.js index 3871e0e5d..44624cb40 100644 --- a/app/javascript/mastodon/features/follow_requests/index.js +++ b/app/javascript/mastodon/features/follow_requests/index.js @@ -56,7 +56,7 @@ class FollowRequests extends ImmutablePureComponent { const emptyMessage = ; return ( - + { } }; +const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2); + export default @connect(mapStateToProps, mapDispatchToProps) @injectIntl class GettingStarted extends ImmutablePureComponent { + static contextTypes = { + router: PropTypes.object.isRequired, + }; + static propTypes = { intl: PropTypes.object.isRequired, myAccount: ImmutablePropTypes.map.isRequired, @@ -72,6 +78,11 @@ class GettingStarted extends ImmutablePureComponent { componentDidMount () { const { myAccount, fetchFollowRequests } = this.props; + if (window.innerWidth >= NAVIGATION_PANEL_BREAKPOINT) { + this.context.router.history.replace('/timelines/home'); + return; + } + if (myAccount.get('locked')) { fetchFollowRequests(); } @@ -123,7 +134,7 @@ class GettingStarted extends ImmutablePureComponent { height += 48*3; if (myAccount.get('locked')) { - navItems.push(); + navItems.push(); height += 48; } diff --git a/app/javascript/mastodon/features/ui/components/follow_requests_nav_link.js b/app/javascript/mastodon/features/ui/components/follow_requests_nav_link.js new file mode 100644 index 000000000..90c953893 --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/follow_requests_nav_link.js @@ -0,0 +1,44 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { fetchFollowRequests } from 'mastodon/actions/accounts'; +import { connect } from 'react-redux'; +import { NavLink, withRouter } from 'react-router-dom'; +import IconWithBadge from 'mastodon/components/icon_with_badge'; +import { me } from 'mastodon/initial_state'; +import { List as ImmutableList } from 'immutable'; +import { FormattedMessage } from 'react-intl'; + +const mapStateToProps = state => ({ + locked: state.getIn(['accounts', me, 'locked']), + count: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size, +}); + +export default @withRouter +@connect(mapStateToProps) +class FollowRequestsNavLink extends React.Component { + + static propTypes = { + dispatch: PropTypes.func.isRequired, + locked: PropTypes.bool, + count: PropTypes.number.isRequired, + }; + + componentDidMount () { + const { dispatch, locked } = this.props; + + if (locked) { + dispatch(fetchFollowRequests()); + } + } + + render () { + const { locked, count } = this.props; + + if (!locked || count === 0) { + return null; + } + + return ; + } + +} diff --git a/app/javascript/mastodon/features/ui/components/list_panel.js b/app/javascript/mastodon/features/ui/components/list_panel.js index 9a52c1b10..1f7ec683a 100644 --- a/app/javascript/mastodon/features/ui/components/list_panel.js +++ b/app/javascript/mastodon/features/ui/components/list_panel.js @@ -13,7 +13,7 @@ const getOrderedLists = createSelector([state => state.get('lists')], lists => { return lists; } - return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title'))); + return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title'))).take(4); }); const mapStateToProps = state => ({ @@ -37,7 +37,7 @@ class ListPanel extends ImmutablePureComponent { render () { const { lists } = this.props; - if (!lists) { + if (!lists || lists.isEmpty()) { return null; } diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.js b/app/javascript/mastodon/features/ui/components/navigation_panel.js index e2d962c63..1fd28c63a 100644 --- a/app/javascript/mastodon/features/ui/components/navigation_panel.js +++ b/app/javascript/mastodon/features/ui/components/navigation_panel.js @@ -3,12 +3,14 @@ import { NavLink, withRouter } from 'react-router-dom'; import { FormattedMessage } from 'react-intl'; import Icon from 'mastodon/components/icon'; import NotificationsCounterIcon from './notifications_counter_icon'; +import FollowRequestsNavLink from './follow_requests_nav_link'; import ListPanel from './list_panel'; const NavigationPanel = () => (
+ diff --git a/app/javascript/mastodon/features/ui/components/notifications_counter_icon.js b/app/javascript/mastodon/features/ui/components/notifications_counter_icon.js index 9673c57fe..da553cd9f 100644 --- a/app/javascript/mastodon/features/ui/components/notifications_counter_icon.js +++ b/app/javascript/mastodon/features/ui/components/notifications_counter_icon.js @@ -1,24 +1,9 @@ -import React from 'react'; -import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import Icon from 'mastodon/components/icon'; +import IconWithBadge from 'mastodon/components/icon_with_badge'; const mapStateToProps = state => ({ count: state.getIn(['notifications', 'unread']), + id: 'bell', }); -const formatNumber = num => num > 99 ? '99+' : num; - -const NotificationsCounterIcon = ({ count, className }) => ( - - - {count > 0 && {formatNumber(count)}} - -); - -NotificationsCounterIcon.propTypes = { - count: PropTypes.number.isRequired, - className: PropTypes.string, -}; - -export default connect(mapStateToProps)(NotificationsCounterIcon); +export default connect(mapStateToProps)(IconWithBadge); From 4a818ac2deffaff9925ce5b160dbc5385b815a87 Mon Sep 17 00:00:00 2001 From: Hanage999 Date: Sun, 26 May 2019 19:22:33 +0900 Subject: [PATCH 09/12] Fix wrong redirect from getting started to home in advanced Web UI (#10839) * update Ruby to 2.5.3 * Link to Getting Started will not redirect to Home in multi-column UI (https://github.com/tootsuite/mastodon/pull/10835) --- app/javascript/mastodon/features/getting_started/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index bafcc275b..129ce77f6 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -76,9 +76,9 @@ class GettingStarted extends ImmutablePureComponent { }; componentDidMount () { - const { myAccount, fetchFollowRequests } = this.props; + const { myAccount, fetchFollowRequests, multiColumn } = this.props; - if (window.innerWidth >= NAVIGATION_PANEL_BREAKPOINT) { + if (!multiColumn && window.innerWidth >= NAVIGATION_PANEL_BREAKPOINT) { this.context.router.history.replace('/timelines/home'); return; } From 3d219c595677de0ea5814300f3ee1c11d24569b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20=C4=BDach?= Date: Sun, 26 May 2019 12:26:39 +0200 Subject: [PATCH 10/12] Update simple_form.sk.yml (#10842) --- config/locales/simple_form.sk.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/config/locales/simple_form.sk.yml b/config/locales/simple_form.sk.yml index 28e8629d2..c6de0009d 100644 --- a/config/locales/simple_form.sk.yml +++ b/config/locales/simple_form.sk.yml @@ -26,6 +26,7 @@ sk: password: Zadaj aspoň osem znakov phrase: Zhoda sa nájde nezávisle od toho, či je text napísaný, veľkými, alebo malými písmenami, či už v tele, alebo v hlavičke scopes: Ktoré API budú povolené aplikácii pre prístup. Ak vyberieš vrcholný stupeň, nemusíš už potom vyberať po jednom. + setting_advanced_layout: Pokročilé užívateľské rozhranie sa skladá z viacero prispôsobiteľných stĺpcov setting_aggregate_reblogs: Nezobrazuj nové vyzdvihnutia pre príspevky, ktoré už boli len nedávno povýšené (týka sa iba nanovo získaných povýšení) setting_default_language: Jazyk tvojích príspevkov môže byť zistený automaticky, ale nieje to vždy presné setting_display_media_default: Skry médiá označené ako citlivé @@ -90,6 +91,7 @@ sk: otp_attempt: Dvoj-faktorový overovací (2FA) kód password: Heslo phrase: Kľúčové slovo, alebo fráza + setting_advanced_layout: Zapni pokročilé užívateľské rozhranie setting_aggregate_reblogs: Zoskupuj vyzdvihnutia v časovej osi setting_auto_play_gif: Automaticky prehrávaj animované GIFy setting_boost_modal: Zobrazuj potvrdzovacie okno pred povýšením @@ -99,7 +101,7 @@ sk: setting_delete_modal: Zobrazuj potvrdzovacie okno pred vymazaním toot-u setting_display_media: Zobrazovanie médií setting_display_media_default: Štandard - setting_display_media_hide_all: Skryť všetky + setting_display_media_hide_all: Skry všetky setting_display_media_show_all: Ukáž všetky setting_expand_spoilers: Stále rozbaľ príspevky označené varovaním o obsahu setting_hide_network: Ukri svoju sieť kontaktov @@ -112,7 +114,7 @@ sk: severity: Závažnosť type: Typ importu username: Prezývka - username_or_email: Prezívka, alebo email + username_or_email: Prezývka, alebo email whole_word: Celé slovo featured_tag: name: Haštag From 988342a56cb58da9ac660eec3e55c3bcbbd6269b Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 26 May 2019 13:48:16 +0200 Subject: [PATCH 11/12] Fix null error in status component when determining showMedia state (#10838) * Fix null error in status component when determining showMedia state Also update the showMedia value if the status passed to the component changes * Refactor media visibility computation into a defaultMediaVisibility function * Fix default media visibility with reblogs --- app/javascript/mastodon/components/status.js | 23 ++++++++++++++++++- .../mastodon/features/status/index.js | 9 ++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 5722d3778..2d3a32d62 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -18,6 +18,7 @@ import classNames from 'classnames'; import Icon from 'mastodon/components/icon'; import PollContainer from 'mastodon/containers/poll_container'; import { displayMedia } from '../initial_state'; +import { is } from 'immutable'; // We use the component (and not the container) since we do not want // to use the progress bar to show download progress @@ -40,6 +41,18 @@ export const textForScreenReader = (intl, status, rebloggedByText = false) => { return values.join(', '); }; +export const defaultMediaVisibility = (status) => { + if (!status) { + return undefined; + } + + if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') { + status = status.get('reblog'); + } + + return (displayMedia !== 'hide_all' && !status.get('sensitive') || displayMedia === 'show_all'); +}; + export default @injectIntl class Status extends ImmutablePureComponent { @@ -87,7 +100,7 @@ class Status extends ImmutablePureComponent { ]; state = { - showMedia: displayMedia !== 'hide_all' && !this.props.status.get('sensitive') || displayMedia === 'show_all', + showMedia: defaultMediaVisibility(this.props.status), }; // Track height changes we know about to compensate scrolling @@ -103,11 +116,19 @@ class Status extends ImmutablePureComponent { } } + componentWillReceiveProps (nextProps) { + if (!is(nextProps.status, this.props.status) && nextProps.status) { + this.setState({ showMedia: defaultMediaVisibility(nextProps.status) }); + } + } + // Compensate height changes componentDidUpdate (prevProps, prevState, snapshot) { const doShowCard = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card'); + if (doShowCard && !this.didShowCard) { this.didShowCard = true; + if (snapshot !== null && this.props.updateScrollBottom) { if (this.node && this.node.offsetTop < snapshot.top) { this.props.updateScrollBottom(snapshot.height - snapshot.top); diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index 499afe6f7..d8c4c50dc 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -41,9 +41,9 @@ 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, displayMedia } from '../../initial_state'; +import { boostModal, deleteModal } from '../../initial_state'; import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen'; -import { textForScreenReader } from '../../components/status'; +import { textForScreenReader, defaultMediaVisibility } from '../../components/status'; import Icon from 'mastodon/components/icon'; const messages = defineMessages({ @@ -131,7 +131,7 @@ class Status extends ImmutablePureComponent { state = { fullscreen: false, - showMedia: !this.props.status ? undefined : (displayMedia !== 'hide_all' && !this.props.status.get('sensitive') || displayMedia === 'show_all'), + showMedia: defaultMediaVisibility(this.props.status), }; componentWillMount () { @@ -147,8 +147,9 @@ class Status extends ImmutablePureComponent { this._scrolledIntoView = false; this.props.dispatch(fetchStatus(nextProps.params.statusId)); } + if (!Immutable.is(nextProps.status, this.props.status) && nextProps.status) { - this.setState({ showMedia: displayMedia !== 'hide_all' && !nextProps.status.get('sensitive') || displayMedia === 'show_all' }); + this.setState({ showMedia: defaultMediaVisibility(nextProps.status) }); } } From 63483ee543eb6c81e2cf6450a682cdb657e92751 Mon Sep 17 00:00:00 2001 From: Neil Moore Date: Sun, 26 May 2019 07:48:45 -0400 Subject: [PATCH 12/12] Create new click handler for status__expand area in status (#10837) This click handler only activates on left-click, resolving #10798. This matches behavior in status_content.js, as added in #536 --- app/javascript/mastodon/components/status.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 2d3a32d62..b67354b01 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -166,6 +166,17 @@ class Status extends ImmutablePureComponent { this.context.router.history.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`); } + handleExpandClick = (e) => { + if (e.button === 0) { + if (!this.context.router) { + return; + } + + const { status } = this.props; + this.context.router.history.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`); + } + } + handleAccountClick = (e) => { if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { const id = e.currentTarget.getAttribute('data-id'); @@ -395,7 +406,7 @@ class Status extends ImmutablePureComponent { {prepend}
-
+