From fbec0edf08ce686e1b4c8332fad4481986e2dad5 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Tue, 16 Apr 2019 18:45:04 +0200 Subject: Fix opening/closing gifv sometimes making the timeline scroll --- app/javascript/flavours/glitch/components/media_gallery.js | 6 ------ app/javascript/flavours/glitch/components/modal_root.js | 2 +- app/javascript/flavours/glitch/styles/components/media.scss | 1 + 3 files changed, 2 insertions(+), 7 deletions(-) (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/components/media_gallery.js b/app/javascript/flavours/glitch/components/media_gallery.js index 6be2b4700..b7360bae4 100644 --- a/app/javascript/flavours/glitch/components/media_gallery.js +++ b/app/javascript/flavours/glitch/components/media_gallery.js @@ -82,11 +82,6 @@ class Item extends React.PureComponent { e.stopPropagation(); } - handleMouseDown = (e) => { - e.preventDefault(); - e.stopPropagation(); - } - render () { const { attachment, index, size, standalone, letterbox, displayWidth } = this.props; @@ -190,7 +185,6 @@ class Item extends React.PureComponent { onClick={this.handleClick} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} - onMouseDown={this.handleMouseDown} autoPlay={autoPlay} loop muted diff --git a/app/javascript/flavours/glitch/components/modal_root.js b/app/javascript/flavours/glitch/components/modal_root.js index 7a90e6b8a..4e8648b49 100644 --- a/app/javascript/flavours/glitch/components/modal_root.js +++ b/app/javascript/flavours/glitch/components/modal_root.js @@ -40,7 +40,7 @@ export default class ModalRoot extends React.PureComponent { this.setState({ revealed: false }); } if (!nextProps.children && !!this.props.children) { - this.activeElement.focus(); + this.activeElement.focus({ preventScroll: true }); this.activeElement = null; } } diff --git a/app/javascript/flavours/glitch/styles/components/media.scss b/app/javascript/flavours/glitch/styles/components/media.scss index e8011bde9..fabef2a56 100644 --- a/app/javascript/flavours/glitch/styles/components/media.scss +++ b/app/javascript/flavours/glitch/styles/components/media.scss @@ -147,6 +147,7 @@ position: relative; z-index: 1; object-fit: contain; + user-select: none; &:not(.letterbox) { height: 100%; -- cgit From 8d57c0e70ea76b2f482c0919fc815d40352ef477 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Mon, 15 Apr 2019 20:40:05 +0200 Subject: When selecting a toot via keyboard, ensure it is scrolled into view --- app/javascript/flavours/glitch/components/status_list.js | 14 ++++++++++---- .../flavours/glitch/features/notifications/index.js | 14 ++++++++++---- app/javascript/flavours/glitch/features/ui/index.js | 9 +++++++-- 3 files changed, 27 insertions(+), 10 deletions(-) (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/components/status_list.js b/app/javascript/flavours/glitch/components/status_list.js index a7629bd54..c1f51b307 100644 --- a/app/javascript/flavours/glitch/components/status_list.js +++ b/app/javascript/flavours/glitch/components/status_list.js @@ -46,22 +46,28 @@ export default class StatusList extends ImmutablePureComponent { handleMoveUp = (id, featured) => { const elementIndex = this.getCurrentStatusIndex(id, featured) - 1; - this._selectChild(elementIndex); + this._selectChild(elementIndex, true); } handleMoveDown = (id, featured) => { const elementIndex = this.getCurrentStatusIndex(id, featured) + 1; - this._selectChild(elementIndex); + this._selectChild(elementIndex, false); } handleLoadOlder = debounce(() => { this.props.onLoadMore(this.props.statusIds.size > 0 ? this.props.statusIds.last() : undefined); }, 300, { leading: true }) - _selectChild (index) { - const element = this.node.node.querySelector(`article:nth-of-type(${index + 1}) .focusable`); + _selectChild (index, align_top) { + const container = this.node.node; + const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`); if (element) { + if (align_top && container.scrollTop > element.offsetTop) { + element.scrollIntoView(true); + } else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) { + element.scrollIntoView(false); + } element.focus(); } } diff --git a/app/javascript/flavours/glitch/features/notifications/index.js b/app/javascript/flavours/glitch/features/notifications/index.js index 6a149927c..f2a1ccc3b 100644 --- a/app/javascript/flavours/glitch/features/notifications/index.js +++ b/app/javascript/flavours/glitch/features/notifications/index.js @@ -133,18 +133,24 @@ export default class Notifications extends React.PureComponent { handleMoveUp = id => { const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) - 1; - this._selectChild(elementIndex); + this._selectChild(elementIndex, true); } handleMoveDown = id => { const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) + 1; - this._selectChild(elementIndex); + this._selectChild(elementIndex, false); } - _selectChild (index) { - const element = this.column.node.querySelector(`article:nth-of-type(${index + 1}) .focusable`); + _selectChild (index, align_top) { + const container = this.column.node; + const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`); if (element) { + if (align_top && container.scrollTop > element.offsetTop) { + element.scrollIntoView(true); + } else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) { + element.scrollIntoView(false); + } element.focus(); } } diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js index a19b3abf1..348125c97 100644 --- a/app/javascript/flavours/glitch/features/ui/index.js +++ b/app/javascript/flavours/glitch/features/ui/index.js @@ -341,11 +341,16 @@ export default class UI extends React.Component { handleHotkeyFocusColumn = e => { const index = (e.key * 1) + 1; // First child is drawer, skip that const column = this.node.querySelector(`.column:nth-child(${index})`); + if (!column) return; + const container = column.querySelector('.scrollable'); - if (column) { - const status = column.querySelector('.focusable'); + if (container) { + const status = container.querySelector('.focusable'); if (status) { + if (container.scrollTop > status.offsetTop) { + status.scrollIntoView(true); + } status.focus(); } } -- cgit From e3c1472040105651fe55158b741c7ba92c1a7332 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Mon, 15 Apr 2019 22:23:05 +0200 Subject: Shift+click on column Back button to return to last pinable column --- .../flavours/glitch/components/column_back_button.js | 9 +++++++-- .../glitch/components/column_back_button_slim.js | 9 +++++++-- .../flavours/glitch/components/column_header.js | 13 +++++++++---- app/javascript/flavours/glitch/components/permalink.js | 4 +++- app/javascript/flavours/glitch/components/status.js | 18 ++++++++++++++---- .../flavours/glitch/components/status_action_bar.js | 4 +++- .../features/account_timeline/components/moved_note.js | 4 +++- .../features/status/components/detailed_status.js | 8 ++++++-- .../flavours/glitch/features/status/index.js | 4 +++- .../glitch/features/ui/components/boost_modal.js | 4 +++- .../glitch/features/ui/components/favourite_modal.js | 4 +++- 11 files changed, 61 insertions(+), 20 deletions(-) (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/components/column_back_button.js b/app/javascript/flavours/glitch/components/column_back_button.js index a562ef9b9..82556d22e 100644 --- a/app/javascript/flavours/glitch/components/column_back_button.js +++ b/app/javascript/flavours/glitch/components/column_back_button.js @@ -8,10 +8,15 @@ export default class ColumnBackButton extends React.PureComponent { router: PropTypes.object, }; - handleClick = () => { + handleClick = (event) => { // if history is exhausted, or we would leave mastodon, just go to root. if (window.history.state) { - this.context.router.history.goBack(); + const state = this.context.router.history.location.state; + if (event.shiftKey && state && state.mastodonBackSteps) { + this.context.router.history.go(-state.mastodonBackSteps); + } else { + this.context.router.history.goBack(); + } } else { this.context.router.history.push('/'); } diff --git a/app/javascript/flavours/glitch/components/column_back_button_slim.js b/app/javascript/flavours/glitch/components/column_back_button_slim.js index c99c202af..38afd3df3 100644 --- a/app/javascript/flavours/glitch/components/column_back_button_slim.js +++ b/app/javascript/flavours/glitch/components/column_back_button_slim.js @@ -8,10 +8,15 @@ export default class ColumnBackButtonSlim extends React.PureComponent { router: PropTypes.object, }; - handleClick = () => { + handleClick = (event) => { // if history is exhausted, or we would leave mastodon, just go to root. if (window.history.state) { - this.context.router.history.goBack(); + const state = this.context.router.history.location.state; + if (event.shiftKey && state && state.mastodonBackSteps) { + this.context.router.history.go(-state.mastodonBackSteps); + } else { + this.context.router.history.goBack(); + } } else { this.context.router.history.push('/'); } diff --git a/app/javascript/flavours/glitch/components/column_header.js b/app/javascript/flavours/glitch/components/column_header.js index 87e848a59..a0ff09986 100644 --- a/app/javascript/flavours/glitch/components/column_header.js +++ b/app/javascript/flavours/glitch/components/column_header.js @@ -47,10 +47,15 @@ export default class ColumnHeader extends React.PureComponent { animatingNCD: false, }; - historyBack = () => { + historyBack = (skip) => { // if history is exhausted, or we would leave mastodon, just go to root. if (window.history.state) { - this.context.router.history.goBack(); + const state = this.context.router.history.location.state; + if (skip && state && state.mastodonBackSteps) { + this.context.router.history.go(-state.mastodonBackSteps); + } else { + this.context.router.history.goBack(); + } } else { this.context.router.history.push('/'); } @@ -73,8 +78,8 @@ export default class ColumnHeader extends React.PureComponent { this.props.onMove(1); } - handleBackClick = () => { - this.historyBack(); + handleBackClick = (event) => { + this.historyBack(event.shiftKey); } handleTransitionEnd = () => { diff --git a/app/javascript/flavours/glitch/components/permalink.js b/app/javascript/flavours/glitch/components/permalink.js index 1ea6a2915..718b02115 100644 --- a/app/javascript/flavours/glitch/components/permalink.js +++ b/app/javascript/flavours/glitch/components/permalink.js @@ -24,7 +24,9 @@ export default class Permalink extends React.PureComponent { if (this.context.router) { e.preventDefault(); - this.context.router.history.push(this.props.to); + let state = {...this.context.router.history.location.state}; + state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; + this.context.router.history.push(this.props.to, state); } } } diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js index c8bf75f79..cd22fb593 100644 --- a/app/javascript/flavours/glitch/components/status.js +++ b/app/javascript/flavours/glitch/components/status.js @@ -295,7 +295,11 @@ export default class Status extends ImmutablePureComponent { else if (e.shiftKey) { this.setCollapsed(true); document.getSelection().removeAllRanges(); - } else router.history.push(destination); + } else { + let state = {...router.history.location.state}; + state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; + router.history.push(destination, state); + } e.preventDefault(); } } @@ -304,7 +308,9 @@ export default class Status extends ImmutablePureComponent { if (this.context.router && e.button === 0) { const id = e.currentTarget.getAttribute('data-id'); e.preventDefault(); - this.context.router.history.push(`/accounts/${id}`); + let state = {...this.context.router.history.location.state}; + state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; + this.context.router.history.push(`/accounts/${id}`, state); } } @@ -337,11 +343,15 @@ export default class Status extends ImmutablePureComponent { } handleHotkeyOpen = () => { - this.context.router.history.push(`/statuses/${this.props.status.get('id')}`); + let state = {...this.context.router.history.location.state}; + state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; + this.context.router.history.push(`/statuses/${this.props.status.get('id')}`, state); } handleHotkeyOpenProfile = () => { - this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); + let state = {...this.context.router.history.location.state}; + state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; + this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`, state); } handleHotkeyMoveUp = e => { diff --git a/app/javascript/flavours/glitch/components/status_action_bar.js b/app/javascript/flavours/glitch/components/status_action_bar.js index e0cc652d2..6d1f54c60 100644 --- a/app/javascript/flavours/glitch/components/status_action_bar.js +++ b/app/javascript/flavours/glitch/components/status_action_bar.js @@ -150,7 +150,9 @@ export default class StatusActionBar extends ImmutablePureComponent { } handleOpen = () => { - this.context.router.history.push(`/statuses/${this.props.status.get('id')}`); + let state = {...this.context.router.history.location.state}; + state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; + this.context.router.history.push(`/statuses/${this.props.status.get('id')}`, state); } handleEmbed = () => { diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.js b/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.js index 280389bba..1fab083db 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.js +++ b/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.js @@ -20,7 +20,9 @@ export default class MovedNote extends ImmutablePureComponent { handleAccountClick = e => { if (e.button === 0) { e.preventDefault(); - this.context.router.history.push(`/accounts/${this.props.to.get('id')}`); + let state = {...this.context.router.history.location.state}; + state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; + this.context.router.history.push(`/accounts/${this.props.to.get('id')}`, state); } e.stopPropagation(); diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.js b/app/javascript/flavours/glitch/features/status/components/detailed_status.js index e9130b1b0..69b646427 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.js @@ -42,7 +42,9 @@ export default class DetailedStatus extends ImmutablePureComponent { handleAccountClick = (e) => { if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey) && this.context.router) { e.preventDefault(); - this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); + let state = {...this.context.router.history.location.state}; + state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; + this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`, state); } e.stopPropagation(); @@ -51,7 +53,9 @@ export default class DetailedStatus extends ImmutablePureComponent { parseClick = (e, destination) => { if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey) && this.context.router) { e.preventDefault(); - this.context.router.history.push(destination); + let state = {...this.context.router.history.location.state}; + state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; + this.context.router.history.push(destination, state); } e.stopPropagation(); diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js index 7f8f02188..a0a9b986c 100644 --- a/app/javascript/flavours/glitch/features/status/index.js +++ b/app/javascript/flavours/glitch/features/status/index.js @@ -331,7 +331,9 @@ export default class Status extends ImmutablePureComponent { } handleHotkeyOpenProfile = () => { - this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); + let state = {...this.context.router.history.location.state}; + state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; + this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`, state); } handleMoveUp = id => { diff --git a/app/javascript/flavours/glitch/features/ui/components/boost_modal.js b/app/javascript/flavours/glitch/features/ui/components/boost_modal.js index 9652bcb2d..0a914dce2 100644 --- a/app/javascript/flavours/glitch/features/ui/components/boost_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/boost_modal.js @@ -40,7 +40,9 @@ export default class BoostModal extends ImmutablePureComponent { if (e.button === 0) { e.preventDefault(); this.props.onClose(); - this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); + let state = {...this.context.router.history.location.state}; + state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; + this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`, state); } } diff --git a/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js b/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js index 70722411d..e0037a15f 100644 --- a/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js @@ -40,7 +40,9 @@ export default class FavouriteModal extends ImmutablePureComponent { if (e.button === 0) { e.preventDefault(); this.props.onClose(); - this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); + let state = {...this.context.router.history.location.state}; + state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1; + this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`, state); } } -- cgit From 8b7b65e995849550957ecfda7fc1bf5fa9c8fcdf Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Fri, 19 Apr 2019 10:48:54 +0200 Subject: [Glitch] Allow modal secondary button to shrink and allow wider confirmation modals Port 7f75792bf305eea297c63731600af3b02968aea4 to glitch-soc --- app/javascript/flavours/glitch/styles/components/modal.scss | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/styles/components/modal.scss b/app/javascript/flavours/glitch/styles/components/modal.scss index fece8593b..05af34680 100644 --- a/app/javascript/flavours/glitch/styles/components/modal.scss +++ b/app/javascript/flavours/glitch/styles/components/modal.scss @@ -488,14 +488,6 @@ font-size: 14px; } -.confirmation-modal { - max-width: 85vw; - - @media screen and (min-width: 480px) { - max-width: 380px; - } -} - .mute-modal { line-height: 24px; } @@ -685,6 +677,10 @@ color: darken($lighter-text-color, 4%); } } + + .confirmation-modal__secondary-button { + flex-shrink: 1; + } } .confirmation-modal__do_not_ask_again { -- cgit From eed2c9dd44d7f58bbb54554c2271c465b67b827c Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Fri, 19 Apr 2019 20:14:32 +0200 Subject: Rename flavours/glitch/features/drawer to flavours/glitch/features/compose --- .../glitch/features/compose/account/index.js | 76 +++++++++ .../glitch/features/compose/header/index.js | 127 +++++++++++++++ .../flavours/glitch/features/compose/index.js | 166 +++++++++++++++++++ .../glitch/features/compose/results/index.js | 117 ++++++++++++++ .../glitch/features/compose/search/index.js | 152 ++++++++++++++++++ .../glitch/features/compose/search/popout/index.js | 109 +++++++++++++ .../glitch/features/drawer/account/index.js | 76 --------- .../glitch/features/drawer/header/index.js | 127 --------------- .../flavours/glitch/features/drawer/index.js | 175 --------------------- .../glitch/features/drawer/results/index.js | 117 -------------- .../glitch/features/drawer/search/index.js | 152 ------------------ .../glitch/features/drawer/search/popout/index.js | 109 ------------- .../glitch/features/ui/components/columns_area.js | 4 +- .../features/ui/components/onboarding_modal.js | 4 +- .../flavours/glitch/features/ui/index.js | 6 +- app/javascript/flavours/glitch/theme.yml | 2 +- .../flavours/glitch/util/async-components.js | 4 +- 17 files changed, 757 insertions(+), 766 deletions(-) create mode 100644 app/javascript/flavours/glitch/features/compose/account/index.js create mode 100644 app/javascript/flavours/glitch/features/compose/header/index.js create mode 100644 app/javascript/flavours/glitch/features/compose/index.js create mode 100644 app/javascript/flavours/glitch/features/compose/results/index.js create mode 100644 app/javascript/flavours/glitch/features/compose/search/index.js create mode 100644 app/javascript/flavours/glitch/features/compose/search/popout/index.js delete mode 100644 app/javascript/flavours/glitch/features/drawer/account/index.js delete mode 100644 app/javascript/flavours/glitch/features/drawer/header/index.js delete mode 100644 app/javascript/flavours/glitch/features/drawer/index.js delete mode 100644 app/javascript/flavours/glitch/features/drawer/results/index.js delete mode 100644 app/javascript/flavours/glitch/features/drawer/search/index.js delete mode 100644 app/javascript/flavours/glitch/features/drawer/search/popout/index.js (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/features/compose/account/index.js b/app/javascript/flavours/glitch/features/compose/account/index.js new file mode 100644 index 000000000..552848641 --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/account/index.js @@ -0,0 +1,76 @@ +// Package imports. +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { + FormattedMessage, + defineMessages, +} from 'react-intl'; + +// Components. +import Avatar from 'flavours/glitch/components/avatar'; +import Permalink from 'flavours/glitch/components/permalink'; + +// Utils. +import { hiddenComponent } from 'flavours/glitch/util/react_helpers'; +import { profileLink } from 'flavours/glitch/util/backend_links'; + +// Messages. +const messages = defineMessages({ + edit: { + defaultMessage: 'Edit profile', + id: 'navigation_bar.edit_profile', + }, +}); + +// The component. +export default function DrawerAccount ({ account }) { + + // We need an account to render. + if (!account) { + return ( +
+ { profileLink !== undefined && ( + + + + )} +
+ ); + } + + // The result. + return ( +
+ + {account.get('acct')} + + + + @{account.get('acct')} + + { profileLink !== undefined && ( + + )} +
+ ); +} + +// Props. +DrawerAccount.propTypes = { account: ImmutablePropTypes.map }; diff --git a/app/javascript/flavours/glitch/features/compose/header/index.js b/app/javascript/flavours/glitch/features/compose/header/index.js new file mode 100644 index 000000000..da5599732 --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/header/index.js @@ -0,0 +1,127 @@ +// Package imports. +import PropTypes from 'prop-types'; +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { defineMessages } from 'react-intl'; +import { Link } from 'react-router-dom'; + +// Components. +import Icon from 'flavours/glitch/components/icon'; + +// Utils. +import { conditionalRender } from 'flavours/glitch/util/react_helpers'; +import { signOutLink } from 'flavours/glitch/util/backend_links'; + +// Messages. +const messages = defineMessages({ + community: { + defaultMessage: 'Local timeline', + id: 'navigation_bar.community_timeline', + }, + home_timeline: { + defaultMessage: 'Home', + id: 'tabs_bar.home', + }, + logout: { + defaultMessage: 'Logout', + id: 'navigation_bar.logout', + }, + notifications: { + defaultMessage: 'Notifications', + id: 'tabs_bar.notifications', + }, + public: { + defaultMessage: 'Federated timeline', + id: 'navigation_bar.public_timeline', + }, + settings: { + defaultMessage: 'App settings', + id: 'navigation_bar.app_settings', + }, + start: { + defaultMessage: 'Getting started', + id: 'getting_started.heading', + }, +}); + +// The component. +export default function DrawerHeader ({ + columns, + unreadNotifications, + showNotificationsBadge, + intl, + onSettingsClick, +}) { + + // Only renders the component if the column isn't being shown. + const renderForColumn = conditionalRender.bind(null, + columnId => !columns || !columns.some( + column => column.get('id') === columnId + ) + ); + + // The result. + return ( + + ); +} + +// Props. +DrawerHeader.propTypes = { + columns: ImmutablePropTypes.list, + unreadNotifications: PropTypes.number, + showNotificationsBadge: PropTypes.bool, + intl: PropTypes.object, + onSettingsClick: PropTypes.func, +}; diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js new file mode 100644 index 000000000..cb261f9d6 --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/index.js @@ -0,0 +1,166 @@ +// Package imports. +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { injectIntl, defineMessages } from 'react-intl'; +import classNames from 'classnames'; + +// Actions. +import { openModal } from 'flavours/glitch/actions/modal'; +import { + changeSearch, + clearSearch, + showSearch, + submitSearch, +} from 'flavours/glitch/actions/search'; +import { cycleElefriendCompose } from 'flavours/glitch/actions/compose'; + +// Components. +import Composer from 'flavours/glitch/features/composer'; +import DrawerAccount from './account'; +import DrawerHeader from './header'; +import DrawerResults from './results'; +import DrawerSearch from './search'; + +// Utils. +import { me, mascot } from 'flavours/glitch/util/initial_state'; +import { wrap } from 'flavours/glitch/util/redux_helpers'; + +// Messages. +const messages = defineMessages({ + compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new toot' }, +}); + +// State mapping. +const mapStateToProps = state => ({ + account: state.getIn(['accounts', me]), + columns: state.getIn(['settings', 'columns']), + elefriend: state.getIn(['compose', 'elefriend']), + results: state.getIn(['search', 'results']), + searchHidden: state.getIn(['search', 'hidden']), + searchValue: state.getIn(['search', 'value']), + submitted: state.getIn(['search', 'submitted']), + unreadNotifications: state.getIn(['notifications', 'unread']), + showNotificationsBadge: state.getIn(['local_settings', 'notifications', 'tab_badge']), +}); + +// Dispatch mapping. +const mapDispatchToProps = (dispatch, { intl }) => ({ + onChange (value) { + dispatch(changeSearch(value)); + }, + onClear () { + dispatch(clearSearch()); + }, + onClickElefriend () { + dispatch(cycleElefriendCompose()); + }, + onShow () { + dispatch(showSearch()); + }, + onSubmit () { + dispatch(submitSearch()); + }, + onOpenSettings (e) { + e.preventDefault(); + e.stopPropagation(); + dispatch(openModal('SETTINGS', {})); + }, +}); + +// The component. +export default @connect(mapStateToProps, mapDispatchToProps) +@injectIntl +class Compose extends React.PureComponent { + static propTypes = { + intl: PropTypes.object.isRequired, + isSearchPage: PropTypes.bool, + multiColumn: PropTypes.bool, + + // State props. + account: ImmutablePropTypes.map, + columns: ImmutablePropTypes.list, + results: ImmutablePropTypes.map, + elefriend: PropTypes.number, + searchHidden: PropTypes.bool, + searchValue: PropTypes.string, + submitted: PropTypes.bool, + unreadNotifications: PropTypes.number, + showNotificationsBadge: PropTypes.bool, + + // Dispatch props. + onChange: PropTypes.func, + onClear: PropTypes.func, + onClickElefriend: PropTypes.func, + onShow: PropTypes.func, + onSubmit: PropTypes.func, + onOpenSettings: PropTypes.func, + }; + + // Rendering. + render () { + const { + account, + columns, + elefriend, + intl, + multiColumn, + onChange, + onClear, + onClickElefriend, + onOpenSettings, + onShow, + onSubmit, + results, + searchHidden, + searchValue, + submitted, + isSearchPage, + unreadNotifications, + showNotificationsBadge, + } = this.props; + const computedClass = classNames('drawer', `mbstobon-${elefriend}`); + + // The result. + return ( +
+ {multiColumn && ( + + )} + {(multiColumn || isSearchPage) && } +
+ {!isSearchPage &&
+ + + {multiColumn && ( +
+ {mascot ? :
+ )} +
} + + {(multiColumn || isSearchPage) && + } +
+
+ ); + } +} diff --git a/app/javascript/flavours/glitch/features/compose/results/index.js b/app/javascript/flavours/glitch/features/compose/results/index.js new file mode 100644 index 000000000..4574c0e1e --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/results/index.js @@ -0,0 +1,117 @@ +// Package imports. +import PropTypes from 'prop-types'; +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { + FormattedMessage, + defineMessages, +} from 'react-intl'; +import spring from 'react-motion/lib/spring'; +import { Link } from 'react-router-dom'; + +// Components. +import Icon from 'flavours/glitch/components/icon'; +import AccountContainer from 'flavours/glitch/containers/account_container'; +import StatusContainer from 'flavours/glitch/containers/status_container'; +import Hashtag from 'flavours/glitch/components/hashtag'; + +// Utils. +import Motion from 'flavours/glitch/util/optional_motion'; + +// Messages. +const messages = defineMessages({ + total: { + defaultMessage: '{count, number} {count, plural, one {result} other {results}}', + id: 'search_results.total', + }, +}); + +// The component. +export default function DrawerResults ({ + results, + visible, +}) { + const accounts = results ? results.get('accounts') : null; + const statuses = results ? results.get('statuses') : null; + const hashtags = results ? results.get('hashtags') : null; + + // This gets the total number of items. + const count = [accounts, statuses, hashtags].reduce(function (size, item) { + if (item && item.size) { + return size + item.size; + } + return size; + }, 0); + + // The result. + return ( + + {({ x }) => ( +
+
+ + +
+ {accounts && accounts.size ? ( +
+
+ + {accounts.map( + accountId => ( + + ) + )} +
+ ) : null} + {statuses && statuses.size ? ( +
+
+ + {statuses.map( + statusId => ( + + ) + )} +
+ ) : null} + {hashtags && hashtags.size ? ( +
+
+ + {hashtags.map(hashtag => )} +
+ ) : null} +
+ )} +
+ ); +} + +// Props. +DrawerResults.propTypes = { + results: ImmutablePropTypes.map, + visible: PropTypes.bool, +}; diff --git a/app/javascript/flavours/glitch/features/compose/search/index.js b/app/javascript/flavours/glitch/features/compose/search/index.js new file mode 100644 index 000000000..8cbb0906c --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/search/index.js @@ -0,0 +1,152 @@ +// Package imports. +import classNames from 'classnames'; +import PropTypes from 'prop-types'; +import React from 'react'; +import { + FormattedMessage, + defineMessages, +} from 'react-intl'; +import Overlay from 'react-overlays/lib/Overlay'; + +// Components. +import Icon from 'flavours/glitch/components/icon'; +import DrawerSearchPopout from './popout'; + +// Utils. +import { focusRoot } from 'flavours/glitch/util/dom_helpers'; +import { + assignHandlers, + hiddenComponent, +} from 'flavours/glitch/util/react_helpers'; + +// Messages. +const messages = defineMessages({ + placeholder: { + defaultMessage: 'Search', + id: 'search.placeholder', + }, +}); + +// Handlers. +const handlers = { + + handleBlur () { + this.setState({ expanded: false }); + }, + + handleChange ({ target: { value } }) { + const { onChange } = this.props; + if (onChange) { + onChange(value); + } + }, + + handleClear (e) { + const { + onClear, + submitted, + value, + } = this.props; + e.preventDefault(); // Prevents focus change ?? + if (onClear && (submitted || value && value.length)) { + onClear(); + } + }, + + handleFocus () { + const { onShow } = this.props; + this.setState({ expanded: true }); + if (onShow) { + onShow(); + } + }, + + handleKeyUp (e) { + const { onSubmit } = this.props; + switch (e.key) { + case 'Enter': + if (onSubmit) { + onSubmit(); + } + break; + case 'Escape': + focusRoot(); + } + }, +}; + +// The component. +export default class DrawerSearch extends React.PureComponent { + + // Constructor. + constructor (props) { + super(props); + assignHandlers(this, handlers); + this.state = { expanded: false }; + } + + // Rendering. + render () { + const { + handleBlur, + handleChange, + handleClear, + handleFocus, + handleKeyUp, + } = this.handlers; + const { + intl, + submitted, + value, + } = this.props; + const { expanded } = this.state; + const active = value && value.length || submitted; + const computedClass = classNames('drawer--search', { active }); + + return ( +
+ +
+ + +
+ +
+ ); + } + +} + +// Props. +DrawerSearch.propTypes = { + value: PropTypes.string, + submitted: PropTypes.bool, + onChange: PropTypes.func, + onSubmit: PropTypes.func, + onClear: PropTypes.func, + onShow: PropTypes.func, + intl: PropTypes.object, +}; diff --git a/app/javascript/flavours/glitch/features/compose/search/popout/index.js b/app/javascript/flavours/glitch/features/compose/search/popout/index.js new file mode 100644 index 000000000..fec090b64 --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/search/popout/index.js @@ -0,0 +1,109 @@ +// Package imports. +import PropTypes from 'prop-types'; +import React from 'react'; +import { + FormattedMessage, + defineMessages, +} from 'react-intl'; +import spring from 'react-motion/lib/spring'; + +// Utils. +import Motion from 'flavours/glitch/util/optional_motion'; +import { searchEnabled } from 'flavours/glitch/util/initial_state'; + +// Messages. +const messages = defineMessages({ + format: { + defaultMessage: 'Advanced search format', + id: 'search_popout.search_format', + }, + hashtag: { + defaultMessage: 'hashtag', + id: 'search_popout.tips.hashtag', + }, + status: { + defaultMessage: 'status', + id: 'search_popout.tips.status', + }, + text: { + defaultMessage: 'Simple text returns matching display names, usernames and hashtags', + id: 'search_popout.tips.text', + }, + 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.', + id: 'search_popout.tips.full_text', + }, + user: { + defaultMessage: 'user', + id: 'search_popout.tips.user', + }, +}); + +// The spring used by our motion. +const motionSpring = spring(1, { damping: 35, stiffness: 400 }); + +// The component. +export default function DrawerSearchPopout ({ style }) { + + // The result. + return ( +
+ + {({ opacity, scaleX, scaleY }) => ( +
+

+
    +
  • + #example + {' '} + +
  • +
  • + @username@domain + {' '} + +
  • +
  • + URL + {' '} + +
  • +
  • + URL + {' '} + +
  • +
+ { searchEnabled ? : } +
+ )} +
+
+ ); +} + +// Props. +DrawerSearchPopout.propTypes = { style: PropTypes.object }; diff --git a/app/javascript/flavours/glitch/features/drawer/account/index.js b/app/javascript/flavours/glitch/features/drawer/account/index.js deleted file mode 100644 index 552848641..000000000 --- a/app/javascript/flavours/glitch/features/drawer/account/index.js +++ /dev/null @@ -1,76 +0,0 @@ -// Package imports. -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { - FormattedMessage, - defineMessages, -} from 'react-intl'; - -// Components. -import Avatar from 'flavours/glitch/components/avatar'; -import Permalink from 'flavours/glitch/components/permalink'; - -// Utils. -import { hiddenComponent } from 'flavours/glitch/util/react_helpers'; -import { profileLink } from 'flavours/glitch/util/backend_links'; - -// Messages. -const messages = defineMessages({ - edit: { - defaultMessage: 'Edit profile', - id: 'navigation_bar.edit_profile', - }, -}); - -// The component. -export default function DrawerAccount ({ account }) { - - // We need an account to render. - if (!account) { - return ( -
- { profileLink !== undefined && ( - - - - )} -
- ); - } - - // The result. - return ( -
- - {account.get('acct')} - - - - @{account.get('acct')} - - { profileLink !== undefined && ( - - )} -
- ); -} - -// Props. -DrawerAccount.propTypes = { account: ImmutablePropTypes.map }; diff --git a/app/javascript/flavours/glitch/features/drawer/header/index.js b/app/javascript/flavours/glitch/features/drawer/header/index.js deleted file mode 100644 index da5599732..000000000 --- a/app/javascript/flavours/glitch/features/drawer/header/index.js +++ /dev/null @@ -1,127 +0,0 @@ -// Package imports. -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { defineMessages } from 'react-intl'; -import { Link } from 'react-router-dom'; - -// Components. -import Icon from 'flavours/glitch/components/icon'; - -// Utils. -import { conditionalRender } from 'flavours/glitch/util/react_helpers'; -import { signOutLink } from 'flavours/glitch/util/backend_links'; - -// Messages. -const messages = defineMessages({ - community: { - defaultMessage: 'Local timeline', - id: 'navigation_bar.community_timeline', - }, - home_timeline: { - defaultMessage: 'Home', - id: 'tabs_bar.home', - }, - logout: { - defaultMessage: 'Logout', - id: 'navigation_bar.logout', - }, - notifications: { - defaultMessage: 'Notifications', - id: 'tabs_bar.notifications', - }, - public: { - defaultMessage: 'Federated timeline', - id: 'navigation_bar.public_timeline', - }, - settings: { - defaultMessage: 'App settings', - id: 'navigation_bar.app_settings', - }, - start: { - defaultMessage: 'Getting started', - id: 'getting_started.heading', - }, -}); - -// The component. -export default function DrawerHeader ({ - columns, - unreadNotifications, - showNotificationsBadge, - intl, - onSettingsClick, -}) { - - // Only renders the component if the column isn't being shown. - const renderForColumn = conditionalRender.bind(null, - columnId => !columns || !columns.some( - column => column.get('id') === columnId - ) - ); - - // The result. - return ( - - ); -} - -// Props. -DrawerHeader.propTypes = { - columns: ImmutablePropTypes.list, - unreadNotifications: PropTypes.number, - showNotificationsBadge: PropTypes.bool, - intl: PropTypes.object, - onSettingsClick: PropTypes.func, -}; diff --git a/app/javascript/flavours/glitch/features/drawer/index.js b/app/javascript/flavours/glitch/features/drawer/index.js deleted file mode 100644 index c8121b8e5..000000000 --- a/app/javascript/flavours/glitch/features/drawer/index.js +++ /dev/null @@ -1,175 +0,0 @@ -// Package imports. -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { defineMessages } from 'react-intl'; -import classNames from 'classnames'; - -// Actions. -import { openModal } from 'flavours/glitch/actions/modal'; -import { - changeSearch, - clearSearch, - showSearch, - submitSearch, -} from 'flavours/glitch/actions/search'; -import { cycleElefriendCompose } from 'flavours/glitch/actions/compose'; - -// Components. -import Composer from 'flavours/glitch/features/composer'; -import DrawerAccount from './account'; -import DrawerHeader from './header'; -import DrawerResults from './results'; -import DrawerSearch from './search'; - -// Utils. -import { me, mascot } from 'flavours/glitch/util/initial_state'; -import { wrap } from 'flavours/glitch/util/redux_helpers'; - -// Messages. -const messages = defineMessages({ - compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new toot' }, -}); - -// State mapping. -const mapStateToProps = state => ({ - account: state.getIn(['accounts', me]), - columns: state.getIn(['settings', 'columns']), - elefriend: state.getIn(['compose', 'elefriend']), - results: state.getIn(['search', 'results']), - searchHidden: state.getIn(['search', 'hidden']), - searchValue: state.getIn(['search', 'value']), - submitted: state.getIn(['search', 'submitted']), - unreadNotifications: state.getIn(['notifications', 'unread']), - showNotificationsBadge: state.getIn(['local_settings', 'notifications', 'tab_badge']), -}); - -// Dispatch mapping. -const mapDispatchToProps = (dispatch, { intl }) => ({ - onChange (value) { - dispatch(changeSearch(value)); - }, - onClear () { - dispatch(clearSearch()); - }, - onClickElefriend () { - dispatch(cycleElefriendCompose()); - }, - onShow () { - dispatch(showSearch()); - }, - onSubmit () { - dispatch(submitSearch()); - }, - onOpenSettings (e) { - e.preventDefault(); - e.stopPropagation(); - dispatch(openModal('SETTINGS', {})); - }, -}); - -// The component. -class Drawer extends React.Component { - - // Constructor. - constructor (props) { - super(props); - } - - // Rendering. - render () { - const { - account, - columns, - elefriend, - intl, - multiColumn, - onChange, - onClear, - onClickElefriend, - onOpenSettings, - onShow, - onSubmit, - results, - searchHidden, - searchValue, - submitted, - isSearchPage, - unreadNotifications, - showNotificationsBadge, - } = this.props; - const computedClass = classNames('drawer', `mbstobon-${elefriend}`); - - // The result. - return ( -
- {multiColumn ? ( - - ) : null} - {(multiColumn || isSearchPage) && } -
- {!isSearchPage &&
- - - {multiColumn && ( -
- {mascot ? :
- )} -
} - - {(multiColumn || isSearchPage) && - } -
-
- ); - } - -} - -// Props. -Drawer.propTypes = { - intl: PropTypes.object.isRequired, - isSearchPage: PropTypes.bool, - multiColumn: PropTypes.bool, - - // State props. - account: ImmutablePropTypes.map, - columns: ImmutablePropTypes.list, - results: ImmutablePropTypes.map, - elefriend: PropTypes.number, - searchHidden: PropTypes.bool, - searchValue: PropTypes.string, - submitted: PropTypes.bool, - unreadNotifications: PropTypes.number, - showNotificationsBadge: PropTypes.bool, - - // Dispatch props. - onChange: PropTypes.func, - onClear: PropTypes.func, - onClickElefriend: PropTypes.func, - onShow: PropTypes.func, - onSubmit: PropTypes.func, - onOpenSettings: PropTypes.func, -}; - -// Connecting and export. -export { Drawer as WrappedComponent }; -export default wrap(Drawer, mapStateToProps, mapDispatchToProps, true); diff --git a/app/javascript/flavours/glitch/features/drawer/results/index.js b/app/javascript/flavours/glitch/features/drawer/results/index.js deleted file mode 100644 index 4574c0e1e..000000000 --- a/app/javascript/flavours/glitch/features/drawer/results/index.js +++ /dev/null @@ -1,117 +0,0 @@ -// Package imports. -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { - FormattedMessage, - defineMessages, -} from 'react-intl'; -import spring from 'react-motion/lib/spring'; -import { Link } from 'react-router-dom'; - -// Components. -import Icon from 'flavours/glitch/components/icon'; -import AccountContainer from 'flavours/glitch/containers/account_container'; -import StatusContainer from 'flavours/glitch/containers/status_container'; -import Hashtag from 'flavours/glitch/components/hashtag'; - -// Utils. -import Motion from 'flavours/glitch/util/optional_motion'; - -// Messages. -const messages = defineMessages({ - total: { - defaultMessage: '{count, number} {count, plural, one {result} other {results}}', - id: 'search_results.total', - }, -}); - -// The component. -export default function DrawerResults ({ - results, - visible, -}) { - const accounts = results ? results.get('accounts') : null; - const statuses = results ? results.get('statuses') : null; - const hashtags = results ? results.get('hashtags') : null; - - // This gets the total number of items. - const count = [accounts, statuses, hashtags].reduce(function (size, item) { - if (item && item.size) { - return size + item.size; - } - return size; - }, 0); - - // The result. - return ( - - {({ x }) => ( -
-
- - -
- {accounts && accounts.size ? ( -
-
- - {accounts.map( - accountId => ( - - ) - )} -
- ) : null} - {statuses && statuses.size ? ( -
-
- - {statuses.map( - statusId => ( - - ) - )} -
- ) : null} - {hashtags && hashtags.size ? ( -
-
- - {hashtags.map(hashtag => )} -
- ) : null} -
- )} -
- ); -} - -// Props. -DrawerResults.propTypes = { - results: ImmutablePropTypes.map, - visible: PropTypes.bool, -}; diff --git a/app/javascript/flavours/glitch/features/drawer/search/index.js b/app/javascript/flavours/glitch/features/drawer/search/index.js deleted file mode 100644 index 8cbb0906c..000000000 --- a/app/javascript/flavours/glitch/features/drawer/search/index.js +++ /dev/null @@ -1,152 +0,0 @@ -// Package imports. -import classNames from 'classnames'; -import PropTypes from 'prop-types'; -import React from 'react'; -import { - FormattedMessage, - defineMessages, -} from 'react-intl'; -import Overlay from 'react-overlays/lib/Overlay'; - -// Components. -import Icon from 'flavours/glitch/components/icon'; -import DrawerSearchPopout from './popout'; - -// Utils. -import { focusRoot } from 'flavours/glitch/util/dom_helpers'; -import { - assignHandlers, - hiddenComponent, -} from 'flavours/glitch/util/react_helpers'; - -// Messages. -const messages = defineMessages({ - placeholder: { - defaultMessage: 'Search', - id: 'search.placeholder', - }, -}); - -// Handlers. -const handlers = { - - handleBlur () { - this.setState({ expanded: false }); - }, - - handleChange ({ target: { value } }) { - const { onChange } = this.props; - if (onChange) { - onChange(value); - } - }, - - handleClear (e) { - const { - onClear, - submitted, - value, - } = this.props; - e.preventDefault(); // Prevents focus change ?? - if (onClear && (submitted || value && value.length)) { - onClear(); - } - }, - - handleFocus () { - const { onShow } = this.props; - this.setState({ expanded: true }); - if (onShow) { - onShow(); - } - }, - - handleKeyUp (e) { - const { onSubmit } = this.props; - switch (e.key) { - case 'Enter': - if (onSubmit) { - onSubmit(); - } - break; - case 'Escape': - focusRoot(); - } - }, -}; - -// The component. -export default class DrawerSearch extends React.PureComponent { - - // Constructor. - constructor (props) { - super(props); - assignHandlers(this, handlers); - this.state = { expanded: false }; - } - - // Rendering. - render () { - const { - handleBlur, - handleChange, - handleClear, - handleFocus, - handleKeyUp, - } = this.handlers; - const { - intl, - submitted, - value, - } = this.props; - const { expanded } = this.state; - const active = value && value.length || submitted; - const computedClass = classNames('drawer--search', { active }); - - return ( -
- -
- - -
- -
- ); - } - -} - -// Props. -DrawerSearch.propTypes = { - value: PropTypes.string, - submitted: PropTypes.bool, - onChange: PropTypes.func, - onSubmit: PropTypes.func, - onClear: PropTypes.func, - onShow: PropTypes.func, - intl: PropTypes.object, -}; diff --git a/app/javascript/flavours/glitch/features/drawer/search/popout/index.js b/app/javascript/flavours/glitch/features/drawer/search/popout/index.js deleted file mode 100644 index fec090b64..000000000 --- a/app/javascript/flavours/glitch/features/drawer/search/popout/index.js +++ /dev/null @@ -1,109 +0,0 @@ -// Package imports. -import PropTypes from 'prop-types'; -import React from 'react'; -import { - FormattedMessage, - defineMessages, -} from 'react-intl'; -import spring from 'react-motion/lib/spring'; - -// Utils. -import Motion from 'flavours/glitch/util/optional_motion'; -import { searchEnabled } from 'flavours/glitch/util/initial_state'; - -// Messages. -const messages = defineMessages({ - format: { - defaultMessage: 'Advanced search format', - id: 'search_popout.search_format', - }, - hashtag: { - defaultMessage: 'hashtag', - id: 'search_popout.tips.hashtag', - }, - status: { - defaultMessage: 'status', - id: 'search_popout.tips.status', - }, - text: { - defaultMessage: 'Simple text returns matching display names, usernames and hashtags', - id: 'search_popout.tips.text', - }, - 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.', - id: 'search_popout.tips.full_text', - }, - user: { - defaultMessage: 'user', - id: 'search_popout.tips.user', - }, -}); - -// The spring used by our motion. -const motionSpring = spring(1, { damping: 35, stiffness: 400 }); - -// The component. -export default function DrawerSearchPopout ({ style }) { - - // The result. - return ( -
- - {({ opacity, scaleX, scaleY }) => ( -
-

-
    -
  • - #example - {' '} - -
  • -
  • - @username@domain - {' '} - -
  • -
  • - URL - {' '} - -
  • -
  • - URL - {' '} - -
  • -
- { searchEnabled ? : } -
- )} -
-
- ); -} - -// Props. -DrawerSearchPopout.propTypes = { style: PropTypes.object }; diff --git a/app/javascript/flavours/glitch/features/ui/components/columns_area.js b/app/javascript/flavours/glitch/features/ui/components/columns_area.js index 83b797305..0fe580b9b 100644 --- a/app/javascript/flavours/glitch/features/ui/components/columns_area.js +++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.js @@ -12,13 +12,13 @@ import BundleContainer from '../containers/bundle_container'; import ColumnLoading from './column_loading'; import DrawerLoading from './drawer_loading'; import BundleColumnError from './bundle_column_error'; -import { Drawer, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses, BookmarkedStatuses, ListTimeline } from 'flavours/glitch/util/async-components'; +import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses, BookmarkedStatuses, ListTimeline } from 'flavours/glitch/util/async-components'; import detectPassiveEvents from 'detect-passive-events'; import { scrollRight } from 'flavours/glitch/util/scroll'; const componentMap = { - 'COMPOSE': Drawer, + 'COMPOSE': Compose, 'HOME': HomeTimeline, 'NOTIFICATIONS': Notifications, 'PUBLIC': PublicTimeline, diff --git a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js index 16355a446..e9c634a50 100644 --- a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js @@ -7,8 +7,8 @@ import ReactSwipeableViews from 'react-swipeable-views'; import classNames from 'classnames'; import Permalink from 'flavours/glitch/components/permalink'; import { WrappedComponent as RawComposer } from 'flavours/glitch/features/composer'; -import DrawerAccount from 'flavours/glitch/features/drawer/account'; -import DrawerSearch from 'flavours/glitch/features/drawer/search'; +import DrawerAccount from 'flavours/glitch/features/compose/account'; +import DrawerSearch from 'flavours/glitch/features/compose/search'; import ColumnHeader from './column_header'; import { me } from 'flavours/glitch/util/initial_state'; diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js index 348125c97..dd527d528 100644 --- a/app/javascript/flavours/glitch/features/ui/index.js +++ b/app/javascript/flavours/glitch/features/ui/index.js @@ -19,7 +19,7 @@ import ColumnsAreaContainer from './containers/columns_area_container'; import classNames from 'classnames'; import Favico from 'favico.js'; import { - Drawer, + Compose, Status, GettingStarted, KeyboardShortcuts, @@ -488,9 +488,9 @@ export default class UI extends React.Component { - + - + diff --git a/app/javascript/flavours/glitch/theme.yml b/app/javascript/flavours/glitch/theme.yml index 587cc0f1e..06e26ade2 100644 --- a/app/javascript/flavours/glitch/theme.yml +++ b/app/javascript/flavours/glitch/theme.yml @@ -11,7 +11,7 @@ pack: home: filename: packs/home.js preload: - - flavours/glitch/async/drawer + - flavours/glitch/async/compose - flavours/glitch/async/getting_started - flavours/glitch/async/home_timeline - flavours/glitch/async/notifications diff --git a/app/javascript/flavours/glitch/util/async-components.js b/app/javascript/flavours/glitch/util/async-components.js index e96af845f..094952204 100644 --- a/app/javascript/flavours/glitch/util/async-components.js +++ b/app/javascript/flavours/glitch/util/async-components.js @@ -2,8 +2,8 @@ export function EmojiPicker () { return import(/* webpackChunkName: "flavours/glitch/async/emoji_picker" */'flavours/glitch/util/emoji/emoji_picker'); } -export function Drawer () { - return import(/* webpackChunkName: "flavours/glitch/async/drawer" */'flavours/glitch/features/drawer'); +export function Compose () { + return import(/* webpackChunkName: "flavours/glitch/async/compose" */'flavours/glitch/features/compose'); } export function Notifications () { -- cgit From c19b983986653c653f8cbc61b9a945bab993244a Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Fri, 19 Apr 2019 20:57:43 +0200 Subject: Refactor a bit DrawerSearch to make it closer to upstream --- .../glitch/features/compose/search/index.js | 108 ++++++++------------- 1 file changed, 41 insertions(+), 67 deletions(-) (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/features/compose/search/index.js b/app/javascript/flavours/glitch/features/compose/search/index.js index 8cbb0906c..8f9e19b7b 100644 --- a/app/javascript/flavours/glitch/features/compose/search/index.js +++ b/app/javascript/flavours/glitch/features/compose/search/index.js @@ -2,8 +2,9 @@ import classNames from 'classnames'; import PropTypes from 'prop-types'; import React from 'react'; +import { connect } from 'react-redux'; import { - FormattedMessage, + injectIntl, defineMessages, } from 'react-intl'; import Overlay from 'react-overlays/lib/Overlay'; @@ -14,10 +15,6 @@ import DrawerSearchPopout from './popout'; // Utils. import { focusRoot } from 'flavours/glitch/util/dom_helpers'; -import { - assignHandlers, - hiddenComponent, -} from 'flavours/glitch/util/react_helpers'; // Messages. const messages = defineMessages({ @@ -27,21 +24,32 @@ const messages = defineMessages({ }, }); -// Handlers. -const handlers = { +// The component. +export default @injectIntl +class DrawerSearch extends React.PureComponent { - handleBlur () { - this.setState({ expanded: false }); - }, + static propTypes = { + value: PropTypes.string.isRequired, + submitted: PropTypes.bool, + onChange: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired, + onClear: PropTypes.func.isRequired, + onShow: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + state = { + expanded: false, + }; - handleChange ({ target: { value } }) { + handleChange = (e) => { const { onChange } = this.props; if (onChange) { - onChange(value); + onChange(e.target.value); } - }, + } - handleClear (e) { + handleClear = (e) => { const { onClear, submitted, @@ -51,17 +59,21 @@ const handlers = { if (onClear && (submitted || value && value.length)) { onClear(); } - }, + } + + handleBlur () { + this.setState({ expanded: false }); + } - handleFocus () { + handleFocus = () => { const { onShow } = this.props; this.setState({ expanded: true }); if (onShow) { onShow(); } - }, + } - handleKeyUp (e) { + handleKeyUp = (e) => { const { onSubmit } = this.props; switch (e.key) { case 'Enter': @@ -72,81 +84,43 @@ const handlers = { case 'Escape': focusRoot(); } - }, -}; - -// The component. -export default class DrawerSearch extends React.PureComponent { - - // Constructor. - constructor (props) { - super(props); - assignHandlers(this, handlers); - this.state = { expanded: false }; } - // Rendering. render () { - const { - handleBlur, - handleChange, - handleClear, - handleFocus, - handleKeyUp, - } = this.handlers; - const { - intl, - submitted, - value, - } = this.props; + const { intl, value, submitted } = this.props; const { expanded } = this.state; - const active = value && value.length || submitted; + const active = value.length > 0 || submitted; const computedClass = classNames('drawer--search', { active }); return (
- + + +
); } } - -// Props. -DrawerSearch.propTypes = { - value: PropTypes.string, - submitted: PropTypes.bool, - onChange: PropTypes.func, - onSubmit: PropTypes.func, - onClear: PropTypes.func, - onShow: PropTypes.func, - intl: PropTypes.object, -}; -- cgit From c92ab35b1925647ef8f7f3d6df6a667becc0271c Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Fri, 19 Apr 2019 21:05:18 +0200 Subject: Inline DrawerSearchPopout in DrawerSearch --- .../glitch/features/compose/search/index.js | 48 +++++++-- .../glitch/features/compose/search/popout/index.js | 109 --------------------- 2 files changed, 40 insertions(+), 117 deletions(-) delete mode 100644 app/javascript/flavours/glitch/features/compose/search/popout/index.js (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/features/compose/search/index.js b/app/javascript/flavours/glitch/features/compose/search/index.js index 8f9e19b7b..06b99dcf0 100644 --- a/app/javascript/flavours/glitch/features/compose/search/index.js +++ b/app/javascript/flavours/glitch/features/compose/search/index.js @@ -3,27 +3,59 @@ import classNames from 'classnames'; import PropTypes from 'prop-types'; import React from 'react'; import { connect } from 'react-redux'; +import spring from 'react-motion/lib/spring'; import { injectIntl, + FormattedMessage, defineMessages, } from 'react-intl'; import Overlay from 'react-overlays/lib/Overlay'; // Components. import Icon from 'flavours/glitch/components/icon'; -import DrawerSearchPopout from './popout'; // Utils. import { focusRoot } from 'flavours/glitch/util/dom_helpers'; +import { searchEnabled } from 'flavours/glitch/util/initial_state'; +import Motion from 'flavours/glitch/util/optional_motion'; -// Messages. const messages = defineMessages({ - placeholder: { - defaultMessage: 'Search', - id: 'search.placeholder', - }, + placeholder: { id: 'search.placeholder', defaultMessage: 'Search' }, }); +class SearchPopout extends React.PureComponent { + + static propTypes = { + style: PropTypes.object, + }; + + render () { + const { style } = this.props; + const extraInformation = searchEnabled ? : ; + return ( +
+ + {({ opacity, scaleX, scaleY }) => ( +
+

+ +
    +
  • #example
  • +
  • @username@domain
  • +
  • URL
  • +
  • URL
  • +
+ + {extraInformation} +
+ )} +
+
+ ); + } + +} + // The component. export default @injectIntl class DrawerSearch extends React.PureComponent { @@ -61,7 +93,7 @@ class DrawerSearch extends React.PureComponent { } } - handleBlur () { + handleBlur = () => { this.setState({ expanded: false }); } @@ -117,7 +149,7 @@ class DrawerSearch extends React.PureComponent { - + ); diff --git a/app/javascript/flavours/glitch/features/compose/search/popout/index.js b/app/javascript/flavours/glitch/features/compose/search/popout/index.js deleted file mode 100644 index fec090b64..000000000 --- a/app/javascript/flavours/glitch/features/compose/search/popout/index.js +++ /dev/null @@ -1,109 +0,0 @@ -// Package imports. -import PropTypes from 'prop-types'; -import React from 'react'; -import { - FormattedMessage, - defineMessages, -} from 'react-intl'; -import spring from 'react-motion/lib/spring'; - -// Utils. -import Motion from 'flavours/glitch/util/optional_motion'; -import { searchEnabled } from 'flavours/glitch/util/initial_state'; - -// Messages. -const messages = defineMessages({ - format: { - defaultMessage: 'Advanced search format', - id: 'search_popout.search_format', - }, - hashtag: { - defaultMessage: 'hashtag', - id: 'search_popout.tips.hashtag', - }, - status: { - defaultMessage: 'status', - id: 'search_popout.tips.status', - }, - text: { - defaultMessage: 'Simple text returns matching display names, usernames and hashtags', - id: 'search_popout.tips.text', - }, - 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.', - id: 'search_popout.tips.full_text', - }, - user: { - defaultMessage: 'user', - id: 'search_popout.tips.user', - }, -}); - -// The spring used by our motion. -const motionSpring = spring(1, { damping: 35, stiffness: 400 }); - -// The component. -export default function DrawerSearchPopout ({ style }) { - - // The result. - return ( -
- - {({ opacity, scaleX, scaleY }) => ( -
-

-
    -
  • - #example - {' '} - -
  • -
  • - @username@domain - {' '} - -
  • -
  • - URL - {' '} - -
  • -
  • - URL - {' '} - -
  • -
- { searchEnabled ? : } -
- )} -
-
- ); -} - -// Props. -DrawerSearchPopout.propTypes = { style: PropTypes.object }; -- cgit From ab3e8fc542f53ce9c7a8d1b39ae57bdb60667dac Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Sat, 20 Apr 2019 17:50:12 +0200 Subject: Move DrawerSearch to Search + SearchContainer --- .../glitch/features/compose/components/search.js | 158 +++++++++++++++++++++ .../compose/containers/search_container.js | 35 +++++ .../flavours/glitch/features/compose/index.js | 41 +----- .../glitch/features/compose/search/index.js | 158 --------------------- .../features/ui/components/onboarding_modal.js | 12 +- 5 files changed, 205 insertions(+), 199 deletions(-) create mode 100644 app/javascript/flavours/glitch/features/compose/components/search.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/search_container.js delete mode 100644 app/javascript/flavours/glitch/features/compose/search/index.js (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/features/compose/components/search.js b/app/javascript/flavours/glitch/features/compose/components/search.js new file mode 100644 index 000000000..5fed1567a --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/components/search.js @@ -0,0 +1,158 @@ +// Package imports. +import classNames from 'classnames'; +import PropTypes from 'prop-types'; +import React from 'react'; +import { connect } from 'react-redux'; +import spring from 'react-motion/lib/spring'; +import { + injectIntl, + FormattedMessage, + defineMessages, +} from 'react-intl'; +import Overlay from 'react-overlays/lib/Overlay'; + +// Components. +import Icon from 'flavours/glitch/components/icon'; + +// Utils. +import { focusRoot } from 'flavours/glitch/util/dom_helpers'; +import { searchEnabled } from 'flavours/glitch/util/initial_state'; +import Motion from 'flavours/glitch/util/optional_motion'; + +const messages = defineMessages({ + placeholder: { id: 'search.placeholder', defaultMessage: 'Search' }, +}); + +class SearchPopout extends React.PureComponent { + + static propTypes = { + style: PropTypes.object, + }; + + render () { + const { style } = this.props; + const extraInformation = searchEnabled ? : ; + return ( +
+ + {({ opacity, scaleX, scaleY }) => ( +
+

+ +
    +
  • #example
  • +
  • @username@domain
  • +
  • URL
  • +
  • URL
  • +
+ + {extraInformation} +
+ )} +
+
+ ); + } + +} + +// The component. +export default @injectIntl +class Search extends React.PureComponent { + + static propTypes = { + value: PropTypes.string.isRequired, + submitted: PropTypes.bool, + onChange: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired, + onClear: PropTypes.func.isRequired, + onShow: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + state = { + expanded: false, + }; + + handleChange = (e) => { + const { onChange } = this.props; + if (onChange) { + onChange(e.target.value); + } + } + + handleClear = (e) => { + const { + onClear, + submitted, + value, + } = this.props; + e.preventDefault(); // Prevents focus change ?? + if (onClear && (submitted || value && value.length)) { + onClear(); + } + } + + handleBlur = () => { + this.setState({ expanded: false }); + } + + handleFocus = () => { + const { onShow } = this.props; + this.setState({ expanded: true }); + if (onShow) { + onShow(); + } + } + + handleKeyUp = (e) => { + const { onSubmit } = this.props; + switch (e.key) { + case 'Enter': + if (onSubmit) { + onSubmit(); + } + break; + case 'Escape': + focusRoot(); + } + } + + render () { + const { intl, value, submitted } = this.props; + const { expanded } = this.state; + const active = value.length > 0 || submitted; + const computedClass = classNames('drawer--search', { active }); + + return ( +
+ +
+ + +
+ + + +
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/features/compose/containers/search_container.js b/app/javascript/flavours/glitch/features/compose/containers/search_container.js new file mode 100644 index 000000000..8f4bfcf08 --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/containers/search_container.js @@ -0,0 +1,35 @@ +import { connect } from 'react-redux'; +import { + changeSearch, + clearSearch, + submitSearch, + showSearch, +} from 'flavours/glitch/actions/search'; +import Search from '../components/search'; + +const mapStateToProps = state => ({ + value: state.getIn(['search', 'value']), + submitted: state.getIn(['search', 'submitted']), +}); + +const mapDispatchToProps = dispatch => ({ + + onChange (value) { + dispatch(changeSearch(value)); + }, + + onClear () { + dispatch(clearSearch()); + }, + + onSubmit () { + dispatch(submitSearch()); + }, + + onShow () { + dispatch(showSearch()); + }, + +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Search); diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js index cb261f9d6..83c5d82b0 100644 --- a/app/javascript/flavours/glitch/features/compose/index.js +++ b/app/javascript/flavours/glitch/features/compose/index.js @@ -8,12 +8,6 @@ import classNames from 'classnames'; // Actions. import { openModal } from 'flavours/glitch/actions/modal'; -import { - changeSearch, - clearSearch, - showSearch, - submitSearch, -} from 'flavours/glitch/actions/search'; import { cycleElefriendCompose } from 'flavours/glitch/actions/compose'; // Components. @@ -21,7 +15,7 @@ import Composer from 'flavours/glitch/features/composer'; import DrawerAccount from './account'; import DrawerHeader from './header'; import DrawerResults from './results'; -import DrawerSearch from './search'; +import SearchContainer from './containers/search_container'; // Utils. import { me, mascot } from 'flavours/glitch/util/initial_state'; @@ -39,7 +33,6 @@ const mapStateToProps = state => ({ elefriend: state.getIn(['compose', 'elefriend']), results: state.getIn(['search', 'results']), searchHidden: state.getIn(['search', 'hidden']), - searchValue: state.getIn(['search', 'value']), submitted: state.getIn(['search', 'submitted']), unreadNotifications: state.getIn(['notifications', 'unread']), showNotificationsBadge: state.getIn(['local_settings', 'notifications', 'tab_badge']), @@ -47,21 +40,9 @@ const mapStateToProps = state => ({ // Dispatch mapping. const mapDispatchToProps = (dispatch, { intl }) => ({ - onChange (value) { - dispatch(changeSearch(value)); - }, - onClear () { - dispatch(clearSearch()); - }, onClickElefriend () { dispatch(cycleElefriendCompose()); }, - onShow () { - dispatch(showSearch()); - }, - onSubmit () { - dispatch(submitSearch()); - }, onOpenSettings (e) { e.preventDefault(); e.stopPropagation(); @@ -84,17 +65,12 @@ class Compose extends React.PureComponent { results: ImmutablePropTypes.map, elefriend: PropTypes.number, searchHidden: PropTypes.bool, - searchValue: PropTypes.string, submitted: PropTypes.bool, unreadNotifications: PropTypes.number, showNotificationsBadge: PropTypes.bool, // Dispatch props. - onChange: PropTypes.func, - onClear: PropTypes.func, onClickElefriend: PropTypes.func, - onShow: PropTypes.func, - onSubmit: PropTypes.func, onOpenSettings: PropTypes.func, }; @@ -106,15 +82,10 @@ class Compose extends React.PureComponent { elefriend, intl, multiColumn, - onChange, - onClear, onClickElefriend, onOpenSettings, - onShow, - onSubmit, results, searchHidden, - searchValue, submitted, isSearchPage, unreadNotifications, @@ -134,15 +105,7 @@ class Compose extends React.PureComponent { onSettingsClick={onOpenSettings} /> )} - {(multiColumn || isSearchPage) && } + {(multiColumn || isSearchPage) && }
{!isSearchPage &&
diff --git a/app/javascript/flavours/glitch/features/compose/search/index.js b/app/javascript/flavours/glitch/features/compose/search/index.js deleted file mode 100644 index 06b99dcf0..000000000 --- a/app/javascript/flavours/glitch/features/compose/search/index.js +++ /dev/null @@ -1,158 +0,0 @@ -// Package imports. -import classNames from 'classnames'; -import PropTypes from 'prop-types'; -import React from 'react'; -import { connect } from 'react-redux'; -import spring from 'react-motion/lib/spring'; -import { - injectIntl, - FormattedMessage, - defineMessages, -} from 'react-intl'; -import Overlay from 'react-overlays/lib/Overlay'; - -// Components. -import Icon from 'flavours/glitch/components/icon'; - -// Utils. -import { focusRoot } from 'flavours/glitch/util/dom_helpers'; -import { searchEnabled } from 'flavours/glitch/util/initial_state'; -import Motion from 'flavours/glitch/util/optional_motion'; - -const messages = defineMessages({ - placeholder: { id: 'search.placeholder', defaultMessage: 'Search' }, -}); - -class SearchPopout extends React.PureComponent { - - static propTypes = { - style: PropTypes.object, - }; - - render () { - const { style } = this.props; - const extraInformation = searchEnabled ? : ; - return ( -
- - {({ opacity, scaleX, scaleY }) => ( -
-

- -
    -
  • #example
  • -
  • @username@domain
  • -
  • URL
  • -
  • URL
  • -
- - {extraInformation} -
- )} -
-
- ); - } - -} - -// The component. -export default @injectIntl -class DrawerSearch extends React.PureComponent { - - static propTypes = { - value: PropTypes.string.isRequired, - submitted: PropTypes.bool, - onChange: PropTypes.func.isRequired, - onSubmit: PropTypes.func.isRequired, - onClear: PropTypes.func.isRequired, - onShow: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - }; - - state = { - expanded: false, - }; - - handleChange = (e) => { - const { onChange } = this.props; - if (onChange) { - onChange(e.target.value); - } - } - - handleClear = (e) => { - const { - onClear, - submitted, - value, - } = this.props; - e.preventDefault(); // Prevents focus change ?? - if (onClear && (submitted || value && value.length)) { - onClear(); - } - } - - handleBlur = () => { - this.setState({ expanded: false }); - } - - handleFocus = () => { - const { onShow } = this.props; - this.setState({ expanded: true }); - if (onShow) { - onShow(); - } - } - - handleKeyUp = (e) => { - const { onSubmit } = this.props; - switch (e.key) { - case 'Enter': - if (onSubmit) { - onSubmit(); - } - break; - case 'Escape': - focusRoot(); - } - } - - render () { - const { intl, value, submitted } = this.props; - const { expanded } = this.state; - const active = value.length > 0 || submitted; - const computedClass = classNames('drawer--search', { active }); - - return ( -
- -
- - -
- - - -
- ); - } - -} diff --git a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js index e9c634a50..38e9b63ec 100644 --- a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js @@ -8,10 +8,12 @@ import classNames from 'classnames'; import Permalink from 'flavours/glitch/components/permalink'; import { WrappedComponent as RawComposer } from 'flavours/glitch/features/composer'; import DrawerAccount from 'flavours/glitch/features/compose/account'; -import DrawerSearch from 'flavours/glitch/features/compose/search'; +import Search from 'flavours/glitch/features/compose/components/search'; import ColumnHeader from './column_header'; import { me } from 'flavours/glitch/util/initial_state'; +const noop = () => { }; + const messages = defineMessages({ home_title: { id: 'column.home', defaultMessage: 'Home' }, notifications_title: { id: 'column.notifications', defaultMessage: 'Notifications' }, @@ -63,7 +65,13 @@ PageTwo.propTypes = { const PageThree = ({ intl, myAccount }) => (
- +
-- cgit From 67771e6d65ea209dcb9156a7495b7ef64c1a762b Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Sat, 20 Apr 2019 18:21:11 +0200 Subject: Rework DrawerResults to make them closer to upstream --- .../flavours/glitch/features/compose/index.js | 20 ++- .../glitch/features/compose/results/index.js | 153 ++++++++------------- .../flavours/glitch/styles/components/drawer.scss | 6 - 3 files changed, 70 insertions(+), 109 deletions(-) (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js index 83c5d82b0..6dc786c8b 100644 --- a/app/javascript/flavours/glitch/features/compose/index.js +++ b/app/javascript/flavours/glitch/features/compose/index.js @@ -16,10 +16,11 @@ import DrawerAccount from './account'; import DrawerHeader from './header'; import DrawerResults from './results'; import SearchContainer from './containers/search_container'; +import spring from 'react-motion/lib/spring'; // Utils. import { me, mascot } from 'flavours/glitch/util/initial_state'; -import { wrap } from 'flavours/glitch/util/redux_helpers'; +import Motion from 'flavours/glitch/util/optional_motion'; // Messages. const messages = defineMessages({ @@ -27,13 +28,14 @@ const messages = defineMessages({ }); // State mapping. -const mapStateToProps = state => ({ +const mapStateToProps = (state, ownProps) => ({ account: state.getIn(['accounts', me]), columns: state.getIn(['settings', 'columns']), elefriend: state.getIn(['compose', 'elefriend']), results: state.getIn(['search', 'results']), searchHidden: state.getIn(['search', 'hidden']), submitted: state.getIn(['search', 'submitted']), + showSearch: ownProps.multiColumn ? state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) : ownProps.isSearchPage, unreadNotifications: state.getIn(['notifications', 'unread']), showNotificationsBadge: state.getIn(['local_settings', 'notifications', 'tab_badge']), }); @@ -58,6 +60,7 @@ class Compose extends React.PureComponent { intl: PropTypes.object.isRequired, isSearchPage: PropTypes.bool, multiColumn: PropTypes.bool, + showSearch: PropTypes.bool, // State props. account: ImmutablePropTypes.map, @@ -90,6 +93,7 @@ class Compose extends React.PureComponent { isSearchPage, unreadNotifications, showNotificationsBadge, + showSearch, } = this.props; const computedClass = classNames('drawer', `mbstobon-${elefriend}`); @@ -117,11 +121,13 @@ class Compose extends React.PureComponent { )}
} - {(multiColumn || isSearchPage) && - } + + {({ x }) => ( +
+ +
+ )} +
); diff --git a/app/javascript/flavours/glitch/features/compose/results/index.js b/app/javascript/flavours/glitch/features/compose/results/index.js index 4574c0e1e..162d14913 100644 --- a/app/javascript/flavours/glitch/features/compose/results/index.js +++ b/app/javascript/flavours/glitch/features/compose/results/index.js @@ -1,12 +1,9 @@ // Package imports. import PropTypes from 'prop-types'; import React from 'react'; +import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { - FormattedMessage, - defineMessages, -} from 'react-intl'; -import spring from 'react-motion/lib/spring'; +import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; import { Link } from 'react-router-dom'; // Components. @@ -15,103 +12,67 @@ import AccountContainer from 'flavours/glitch/containers/account_container'; import StatusContainer from 'flavours/glitch/containers/status_container'; import Hashtag from 'flavours/glitch/components/hashtag'; -// Utils. -import Motion from 'flavours/glitch/util/optional_motion'; - // Messages. -const messages = defineMessages({ - total: { - defaultMessage: '{count, number} {count, plural, one {result} other {results}}', - id: 'search_results.total', - }, -}); - // The component. -export default function DrawerResults ({ - results, - visible, -}) { - const accounts = results ? results.get('accounts') : null; - const statuses = results ? results.get('statuses') : null; - const hashtags = results ? results.get('hashtags') : null; +export default @injectIntl +class DrawerResults extends ImmutablePureComponent { + + static propTypes = { + results: ImmutablePropTypes.map.isRequired, + intl: PropTypes.object.isRequired, + }; + + render() { + const { intl, results } = this.props; + + let accounts, statuses, hashtags; + let count = 0; + + if (results.get('accounts') && results.get('accounts').size > 0) { + count += results.get('accounts').size; + accounts = ( +
+
- // This gets the total number of items. - const count = [accounts, statuses, hashtags].reduce(function (size, item) { - if (item && item.size) { - return size + item.size; + {results.get('accounts').map(accountId => )} +
+ ); } - return size; - }, 0); - // The result. - return ( - - {({ x }) => ( -
-
- - -
- {accounts && accounts.size ? ( -
-
+ if (results.get('statuses') && results.get('statuses').size > 0) { + count += results.get('statuses').size; + statuses = ( +
+
- {accounts.map( - accountId => ( - - ) - )} -
- ) : null} - {statuses && statuses.size ? ( -
-
+ {results.get('statuses').map(statusId => )} +
+ ); + } - {statuses.map( - statusId => ( - - ) - )} -
- ) : null} - {hashtags && hashtags.size ? ( -
-
+ if (results.get('hashtags') && results.get('hashtags').size > 0) { + count += results.get('hashtags').size; + hashtags = ( +
+
- {hashtags.map(hashtag => )} -
- ) : null} -
- )} -
- ); -} + {results.get('hashtags').map(hashtag => )} + + ); + } -// Props. -DrawerResults.propTypes = { - results: ImmutablePropTypes.map, - visible: PropTypes.bool, -}; + // The result. + return ( +
+
+ + +
+ + {accounts} + {statuses} + {hashtags} +
+ ); + }; +} diff --git a/app/javascript/flavours/glitch/styles/components/drawer.scss b/app/javascript/flavours/glitch/styles/components/drawer.scss index d22783b94..41c794790 100644 --- a/app/javascript/flavours/glitch/styles/components/drawer.scss +++ b/app/javascript/flavours/glitch/styles/components/drawer.scss @@ -185,12 +185,6 @@ } .drawer--results { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - padding: 0; background: $ui-base-color; overflow-x: hidden; overflow-y: auto; -- cgit From 149aa07409ef7cd17098a28510e515530b173f13 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Sat, 20 Apr 2019 19:18:26 +0200 Subject: DrawerResults → SearchResults + SearchResultsContainer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/compose/components/search_results.js | 72 ++++++++++++++++++++ .../compose/containers/search_results_container.js | 8 +++ .../flavours/glitch/features/compose/index.js | 13 +--- .../glitch/features/compose/results/index.js | 78 ---------------------- 4 files changed, 82 insertions(+), 89 deletions(-) create mode 100644 app/javascript/flavours/glitch/features/compose/components/search_results.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/search_results_container.js delete mode 100644 app/javascript/flavours/glitch/features/compose/results/index.js (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/features/compose/components/search_results.js b/app/javascript/flavours/glitch/features/compose/components/search_results.js new file mode 100644 index 000000000..3d29675b4 --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/components/search_results.js @@ -0,0 +1,72 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; +import AccountContainer from 'flavours/glitch/containers/account_container'; +import StatusContainer from 'flavours/glitch/containers/status_container'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import Hashtag from 'flavours/glitch/components/hashtag'; +import Icon from 'flavours/glitch/components/icon'; + +export default @injectIntl +class SearchResults extends ImmutablePureComponent { + + static propTypes = { + results: ImmutablePropTypes.map.isRequired, + intl: PropTypes.object.isRequired, + }; + + render() { + const { intl, results } = this.props; + + let accounts, statuses, hashtags; + let count = 0; + + if (results.get('accounts') && results.get('accounts').size > 0) { + count += results.get('accounts').size; + accounts = ( +
+
+ + {results.get('accounts').map(accountId => )} +
+ ); + } + + if (results.get('statuses') && results.get('statuses').size > 0) { + count += results.get('statuses').size; + statuses = ( +
+
+ + {results.get('statuses').map(statusId => )} +
+ ); + } + + if (results.get('hashtags') && results.get('hashtags').size > 0) { + count += results.get('hashtags').size; + hashtags = ( +
+
+ + {results.get('hashtags').map(hashtag => )} +
+ ); + } + + // The result. + return ( +
+
+ + +
+ + {accounts} + {statuses} + {hashtags} +
+ ); + }; +} diff --git a/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js b/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js new file mode 100644 index 000000000..16d95d417 --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js @@ -0,0 +1,8 @@ +import { connect } from 'react-redux'; +import SearchResults from '../components/search_results'; + +const mapStateToProps = state => ({ + results: state.getIn(['search', 'results']), +}); + +export default connect(mapStateToProps)(SearchResults); diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js index 6dc786c8b..0865a7ff9 100644 --- a/app/javascript/flavours/glitch/features/compose/index.js +++ b/app/javascript/flavours/glitch/features/compose/index.js @@ -14,8 +14,8 @@ import { cycleElefriendCompose } from 'flavours/glitch/actions/compose'; import Composer from 'flavours/glitch/features/composer'; import DrawerAccount from './account'; import DrawerHeader from './header'; -import DrawerResults from './results'; import SearchContainer from './containers/search_container'; +import SearchResultsContainer from './containers/search_results_container'; import spring from 'react-motion/lib/spring'; // Utils. @@ -32,9 +32,6 @@ const mapStateToProps = (state, ownProps) => ({ account: state.getIn(['accounts', me]), columns: state.getIn(['settings', 'columns']), elefriend: state.getIn(['compose', 'elefriend']), - results: state.getIn(['search', 'results']), - searchHidden: state.getIn(['search', 'hidden']), - submitted: state.getIn(['search', 'submitted']), showSearch: ownProps.multiColumn ? state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) : ownProps.isSearchPage, unreadNotifications: state.getIn(['notifications', 'unread']), showNotificationsBadge: state.getIn(['local_settings', 'notifications', 'tab_badge']), @@ -65,10 +62,7 @@ class Compose extends React.PureComponent { // State props. account: ImmutablePropTypes.map, columns: ImmutablePropTypes.list, - results: ImmutablePropTypes.map, elefriend: PropTypes.number, - searchHidden: PropTypes.bool, - submitted: PropTypes.bool, unreadNotifications: PropTypes.number, showNotificationsBadge: PropTypes.bool, @@ -87,9 +81,6 @@ class Compose extends React.PureComponent { multiColumn, onClickElefriend, onOpenSettings, - results, - searchHidden, - submitted, isSearchPage, unreadNotifications, showNotificationsBadge, @@ -124,7 +115,7 @@ class Compose extends React.PureComponent { {({ x }) => (
- +
)}
diff --git a/app/javascript/flavours/glitch/features/compose/results/index.js b/app/javascript/flavours/glitch/features/compose/results/index.js deleted file mode 100644 index 162d14913..000000000 --- a/app/javascript/flavours/glitch/features/compose/results/index.js +++ /dev/null @@ -1,78 +0,0 @@ -// Package imports. -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; -import { Link } from 'react-router-dom'; - -// Components. -import Icon from 'flavours/glitch/components/icon'; -import AccountContainer from 'flavours/glitch/containers/account_container'; -import StatusContainer from 'flavours/glitch/containers/status_container'; -import Hashtag from 'flavours/glitch/components/hashtag'; - -// Messages. -// The component. -export default @injectIntl -class DrawerResults extends ImmutablePureComponent { - - static propTypes = { - results: ImmutablePropTypes.map.isRequired, - intl: PropTypes.object.isRequired, - }; - - render() { - const { intl, results } = this.props; - - let accounts, statuses, hashtags; - let count = 0; - - if (results.get('accounts') && results.get('accounts').size > 0) { - count += results.get('accounts').size; - accounts = ( -
-
- - {results.get('accounts').map(accountId => )} -
- ); - } - - if (results.get('statuses') && results.get('statuses').size > 0) { - count += results.get('statuses').size; - statuses = ( -
-
- - {results.get('statuses').map(statusId => )} -
- ); - } - - if (results.get('hashtags') && results.get('hashtags').size > 0) { - count += results.get('hashtags').size; - hashtags = ( -
-
- - {results.get('hashtags').map(hashtag => )} -
- ); - } - - // The result. - return ( -
-
- - -
- - {accounts} - {statuses} - {hashtags} -
- ); - }; -} -- cgit From 9b9816aba6c97eae9ea35698b185fe3deb3a870a Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 23 Oct 2018 00:08:39 +0200 Subject: [Glitch] Show suggested follows on search screen in mobile layout Port ad510db3a19640267f94062756d558a45472af14 to glitch-soc --- .../flavours/glitch/actions/suggestions.js | 52 ++++++++++++++++++++++ .../flavours/glitch/components/account.js | 14 +++++- .../features/compose/components/search_results.js | 36 ++++++++++++++- .../compose/containers/search_results_container.js | 9 +++- app/javascript/flavours/glitch/reducers/index.js | 2 + .../flavours/glitch/reducers/suggestions.js | 30 +++++++++++++ 6 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 app/javascript/flavours/glitch/actions/suggestions.js create mode 100644 app/javascript/flavours/glitch/reducers/suggestions.js (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/actions/suggestions.js b/app/javascript/flavours/glitch/actions/suggestions.js new file mode 100644 index 000000000..3687136ff --- /dev/null +++ b/app/javascript/flavours/glitch/actions/suggestions.js @@ -0,0 +1,52 @@ +import api from 'flavours/glitch/util/api'; +import { importFetchedAccounts } from './importer'; + +export const SUGGESTIONS_FETCH_REQUEST = 'SUGGESTIONS_FETCH_REQUEST'; +export const SUGGESTIONS_FETCH_SUCCESS = 'SUGGESTIONS_FETCH_SUCCESS'; +export const SUGGESTIONS_FETCH_FAIL = 'SUGGESTIONS_FETCH_FAIL'; + +export const SUGGESTIONS_DISMISS = 'SUGGESTIONS_DISMISS'; + +export function fetchSuggestions() { + return (dispatch, getState) => { + dispatch(fetchSuggestionsRequest()); + + api(getState).get('/api/v1/suggestions').then(response => { + dispatch(importFetchedAccounts(response.data)); + dispatch(fetchSuggestionsSuccess(response.data)); + }).catch(error => dispatch(fetchSuggestionsFail(error))); + }; +}; + +export function fetchSuggestionsRequest() { + return { + type: SUGGESTIONS_FETCH_REQUEST, + skipLoading: true, + }; +}; + +export function fetchSuggestionsSuccess(accounts) { + return { + type: SUGGESTIONS_FETCH_SUCCESS, + accounts, + skipLoading: true, + }; +}; + +export function fetchSuggestionsFail(error) { + return { + type: SUGGESTIONS_FETCH_FAIL, + error, + skipLoading: true, + skipAlert: true, + }; +}; + +export const dismissSuggestion = accountId => (dispatch, getState) => { + dispatch({ + type: SUGGESTIONS_DISMISS, + id: accountId, + }); + + api(getState).delete(`/api/v1/suggestions/${accountId}`); +}; diff --git a/app/javascript/flavours/glitch/components/account.js b/app/javascript/flavours/glitch/components/account.js index 4fcafc509..3fc18cb72 100644 --- a/app/javascript/flavours/glitch/components/account.js +++ b/app/javascript/flavours/glitch/components/account.js @@ -31,6 +31,9 @@ export default class Account extends ImmutablePureComponent { intl: PropTypes.object.isRequired, hidden: PropTypes.bool, small: PropTypes.bool, + actionIcon: PropTypes.string, + actionTitle: PropTypes.string, + onActionClick: PropTypes.func, }; handleFollow = () => { @@ -53,12 +56,19 @@ export default class Account extends ImmutablePureComponent { this.props.onMuteNotifications(this.props.account, false); } + handleAction = () => { + this.props.onActionClick(this.props.account); + } + render () { const { account, hidden, intl, small, + onActionClick, + actionIcon, + actionTitle, } = this.props; if (!account) { @@ -76,7 +86,9 @@ export default class Account extends ImmutablePureComponent { let buttons; - if (account.get('id') !== me && !small && account.get('relationship', null) !== null) { + if (onActionClick && actionIcon) { + buttons = ; + } else if (account.get('id') !== me && !small && account.get('relationship', null) !== null) { const following = account.getIn(['relationship', 'following']); const requested = account.getIn(['relationship', 'requested']); const blocking = account.getIn(['relationship', 'blocking']); diff --git a/app/javascript/flavours/glitch/features/compose/components/search_results.js b/app/javascript/flavours/glitch/features/compose/components/search_results.js index 3d29675b4..69df8cdc9 100644 --- a/app/javascript/flavours/glitch/features/compose/components/search_results.js +++ b/app/javascript/flavours/glitch/features/compose/components/search_results.js @@ -8,16 +8,50 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import Hashtag from 'flavours/glitch/components/hashtag'; import Icon from 'flavours/glitch/components/icon'; +const messages = defineMessages({ + dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' }, +}); + export default @injectIntl class SearchResults extends ImmutablePureComponent { static propTypes = { results: ImmutablePropTypes.map.isRequired, + suggestions: ImmutablePropTypes.list.isRequired, + fetchSuggestions: PropTypes.func.isRequired, + dismissSuggestion: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, }; + componentDidMount () { + this.props.fetchSuggestions(); + } + render() { - const { intl, results } = this.props; + const { intl, results, suggestions, dismissSuggestion } = this.props; + + if (results.isEmpty() && !suggestions.isEmpty()) { + return ( +
+
+
+ + +
+ + {suggestions && suggestions.map(accountId => ( + + ))} +
+
+ ); + } let accounts, statuses, hashtags; let count = 0; diff --git a/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js b/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js index 16d95d417..f9637861a 100644 --- a/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js +++ b/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js @@ -1,8 +1,15 @@ import { connect } from 'react-redux'; import SearchResults from '../components/search_results'; +import { fetchSuggestions, dismissSuggestion } from '../../../actions/suggestions'; const mapStateToProps = state => ({ results: state.getIn(['search', 'results']), + suggestions: state.getIn(['suggestions', 'items']), }); -export default connect(mapStateToProps)(SearchResults); +const mapDispatchToProps = dispatch => ({ + fetchSuggestions: () => dispatch(fetchSuggestions()), + dismissSuggestion: account => dispatch(dismissSuggestion(account.get('id'))), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(SearchResults); diff --git a/app/javascript/flavours/glitch/reducers/index.js b/app/javascript/flavours/glitch/reducers/index.js index 76b38adb4..45b93b92c 100644 --- a/app/javascript/flavours/glitch/reducers/index.js +++ b/app/javascript/flavours/glitch/reducers/index.js @@ -28,6 +28,7 @@ import lists from './lists'; import listEditor from './list_editor'; import listAdder from './list_adder'; import filters from './filters'; +import suggestions from './suggestions'; import pinnedAccountsEditor from './pinned_accounts_editor'; import polls from './polls'; import identity_proofs from './identity_proofs'; @@ -63,6 +64,7 @@ const reducers = { listEditor, listAdder, filters, + suggestions, pinnedAccountsEditor, polls, }; diff --git a/app/javascript/flavours/glitch/reducers/suggestions.js b/app/javascript/flavours/glitch/reducers/suggestions.js new file mode 100644 index 000000000..9f4b89d58 --- /dev/null +++ b/app/javascript/flavours/glitch/reducers/suggestions.js @@ -0,0 +1,30 @@ +import { + SUGGESTIONS_FETCH_REQUEST, + SUGGESTIONS_FETCH_SUCCESS, + SUGGESTIONS_FETCH_FAIL, + SUGGESTIONS_DISMISS, +} from '../actions/suggestions'; +import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; + +const initialState = ImmutableMap({ + items: ImmutableList(), + isLoading: false, +}); + +export default function suggestionsReducer(state = initialState, action) { + switch(action.type) { + case SUGGESTIONS_FETCH_REQUEST: + return state.set('isLoading', true); + case SUGGESTIONS_FETCH_SUCCESS: + return state.withMutations(map => { + map.set('items', fromJS(action.accounts.map(x => x.id))); + map.set('isLoading', false); + }); + case SUGGESTIONS_FETCH_FAIL: + return state.set('isLoading', false); + case SUGGESTIONS_DISMISS: + return state.update('items', list => list.filterNot(id => id === action.id)); + default: + return state; + } +}; -- cgit From 9a2f10fe8b24d48f0b7c5dde545b2df3c8952330 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Sat, 20 Apr 2019 20:32:16 +0200 Subject: DrawerAccount → NavigationBar + NavigationContainer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../glitch/features/compose/account/index.js | 76 ---------------------- .../features/compose/components/navigation_bar.js | 36 ++++++++++ .../compose/containers/navigation_container.js | 11 ++++ .../flavours/glitch/features/compose/index.js | 7 +- .../features/ui/components/onboarding_modal.js | 2 +- 5 files changed, 50 insertions(+), 82 deletions(-) delete mode 100644 app/javascript/flavours/glitch/features/compose/account/index.js create mode 100644 app/javascript/flavours/glitch/features/compose/components/navigation_bar.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/navigation_container.js (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/features/compose/account/index.js b/app/javascript/flavours/glitch/features/compose/account/index.js deleted file mode 100644 index 552848641..000000000 --- a/app/javascript/flavours/glitch/features/compose/account/index.js +++ /dev/null @@ -1,76 +0,0 @@ -// Package imports. -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { - FormattedMessage, - defineMessages, -} from 'react-intl'; - -// Components. -import Avatar from 'flavours/glitch/components/avatar'; -import Permalink from 'flavours/glitch/components/permalink'; - -// Utils. -import { hiddenComponent } from 'flavours/glitch/util/react_helpers'; -import { profileLink } from 'flavours/glitch/util/backend_links'; - -// Messages. -const messages = defineMessages({ - edit: { - defaultMessage: 'Edit profile', - id: 'navigation_bar.edit_profile', - }, -}); - -// The component. -export default function DrawerAccount ({ account }) { - - // We need an account to render. - if (!account) { - return ( -
- { profileLink !== undefined && ( - - - - )} -
- ); - } - - // The result. - return ( -
- - {account.get('acct')} - - - - @{account.get('acct')} - - { profileLink !== undefined && ( - - )} -
- ); -} - -// Props. -DrawerAccount.propTypes = { account: ImmutablePropTypes.map }; diff --git a/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js b/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js new file mode 100644 index 000000000..59172bb23 --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js @@ -0,0 +1,36 @@ +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import Avatar from 'flavours/glitch/components/avatar'; +import Permalink from 'flavours/glitch/components/permalink'; +import { FormattedMessage } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { profileLink } from 'flavours/glitch/util/backend_links'; + +export default class NavigationBar extends ImmutablePureComponent { + + static propTypes = { + account: ImmutablePropTypes.map.isRequired, + }; + + render () { + return ( +
+ + {this.props.account.get('acct')} + + + + + @{this.props.account.get('acct')} + + + { profileLink !== undefined && ( + + )} +
+ ); + }; +} diff --git a/app/javascript/flavours/glitch/features/compose/containers/navigation_container.js b/app/javascript/flavours/glitch/features/compose/containers/navigation_container.js new file mode 100644 index 000000000..eb630ffbb --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/containers/navigation_container.js @@ -0,0 +1,11 @@ +import { connect } from 'react-redux'; +import NavigationBar from '../components/navigation_bar'; +import { me } from 'flavours/glitch/util/initial_state'; + +const mapStateToProps = state => { + return { + account: state.getIn(['accounts', me]), + }; +}; + +export default connect(mapStateToProps)(NavigationBar); diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js index 0865a7ff9..9fe510028 100644 --- a/app/javascript/flavours/glitch/features/compose/index.js +++ b/app/javascript/flavours/glitch/features/compose/index.js @@ -12,10 +12,10 @@ import { cycleElefriendCompose } from 'flavours/glitch/actions/compose'; // Components. import Composer from 'flavours/glitch/features/composer'; -import DrawerAccount from './account'; import DrawerHeader from './header'; import SearchContainer from './containers/search_container'; import SearchResultsContainer from './containers/search_results_container'; +import NavigationContainer from './containers/navigation_container'; import spring from 'react-motion/lib/spring'; // Utils. @@ -29,7 +29,6 @@ const messages = defineMessages({ // State mapping. const mapStateToProps = (state, ownProps) => ({ - account: state.getIn(['accounts', me]), columns: state.getIn(['settings', 'columns']), elefriend: state.getIn(['compose', 'elefriend']), showSearch: ownProps.multiColumn ? state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) : ownProps.isSearchPage, @@ -60,7 +59,6 @@ class Compose extends React.PureComponent { showSearch: PropTypes.bool, // State props. - account: ImmutablePropTypes.map, columns: ImmutablePropTypes.list, elefriend: PropTypes.number, unreadNotifications: PropTypes.number, @@ -74,7 +72,6 @@ class Compose extends React.PureComponent { // Rendering. render () { const { - account, columns, elefriend, intl, @@ -103,7 +100,7 @@ class Compose extends React.PureComponent { {(multiColumn || isSearchPage) && }
{!isSearchPage &&
- + {multiColumn && (
diff --git a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js index 38e9b63ec..a8ac83366 100644 --- a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js @@ -7,7 +7,7 @@ import ReactSwipeableViews from 'react-swipeable-views'; import classNames from 'classnames'; import Permalink from 'flavours/glitch/components/permalink'; import { WrappedComponent as RawComposer } from 'flavours/glitch/features/composer'; -import DrawerAccount from 'flavours/glitch/features/compose/account'; +import DrawerAccount from 'flavours/glitch/features/compose/components/navigation_bar'; import Search from 'flavours/glitch/features/compose/components/search'; import ColumnHeader from './column_header'; import { me } from 'flavours/glitch/util/initial_state'; -- cgit From 281a82d8784fec7e79e309095cbe61428173b44f Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Sat, 20 Apr 2019 20:52:07 +0200 Subject: DrawerHeader → Header + HeaderContainer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../glitch/features/compose/components/header.js | 124 ++++++++++++++++++++ .../compose/containers/header_container.js | 21 ++++ .../glitch/features/compose/header/index.js | 127 --------------------- .../flavours/glitch/features/compose/index.js | 30 +---- 4 files changed, 148 insertions(+), 154 deletions(-) create mode 100644 app/javascript/flavours/glitch/features/compose/components/header.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/header_container.js delete mode 100644 app/javascript/flavours/glitch/features/compose/header/index.js (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/features/compose/components/header.js b/app/javascript/flavours/glitch/features/compose/components/header.js new file mode 100644 index 000000000..2e29084f2 --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/components/header.js @@ -0,0 +1,124 @@ +// Package imports. +import PropTypes from 'prop-types'; +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { injectIntl, defineMessages } from 'react-intl'; +import { Link } from 'react-router-dom'; +import ImmutablePureComponent from 'react-immutable-pure-component'; + +// Components. +import Icon from 'flavours/glitch/components/icon'; + +// Utils. +import { conditionalRender } from 'flavours/glitch/util/react_helpers'; +import { signOutLink } from 'flavours/glitch/util/backend_links'; + +// Messages. +const messages = defineMessages({ + community: { + defaultMessage: 'Local timeline', + id: 'navigation_bar.community_timeline', + }, + home_timeline: { + defaultMessage: 'Home', + id: 'tabs_bar.home', + }, + logout: { + defaultMessage: 'Logout', + id: 'navigation_bar.logout', + }, + notifications: { + defaultMessage: 'Notifications', + id: 'tabs_bar.notifications', + }, + public: { + defaultMessage: 'Federated timeline', + id: 'navigation_bar.public_timeline', + }, + settings: { + defaultMessage: 'App settings', + id: 'navigation_bar.app_settings', + }, + start: { + defaultMessage: 'Getting started', + id: 'getting_started.heading', + }, +}); + +export default @injectIntl +class Header extends ImmutablePureComponent { + static propTypes = { + columns: ImmutablePropTypes.list, + unreadNotifications: PropTypes.number, + showNotificationsBadge: PropTypes.bool, + intl: PropTypes.object, + onSettingsClick: PropTypes.func, + }; + + render () { + const { intl, columns, unreadNotifications, showNotificationsBadge, onSettingsClick } = this.props; + + // Only renders the component if the column isn't being shown. + const renderForColumn = conditionalRender.bind(null, + columnId => !columns || !columns.some( + column => column.get('id') === columnId + ) + ); + + // The result. + return ( + + ); + }; +} diff --git a/app/javascript/flavours/glitch/features/compose/containers/header_container.js b/app/javascript/flavours/glitch/features/compose/containers/header_container.js new file mode 100644 index 000000000..6f1978807 --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/containers/header_container.js @@ -0,0 +1,21 @@ +import { openModal } from 'flavours/glitch/actions/modal'; +import { connect } from 'react-redux'; +import Header from '../components/header'; + +const mapStateToProps = state => { + return { + columns: state.getIn(['settings', 'columns']), + unreadNotifications: state.getIn(['notifications', 'unread']), + showNotificationsBadge: state.getIn(['local_settings', 'notifications', 'tab_badge']), + }; +}; + +const mapDispatchToProps = (dispatch, { intl }) => ({ + onOpenSettings (e) { + e.preventDefault(); + e.stopPropagation(); + dispatch(openModal('SETTINGS', {})); + }, +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Header); diff --git a/app/javascript/flavours/glitch/features/compose/header/index.js b/app/javascript/flavours/glitch/features/compose/header/index.js deleted file mode 100644 index da5599732..000000000 --- a/app/javascript/flavours/glitch/features/compose/header/index.js +++ /dev/null @@ -1,127 +0,0 @@ -// Package imports. -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { defineMessages } from 'react-intl'; -import { Link } from 'react-router-dom'; - -// Components. -import Icon from 'flavours/glitch/components/icon'; - -// Utils. -import { conditionalRender } from 'flavours/glitch/util/react_helpers'; -import { signOutLink } from 'flavours/glitch/util/backend_links'; - -// Messages. -const messages = defineMessages({ - community: { - defaultMessage: 'Local timeline', - id: 'navigation_bar.community_timeline', - }, - home_timeline: { - defaultMessage: 'Home', - id: 'tabs_bar.home', - }, - logout: { - defaultMessage: 'Logout', - id: 'navigation_bar.logout', - }, - notifications: { - defaultMessage: 'Notifications', - id: 'tabs_bar.notifications', - }, - public: { - defaultMessage: 'Federated timeline', - id: 'navigation_bar.public_timeline', - }, - settings: { - defaultMessage: 'App settings', - id: 'navigation_bar.app_settings', - }, - start: { - defaultMessage: 'Getting started', - id: 'getting_started.heading', - }, -}); - -// The component. -export default function DrawerHeader ({ - columns, - unreadNotifications, - showNotificationsBadge, - intl, - onSettingsClick, -}) { - - // Only renders the component if the column isn't being shown. - const renderForColumn = conditionalRender.bind(null, - columnId => !columns || !columns.some( - column => column.get('id') === columnId - ) - ); - - // The result. - return ( - - ); -} - -// Props. -DrawerHeader.propTypes = { - columns: ImmutablePropTypes.list, - unreadNotifications: PropTypes.number, - showNotificationsBadge: PropTypes.bool, - intl: PropTypes.object, - onSettingsClick: PropTypes.func, -}; diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js index 9fe510028..923d379ec 100644 --- a/app/javascript/flavours/glitch/features/compose/index.js +++ b/app/javascript/flavours/glitch/features/compose/index.js @@ -7,12 +7,11 @@ import { injectIntl, defineMessages } from 'react-intl'; import classNames from 'classnames'; // Actions. -import { openModal } from 'flavours/glitch/actions/modal'; import { cycleElefriendCompose } from 'flavours/glitch/actions/compose'; // Components. import Composer from 'flavours/glitch/features/composer'; -import DrawerHeader from './header'; +import HeaderContainer from './containers/header_container'; import SearchContainer from './containers/search_container'; import SearchResultsContainer from './containers/search_results_container'; import NavigationContainer from './containers/navigation_container'; @@ -29,11 +28,8 @@ const messages = defineMessages({ // State mapping. const mapStateToProps = (state, ownProps) => ({ - columns: state.getIn(['settings', 'columns']), elefriend: state.getIn(['compose', 'elefriend']), showSearch: ownProps.multiColumn ? state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) : ownProps.isSearchPage, - unreadNotifications: state.getIn(['notifications', 'unread']), - showNotificationsBadge: state.getIn(['local_settings', 'notifications', 'tab_badge']), }); // Dispatch mapping. @@ -41,11 +37,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ onClickElefriend () { dispatch(cycleElefriendCompose()); }, - onOpenSettings (e) { - e.preventDefault(); - e.stopPropagation(); - dispatch(openModal('SETTINGS', {})); - }, }); // The component. @@ -59,28 +50,21 @@ class Compose extends React.PureComponent { showSearch: PropTypes.bool, // State props. - columns: ImmutablePropTypes.list, elefriend: PropTypes.number, unreadNotifications: PropTypes.number, - showNotificationsBadge: PropTypes.bool, // Dispatch props. onClickElefriend: PropTypes.func, - onOpenSettings: PropTypes.func, }; // Rendering. render () { const { - columns, elefriend, intl, multiColumn, onClickElefriend, - onOpenSettings, isSearchPage, - unreadNotifications, - showNotificationsBadge, showSearch, } = this.props; const computedClass = classNames('drawer', `mbstobon-${elefriend}`); @@ -88,16 +72,8 @@ class Compose extends React.PureComponent { // The result. return (
- {multiColumn && ( - - )} - {(multiColumn || isSearchPage) && } + {multiColumn && } + {(multiColumn || isSearchPage) && }
{!isSearchPage &&
-- cgit From 1bc4b8a0a57a4046364f4afbb741f2d4e7d48dcb Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Sat, 20 Apr 2019 21:28:03 +0200 Subject: features/composer/index.js → ComposeForm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/compose/components/compose_form.js | 415 ++++++++++++++ .../compose/containers/compose_form_container.js | 175 ++++++ .../flavours/glitch/features/compose/index.js | 7 +- .../flavours/glitch/features/composer/index.js | 600 --------------------- .../glitch/features/standalone/compose/index.js | 4 +- .../features/ui/components/onboarding_modal.js | 5 +- 6 files changed, 599 insertions(+), 607 deletions(-) create mode 100644 app/javascript/flavours/glitch/features/compose/components/compose_form.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js delete mode 100644 app/javascript/flavours/glitch/features/composer/index.js (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js new file mode 100644 index 000000000..0f9b11fa3 --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js @@ -0,0 +1,415 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { defineMessages, injectIntl } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; + +const APPROX_HASHTAG_RE = /(?:^|[^\/\)\w])#(\S+)/i; + +// Components. +import ComposerOptions from '../../composer/options'; +import ComposerPublisher from '../../composer/publisher'; +import ComposerReply from '../../composer/reply'; +import ComposerSpoiler from '../../composer/spoiler'; +import ComposerTextarea from '../../composer/textarea'; +import ComposerUploadForm from '../../composer/upload_form'; +import ComposerPollForm from '../../composer/poll_form'; +import ComposerWarning from '../../composer/warning'; +import ComposerHashtagWarning from '../../composer/hashtag_warning'; +import ComposerDirectWarning from '../../composer/direct_warning'; + +// Utils. +import { countableText } from 'flavours/glitch/util/counter'; +import { isMobile } from 'flavours/glitch/util/is_mobile'; + +const messages = defineMessages({ + missingDescriptionMessage: { id: 'confirmations.missing_media_description.message', + defaultMessage: 'At least one media attachment is lacking a description. Consider describing all media attachments for the visually impaired before sending your toot.' }, + missingDescriptionConfirm: { id: 'confirmations.missing_media_description.confirm', + defaultMessage: 'Send anyway' }, +}); + +export default @injectIntl +class ComposeForm extends ImmutablePureComponent { + + static contextTypes = { + router: PropTypes.object, + }; + + static propTypes = { + intl: PropTypes.object.isRequired, + + // State props. + acceptContentTypes: PropTypes.string, + advancedOptions: ImmutablePropTypes.map, + amUnlocked: PropTypes.bool, + focusDate: PropTypes.instanceOf(Date), + caretPosition: PropTypes.number, + isSubmitting: PropTypes.bool, + isChangingUpload: PropTypes.bool, + isUploading: PropTypes.bool, + layout: PropTypes.string, + media: ImmutablePropTypes.list, + preselectDate: PropTypes.instanceOf(Date), + privacy: PropTypes.string, + progress: PropTypes.number, + inReplyTo: ImmutablePropTypes.map, + resetFileKey: PropTypes.number, + sideArm: PropTypes.string, + sensitive: PropTypes.bool, + showSearch: PropTypes.bool, + spoiler: PropTypes.bool, + spoilerText: PropTypes.string, + suggestionToken: PropTypes.string, + suggestions: ImmutablePropTypes.list, + text: PropTypes.string, + anyMedia: PropTypes.bool, + spoilersAlwaysOn: PropTypes.bool, + mediaDescriptionConfirmation: PropTypes.bool, + preselectOnReply: PropTypes.bool, + + // Dispatch props. + onCancelReply: PropTypes.func, + onChangeAdvancedOption: PropTypes.func, + onChangeDescription: PropTypes.func, + onChangeSensitivity: PropTypes.func, + onChangeSpoilerText: PropTypes.func, + onChangeSpoilerness: PropTypes.func, + onChangeText: PropTypes.func, + onChangeVisibility: PropTypes.func, + onClearSuggestions: PropTypes.func, + onCloseModal: PropTypes.func, + onFetchSuggestions: PropTypes.func, + onInsertEmoji: PropTypes.func, + onMount: PropTypes.func, + onOpenActionsModal: PropTypes.func, + onOpenDoodleModal: PropTypes.func, + onSelectSuggestion: PropTypes.func, + onSubmit: PropTypes.func, + onUndoUpload: PropTypes.func, + onUnmount: PropTypes.func, + onUpload: PropTypes.func, + onMediaDescriptionConfirm: PropTypes.func, + }; + + // Changes the text value of the spoiler. + handleChangeSpoiler = ({ target: { value } }) => { + const { onChangeSpoilerText } = this.props; + if (onChangeSpoilerText) { + onChangeSpoilerText(value); + } + } + + // Inserts an emoji at the caret. + handleEmoji = (data) => { + const { textarea: { selectionStart } } = this; + const { onInsertEmoji } = this.props; + if (onInsertEmoji) { + onInsertEmoji(selectionStart, data); + } + } + + // Handles the secondary submit button. + handleSecondarySubmit = () => { + const { handleSubmit } = this.handlers; + const { + onChangeVisibility, + sideArm, + } = this.props; + if (sideArm !== 'none' && onChangeVisibility) { + onChangeVisibility(sideArm); + } + handleSubmit(); + } + + // Selects a suggestion from the autofill. + handleSelect = (tokenStart, token, value) => { + const { onSelectSuggestion } = this.props; + if (onSelectSuggestion) { + onSelectSuggestion(tokenStart, token, value); + } + } + + // Submits the status. + handleSubmit = () => { + const { textarea: { value }, uploadForm } = this; + const { + onChangeText, + onSubmit, + isSubmitting, + isChangingUpload, + isUploading, + media, + anyMedia, + text, + mediaDescriptionConfirmation, + onMediaDescriptionConfirm, + } = this.props; + + // If something changes inside the textarea, then we update the + // state before submitting. + if (onChangeText && text !== value) { + onChangeText(value); + } + + // Submit disabled: + if (isSubmitting || isUploading || isChangingUpload || (!text.trim().length && !anyMedia)) { + return; + } + + // Submit unless there are media with missing descriptions + if (mediaDescriptionConfirmation && onMediaDescriptionConfirm && media && media.some(item => !item.get('description'))) { + const firstWithoutDescription = media.findIndex(item => !item.get('description')); + if (uploadForm) { + const inputs = uploadForm.querySelectorAll('.composer--upload_form--item input'); + if (inputs.length == media.size && firstWithoutDescription !== -1) { + inputs[firstWithoutDescription].focus(); + } + } + onMediaDescriptionConfirm(this.context.router ? this.context.router.history : null); + } else if (onSubmit) { + onSubmit(this.context.router ? this.context.router.history : null); + } + } + + // Sets a reference to the upload form. + handleRefUploadForm = (uploadFormComponent) => { + this.uploadForm = uploadFormComponent; + } + + // Sets a reference to the textarea. + handleRefTextarea = (textareaComponent) => { + if (textareaComponent) { + this.textarea = textareaComponent.textarea; + } + } + + // Sets a reference to the CW field. + handleRefSpoilerText = (spoilerComponent) => { + if (spoilerComponent) { + this.spoilerText = spoilerComponent.spoilerText; + } + } + + // 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(); + } + } + + // This statement does several things: + // - If we're beginning a reply, and, + // - Replying to zero or one users, places the cursor at the end + // of the textbox. + // - Replying to more than one user, selects any usernames past + // the first; this provides a convenient shortcut to drop + // everyone else from the conversation. + componentDidUpdate (prevProps) { + const { + textarea, + spoilerText, + } = this; + const { + focusDate, + caretPosition, + isSubmitting, + preselectDate, + text, + preselectOnReply, + } = this.props; + let selectionEnd, selectionStart; + + // Caret/selection handling. + if (focusDate !== prevProps.focusDate) { + switch (true) { + case preselectDate !== prevProps.preselectDate && preselectOnReply: + selectionStart = text.search(/\s/) + 1; + selectionEnd = text.length; + break; + case !isNaN(caretPosition) && caretPosition !== null: + selectionStart = selectionEnd = caretPosition; + break; + default: + selectionStart = selectionEnd = text.length; + } + if (textarea) { + textarea.setSelectionRange(selectionStart, selectionEnd); + textarea.focus(); + textarea.scrollIntoView(); + } + + // Refocuses the textarea after submitting. + } else if (textarea && prevProps.isSubmitting && !isSubmitting) { + textarea.focus(); + } else if (this.props.spoiler !== prevProps.spoiler) { + if (this.props.spoiler) { + if (spoilerText) { + spoilerText.focus(); + } + } else { + if (textarea) { + textarea.focus(); + } + } + } + } + + render () { + const { + handleChangeSpoiler, + handleEmoji, + handleSecondarySubmit, + handleSelect, + handleSubmit, + handleRefUploadForm, + handleRefTextarea, + handleRefSpoilerText, + } = this; + const { + acceptContentTypes, + advancedOptions, + amUnlocked, + anyMedia, + intl, + isSubmitting, + isChangingUpload, + isUploading, + layout, + media, + poll, + onCancelReply, + onChangeAdvancedOption, + onChangeDescription, + onChangeSensitivity, + onChangeSpoilerness, + onChangeText, + onChangeVisibility, + onTogglePoll, + onClearSuggestions, + onCloseModal, + onFetchSuggestions, + onOpenActionsModal, + onOpenDoodleModal, + onOpenFocalPointModal, + onUndoUpload, + onUpload, + privacy, + progress, + inReplyTo, + resetFileKey, + sensitive, + showSearch, + sideArm, + spoiler, + spoilerText, + suggestions, + text, + spoilersAlwaysOn, + } = this.props; + + let disabledButton = isSubmitting || isUploading || isChangingUpload || (!text.trim().length && !anyMedia); + + return ( +
+ {privacy === 'direct' ? : null} + {privacy === 'private' && amUnlocked ? : null} + {privacy !== 'public' && APPROX_HASHTAG_RE.test(text) ? : null} + {inReplyTo && ( + + )} +
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js new file mode 100644 index 000000000..18fc31dce --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js @@ -0,0 +1,175 @@ +import { connect } from 'react-redux'; +import ComposeForm from '../components/compose_form'; +import { + cancelReplyCompose, + changeCompose, + changeComposeAdvancedOption, + changeComposeSensitivity, + changeComposeSpoilerText, + changeComposeSpoilerness, + changeComposeVisibility, + changeUploadCompose, + clearComposeSuggestions, + fetchComposeSuggestions, + insertEmojiCompose, + mountCompose, + selectComposeSuggestion, + submitCompose, + undoUploadCompose, + unmountCompose, + uploadCompose, +} from 'flavours/glitch/actions/compose'; +import { + closeModal, + openModal, +} from 'flavours/glitch/actions/modal'; +import { changeLocalSetting } from 'flavours/glitch/actions/local_settings'; +import { addPoll, removePoll } from 'flavours/glitch/actions/compose'; + +import { privacyPreference } from 'flavours/glitch/util/privacy_preference'; +import { me } from 'flavours/glitch/util/initial_state'; + +const messages = defineMessages({ + missingDescriptionMessage: { id: 'confirmations.missing_media_description.message', + defaultMessage: 'At least one media attachment is lacking a description. Consider describing all media attachments for the visually impaired before sending your toot.' }, + missingDescriptionConfirm: { id: 'confirmations.missing_media_description.confirm', + defaultMessage: 'Send anyway' }, +}); +import { defineMessages } from 'react-intl'; + +// State mapping. +function mapStateToProps (state) { + const spoilersAlwaysOn = state.getIn(['local_settings', 'always_show_spoilers_field']); + const inReplyTo = state.getIn(['compose', 'in_reply_to']); + const replyPrivacy = inReplyTo ? state.getIn(['statuses', inReplyTo, 'visibility']) : null; + const sideArmBasePrivacy = state.getIn(['local_settings', 'side_arm']); + const sideArmRestrictedPrivacy = replyPrivacy ? privacyPreference(replyPrivacy, sideArmBasePrivacy) : null; + let sideArmPrivacy = null; + switch (state.getIn(['local_settings', 'side_arm_reply_mode'])) { + case 'copy': + sideArmPrivacy = replyPrivacy; + break; + case 'restrict': + sideArmPrivacy = sideArmRestrictedPrivacy; + break; + } + sideArmPrivacy = sideArmPrivacy || sideArmBasePrivacy; + return { + acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']).toArray().join(','), + advancedOptions: state.getIn(['compose', 'advanced_options']), + amUnlocked: !state.getIn(['accounts', me, 'locked']), + focusDate: state.getIn(['compose', 'focusDate']), + caretPosition: state.getIn(['compose', 'caretPosition']), + isSubmitting: state.getIn(['compose', 'is_submitting']), + isChangingUpload: state.getIn(['compose', 'is_changing_upload']), + isUploading: state.getIn(['compose', 'is_uploading']), + layout: state.getIn(['local_settings', 'layout']), + media: state.getIn(['compose', 'media_attachments']), + preselectDate: state.getIn(['compose', 'preselectDate']), + privacy: state.getIn(['compose', 'privacy']), + progress: state.getIn(['compose', 'progress']), + inReplyTo: inReplyTo ? state.getIn(['statuses', inReplyTo]) : null, + replyAccount: inReplyTo ? state.getIn(['statuses', inReplyTo, 'account']) : null, + replyContent: inReplyTo ? state.getIn(['statuses', inReplyTo, 'contentHtml']) : null, + resetFileKey: state.getIn(['compose', 'resetFileKey']), + sideArm: sideArmPrivacy, + sensitive: state.getIn(['compose', 'sensitive']), + showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']), + spoiler: spoilersAlwaysOn || state.getIn(['compose', 'spoiler']), + spoilerText: state.getIn(['compose', 'spoiler_text']), + suggestionToken: state.getIn(['compose', 'suggestion_token']), + suggestions: state.getIn(['compose', 'suggestions']), + text: state.getIn(['compose', 'text']), + anyMedia: state.getIn(['compose', 'media_attachments']).size > 0, + poll: state.getIn(['compose', 'poll']), + spoilersAlwaysOn: spoilersAlwaysOn, + mediaDescriptionConfirmation: state.getIn(['local_settings', 'confirm_missing_media_description']), + preselectOnReply: state.getIn(['local_settings', 'preselect_on_reply']), + }; +}; + +// Dispatch mapping. +const mapDispatchToProps = (dispatch, { intl }) => ({ + onCancelReply() { + dispatch(cancelReplyCompose()); + }, + onChangeAdvancedOption(option, value) { + dispatch(changeComposeAdvancedOption(option, value)); + }, + onChangeDescription(id, description) { + dispatch(changeUploadCompose(id, { description })); + }, + onChangeSensitivity() { + dispatch(changeComposeSensitivity()); + }, + onChangeSpoilerText(text) { + dispatch(changeComposeSpoilerText(text)); + }, + onChangeSpoilerness() { + dispatch(changeComposeSpoilerness()); + }, + onChangeText(text) { + dispatch(changeCompose(text)); + }, + onChangeVisibility(value) { + dispatch(changeComposeVisibility(value)); + }, + onTogglePoll() { + dispatch((_, getState) => { + if (getState().getIn(['compose', 'poll'])) { + dispatch(removePoll()); + } else { + dispatch(addPoll()); + } + }); + }, + onClearSuggestions() { + dispatch(clearComposeSuggestions()); + }, + onCloseModal() { + dispatch(closeModal()); + }, + onFetchSuggestions(token) { + dispatch(fetchComposeSuggestions(token)); + }, + onInsertEmoji(position, emoji) { + dispatch(insertEmojiCompose(position, emoji)); + }, + onMount() { + dispatch(mountCompose()); + }, + onOpenActionsModal(props) { + dispatch(openModal('ACTIONS', props)); + }, + onOpenDoodleModal() { + dispatch(openModal('DOODLE', { noEsc: true })); + }, + onOpenFocalPointModal(id) { + dispatch(openModal('FOCAL_POINT', { id })); + }, + onSelectSuggestion(position, token, suggestion) { + dispatch(selectComposeSuggestion(position, token, suggestion)); + }, + onMediaDescriptionConfirm(routerHistory) { + dispatch(openModal('CONFIRM', { + message: intl.formatMessage(messages.missingDescriptionMessage), + confirm: intl.formatMessage(messages.missingDescriptionConfirm), + onConfirm: () => dispatch(submitCompose(routerHistory)), + onDoNotAsk: () => dispatch(changeLocalSetting(['confirm_missing_media_description'], false)), + })); + }, + onSubmit(routerHistory) { + dispatch(submitCompose(routerHistory)); + }, + onUndoUpload(id) { + dispatch(undoUploadCompose(id)); + }, + onUnmount() { + dispatch(unmountCompose()); + }, + onUpload(files) { + dispatch(uploadCompose(files)); + }, +}); + +export default connect(mapStateToProps, mapDispatchToProps)(ComposeForm); diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js index 923d379ec..01e7d1906 100644 --- a/app/javascript/flavours/glitch/features/compose/index.js +++ b/app/javascript/flavours/glitch/features/compose/index.js @@ -10,7 +10,7 @@ import classNames from 'classnames'; import { cycleElefriendCompose } from 'flavours/glitch/actions/compose'; // Components. -import Composer from 'flavours/glitch/features/composer'; +import ComposeFormContainer from './containers/compose_form_container'; import HeaderContainer from './containers/header_container'; import SearchContainer from './containers/search_container'; import SearchResultsContainer from './containers/search_results_container'; @@ -73,11 +73,13 @@ class Compose extends React.PureComponent { return (
{multiColumn && } + {(multiColumn || isSearchPage) && } +
{!isSearchPage &&
- + {multiColumn && (
{mascot ? :
); } + } diff --git a/app/javascript/flavours/glitch/features/composer/index.js b/app/javascript/flavours/glitch/features/composer/index.js deleted file mode 100644 index 9d2e0b3da..000000000 --- a/app/javascript/flavours/glitch/features/composer/index.js +++ /dev/null @@ -1,600 +0,0 @@ -// Package imports. -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { defineMessages } from 'react-intl'; - -const APPROX_HASHTAG_RE = /(?:^|[^\/\)\w])#(\S+)/i; - -// Actions. -import { - cancelReplyCompose, - changeCompose, - changeComposeAdvancedOption, - changeComposeSensitivity, - changeComposeSpoilerText, - changeComposeSpoilerness, - changeComposeVisibility, - changeUploadCompose, - clearComposeSuggestions, - fetchComposeSuggestions, - insertEmojiCompose, - mountCompose, - selectComposeSuggestion, - submitCompose, - undoUploadCompose, - unmountCompose, - uploadCompose, -} from 'flavours/glitch/actions/compose'; -import { - closeModal, - openModal, -} from 'flavours/glitch/actions/modal'; -import { changeLocalSetting } from 'flavours/glitch/actions/local_settings'; -import { addPoll, removePoll } from 'flavours/glitch/actions/compose'; - -// Components. -import ComposerOptions from './options'; -import ComposerPublisher from './publisher'; -import ComposerReply from './reply'; -import ComposerSpoiler from './spoiler'; -import ComposerTextarea from './textarea'; -import ComposerUploadForm from './upload_form'; -import ComposerPollForm from './poll_form'; -import ComposerWarning from './warning'; -import ComposerHashtagWarning from './hashtag_warning'; -import ComposerDirectWarning from './direct_warning'; - -// Utils. -import { countableText } from 'flavours/glitch/util/counter'; -import { me } from 'flavours/glitch/util/initial_state'; -import { isMobile } from 'flavours/glitch/util/is_mobile'; -import { assignHandlers } from 'flavours/glitch/util/react_helpers'; -import { wrap } from 'flavours/glitch/util/redux_helpers'; -import { privacyPreference } from 'flavours/glitch/util/privacy_preference'; - -const messages = defineMessages({ - missingDescriptionMessage: { id: 'confirmations.missing_media_description.message', - defaultMessage: 'At least one media attachment is lacking a description. Consider describing all media attachments for the visually impaired before sending your toot.' }, - missingDescriptionConfirm: { id: 'confirmations.missing_media_description.confirm', - defaultMessage: 'Send anyway' }, -}); - -// State mapping. -function mapStateToProps (state) { - const spoilersAlwaysOn = state.getIn(['local_settings', 'always_show_spoilers_field']); - const inReplyTo = state.getIn(['compose', 'in_reply_to']); - const replyPrivacy = inReplyTo ? state.getIn(['statuses', inReplyTo, 'visibility']) : null; - const sideArmBasePrivacy = state.getIn(['local_settings', 'side_arm']); - const sideArmRestrictedPrivacy = replyPrivacy ? privacyPreference(replyPrivacy, sideArmBasePrivacy) : null; - let sideArmPrivacy = null; - switch (state.getIn(['local_settings', 'side_arm_reply_mode'])) { - case 'copy': - sideArmPrivacy = replyPrivacy; - break; - case 'restrict': - sideArmPrivacy = sideArmRestrictedPrivacy; - break; - } - sideArmPrivacy = sideArmPrivacy || sideArmBasePrivacy; - return { - acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']).toArray().join(','), - advancedOptions: state.getIn(['compose', 'advanced_options']), - amUnlocked: !state.getIn(['accounts', me, 'locked']), - focusDate: state.getIn(['compose', 'focusDate']), - caretPosition: state.getIn(['compose', 'caretPosition']), - isSubmitting: state.getIn(['compose', 'is_submitting']), - isChangingUpload: state.getIn(['compose', 'is_changing_upload']), - isUploading: state.getIn(['compose', 'is_uploading']), - layout: state.getIn(['local_settings', 'layout']), - media: state.getIn(['compose', 'media_attachments']), - preselectDate: state.getIn(['compose', 'preselectDate']), - privacy: state.getIn(['compose', 'privacy']), - progress: state.getIn(['compose', 'progress']), - inReplyTo: inReplyTo ? state.getIn(['statuses', inReplyTo]) : null, - replyAccount: inReplyTo ? state.getIn(['statuses', inReplyTo, 'account']) : null, - replyContent: inReplyTo ? state.getIn(['statuses', inReplyTo, 'contentHtml']) : null, - resetFileKey: state.getIn(['compose', 'resetFileKey']), - sideArm: sideArmPrivacy, - sensitive: state.getIn(['compose', 'sensitive']), - showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']), - spoiler: spoilersAlwaysOn || state.getIn(['compose', 'spoiler']), - spoilerText: state.getIn(['compose', 'spoiler_text']), - suggestionToken: state.getIn(['compose', 'suggestion_token']), - suggestions: state.getIn(['compose', 'suggestions']), - text: state.getIn(['compose', 'text']), - anyMedia: state.getIn(['compose', 'media_attachments']).size > 0, - poll: state.getIn(['compose', 'poll']), - spoilersAlwaysOn: spoilersAlwaysOn, - mediaDescriptionConfirmation: state.getIn(['local_settings', 'confirm_missing_media_description']), - preselectOnReply: state.getIn(['local_settings', 'preselect_on_reply']), - }; -}; - -// Dispatch mapping. -const mapDispatchToProps = (dispatch, { intl }) => ({ - onCancelReply() { - dispatch(cancelReplyCompose()); - }, - onChangeAdvancedOption(option, value) { - dispatch(changeComposeAdvancedOption(option, value)); - }, - onChangeDescription(id, description) { - dispatch(changeUploadCompose(id, { description })); - }, - onChangeSensitivity() { - dispatch(changeComposeSensitivity()); - }, - onChangeSpoilerText(text) { - dispatch(changeComposeSpoilerText(text)); - }, - onChangeSpoilerness() { - dispatch(changeComposeSpoilerness()); - }, - onChangeText(text) { - dispatch(changeCompose(text)); - }, - onChangeVisibility(value) { - dispatch(changeComposeVisibility(value)); - }, - onTogglePoll() { - dispatch((_, getState) => { - if (getState().getIn(['compose', 'poll'])) { - dispatch(removePoll()); - } else { - dispatch(addPoll()); - } - }); - }, - onClearSuggestions() { - dispatch(clearComposeSuggestions()); - }, - onCloseModal() { - dispatch(closeModal()); - }, - onFetchSuggestions(token) { - dispatch(fetchComposeSuggestions(token)); - }, - onInsertEmoji(position, emoji) { - dispatch(insertEmojiCompose(position, emoji)); - }, - onMount() { - dispatch(mountCompose()); - }, - onOpenActionsModal(props) { - dispatch(openModal('ACTIONS', props)); - }, - onOpenDoodleModal() { - dispatch(openModal('DOODLE', { noEsc: true })); - }, - onOpenFocalPointModal(id) { - dispatch(openModal('FOCAL_POINT', { id })); - }, - onSelectSuggestion(position, token, suggestion) { - dispatch(selectComposeSuggestion(position, token, suggestion)); - }, - onMediaDescriptionConfirm(routerHistory) { - dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.missingDescriptionMessage), - confirm: intl.formatMessage(messages.missingDescriptionConfirm), - onConfirm: () => dispatch(submitCompose(routerHistory)), - onDoNotAsk: () => dispatch(changeLocalSetting(['confirm_missing_media_description'], false)), - })); - }, - onSubmit(routerHistory) { - dispatch(submitCompose(routerHistory)); - }, - onUndoUpload(id) { - dispatch(undoUploadCompose(id)); - }, - onUnmount() { - dispatch(unmountCompose()); - }, - onUpload(files) { - dispatch(uploadCompose(files)); - }, -}); - -// Handlers. -const handlers = { - - // Changes the text value of the spoiler. - handleChangeSpoiler ({ target: { value } }) { - const { onChangeSpoilerText } = this.props; - if (onChangeSpoilerText) { - onChangeSpoilerText(value); - } - }, - - // Inserts an emoji at the caret. - handleEmoji (data) { - const { textarea: { selectionStart } } = this; - const { onInsertEmoji } = this.props; - if (onInsertEmoji) { - onInsertEmoji(selectionStart, data); - } - }, - - // Handles the secondary submit button. - handleSecondarySubmit () { - const { handleSubmit } = this.handlers; - const { - onChangeVisibility, - sideArm, - } = this.props; - if (sideArm !== 'none' && onChangeVisibility) { - onChangeVisibility(sideArm); - } - handleSubmit(); - }, - - // Selects a suggestion from the autofill. - handleSelect (tokenStart, token, value) { - const { onSelectSuggestion } = this.props; - if (onSelectSuggestion) { - onSelectSuggestion(tokenStart, token, value); - } - }, - - // Submits the status. - handleSubmit () { - const { textarea: { value }, uploadForm } = this; - const { - onChangeText, - onSubmit, - isSubmitting, - isChangingUpload, - isUploading, - media, - anyMedia, - text, - mediaDescriptionConfirmation, - onMediaDescriptionConfirm, - } = this.props; - - // If something changes inside the textarea, then we update the - // state before submitting. - if (onChangeText && text !== value) { - onChangeText(value); - } - - // Submit disabled: - if (isSubmitting || isUploading || isChangingUpload || (!text.trim().length && !anyMedia)) { - return; - } - - // Submit unless there are media with missing descriptions - if (mediaDescriptionConfirmation && onMediaDescriptionConfirm && media && media.some(item => !item.get('description'))) { - const firstWithoutDescription = media.findIndex(item => !item.get('description')); - if (uploadForm) { - const inputs = uploadForm.querySelectorAll('.composer--upload_form--item input'); - if (inputs.length == media.size && firstWithoutDescription !== -1) { - inputs[firstWithoutDescription].focus(); - } - } - onMediaDescriptionConfirm(this.context.router ? this.context.router.history : null); - } else if (onSubmit) { - onSubmit(this.context.router ? this.context.router.history : null); - } - }, - - // Sets a reference to the upload form. - handleRefUploadForm (uploadFormComponent) { - this.uploadForm = uploadFormComponent; - }, - - // Sets a reference to the textarea. - handleRefTextarea (textareaComponent) { - if (textareaComponent) { - this.textarea = textareaComponent.textarea; - } - }, - - // Sets a reference to the CW field. - handleRefSpoilerText (spoilerComponent) { - if (spoilerComponent) { - this.spoilerText = spoilerComponent.spoilerText; - } - } -}; - -// The component. -class Composer extends React.Component { - - // Constructor. - constructor (props) { - super(props); - assignHandlers(this, handlers); - - // Instance variables. - this.textarea = null; - this.spoilerText = null; - } - - // 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(); - } - } - - // This statement does several things: - // - If we're beginning a reply, and, - // - Replying to zero or one users, places the cursor at the end - // of the textbox. - // - Replying to more than one user, selects any usernames past - // the first; this provides a convenient shortcut to drop - // everyone else from the conversation. - componentDidUpdate (prevProps) { - const { - textarea, - spoilerText, - } = this; - const { - focusDate, - caretPosition, - isSubmitting, - preselectDate, - text, - preselectOnReply, - } = this.props; - let selectionEnd, selectionStart; - - // Caret/selection handling. - if (focusDate !== prevProps.focusDate) { - switch (true) { - case preselectDate !== prevProps.preselectDate && preselectOnReply: - selectionStart = text.search(/\s/) + 1; - selectionEnd = text.length; - break; - case !isNaN(caretPosition) && caretPosition !== null: - selectionStart = selectionEnd = caretPosition; - break; - default: - selectionStart = selectionEnd = text.length; - } - if (textarea) { - textarea.setSelectionRange(selectionStart, selectionEnd); - textarea.focus(); - textarea.scrollIntoView(); - } - - // Refocuses the textarea after submitting. - } else if (textarea && prevProps.isSubmitting && !isSubmitting) { - textarea.focus(); - } else if (this.props.spoiler !== prevProps.spoiler) { - if (this.props.spoiler) { - if (spoilerText) { - spoilerText.focus(); - } - } else { - if (textarea) { - textarea.focus(); - } - } - } - } - - render () { - const { - handleChangeSpoiler, - handleEmoji, - handleSecondarySubmit, - handleSelect, - handleSubmit, - handleRefUploadForm, - handleRefTextarea, - handleRefSpoilerText, - } = this.handlers; - const { - acceptContentTypes, - advancedOptions, - amUnlocked, - anyMedia, - intl, - isSubmitting, - isChangingUpload, - isUploading, - layout, - media, - poll, - onCancelReply, - onChangeAdvancedOption, - onChangeDescription, - onChangeSensitivity, - onChangeSpoilerness, - onChangeText, - onChangeVisibility, - onTogglePoll, - onClearSuggestions, - onCloseModal, - onFetchSuggestions, - onOpenActionsModal, - onOpenDoodleModal, - onOpenFocalPointModal, - onUndoUpload, - onUpload, - privacy, - progress, - inReplyTo, - resetFileKey, - sensitive, - showSearch, - sideArm, - spoiler, - spoilerText, - suggestions, - text, - spoilersAlwaysOn, - } = this.props; - - let disabledButton = isSubmitting || isUploading || isChangingUpload || (!text.trim().length && !anyMedia); - - return ( -
- {privacy === 'direct' ? : null} - {privacy === 'private' && amUnlocked ? : null} - {privacy !== 'public' && APPROX_HASHTAG_RE.test(text) ? : null} - {inReplyTo && ( - - )} -
- ); - } - -} - -// Props. -Composer.propTypes = { - intl: PropTypes.object.isRequired, - - // State props. - acceptContentTypes: PropTypes.string, - advancedOptions: ImmutablePropTypes.map, - amUnlocked: PropTypes.bool, - focusDate: PropTypes.instanceOf(Date), - caretPosition: PropTypes.number, - isSubmitting: PropTypes.bool, - isChangingUpload: PropTypes.bool, - isUploading: PropTypes.bool, - layout: PropTypes.string, - media: ImmutablePropTypes.list, - preselectDate: PropTypes.instanceOf(Date), - privacy: PropTypes.string, - progress: PropTypes.number, - inReplyTo: ImmutablePropTypes.map, - resetFileKey: PropTypes.number, - sideArm: PropTypes.string, - sensitive: PropTypes.bool, - showSearch: PropTypes.bool, - spoiler: PropTypes.bool, - spoilerText: PropTypes.string, - suggestionToken: PropTypes.string, - suggestions: ImmutablePropTypes.list, - text: PropTypes.string, - anyMedia: PropTypes.bool, - spoilersAlwaysOn: PropTypes.bool, - mediaDescriptionConfirmation: PropTypes.bool, - preselectOnReply: PropTypes.bool, - - // Dispatch props. - onCancelReply: PropTypes.func, - onChangeAdvancedOption: PropTypes.func, - onChangeDescription: PropTypes.func, - onChangeSensitivity: PropTypes.func, - onChangeSpoilerText: PropTypes.func, - onChangeSpoilerness: PropTypes.func, - onChangeText: PropTypes.func, - onChangeVisibility: PropTypes.func, - onClearSuggestions: PropTypes.func, - onCloseModal: PropTypes.func, - onFetchSuggestions: PropTypes.func, - onInsertEmoji: PropTypes.func, - onMount: PropTypes.func, - onOpenActionsModal: PropTypes.func, - onOpenDoodleModal: PropTypes.func, - onSelectSuggestion: PropTypes.func, - onSubmit: PropTypes.func, - onUndoUpload: PropTypes.func, - onUnmount: PropTypes.func, - onUpload: PropTypes.func, - onMediaDescriptionConfirm: PropTypes.func, -}; - -Composer.contextTypes = { - router: PropTypes.object, -}; - -// Connecting and export. -export { Composer as WrappedComponent }; -export default wrap(Composer, mapStateToProps, mapDispatchToProps, true); diff --git a/app/javascript/flavours/glitch/features/standalone/compose/index.js b/app/javascript/flavours/glitch/features/standalone/compose/index.js index a77b59448..b33c21cb5 100644 --- a/app/javascript/flavours/glitch/features/standalone/compose/index.js +++ b/app/javascript/flavours/glitch/features/standalone/compose/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import Composer from 'flavours/glitch/features/composer'; +import ComposeFormContainer from 'flavours/glitch/features/compose/containers/compose_form_container'; import NotificationsContainer from 'flavours/glitch/features/ui/containers/notifications_container'; import LoadingBarContainer from 'flavours/glitch/features/ui/containers/loading_bar_container'; import ModalContainer from 'flavours/glitch/features/ui/containers/modal_container'; @@ -9,7 +9,7 @@ export default class Compose extends React.PureComponent { render () { return (
- + diff --git a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js index a8ac83366..f13e2e645 100644 --- a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js @@ -6,7 +6,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ReactSwipeableViews from 'react-swipeable-views'; import classNames from 'classnames'; import Permalink from 'flavours/glitch/components/permalink'; -import { WrappedComponent as RawComposer } from 'flavours/glitch/features/composer'; +import { ComposeForm } from 'flavours/glitch/features/compose/components/compose_form'; import DrawerAccount from 'flavours/glitch/features/compose/components/navigation_bar'; import Search from 'flavours/glitch/features/compose/components/search'; import ColumnHeader from './column_header'; @@ -45,8 +45,7 @@ const PageTwo = ({ intl, myAccount }) => (
- -- cgit From f72af5794da52d22fbb2a77e0fcbc111633fcab2 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Sat, 20 Apr 2019 22:05:09 +0200 Subject: Refactor Compose*Warning → ContainerWarning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Regression: only one warning at a time --- .../features/compose/components/compose_form.js | 13 ++--- .../glitch/features/compose/components/warning.js | 26 ++++++++++ .../compose/containers/warning_container.js | 44 ++++++++++++++++ .../features/composer/direct_warning/index.js | 55 -------------------- .../features/composer/hashtag_warning/index.js | 49 ------------------ .../glitch/features/composer/warning/index.js | 59 ---------------------- 6 files changed, 75 insertions(+), 171 deletions(-) create mode 100644 app/javascript/flavours/glitch/features/compose/components/warning.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/warning_container.js delete mode 100644 app/javascript/flavours/glitch/features/composer/direct_warning/index.js delete mode 100644 app/javascript/flavours/glitch/features/composer/hashtag_warning/index.js delete mode 100644 app/javascript/flavours/glitch/features/composer/warning/index.js (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js index 0f9b11fa3..1f37a1da5 100644 --- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js +++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js @@ -4,8 +4,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; -const APPROX_HASHTAG_RE = /(?:^|[^\/\)\w])#(\S+)/i; - // Components. import ComposerOptions from '../../composer/options'; import ComposerPublisher from '../../composer/publisher'; @@ -14,9 +12,7 @@ import ComposerSpoiler from '../../composer/spoiler'; import ComposerTextarea from '../../composer/textarea'; import ComposerUploadForm from '../../composer/upload_form'; import ComposerPollForm from '../../composer/poll_form'; -import ComposerWarning from '../../composer/warning'; -import ComposerHashtagWarning from '../../composer/hashtag_warning'; -import ComposerDirectWarning from '../../composer/direct_warning'; +import WarningContainer from '../containers/warning_container'; // Utils. import { countableText } from 'flavours/glitch/util/counter'; @@ -321,9 +317,8 @@ class ComposeForm extends ImmutablePureComponent { return (
- {privacy === 'direct' ? : null} - {privacy === 'private' && amUnlocked ? : null} - {privacy !== 'public' && APPROX_HASHTAG_RE.test(text) ? : null} + + {inReplyTo && ( )} +
{ + this.props.onChange(this.props.index, e.target.value); + }; + + handleOptionRemove = () => { + this.props.onRemove(this.props.index); + }; + + render () { + const { isPollMultiple, title, index, intl } = this.props; + + return ( +
  • + + +
    + +
    +
  • + ); + } + +} + +export default +@injectIntl +class PollForm extends ImmutablePureComponent { + + static propTypes = { + options: ImmutablePropTypes.list, + expiresIn: PropTypes.number, + isMultiple: PropTypes.bool, + onChangeOption: PropTypes.func.isRequired, + onAddOption: PropTypes.func.isRequired, + onRemoveOption: PropTypes.func.isRequired, + onChangeSettings: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + handleAddOption = () => { + this.props.onAddOption(''); + }; + + handleSelectDuration = e => { + this.props.onChangeSettings(e.target.value, this.props.isMultiple); + }; + + handleSelectMultiple = e => { + this.props.onChangeSettings(this.props.expiresIn, e.target.value === 'true'); + }; + + render () { + const { options, expiresIn, isMultiple, onChangeOption, onRemoveOption, intl } = this.props; + + if (!options) { + return null; + } + + return ( +
    +
      + {options.map((title, i) =>
    + +
    + + + +
    +
    + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/compose/containers/poll_form_container.js b/app/javascript/flavours/glitch/features/compose/containers/poll_form_container.js new file mode 100644 index 000000000..01df024c8 --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/containers/poll_form_container.js @@ -0,0 +1,29 @@ +import { connect } from 'react-redux'; +import PollForm from '../components/poll_form'; +import { addPollOption, removePollOption, changePollOption, changePollSettings } from 'flavours/glitch/actions/compose'; + +const mapStateToProps = state => ({ + options: state.getIn(['compose', 'poll', 'options']), + expiresIn: state.getIn(['compose', 'poll', 'expires_in']), + isMultiple: state.getIn(['compose', 'poll', 'multiple']), +}); + +const mapDispatchToProps = dispatch => ({ + onAddOption(title) { + dispatch(addPollOption(title)); + }, + + onRemoveOption(index) { + dispatch(removePollOption(index)); + }, + + onChangeOption(index, title) { + dispatch(changePollOption(index, title)); + }, + + onChangeSettings(expiresIn, isMultiple) { + dispatch(changePollSettings(expiresIn, isMultiple)); + }, +}); + +export default connect(mapStateToProps, mapDispatchToProps)(PollForm); diff --git a/app/javascript/flavours/glitch/features/composer/poll_form/components/poll_form.js b/app/javascript/flavours/glitch/features/composer/poll_form/components/poll_form.js deleted file mode 100644 index 1915b62d5..000000000 --- a/app/javascript/flavours/glitch/features/composer/poll_form/components/poll_form.js +++ /dev/null @@ -1,135 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import IconButton from 'flavours/glitch/components/icon_button'; -import Icon from 'flavours/glitch/components/icon'; -import classNames from 'classnames'; -import { pollLimits } from 'flavours/glitch/util/initial_state'; - -const messages = defineMessages({ - option_placeholder: { id: 'compose_form.poll.option_placeholder', defaultMessage: 'Choice {number}' }, - add_option: { id: 'compose_form.poll.add_option', defaultMessage: 'Add a choice' }, - remove_option: { id: 'compose_form.poll.remove_option', defaultMessage: 'Remove this choice' }, - poll_duration: { id: 'compose_form.poll.duration', defaultMessage: 'Poll duration' }, - single_choice: { id: 'compose_form.poll.single_choice', defaultMessage: 'Allow one choice' }, - multiple_choices: { id: 'compose_form.poll.multiple_choices', defaultMessage: 'Allow multiple choices' }, - minutes: { id: 'intervals.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}}' }, - hours: { id: 'intervals.full.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}}' }, - days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' }, -}); - -@injectIntl -class Option extends React.PureComponent { - - static propTypes = { - title: PropTypes.string.isRequired, - index: PropTypes.number.isRequired, - isPollMultiple: PropTypes.bool, - onChange: PropTypes.func.isRequired, - onRemove: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - }; - - handleOptionTitleChange = e => { - this.props.onChange(this.props.index, e.target.value); - }; - - handleOptionRemove = () => { - this.props.onRemove(this.props.index); - }; - - render () { - const { isPollMultiple, title, index, intl } = this.props; - - return ( -
  • - - -
    - -
    -
  • - ); - } - -} - -export default -@injectIntl -class PollForm extends ImmutablePureComponent { - - static propTypes = { - options: ImmutablePropTypes.list, - expiresIn: PropTypes.number, - isMultiple: PropTypes.bool, - onChangeOption: PropTypes.func.isRequired, - onAddOption: PropTypes.func.isRequired, - onRemoveOption: PropTypes.func.isRequired, - onChangeSettings: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - }; - - handleAddOption = () => { - this.props.onAddOption(''); - }; - - handleSelectDuration = e => { - this.props.onChangeSettings(e.target.value, this.props.isMultiple); - }; - - handleSelectMultiple = e => { - this.props.onChangeSettings(this.props.expiresIn, e.target.value === 'true'); - }; - - render () { - const { options, expiresIn, isMultiple, onChangeOption, onRemoveOption, intl } = this.props; - - if (!options) { - return null; - } - - return ( -
    -
      - {options.map((title, i) =>
    - -
    - - - -
    -
    - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/composer/poll_form/index.js b/app/javascript/flavours/glitch/features/composer/poll_form/index.js deleted file mode 100644 index 5232c3b31..000000000 --- a/app/javascript/flavours/glitch/features/composer/poll_form/index.js +++ /dev/null @@ -1,29 +0,0 @@ -import { connect } from 'react-redux'; -import PollForm from './components/poll_form'; -import { addPollOption, removePollOption, changePollOption, changePollSettings } from '../../../actions/compose'; - -const mapStateToProps = state => ({ - options: state.getIn(['compose', 'poll', 'options']), - expiresIn: state.getIn(['compose', 'poll', 'expires_in']), - isMultiple: state.getIn(['compose', 'poll', 'multiple']), -}); - -const mapDispatchToProps = dispatch => ({ - onAddOption(title) { - dispatch(addPollOption(title)); - }, - - onRemoveOption(index) { - dispatch(removePollOption(index)); - }, - - onChangeOption(index, title) { - dispatch(changePollOption(index, title)); - }, - - onChangeSettings(expiresIn, isMultiple) { - dispatch(changePollSettings(expiresIn, isMultiple)); - }, -}); - -export default connect(mapStateToProps, mapDispatchToProps)(PollForm); -- cgit From a243567a3e6100d65477162308e2c1bb5e056c21 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Sun, 21 Apr 2019 12:09:52 +0200 Subject: ComposerUploadForm → UploadForm + UploadFormContainer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/compose/components/compose_form.js | 28 +-- .../glitch/features/compose/components/upload.js | 131 +++++++++++++ .../features/compose/components/upload_form.js | 28 +++ .../features/compose/components/upload_progress.js | 42 +++++ .../compose/containers/compose_form_container.js | 12 -- .../compose/containers/upload_container.js | 31 ++++ .../compose/containers/upload_form_container.js | 8 + .../containers/upload_progress_container.js | 9 + .../glitch/features/composer/upload_form/index.js | 60 ------ .../features/composer/upload_form/item/index.js | 202 --------------------- .../composer/upload_form/progress/index.js | 52 ------ 11 files changed, 251 insertions(+), 352 deletions(-) create mode 100644 app/javascript/flavours/glitch/features/compose/components/upload.js create mode 100644 app/javascript/flavours/glitch/features/compose/components/upload_form.js create mode 100644 app/javascript/flavours/glitch/features/compose/components/upload_progress.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/upload_container.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/upload_form_container.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/upload_progress_container.js delete mode 100644 app/javascript/flavours/glitch/features/composer/upload_form/index.js delete mode 100644 app/javascript/flavours/glitch/features/composer/upload_form/item/index.js delete mode 100644 app/javascript/flavours/glitch/features/composer/upload_form/progress/index.js (limited to 'app/javascript/flavours/glitch') diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js index ccbcba571..ecd1aed69 100644 --- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js +++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js @@ -8,7 +8,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import ComposerOptions from '../../composer/options'; import ComposerPublisher from '../../composer/publisher'; import ComposerTextarea from '../../composer/textarea'; -import ComposerUploadForm from '../../composer/upload_form'; +import UploadFormContainer from '../containers/upload_form_container'; import PollFormContainer from '../containers/poll_form_container'; import WarningContainer from '../containers/warning_container'; import ReplyIndicatorContainer from '../containers/reply_indicator_container'; @@ -48,7 +48,6 @@ class ComposeForm extends ImmutablePureComponent { media: ImmutablePropTypes.list, preselectDate: PropTypes.instanceOf(Date), privacy: PropTypes.string, - progress: PropTypes.number, resetFileKey: PropTypes.number, sideArm: PropTypes.string, sensitive: PropTypes.bool, @@ -65,7 +64,6 @@ class ComposeForm extends ImmutablePureComponent { // Dispatch props. onChangeAdvancedOption: PropTypes.func, - onChangeDescription: PropTypes.func, onChangeSensitivity: PropTypes.func, onChangeSpoilerText: PropTypes.func, onChangeSpoilerness: PropTypes.func, @@ -80,7 +78,6 @@ class ComposeForm extends ImmutablePureComponent { onOpenDoodleModal: PropTypes.func, onSelectSuggestion: PropTypes.func, onSubmit: PropTypes.func, - onUndoUpload: PropTypes.func, onUnmount: PropTypes.func, onUpload: PropTypes.func, onMediaDescriptionConfirm: PropTypes.func, @@ -185,11 +182,6 @@ class ComposeForm extends ImmutablePureComponent { } } - // Sets a reference to the upload form. - handleRefUploadForm = (uploadFormComponent) => { - this.uploadForm = uploadFormComponent; - } - // Sets a reference to the textarea. handleRefTextarea = (textareaComponent) => { if (textareaComponent) { @@ -283,7 +275,6 @@ class ComposeForm extends ImmutablePureComponent { handleSecondarySubmit, handleSelect, handleSubmit, - handleRefUploadForm, handleRefTextarea, } = this; const { @@ -299,7 +290,6 @@ class ComposeForm extends ImmutablePureComponent { media, poll, onChangeAdvancedOption, - onChangeDescription, onChangeSensitivity, onChangeSpoilerness, onChangeText, @@ -310,11 +300,8 @@ class ComposeForm extends ImmutablePureComponent { onFetchSuggestions, onOpenActionsModal, onOpenDoodleModal, - onOpenFocalPointModal, - onUndoUpload, onUpload, privacy, - progress, resetFileKey, sensitive, showSearch, @@ -370,18 +357,7 @@ class ComposeForm extends ImmutablePureComponent { value={text} />
    - {isUploading || media && media.size ? ( - - ) : null} +
    { + if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { + this.handleSubmit(); + } + } + + handleSubmit = () => { + this.handleInputBlur(); + this.props.onSubmit(this.context.router.history); + } + + handleUndoClick = e => { + e.stopPropagation(); + this.props.onUndo(this.props.media.get('id')); + } + + handleFocalPointClick = e => { + e.stopPropagation(); + this.props.onOpenFocalPoint(this.props.media.get('id')); + } + + handleInputChange = e => { + this.setState({ dirtyDescription: e.target.value }); + } + + handleMouseEnter = () => { + this.setState({ hovered: true }); + } + + handleMouseLeave = () => { + this.setState({ hovered: false }); + } + + handleInputFocus = () => { + this.setState({ focused: true }); + } + + handleClick = () => { + this.setState({ focused: true }); + } + + handleInputBlur = () => { + const { dirtyDescription } = this.state; + + this.setState({ focused: false, dirtyDescription: null }); + + if (dirtyDescription !== null) { + this.props.onDescriptionChange(this.props.media.get('id'), dirtyDescription); + } + } + + render () { + const { intl, media } = this.props; + const active = this.state.hovered || this.state.focused || isUserTouching(); + const description = this.state.dirtyDescription || (this.state.dirtyDescription !== '' && media.get('description')) || ''; + const computedClass = classNames('composer--upload_form--item', { active }); + const focusX = media.getIn(['meta', 'focus', 'x']); + const focusY = media.getIn(['meta', 'focus', 'y']); + const x = ((focusX / 2) + .5) * 100; + const y = ((focusY / -2) + .5) * 100; + + return ( +
    + + {({ scale }) => ( +
    +
    + + {media.get('type') === 'image' && } +
    + +
    +