Browse Source

Merge branch 'master' into live

mp-additions
Zac 5 months ago
parent
commit
42d066e8f4
100 changed files with 2067 additions and 1071 deletions
  1. 4
    1
      CHANGELOG.md
  2. 1
    1
      app/javascript/core/theme.yml
  3. 14
    12
      app/javascript/flavours/glitch/actions/compose.js
  4. 3
    4
      app/javascript/flavours/glitch/actions/statuses.js
  5. 30
    25
      app/javascript/flavours/glitch/components/autosuggest_textarea.js
  6. 20
    0
      app/javascript/flavours/glitch/components/icon_with_badge.js
  7. 28
    48
      app/javascript/flavours/glitch/features/compose/components/compose_form.js
  8. 1
    1
      app/javascript/flavours/glitch/features/compose/components/navigation_bar.js
  9. 10
    3
      app/javascript/flavours/glitch/features/compose/components/search.js
  10. 0
    10
      app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
  11. 32
    5
      app/javascript/flavours/glitch/features/compose/index.js
  12. 1
    1
      app/javascript/flavours/glitch/features/follow_requests/index.js
  13. 20
    26
      app/javascript/flavours/glitch/features/getting_started/index.js
  14. 6
    3
      app/javascript/flavours/glitch/features/local_settings/page/index.js
  15. 17
    0
      app/javascript/flavours/glitch/features/search/index.js
  16. 8
    0
      app/javascript/flavours/glitch/features/ui/components/boost_modal.js
  17. 36
    12
      app/javascript/flavours/glitch/features/ui/components/columns_area.js
  18. 16
    0
      app/javascript/flavours/glitch/features/ui/components/compose_panel.js
  19. 44
    0
      app/javascript/flavours/glitch/features/ui/components/follow_requests_nav_link.js
  20. 36
    0
      app/javascript/flavours/glitch/features/ui/components/link_footer.js
  21. 55
    0
      app/javascript/flavours/glitch/features/ui/components/list_panel.js
  22. 32
    0
      app/javascript/flavours/glitch/features/ui/components/navigation_panel.js
  23. 9
    0
      app/javascript/flavours/glitch/features/ui/components/notifications_counter_icon.js
  24. 7
    31
      app/javascript/flavours/glitch/features/ui/components/tabs_bar.js
  25. 10
    1
      app/javascript/flavours/glitch/features/ui/containers/columns_area_container.js
  26. 7
    21
      app/javascript/flavours/glitch/features/ui/index.js
  27. 1
    1
      app/javascript/flavours/glitch/reducers/notifications.js
  28. 62
    89
      app/javascript/flavours/glitch/styles/components/columns.scss
  29. 13
    2
      app/javascript/flavours/glitch/styles/components/composer.scss
  30. 6
    2
      app/javascript/flavours/glitch/styles/components/drawer.scss
  31. 116
    9
      app/javascript/flavours/glitch/styles/components/index.scss
  32. 2
    2
      app/javascript/flavours/glitch/styles/components/search.scss
  33. 232
    0
      app/javascript/flavours/glitch/styles/components/single_column.scss
  34. 1
    1
      app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
  35. 4
    0
      app/javascript/flavours/glitch/util/async-components.js
  36. 1
    0
      app/javascript/flavours/glitch/util/initial_state.js
  37. 2
    1
      app/javascript/flavours/glitch/util/is_mobile.js
  38. 6
    2
      app/javascript/mastodon/components/status_content.js
  39. 1
    4
      app/javascript/mastodon/features/compose/components/compose_form.js
  40. 8
    0
      app/javascript/mastodon/features/compose/components/reply_indicator.js
  41. 8
    0
      app/javascript/mastodon/features/ui/components/boost_modal.js
  42. 2
    1
      app/javascript/mastodon/features/ui/components/navigation_panel.js
  43. 59
    59
      app/javascript/mastodon/locales/ar.json
  44. 1
    1
      app/javascript/mastodon/locales/ast.json
  45. 1
    1
      app/javascript/mastodon/locales/bg.json
  46. 1
    1
      app/javascript/mastodon/locales/bn.json
  47. 41
    41
      app/javascript/mastodon/locales/ca.json
  48. 2
    2
      app/javascript/mastodon/locales/co.json
  49. 54
    54
      app/javascript/mastodon/locales/cy.json
  50. 54
    54
      app/javascript/mastodon/locales/de.json
  51. 14
    14
      app/javascript/mastodon/locales/el.json
  52. 6
    6
      app/javascript/mastodon/locales/eo.json
  53. 4
    4
      app/javascript/mastodon/locales/es.json
  54. 48
    48
      app/javascript/mastodon/locales/eu.json
  55. 1
    1
      app/javascript/mastodon/locales/fi.json
  56. 3
    3
      app/javascript/mastodon/locales/fr.json
  57. 35
    35
      app/javascript/mastodon/locales/gl.json
  58. 1
    1
      app/javascript/mastodon/locales/he.json
  59. 1
    1
      app/javascript/mastodon/locales/hr.json
  60. 1
    1
      app/javascript/mastodon/locales/hu.json
  61. 1
    1
      app/javascript/mastodon/locales/hy.json
  62. 1
    1
      app/javascript/mastodon/locales/id.json
  63. 1
    1
      app/javascript/mastodon/locales/io.json
  64. 1
    1
      app/javascript/mastodon/locales/ja.json
  65. 1
    1
      app/javascript/mastodon/locales/ka.json
  66. 3
    3
      app/javascript/mastodon/locales/ko.json
  67. 388
    0
      app/javascript/mastodon/locales/lt.json
  68. 1
    1
      app/javascript/mastodon/locales/lv.json
  69. 1
    1
      app/javascript/mastodon/locales/ms.json
  70. 10
    10
      app/javascript/mastodon/locales/nl.json
  71. 1
    1
      app/javascript/mastodon/locales/no.json
  72. 10
    10
      app/javascript/mastodon/locales/pl.json
  73. 6
    6
      app/javascript/mastodon/locales/ru.json
  74. 1
    1
      app/javascript/mastodon/locales/sl.json
  75. 3
    3
      app/javascript/mastodon/locales/sq.json
  76. 1
    1
      app/javascript/mastodon/locales/sr-Latn.json
  77. 1
    1
      app/javascript/mastodon/locales/sr.json
  78. 13
    13
      app/javascript/mastodon/locales/sv.json
  79. 329
    329
      app/javascript/mastodon/locales/ta.json
  80. 3
    3
      app/javascript/mastodon/locales/th.json
  81. 1
    1
      app/javascript/mastodon/locales/uk.json
  82. 2
    0
      app/javascript/mastodon/locales/whitelist_lt.json
  83. 0
    1
      app/javascript/mastodon/locales/zh-CN.json
  84. 1
    1
      app/javascript/mastodon/locales/zh-HK.json
  85. 5
    15
      app/javascript/styles/mastodon/components.scss
  86. 5
    2
      app/views/oauth/authorizations/show.html.haml
  87. 4
    0
      config/locales/activerecord.ar.yml
  88. 1
    0
      config/locales/activerecord.bg.yml
  89. 1
    0
      config/locales/activerecord.bn.yml
  90. 3
    2
      config/locales/activerecord.ca.yml
  91. 0
    3
      config/locales/activerecord.da.yml
  92. 2
    4
      config/locales/activerecord.de.yml
  93. 0
    2
      config/locales/activerecord.el.yml
  94. 17
    0
      config/locales/activerecord.eo.yml
  95. 5
    1
      config/locales/activerecord.es.yml
  96. 4
    0
      config/locales/activerecord.eu.yml
  97. 0
    3
      config/locales/activerecord.fa.yml
  98. 1
    0
      config/locales/activerecord.fi.yml
  99. 0
    2
      config/locales/activerecord.gl.yml
  100. 0
    0
      config/locales/activerecord.hr.yml

+ 4
- 1
CHANGELOG.md View File

@@ -3,7 +3,7 @@ Changelog

All notable changes to this project will be documented in this file.

## [Unreleased]
## [2.9.0] - 2019-06-13
### Added

- **Add single-column mode in web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/10807), [Gargron](https://github.com/tootsuite/mastodon/pull/10848), [Gargron](https://github.com/tootsuite/mastodon/pull/11003), [Gargron](https://github.com/tootsuite/mastodon/pull/10961), [Hanage999](https://github.com/tootsuite/mastodon/pull/10915), [noellabo](https://github.com/tootsuite/mastodon/pull/10917), [abcang](https://github.com/tootsuite/mastodon/pull/10859), [Gargron](https://github.com/tootsuite/mastodon/pull/10820), [Gargron](https://github.com/tootsuite/mastodon/pull/10835), [Gargron](https://github.com/tootsuite/mastodon/pull/10809), [Gargron](https://github.com/tootsuite/mastodon/pull/10963), [noellabo](https://github.com/tootsuite/mastodon/pull/10883), [Hanage999](https://github.com/tootsuite/mastodon/pull/10839))
@@ -14,6 +14,7 @@ All notable changes to this project will be documented in this file.
- Add emoji suggestions to content warning and poll option fields in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10555))
- Add `source` attribute to response of `DELETE /api/v1/statuses/:id` ([ThibG](https://github.com/tootsuite/mastodon/pull/10669))
- Add some caching for HTML versions of public status pages ([ThibG](https://github.com/tootsuite/mastodon/pull/10701))
- Add button to conveniently copy OAuth code ([ThibG](https://github.com/tootsuite/mastodon/pull/11065))

### Changed

@@ -53,6 +54,8 @@ All notable changes to this project will be documented in this file.
- Fix avatar preview aspect ratio on edit profile page ([Kjwon15](https://github.com/tootsuite/mastodon/pull/10931))
- Fix web push notifications not being sent for polls ([ThibG](https://github.com/tootsuite/mastodon/pull/10864))
- Fix cut off letters in last paragraph of statuses in web UI ([ariasuni](https://github.com/tootsuite/mastodon/pull/10821))
- Fix list not being automatically unpinned when it returns 404 in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11045))
- Fix login sometimes redirecting to paths that are not pages ([Gargron](https://github.com/tootsuite/mastodon/pull/11019))

## [2.8.4] - 2019-05-24
### Fixed

+ 1
- 1
app/javascript/core/theme.yml View File

@@ -3,7 +3,7 @@
pack:
about:
admin: admin.js
auth:
auth: settings.js
common:
filename: common.js
stylesheet: true

+ 14
- 12
app/javascript/flavours/glitch/actions/compose.js View File

@@ -68,6 +68,14 @@ const messages = defineMessages({
uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
});

const COMPOSE_PANEL_BREAKPOINT = 600 + (285 * 1) + (10 * 1);

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,
@@ -81,16 +89,14 @@ export function cycleElefriendCompose() {
};
};

export function replyCompose(status, router) {
export function replyCompose(status, routerHistory) {
return (dispatch, getState) => {
dispatch({
type: COMPOSE_REPLY,
status: status,
});

if (router && !getState().getIn(['compose', 'mounted'])) {
router.push('/statuses/new');
}
ensureComposeIsVisible(getState, routerHistory);
};
};

@@ -106,29 +112,25 @@ export function resetCompose() {
};
};

export function mentionCompose(account, router) {
export function mentionCompose(account, routerHistory) {
return (dispatch, getState) => {
dispatch({
type: COMPOSE_MENTION,
account: account,
});

if (!getState().getIn(['compose', 'mounted'])) {
router.push('/statuses/new');
}
ensureComposeIsVisible(getState, routerHistory);
};
};

export function directCompose(account, router) {
export function directCompose(account, routerHistory) {
return (dispatch, getState) => {
dispatch({
type: COMPOSE_DIRECT,
account: account,
});

if (!getState().getIn(['compose', 'mounted'])) {
router.push('/statuses/new');
}
ensureComposeIsVisible(getState, routerHistory);
};
};


+ 3
- 4
app/javascript/flavours/glitch/actions/statuses.js View File

@@ -2,6 +2,7 @@ import api from 'flavours/glitch/util/api';

import { deleteFromTimelines } from './timelines';
import { importFetchedStatus, importFetchedStatuses } from './importer';
import { ensureComposeIsVisible } from './compose';

export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST';
export const STATUS_FETCH_SUCCESS = 'STATUS_FETCH_SUCCESS';
@@ -80,7 +81,7 @@ export function redraft(status, raw_text, content_type) {
};
};

export function deleteStatus(id, router, withRedraft = false) {
export function deleteStatus(id, routerHistory, withRedraft = false) {
return (dispatch, getState) => {
let status = getState().getIn(['statuses', id]);

@@ -97,9 +98,7 @@ export function deleteStatus(id, router, withRedraft = false) {
if (withRedraft) {
dispatch(redraft(status, response.data.text, response.data.content_type));

if (!getState().getIn(['compose', 'mounted'])) {
router.push('/statuses/new');
}
ensureComposeIsVisible(getState, routerHistory);
}
}).catch(error => {
dispatch(deleteStatusFail(id, error));

+ 30
- 25
app/javascript/flavours/glitch/components/autosuggest_textarea.js View File

@@ -192,7 +192,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
}

render () {
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus } = this.props;
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, children } = this.props;
const { suggestionsHidden } = this.state;
const style = { direction: 'ltr' };

@@ -200,34 +200,39 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
style.direction = 'rtl';
}

return (
<div className='autosuggest-textarea'>
<label>
<span style={{ display: 'none' }}>{placeholder}</span>

<Textarea
inputRef={this.setTextarea}
className='autosuggest-textarea__textarea'
disabled={disabled}
placeholder={placeholder}
autoFocus={autoFocus}
value={value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
onKeyUp={onKeyUp}
onFocus={this.onFocus}
onBlur={this.onBlur}
onPaste={this.onPaste}
style={style}
aria-autocomplete='list'
/>
</label>
return [
<div className='compose-form__autosuggest-wrapper' key='autosuggest-wrapper'>
<div className='autosuggest-textarea'>
<label>
<span style={{ display: 'none' }}>{placeholder}</span>

<Textarea
inputRef={this.setTextarea}
className='autosuggest-textarea__textarea'
disabled={disabled}
placeholder={placeholder}
autoFocus={autoFocus}
value={value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
onKeyUp={onKeyUp}
onFocus={this.onFocus}
onBlur={this.onBlur}
onPaste={this.onPaste}
style={style}
aria-autocomplete='list'
/>
</label>
</div>
{children}
</div>,

<div className='autosuggest-textarea__suggestions-wrapper' key='suggestions-wrapper'>
<div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}>
{suggestions.map(this.renderSuggestion)}
</div>
</div>
);
</div>,
];
}

}

+ 20
- 0
app/javascript/flavours/glitch/components/icon_with_badge.js View File

@@ -0,0 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';
import Icon from 'flavours/glitch/components/icon';

const formatNumber = num => num > 40 ? '40+' : num;

const IconWithBadge = ({ id, count, className }) => (
<i className='icon-with-badge'>
<Icon icon={id} fixedWidth className={className} />
{count > 0 && <i className='icon-with-badge__badge'>{formatNumber(count)}</i>}
</i>
);

IconWithBadge.propTypes = {
id: PropTypes.string.isRequired,
count: PropTypes.number.isRequired,
className: PropTypes.string,
};

export default IconWithBadge;

+ 28
- 48
app/javascript/flavours/glitch/features/compose/components/compose_form.js View File

@@ -28,10 +28,6 @@ const messages = defineMessages({
export default @injectIntl
class ComposeForm extends ImmutablePureComponent {

setRef = c => {
this.composeForm = c;
};

static contextTypes = {
router: PropTypes.object,
};
@@ -70,8 +66,6 @@ class ComposeForm extends ImmutablePureComponent {
preselectOnReply: PropTypes.bool,
onChangeSpoilerness: PropTypes.func,
onChangeVisibility: PropTypes.func,
onMount: PropTypes.func,
onUnmount: PropTypes.func,
onPaste: PropTypes.func,
onMediaDescriptionConfirm: PropTypes.func,
};
@@ -145,6 +139,10 @@ class ComposeForm extends ImmutablePureComponent {
}
}

setRef = c => {
this.composeForm = c;
};

// Inserts an emoji at the caret.
handleEmoji = (data) => {
const { textarea: { selectionStart } } = this;
@@ -196,24 +194,10 @@ class ComposeForm extends ImmutablePureComponent {
}
}

// Tells our state the composer has been mounted.
componentDidMount () {
const { onMount } = this.props;
if (onMount) {
onMount();
}
}

// Tells our state the composer has been unmounted.
componentWillUnmount () {
const { onUnmount } = this.props;
if (onUnmount) {
onUnmount();
}
}

handleFocus = () => {
this.composeForm.scrollIntoView();
if (this.composeForm) {
this.composeForm.scrollIntoView();
}
}

// This statement does several things:
@@ -335,32 +319,28 @@ class ComposeForm extends ImmutablePureComponent {
/>
</div>

<div className='composer--textarea'>
<TextareaIcons advancedOptions={advancedOptions} />

<AutosuggestTextarea
ref={this.setAutosuggestTextarea}
placeholder={intl.formatMessage(messages.placeholder)}
disabled={isSubmitting}
value={this.props.text}
onChange={this.handleChange}
suggestions={this.props.suggestions}
onFocus={this.handleFocus}
onKeyDown={this.handleKeyDown}
onSuggestionsFetchRequested={onFetchSuggestions}
onSuggestionsClearRequested={onClearSuggestions}
onSuggestionSelected={this.onSuggestionSelected}
onPaste={onPaste}
autoFocus={!showSearch && !isMobile(window.innerWidth, layout)}
/>

<AutosuggestTextarea
ref={this.setAutosuggestTextarea}
placeholder={intl.formatMessage(messages.placeholder)}
disabled={isSubmitting}
value={this.props.text}
onChange={this.handleChange}
suggestions={this.props.suggestions}
onFocus={this.handleFocus}
onKeyDown={this.handleKeyDown}
onSuggestionsFetchRequested={onFetchSuggestions}
onSuggestionsClearRequested={onClearSuggestions}
onSuggestionSelected={this.onSuggestionSelected}
onPaste={onPaste}
autoFocus={!showSearch && !isMobile(window.innerWidth, layout)}
>
<EmojiPicker onPickEmoji={handleEmoji} />
</div>
<div className='compose-form__modifiers'>
<UploadFormContainer />
<PollFormContainer />
</div>
<TextareaIcons advancedOptions={advancedOptions} />
<div className='compose-form__modifiers'>
<UploadFormContainer />
<PollFormContainer />
</div>
</AutosuggestTextarea>

<OptionsContainer
advancedOptions={advancedOptions}

+ 1
- 1
app/javascript/flavours/glitch/features/compose/components/navigation_bar.js View File

@@ -17,7 +17,7 @@ export default class NavigationBar extends ImmutablePureComponent {
<div className='drawer--account'>
<Permalink className='avatar' href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}>
<span style={{ display: 'none' }}>{this.props.account.get('acct')}</span>
<Avatar account={this.props.account} size={40} />
<Avatar account={this.props.account} size={48} />
</Permalink>

<Permalink className='acct' href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}>

+ 10
- 3
app/javascript/flavours/glitch/features/compose/components/search.js View File

@@ -33,7 +33,7 @@ class SearchPopout extends React.PureComponent {
const { style } = this.props;
const extraInformation = searchEnabled ? <FormattedMessage id='search_popout.tips.full_text' defaultMessage='Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.' /> : <FormattedMessage id='search_popout.tips.text' defaultMessage='Simple text returns matching display names, usernames and hashtags' />;
return (
<div style={{ ...style, position: 'absolute', width: 285 }}>
<div style={{ ...style, position: 'absolute', width: 285, zIndex: 2 }}>
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
{({ opacity, scaleX, scaleY }) => (
<div className='drawer--search--popout' style={{ opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }}>
@@ -60,6 +60,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,
@@ -67,6 +71,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,
};

@@ -109,8 +114,10 @@ class Search extends React.PureComponent {
const { onSubmit } = this.props;
switch (e.key) {
case 'Enter':
if (onSubmit) {
onSubmit();
onSubmit();

if (this.props.openInRoute) {
this.context.router.history.push('/search');
}
break;
case 'Escape':

+ 0
- 10
app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js View File

@@ -9,10 +9,8 @@ import {
clearComposeSuggestions,
fetchComposeSuggestions,
insertEmojiCompose,
mountCompose,
selectComposeSuggestion,
submitCompose,
unmountCompose,
uploadCompose,
} from 'flavours/glitch/actions/compose';
import {
@@ -114,14 +112,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
dispatch(changeComposeVisibility(value));
},

onMount() {
dispatch(mountCompose());
},

onUnmount() {
dispatch(unmountCompose());
},

onMediaDescriptionConfirm(routerHistory) {
dispatch(openModal('CONFIRM', {
message: intl.formatMessage(messages.missingDescriptionMessage),

+ 32
- 5
app/javascript/flavours/glitch/features/compose/index.js View File

@@ -4,6 +4,7 @@ import NavigationContainer from './containers/navigation_container';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import { mountCompose, unmountCompose } from 'flavours/glitch/actions/compose';
import { injectIntl, defineMessages } from 'react-intl';
import classNames from 'classnames';
import SearchContainer from './containers/search_container';
@@ -27,6 +28,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
onClickElefriend () {
dispatch(cycleElefriendCompose());
},

onMount () {
dispatch(mountCompose());
},

onUnmount () {
dispatch(unmountCompose());
},
});

export default @connect(mapStateToProps, mapDispatchToProps)
@@ -38,9 +47,27 @@ class Compose extends React.PureComponent {
isSearchPage: PropTypes.bool,
elefriend: PropTypes.number,
onClickElefriend: PropTypes.func,
onMount: PropTypes.func,
onUnmount: PropTypes.func,
intl: PropTypes.object.isRequired,
};

componentDidMount () {
const { isSearchPage } = this.props;

if (!isSearchPage) {
this.props.onMount();
}
}

componentWillUnmount () {
const { isSearchPage } = this.props;

if (!isSearchPage) {
this.props.onUnmount();
}
}

render () {
const {
elefriend,
@@ -61,12 +88,12 @@ class Compose extends React.PureComponent {
<div className='drawer__pager'>
{!isSearchPage && <div className='drawer__inner'>
<NavigationContainer />

<ComposeFormContainer />
{multiColumn && (
<div className='drawer__inner__mastodon'>
{mascot ? <img alt='' draggable='false' src={mascot} /> : <button className='mastodon' onClick={onClickElefriend} />}
</div>
)}

<div className='drawer__inner__mastodon'>
{mascot ? <img alt='' draggable='false' src={mascot} /> : <button className='mastodon' onClick={onClickElefriend} />}
</div>
</div>}

<Motion defaultStyle={{ x: isSearchPage ? 0 : -100 }} style={{ x: spring(showSearch || isSearchPage ? 0 : -100, { stiffness: 210, damping: 20 }) }}>

+ 1
- 1
app/javascript/flavours/glitch/features/follow_requests/index.js View File

@@ -59,7 +59,7 @@ export default class FollowRequests extends ImmutablePureComponent {
}

return (
<Column name='follow-requests' icon='users' heading={intl.formatMessage(messages.heading)}>
<Column name='follow-requests' icon='user-plus' heading={intl.formatMessage(messages.heading)}>
<ColumnBackButtonSlim />

<ScrollContainer scrollKey='follow_requests' shouldUpdateScroll={this.shouldUpdateScroll}>

+ 20
- 26
app/javascript/flavours/glitch/features/getting_started/index.js View File

@@ -8,12 +8,13 @@ import { openModal } from 'flavours/glitch/actions/modal';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { me, invitesEnabled, version } from 'flavours/glitch/util/initial_state';
import { me } from 'flavours/glitch/util/initial_state';
import { fetchFollowRequests } from 'flavours/glitch/actions/accounts';
import { List as ImmutableList } from 'immutable';
import { createSelector } from 'reselect';
import { fetchLists } from 'flavours/glitch/actions/lists';
import { preferencesLink, profileLink, signOutLink } from 'flavours/glitch/util/backend_links';
import { preferencesLink, signOutLink } from 'flavours/glitch/util/backend_links';
import LinkFooter from 'flavours/glitch/features/ui/components/link_footer';

const messages = defineMessages({
heading: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
@@ -73,9 +74,15 @@ const badgeDisplay = (number, limit) => {
}
};

@connect(makeMapStateToProps, mapDispatchToProps)
@injectIntl
export default class GettingStarted extends ImmutablePureComponent {
const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2);

export default @connect(makeMapStateToProps, mapDispatchToProps)
@injectIntl
class GettingStarted extends ImmutablePureComponent {

static contextTypes = {
router: PropTypes.object.isRequired,
};

static propTypes = {
intl: PropTypes.object.isRequired,
@@ -95,7 +102,12 @@ export default class GettingStarted extends ImmutablePureComponent {
}

componentDidMount () {
const { myAccount, fetchFollowRequests } = this.props;
const { myAccount, fetchFollowRequests, multiColumn } = this.props;

if (!multiColumn && window.innerWidth >= NAVIGATION_PANEL_BREAKPOINT) {
this.context.router.history.replace('/timelines/home');
return;
}

if (myAccount.get('locked')) {
fetchFollowRequests();
@@ -135,7 +147,7 @@ export default class GettingStarted extends ImmutablePureComponent {
}

if (myAccount.get('locked')) {
navItems.push(<ColumnLink key='6' icon='users' text={intl.formatMessage(messages.follow_requests)} badge={badgeDisplay(unreadFollowRequests, 40)} to='/follow_requests' />);
navItems.push(<ColumnLink key='6' icon='user-plus' text={intl.formatMessage(messages.follow_requests)} badge={badgeDisplay(unreadFollowRequests, 40)} to='/follow_requests' />);
}

navItems.push(<ColumnLink key='7' icon='ellipsis-h' text={intl.formatMessage(messages.misc)} to='/getting-started-misc' />);
@@ -163,25 +175,7 @@ export default class GettingStarted extends ImmutablePureComponent {
<ColumnLink icon='sign-out' text={intl.formatMessage(messages.sign_out)} href={signOutLink} method='delete' />
</div>

<div className='getting-started__footer'>
<ul>
{invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>}
<li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a> · </li>
<li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li>
<li><a href='/terms' target='_blank'><FormattedMessage id='getting_started.terms' defaultMessage='Terms of service' /></a> · </li>
<li><a href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a></li>
</ul>

<p>
<FormattedMessage
id='getting_started.open_source_notice'
defaultMessage='Glitchsoc is open source software, a friendly fork of {Mastodon}. You can contribute or report issues on GitHub at {github}.'
values={{
github: <span><a href='https://github.com/glitch-soc/mastodon' rel='noopener' target='_blank'>glitch-soc/mastodon</a> (v{version})</span>,
Mastodon: <a href='https://github.com/tootsuite/mastodon' rel='noopener' target='_blank'>Mastodon</a> }}
/>
</p>
</div>
<LinkFooter />
</div>
</Column>
);

+ 6
- 3
app/javascript/flavours/glitch/features/local_settings/page/index.js View File

@@ -11,8 +11,11 @@ import LocalSettingsPageItem from './item';

const messages = defineMessages({
layout_auto: { id: 'layout.auto', defaultMessage: 'Auto' },
layout_auto_hint: { id: 'layout.hint.auto', defaultMessage: 'Automatically chose layout based on “Enable advanced web interface” setting and screen size.' },
layout_desktop: { id: 'layout.desktop', defaultMessage: 'Desktop' },
layout_desktop_hint: { id: 'layout.hint.desktop', defaultMessage: 'Use multiple-column layout regardless of the “Enable advanced web interface” setting or screen size.' },
layout_mobile: { id: 'layout.single', defaultMessage: 'Mobile' },
layout_mobile_hint: { id: 'layout.hint.single', defaultMessage: 'Use single-column layout regardless of the “Enable advanced web interface” setting or screen size.' },
side_arm_none: { id: 'settings.side_arm.none', defaultMessage: 'None' },
side_arm_keep: { id: 'settings.side_arm_reply_mode.keep', defaultMessage: 'Keep secondary toot button to set privacy' },
side_arm_copy: { id: 'settings.side_arm_reply_mode.copy', defaultMessage: 'Copy privacy setting of the toot being replied to' },
@@ -87,9 +90,9 @@ export default class LocalSettingsPage extends React.PureComponent {
item={['layout']}
id='mastodon-settings--layout'
options={[
{ value: 'auto', message: intl.formatMessage(messages.layout_auto) },
{ value: 'multiple', message: intl.formatMessage(messages.layout_desktop) },
{ value: 'single', message: intl.formatMessage(messages.layout_mobile) },
{ value: 'auto', message: intl.formatMessage(messages.layout_auto), hint: intl.formatMessage(messages.layout_auto_hint) },
{ value: 'multiple', message: intl.formatMessage(messages.layout_desktop), hint: intl.formatMessage(messages.layout_desktop_hint) },
{ value: 'single', message: intl.formatMessage(messages.layout_mobile), hint: intl.formatMessage(messages.layout_mobile_hint) },
]}
onChange={onChange}
>

+ 17
- 0
app/javascript/flavours/glitch/features/search/index.js View File

@@ -0,0 +1,17 @@
import React from 'react';
import SearchContainer from 'flavours/glitch/features/compose/containers/search_container';
import SearchResultsContainer from 'flavours/glitch/features/compose/containers/search_results_container';

const Search = () => (
<div className='column search-page'>
<SearchContainer />

<div className='drawer__pager'>
<div className='drawer__inner darker'>
<SearchResultsContainer />
</div>
</div>
</div>
);

export default Search;

+ 8
- 0
app/javascript/flavours/glitch/features/ui/components/boost_modal.js View File

@@ -7,6 +7,7 @@ import StatusContent from 'flavours/glitch/components/status_content';
import Avatar from 'flavours/glitch/components/avatar';
import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp';
import DisplayName from 'flavours/glitch/components/display_name';
import AttachmentList from 'flavours/glitch/components/attachment_list';
import ImmutablePureComponent from 'react-immutable-pure-component';

const messages = defineMessages({
@@ -75,6 +76,13 @@ export default class BoostModal extends ImmutablePureComponent {
</div>

<StatusContent status={status} />

{status.get('media_attachments').size > 0 && (
<AttachmentList
compact
media={status.get('media_attachments')}
/>
)}
</div>
</div>


+ 36
- 12
app/javascript/flavours/glitch/features/ui/components/columns_area.js View File

@@ -5,7 +5,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';

import ReactSwipeableViews from 'react-swipeable-views';
import { links, getIndex, getLink } from './tabs_bar';
import TabsBar, { links, getIndex, getLink } from './tabs_bar';
import { Link } from 'react-router-dom';

import BundleContainer from '../containers/bundle_container';
@@ -13,6 +13,8 @@ import ColumnLoading from './column_loading';
import DrawerLoading from './drawer_loading';
import BundleColumnError from './bundle_column_error';
import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses, BookmarkedStatuses, ListTimeline } from 'flavours/glitch/util/async-components';
import ComposePanel from './compose_panel';
import NavigationPanel from './navigation_panel';

import detectPassiveEvents from 'detect-passive-events';
import { scrollRight } from 'flavours/glitch/util/scroll';
@@ -49,6 +51,8 @@ export default class ColumnsArea extends ImmutablePureComponent {
swipeToChangeColumns: PropTypes.bool,
singleColumn: PropTypes.bool,
children: PropTypes.node,
navbarUnder: PropTypes.bool,
openSettings: PropTypes.func,
};

state = {
@@ -139,7 +143,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
<ColumnLoading title={title} icon={icon} />;

return (
<div className='columns-area' key={index}>
<div className='columns-area columns-area--mobile' key={index}>
{view}
</div>
);
@@ -154,7 +158,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
}

render () {
const { columns, children, singleColumn, swipeToChangeColumns, intl } = this.props;
const { columns, children, singleColumn, swipeToChangeColumns, intl, navbarUnder, openSettings } = this.props;
const { shouldAnimate } = this.state;

const columnIndex = getIndex(this.context.router.history.location.pathname);
@@ -163,17 +167,37 @@ export default class ColumnsArea extends ImmutablePureComponent {
if (singleColumn) {
const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <Link key='floating-action-button' to='/statuses/new' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><i className='fa fa-pencil' /></Link>;

return columnIndex !== -1 ? [
const content = columnIndex !== -1 ? (
<ReactSwipeableViews key='content' index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }} disabled={!swipeToChangeColumns}>
{links.map(this.renderView)}
</ReactSwipeableViews>,

floatingActionButton,
] : [
<div className='columns-area'>{children}</div>,

floatingActionButton,
];
</ReactSwipeableViews>
) : (
<div key='content' className='columns-area columns-area--mobile'>{children}</div>
);

return (
<div className='columns-area__panels'>
<div className='columns-area__panels__pane columns-area__panels__pane--compositional'>
<div className='columns-area__panels__pane__inner'>
<ComposePanel />
</div>
</div>

<div className='columns-area__panels__main'>
{!navbarUnder && <TabsBar key='tabs' />}
{content}
{navbarUnder && <TabsBar key='tabs' />}
</div>

<div className='columns-area__panels__pane columns-area__panels__pane--start columns-area__panels__pane--navigational'>
<div className='columns-area__panels__pane__inner'>
<NavigationPanel onOpenSettings={openSettings} />
</div>
</div>

{floatingActionButton}
</div>
);
}

return (

+ 16
- 0
app/javascript/flavours/glitch/features/ui/components/compose_panel.js View File

@@ -0,0 +1,16 @@
import React from 'react';
import SearchContainer from 'flavours/glitch/features/compose/containers/search_container';
import ComposeFormContainer from 'flavours/glitch/features/compose/containers/compose_form_container';
import NavigationContainer from 'flavours/glitch/features/compose/containers/navigation_container';
import LinkFooter from './link_footer';

const ComposePanel = () => (
<div className='compose-panel'>
<SearchContainer openInRoute />
<NavigationContainer />
<ComposeFormContainer />
<LinkFooter withHotkeys />
</div>
);

export default ComposePanel;

+ 44
- 0
app/javascript/flavours/glitch/features/ui/components/follow_requests_nav_link.js View File

@@ -0,0 +1,44 @@
import React from 'react';
import PropTypes from 'prop-types';
import { fetchFollowRequests } from 'flavours/glitch/actions/accounts';
import { connect } from 'react-redux';
import { NavLink, withRouter } from 'react-router-dom';
import IconWithBadge from 'flavours/glitch/components/icon_with_badge';
import { me } from 'flavours/glitch/util/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 <NavLink className='column-link column-link--transparent' to='/follow_requests'><IconWithBadge className='column-link__icon' id='user-plus' count={count} /><FormattedMessage id='navigation_bar.follow_requests' defaultMessage='Follow requests' /></NavLink>;
}

}

+ 36
- 0
app/javascript/flavours/glitch/features/ui/components/link_footer.js View File

@@ -0,0 +1,36 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import { invitesEnabled, version, repository, source_url } from 'flavours/glitch/util/initial_state';
import { signOutLink } from 'flavours/glitch/util/backend_links';

const LinkFooter = () => (
<div className='getting-started__footer'>
<ul>
{invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>}
<li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li>
<li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a> · </li>
<li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li>
<li><a href='/terms' target='_blank'><FormattedMessage id='getting_started.terms' defaultMessage='Terms of service' /></a> · </li>
<li><a href='/settings/applications' target='_blank'><FormattedMessage id='getting_started.developers' defaultMessage='Developers' /></a> · </li>
<li><a href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a> · </li>
<li><a href={signOutLink} data-method='delete'><FormattedMessage id='navigation_bar.logout' defaultMessage='Logout' /></a></li>
</ul>

<p>
<FormattedMessage
id='getting_started.open_source_notice'
defaultMessage='Glitchsoc is open source software, a friendly fork of {Mastodon}. You can contribute or report issues on GitHub at {github}.'
values={{
github: <span><a href='https://github.com/glitch-soc/mastodon' rel='noopener' target='_blank'>glitch-soc/mastodon</a> (v{version})</span>,
Mastodon: <a href='https://github.com/tootsuite/mastodon' rel='noopener' target='_blank'>Mastodon</a> }}
/>
</p>
</div>
);

LinkFooter.propTypes = {
};

export default LinkFooter;

+ 55
- 0
app/javascript/flavours/glitch/features/ui/components/list_panel.js View File

@@ -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 'flavours/glitch/actions/lists';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { NavLink, withRouter } from 'react-router-dom';
import Icon from 'flavours/glitch/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'))).take(4);
});

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 || lists.isEmpty()) {
return null;
}

return (
<div>
<hr />

{lists.map(list => (
<NavLink key={list.get('id')} className='column-link column-link--transparent' strict to={`/timelines/list/${list.get('id')}`}><Icon className='column-link__icon' icon='list-ul' fixedWidth />{list.get('title')}</NavLink>
))}
</div>
);
}

}

+ 32
- 0
app/javascript/flavours/glitch/features/ui/components/navigation_panel.js View File

@@ -0,0 +1,32 @@
import React from 'react';
import { NavLink, withRouter } from 'react-router-dom';
import { FormattedMessage } from 'react-intl';
import Icon from 'flavours/glitch/components/icon';
import { profile_directory } from 'flavours/glitch/util/initial_state';
import NotificationsCounterIcon from './notifications_counter_icon';
import FollowRequestsNavLink from './follow_requests_nav_link';
import ListPanel from './list_panel';

const NavigationPanel = ({ onOpenSettings }) => (
<div className='navigation-panel'>
<NavLink className='column-link column-link--transparent' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon className='column-link__icon' icon='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>
<NavLink className='column-link column-link--transparent' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon className='column-link__icon' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>
<FollowRequestsNavLink />
<NavLink className='column-link column-link--transparent' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon className='column-link__icon' icon='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>
<NavLink className='column-link column-link--transparent' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon className='column-link__icon' icon='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>
<NavLink className='column-link column-link--transparent' to='/timelines/direct'><Icon className='column-link__icon' icon='envelope' fixedWidth /><FormattedMessage id='navigation_bar.direct' defaultMessage='Direct messages' /></NavLink>
<NavLink className='column-link column-link--transparent' to='/bookmarks'><Icon className='column-link__icon' icon='bookmark' fixedWidth /><FormattedMessage id='navigation_bar.bookmarks' defaultMessage='Bookmarks' /></NavLink>
<NavLink className='column-link column-link--transparent' to='/lists'><Icon className='column-link__icon' icon='list-ul' fixedWidth /><FormattedMessage id='navigation_bar.lists' defaultMessage='Lists' /></NavLink>

<ListPanel />

<hr />

<a className='column-link column-link--transparent' href='/settings/preferences' target='_blank'><Icon className='column-link__icon' icon='cog' fixedWidth /><FormattedMessage id='navigation_bar.preferences' defaultMessage='Preferences' /></a>
<a className='column-link column-link--transparent' href='#' onClick={onOpenSettings}><Icon className='column-link__icon' icon='cogs' fixedWidth /><FormattedMessage id='navigation_bar.app_settings' defaultMessage='App settings' /></a>
<a className='column-link column-link--transparent' href='/relationships' target='_blank'><Icon className='column-link__icon' icon='users' fixedWidth /><FormattedMessage id='navigation_bar.follows_and_followers' defaultMessage='Follows and followers' /></a>
{!!profile_directory && <a className='column-link column-link--transparent' href='/explore'><Icon className='column-link__icon' id='address-book-o' fixedWidth /><FormattedMessage id='navigation_bar.profile_directory' defaultMessage='Profile directory' /></a>}
</div>
);

export default withRouter(NavigationPanel);

+ 9
- 0
app/javascript/flavours/glitch/features/ui/components/notifications_counter_icon.js View File

@@ -0,0 +1,9 @@
import { connect } from 'react-redux';
import IconWithBadge from 'flavours/glitch/components/icon_with_badge';

const mapStateToProps = state => ({
count: state.getIn(['local_settings', 'notifications', 'tab_badge']) ? state.getIn(['notifications', 'unread']) : 0,
id: 'bell',
});

export default connect(mapStateToProps)(IconWithBadge);

+ 7
- 31
app/javascript/flavours/glitch/features/ui/components/tabs_bar.js View File

@@ -4,40 +4,16 @@ import { NavLink, withRouter } from 'react-router-dom';
import { FormattedMessage, injectIntl } from 'react-intl';
import { debounce } from 'lodash';
import { isUserTouching } from 'flavours/glitch/util/is_mobile';
import { connect } from 'react-redux';

const mapStateToProps = state => ({
unreadNotifications: state.getIn(['notifications', 'unread']),
showBadge: state.getIn(['local_settings', 'notifications', 'tab_badge']),
});

@connect(mapStateToProps)
class NotificationsIcon extends React.PureComponent {
static propTypes = {
unreadNotifications: PropTypes.number,
showBadge: PropTypes.bool,
};

render() {
const { unreadNotifications, showBadge } = this.props;
return (
<span className='icon-badge-wrapper'>
<i className='fa fa-fw fa-bell' />
{ showBadge && unreadNotifications > 0 && <div className='icon-badge' />}
</span>
);
}
}
import NotificationsCounterIcon from './notifications_counter_icon';

export const links = [
<NavLink className='tabs-bar__link primary' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><i className='fa fa-fw fa-home' /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>,
<NavLink className='tabs-bar__link primary' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsIcon /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>,

<NavLink className='tabs-bar__link secondary' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><i className='fa fa-fw fa-users' /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>,
<NavLink className='tabs-bar__link secondary' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><i className='fa fa-fw fa-globe' /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>,
<NavLink className='tabs-bar__link primary' to='/search' data-preview-title-id='tabs_bar.search' data-preview-icon='bell' ><i className='fa fa-fw fa-search' /><FormattedMessage id='tabs_bar.search' defaultMessage='Search' /></NavLink>,
<NavLink className='tabs-bar__link' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><i className='fa fa-fw fa-home' /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>,
<NavLink className='tabs-bar__link' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>,

<NavLink className='tabs-bar__link primary' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='bars' ><i className='fa fa-fw fa-bars' /></NavLink>,
<NavLink className='tabs-bar__link' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><i className='fa fa-fw fa-users' /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>,
<NavLink className='tabs-bar__link' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><i className='fa fa-fw fa-globe' /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>,
<NavLink className='tabs-bar__link optional' to='/search' data-preview-title-id='tabs_bar.search' data-preview-icon='bell' ><i className='fa fa-fw fa-search' /><FormattedMessage id='tabs_bar.search' defaultMessage='Search' /></NavLink>,
<NavLink className='tabs-bar__link' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='bars' ><i className='fa fa-fw fa-bars' /></NavLink>,
];

export function getIndex (path) {

+ 10
- 1
app/javascript/flavours/glitch/features/ui/containers/columns_area_container.js View File

@@ -1,9 +1,18 @@
import { connect } from 'react-redux';
import ColumnsArea from '../components/columns_area';
import { openModal } from 'flavours/glitch/actions/modal';

const mapStateToProps = state => ({
columns: state.getIn(['settings', 'columns']),
swipeToChangeColumns: state.getIn(['local_settings', 'swipe_to_change_columns']),
});

export default connect(mapStateToProps, null, null, { forwardRef: true })(ColumnsArea);
const mapDispatchToProps = dispatch => ({
openSettings (e) {
e.preventDefault();
e.stopPropagation();
dispatch(openModal('SETTINGS', {}));
},
});

export default connect(mapStateToProps, mapDispatchToProps, null, { forwardRef: true })(ColumnsArea);

+ 7
- 21
app/javascript/flavours/glitch/features/ui/index.js View File

@@ -2,7 +2,6 @@ import React from 'react';
import NotificationsContainer from './containers/notifications_container';
import PropTypes from 'prop-types';
import LoadingBarContainer from './containers/loading_bar_container';
import TabsBar from './components/tabs_bar';
import ModalContainer from './containers/modal_container';
import { connect } from 'react-redux';
import { Redirect, withRouter } from 'react-router-dom';
@@ -45,6 +44,7 @@ import {
Mutes,
PinnedStatuses,
Lists,
Search,
GettingStartedMisc,
} from 'flavours/glitch/util/async-components';
import { HotKeys } from 'react-hotkeys';
@@ -270,19 +270,6 @@ export default class UI extends React.Component {
};
}

shouldComponentUpdate (nextProps) {
if (nextProps.navbarUnder !== this.props.navbarUnder) {
// Avoid expensive update just to toggle a class
this.node.classList.toggle('navbar-under', nextProps.navbarUnder);

return false;
}

// Why isn't this working?!?
// return super.shouldComponentUpdate(nextProps, nextState);
return true;
}

componentDidUpdate (prevProps) {
if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) {
this.columnsAreaNode.handleChildrenContentChange();
@@ -320,7 +307,7 @@ export default class UI extends React.Component {
handleHotkeyNew = e => {
e.preventDefault();

const element = this.node.querySelector('.composer--textarea textarea');
const element = this.node.querySelector('.compose-form__autosuggest-wrapper textarea');

if (element) {
element.focus();
@@ -432,6 +419,8 @@ export default class UI extends React.Component {
render () {
const { width, draggingOver } = this.state;
const { children, layout, isWide, navbarUnder, dropdownMenuIsOpen } = this.props;
const singleColumn = isMobile(width, layout);
const redirect = singleColumn ? <Redirect from='/' to='/timelines/home' exact /> : <Redirect from='/' to='/getting-started' exact />;

const columnsClass = layout => {
switch (layout) {
@@ -475,11 +464,9 @@ export default class UI extends React.Component {
return (
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef} attach={window} focused>
<div className={className} ref={this.setRef} style={{ pointerEvents: dropdownMenuIsOpen ? 'none' : null }}>
{navbarUnder ? null : (<TabsBar />)}

<ColumnsAreaContainer ref={this.setColumnsAreaRef} singleColumn={isMobile(width, layout)}>
<ColumnsAreaContainer ref={this.setColumnsAreaRef} singleColumn={singleColumn} navbarUnder={navbarUnder}>
<WrappedSwitch>
<Redirect from='/' to='/getting-started' exact />
{redirect}
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
<WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
@@ -493,7 +480,7 @@ export default class UI extends React.Component {
<WrappedRoute path='/bookmarks' component={BookmarkedStatuses} content={children} />
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />

<WrappedRoute path='/search' component={Compose} content={children} componentParams={{ isSearchPage: true }} />
<WrappedRoute path='/search' component={Search} content={children} />

<WrappedRoute path='/statuses/new' component={Compose} content={children} />
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
@@ -518,7 +505,6 @@ export default class UI extends React.Component {
</ColumnsAreaContainer>

<NotificationsContainer />
{navbarUnder ? (<TabsBar />) : null}
<LoadingBarContainer className='loading-bar' />
<ModalContainer />
<UploadArea active={draggingOver} onClose={this.closeUploadModal} />

+ 1
- 1
app/javascript/flavours/glitch/reducers/notifications.js View File

@@ -27,7 +27,7 @@ import compareId from 'flavours/glitch/util/compare_id';
const initialState = ImmutableMap({
items: ImmutableList(),
hasMore: true,
top: true,
top: false,
mounted: 0,
unread: 0,
lastReadId: '0',

+ 62
- 89
app/javascript/flavours/glitch/styles/components/columns.scss View File

@@ -11,15 +11,42 @@
justify-content: flex-start;
overflow-x: auto;
position: relative;
}

@include limited-single-column('screen and (min-width: 360px)', $parent: null) {
.columns-area {
padding: 10px;
}
&__panels {
display: flex;
justify-content: center;
width: 100%;
height: 100%;

&__pane {
height: 100%;
overflow: hidden;
pointer-events: none;
display: flex;
justify-content: flex-end;

&--start {
justify-content: flex-start;
}

&__inner {
width: 285px;
pointer-events: auto;
height: 100%;
}
}

.react-swipeable-view-container .columns-area {
height: calc(100% - 20px) !important;
&__main {
box-sizing: border-box;
width: 100%;
max-width: 600px;
display: flex;
flex-direction: column;

@media screen and (min-width: 360px) {
padding: 0 10px;
}
}
}
}

@@ -63,62 +90,6 @@
overflow: hidden;
}

@include limited-single-column('screen and (min-width: 360px)', $parent: null) {
.tabs-bar {
margin: 10px;
margin-bottom: 0;
}
}

:root { // Overrides .wide stylings for mobile view
@include single-column('screen and (max-width: 630px)', $parent: null) {
.column {
flex: auto;
width: 100%;
min-width: 0;
max-width: none;
padding: 0;
}

.columns-area {
flex-direction: column;
}

.search__input,
.autosuggest-textarea__textarea {
font-size: 16px;
}
}
}

@include multi-columns('screen and (min-width: 631px)', $parent: null) {
.columns-area {
padding: 0;
}

.column {
flex: 0 0 auto;
padding: 10px;
padding-left: 5px;
padding-right: 5px;

&:first-child {
padding-left: 10px;
}

&:last-child {
padding-right: 10px;
}
}

.columns-area > div {
.column {
padding-left: 5px;
padding-right: 5px;
}
}
}

.column-back-button {
background: lighten($ui-base-color, 4%);
color: $highlight-text-color;
@@ -183,9 +154,31 @@
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 {
@@ -277,7 +270,7 @@
flex-direction: column;
overflow: hidden;

.wide & {
.wide .columns-area:not(.columns-area--mobile) & {
flex: auto;
flex-grow: 1;
// min-width: 330px;
@@ -443,6 +436,10 @@
contain: strict;
}

& > span {
max-width: 400px;
}

a {
color: $highlight-text-color;
text-decoration: none;
@@ -508,27 +505,3 @@
margin: 0 5px;
}
}

.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%);
}
}

+ 13
- 2
app/javascript/flavours/glitch/styles/components/composer.scss View File

@@ -12,7 +12,8 @@
opacity: 0.0;

&.composer--spoiler--visible {
height: 47px;
height: 36px;
margin-bottom: 11px;
opacity: 1.0;
}

@@ -98,6 +99,9 @@
border-radius: 4px;
padding: 10px;
background: $ui-primary-color;
min-height: 23px;
overflow-y: auto;
flex: 0 2 auto;

& > header {
margin-bottom: 5px;
@@ -225,7 +229,7 @@
}
}

.composer--textarea,
.compose-form__autosuggest-wrapper,
.autosuggest-input {
position: relative;

@@ -284,6 +288,11 @@
}
}

.autosuggest-textarea__suggestions-wrapper {
position: relative;
height: 0;
}

.autosuggest-textarea__suggestions {
display: block;
position: absolute;
@@ -485,6 +494,7 @@
box-shadow: inset 0 5px 5px rgba($base-shadow-color, 0.05);
border-radius: 0 0 4px 4px;
height: 27px;
flex: 0 0 auto;

& > * {
display: inline-block;
@@ -575,6 +585,7 @@
text-align: right;
white-space: nowrap;
overflow: hidden;
justify-content: flex-end;

& > .count {
display: inline-block;

+ 6
- 2
app/javascript/flavours/glitch/styles/components/drawer.scss View File

@@ -87,9 +87,8 @@
box-sizing: border-box;
margin: 0;
border: none;
padding: 10px 30px 10px 10px;
padding: 15px 30px 15px 15px;
width: 100%;
height: 36px;
outline: 0;
color: $darker-text-color;
background: $ui-base-color;
@@ -277,6 +276,7 @@
background: lighten($ui-base-color, 13%) url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.80078 31.757813" width="234.80078" height="31.757812"><path d="M19.599609 0c-1.05 0-2.10039.375-2.90039 1.125L0 16.925781v14.832031h234.80078V17.025391l-16.5-15.900391c-1.6-1.5-4.20078-1.5-5.80078 0l-13.80078 13.099609c-1.6 1.5-4.19883 1.5-5.79883 0L179.09961 1.125c-1.6-1.5-4.19883-1.5-5.79883 0L159.5 14.224609c-1.6 1.5-4.20078 1.5-5.80078 0L139.90039 1.125c-1.6-1.5-4.20078-1.5-5.80078 0l-13.79883 13.099609c-1.6 1.5-4.20078 1.5-5.80078 0L100.69922 1.125c-1.600001-1.5-4.198829-1.5-5.798829 0l-13.59961 13.099609c-1.6 1.5-4.200781 1.5-5.800781 0L61.699219 1.125c-1.6-1.5-4.198828-1.5-5.798828 0L42.099609 14.224609c-1.6 1.5-4.198828 1.5-5.798828 0L22.5 1.125C21.7.375 20.649609 0 19.599609 0z" fill="#{hex-color($ui-base-color)}"/></svg>') no-repeat bottom / 100% auto;
flex: 1;
min-height: 47px;
display: none;

> img {
display: block;
@@ -296,6 +296,10 @@
border: none;
cursor: inherit;
}

@media screen and (min-height: 640px) {
display: block;
}
}

.pseudo-drawer {

+ 116
- 9
app/javascript/flavours/glitch/styles/components/index.scss View File

@@ -552,7 +552,44 @@
}
}

.column,
.drawer {
flex: 1 1 100%;
overflow: hidden;
}

@media screen and (min-width: 631px) {
.columns-area {
padding: 0;
}

.column,
.drawer {
flex: 0 0 auto;
padding: 10px;
padding-left: 5px;
padding-right: 5px;

&:first-child {
padding-left: 10px;
}

&:last-child {
padding-right: 10px;
}
}

.columns-area > div {
.column,
.drawer {
padding-left: 5px;
padding-right: 5px;
}
}
}

.tabs-bar {
box-sizing: border-box;
display: flex;
background: lighten($ui-base-color, 8%);
flex: 0 0 auto;
@@ -563,6 +600,7 @@
display: block;
flex: 1 1 auto;
padding: 15px 10px;
padding-bottom: 13px;
color: $primary-text-color;
text-decoration: none;
text-align: center;
@@ -577,31 +615,53 @@
font-size: 16px;
}

&.active {
border-bottom: 2px solid $ui-highlight-color;
color: $highlight-text-color;
}

&:hover,
&:focus,
&:active {
@include multi-columns('screen and (min-width: 631px)') {
background: lighten($ui-base-color, 14%);
border-bottom-color: lighten($ui-base-color, 14%);
}
}

span:last-child {
&.active {
border-bottom: 2px solid $ui-highlight-color;
color: $highlight-text-color;
}

span {
margin-left: 5px;
display: none;
}

span.icon {
margin-left: 0;
display: inline;
}
}

@include multi-columns('screen and (min-width: 631px)', $parent: null) {
.tabs-bar {
display: none;
.icon-with-badge {
position: relative;

&__badge {
position: absolute;
left: 9px;
top: -13px;
background: $ui-highlight-color;
border: 2px solid lighten($ui-base-color, 8%);
padding: 1px 6px;
border-radius: 6px;
font-size: 10px;
font-weight: 500;
line-height: 14px;
color: $primary-text-color;
}
}

.column-link--transparent .icon-with-badge__badge {
border-color: darken($ui-base-color, 8%);
}

.scrollable {
overflow-y: scroll;
overflow-x: hidden;
@@ -1272,6 +1332,52 @@
height: 1em;
}

.layout-toggle {
display: flex;
padding: 5px;

button {
box-sizing: border-box;
flex: 0 0 50%;
background: transparent;
padding: 5px;
border: 0;
position: relative;

&:hover,
&:focus,
&:active {
svg path:first-child {
fill: lighten($ui-base-color, 16%);
}
}
}

svg {
width: 100%;
height: auto;

path:first-child {
fill: lighten($ui-base-color, 12%);
}

path:last-child {
fill: darken($ui-base-color, 14%);
}
}

&__active {
color: $ui-highlight-color;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: lighten($ui-base-color, 12%);
border-radius: 50%;
padding: 0.35rem;
}
}

::-webkit-scrollbar-thumb {
border-radius: 0;
}
@@ -1326,3 +1432,4 @@ noscript {
@import 'emoji_picker';
@import 'local_settings';
@import 'error_boundary';
@import 'single_column';

+ 2
- 2
app/javascript/flavours/glitch/styles/components/search.scss View File

@@ -12,7 +12,7 @@
.search__icon {
.fa {
position: absolute;
top: 10px;
top: 16px;
right: 10px;
z-index: 2;
display: inline-block;
@@ -42,7 +42,7 @@
}

.fa-times-circle {
top: 11px;
top: 17px;
transform: rotate(0deg);
cursor: pointer;


+ 232
- 0
app/javascript/flavours/glitch/styles/components/single_column.scss View File

@@ -0,0 +1,232 @@
.compose-panel {
width: 285px;
margin-top: 10px;
display: flex;
flex-direction: column;
height: calc(100% - 10px);
overflow-y: hidden;

.drawer--search input {
line-height: 18px;
font-size: 16px;
padding: 15px;
padding-right: 30px;
}

.search__icon .fa {
top: 15px;
}

.drawer--account {
flex: 0 1 48px;
}

.flex-spacer {
background: transparent;
}

.composer {
flex: 1;
overflow-y: hidden;
display: flex;
flex-direction: column;
min-height: 310px;
}

.compose-form__autosuggest-wrapper {
overflow-y: auto;
background-color: $white;
border-radius: 4px 4px 0 0;
flex: 0 1 auto;
}

.autosuggest-textarea__textarea {
overflow-y: hidden;
}

.compose-form__upload-thumbnail {
height: 80px;
}
}

.navigation-panel {
margin-top: 10px;
margin-bottom: 10px;
height: calc(100% - 20px);
overflow-y: auto;

hr {
border: 0;
background: transparent;
border-top: 1px solid lighten($ui-base-color, 4%);
margin: 10px 0;
}
}

@media screen and (min-width: 600px) {
.tabs-bar__link {
span {
display: inline;
}
}
}

.columns-area--mobile {
flex-direction: column;
width: 100%;
margin: 0 auto;

.column,
.drawer {
width: 100%;
height: 100%;
padding: 0;
}

.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;
}

@media screen and (min-width: 630px) {
.detailed-status {
padding: 15px;

.media-gallery,
.video-player {
margin-top: 15px;
}
}

.account__header__bar {
padding: 5px 10px;
}

.navigation-bar,
.compose-form {
padding: 15px;
}

.compose-form .compose-form__publish .compose-form__publish-button-wrapper {
padding-top: 15px;
}

.status {
padding: 15px;
min-height: 48px + 2px;

.media-gallery,
&__action-bar,
.video-player {
margin-top: 10px;
}
}

.account {
padding: 15px 10px;

&__header__bio {
margin: 0 -10px;
}
}

.notification {
&__message {
padding-top: 15px;
}

.status {
padding-top: 8px;
}

.account {
padding-top: 8px;
}
}
}
}

.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: 360px) {
.tabs-bar {
margin: 10px auto;
margin-bottom: 0;
width: 100%;
}

.react-swipeable-view-container .columns-area--mobile {
height: calc(100% - 20px) !important;
}

.getting-started__wrapper,
.getting-started__trends,
.search {
margin-bottom: 10px;
}
}

@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;
}
}

+ 1
- 1
app/javascript/flavours/glitch/styles/mastodon-light/diff.scss View File

@@ -125,7 +125,7 @@
// Change the default color of several parts of the compose form
.composer {

.composer--spoiler input, .composer--textarea textarea {
.composer--spoiler input, .compose-form__autosuggest-wrapper textarea {
color: lighten($ui-base-color, 80%);

&:disabled { background: lighten($simple-background-color, 10%) }

+ 4
- 0
app/javascript/flavours/glitch/util/async-components.js View File

@@ -149,3 +149,7 @@ export function GettingStartedMisc () {
export function ListAdder () {
return import(/* webpackChunkName: "features/glitch/async/list_adder" */'flavours/glitch/features/list_adder');
}

export function Search () {
return import(/*webpackChunkName: "features/glitch/async/search" */'flavours/glitch/features/search');
}

+ 1
- 0
app/javascript/flavours/glitch/util/initial_state.js View File

@@ -28,5 +28,6 @@ export const version = getMeta('version');
export const mascot = getMeta('mascot');
export const isStaff = getMeta('is_staff');
export const defaultContentType = getMeta('default_content_type');
export const forceSingleColumn = getMeta('advanced_layout') === false;

export default initialState;

+ 2
- 1
app/javascript/flavours/glitch/util/is_mobile.js View File

@@ -1,4 +1,5 @@
import detectPassiveEvents from 'detect-passive-events';
import { forceSingleColumn } from 'flavours/glitch/util/initial_state';

const LAYOUT_BREAKPOINT = 630;

@@ -9,7 +10,7 @@ export function isMobile(width, columns) {
case 'single':
return true;
default:
return width <= LAYOUT_BREAKPOINT;
return forceSingleColumn || width <= LAYOUT_BREAKPOINT;
}
};


+ 6
- 2
app/javascript/mastodon/components/status_content.js View File

@@ -107,8 +107,12 @@ export default class StatusContent extends React.PureComponent {
const [ startX, startY ] = this.startXY;
const [ deltaX, deltaY ] = [Math.abs(e.clientX - startX), Math.abs(e.clientY - startY)];

if (e.target.localName === 'button' || e.target.localName === 'a' || (e.target.parentNode && (e.target.parentNode.localName === 'button' || e.target.parentNode.localName === 'a'))) {
return;
let element = e.target;
while (element) {
if (element.localName === 'button' || element.localName === 'a' || element.localName === 'label') {
return;
}
element = element.parentNode;
}

if (deltaX + deltaY < 5 && e.button === 0 && this.props.onClick) {

+ 1
- 4
app/javascript/mastodon/features/compose/components/compose_form.js View File

@@ -211,10 +211,6 @@ class ComposeForm extends ImmutablePureComponent {
/>
</div>

<div className={`emoji-picker-wrapper ${this.props.showSearch ? 'emoji-picker-wrapper--hidden' : ''}`}>
<EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />
</div>

<AutosuggestTextarea
ref={this.setAutosuggestTextarea}
placeholder={intl.formatMessage(messages.placeholder)}
@@ -230,6 +226,7 @@ class ComposeForm extends ImmutablePureComponent {
onPaste={onPaste}
autoFocus={!showSearch && !isMobile(window.innerWidth)}
>
<EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />
<div className='compose-form__modifiers'>
<UploadFormContainer />
<PollFormContainer />

+ 8
- 0
app/javascript/mastodon/features/compose/components/reply_indicator.js View File

@@ -7,6 +7,7 @@ import DisplayName from '../../../components/display_name';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { isRtl } from '../../../rtl';
import AttachmentList from 'mastodon/components/attachment_list';

const messages = defineMessages({
cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' },
@@ -60,6 +61,13 @@ class ReplyIndicator extends ImmutablePureComponent {
</div>

<div className='reply-indicator__content' style={style} dangerouslySetInnerHTML={content} />

{status.get('media_attachments').size > 0 && (
<AttachmentList
compact
media={status.get('media_attachments')}
/>
)}
</div>
);
}

+ 8
- 0
app/javascript/mastodon/features/ui/components/boost_modal.js View File

@@ -9,6 +9,7 @@ import RelativeTimestamp from '../../../components/relative_timestamp';
import DisplayName from '../../../components/display_name';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Icon from 'mastodon/components/icon';
import AttachmentList from 'mastodon/components/attachment_list';

const messages = defineMessages({
cancel_reblog: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
@@ -73,6 +74,13 @@ class BoostModal extends ImmutablePureComponent {
</div>

<StatusContent status={status} />

{status.get('media_attachments').size > 0 && (
<AttachmentList
compact
media={status.get('media_attachments')}
/>
)}
</div>
</div>


+ 2
- 1
app/javascript/mastodon/features/ui/components/navigation_panel.js View File

@@ -2,6 +2,7 @@ import React from 'react';
import { NavLink, withRouter } from 'react-router-dom';
import { FormattedMessage } from 'react-intl';
import Icon from 'mastodon/components/icon';
import { profile_directory } from 'mastodon/initial_state';
import NotificationsCounterIcon from './notifications_counter_icon';
import FollowRequestsNavLink from './follow_requests_nav_link';
import ListPanel from './list_panel';
@@ -23,7 +24,7 @@ const NavigationPanel = () => (

<a className='column-link column-link--transparent' href='/settings/preferences'><Icon className='column-link__icon' id='cog' fixedWidth /><FormattedMessage id='navigation_bar.preferences' defaultMessage='Preferences' /></a>
<a className='column-link column-link--transparent' href='/relationships'><Icon className='column-link__icon' id='users' fixedWidth /><FormattedMessage id='navigation_bar.follows_and_followers' defaultMessage='Follows and followers' /></a>
<a className='column-link column-link--transparent' href='/explore'><Icon className='column-link__icon' id='address-book-o' fixedWidth /><FormattedMessage id='navigation_bar.profile_directory' defaultMessage='Profile directory' /></a>
{!!profile_directo