-
+
{multiColumn && (
{mascot ?
:
}
@@ -96,4 +98,5 @@ class Compose extends React.PureComponent {
);
}
+
}
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 && (
-
- )}
-
-
-
- {isUploading || media && media.size ? (
-
- ) : null}
- {!!poll && (
-
- )}
-
-
item.get('type') === 'video'
- ) : true)}
- hasMedia={media && !!media.size}
- allowPoll={!(media && !!media.size)}
- hasPoll={!!poll}
- intl={intl}
- onChangeAdvancedOption={onChangeAdvancedOption}
- onChangeSensitivity={onChangeSensitivity}
- onChangeVisibility={onChangeVisibility}
- onTogglePoll={onTogglePoll}
- onDoodleOpen={onOpenDoodleModal}
- onModalClose={onCloseModal}
- onModalOpen={onOpenActionsModal}
- onToggleSpoiler={spoilersAlwaysOn ? null : onChangeSpoilerness}
- onUpload={onUpload}
- privacy={privacy}
- resetFileKey={resetFileKey}
- sensitive={sensitive || (spoilersAlwaysOn && spoilerText && spoilerText.length > 0)}
- spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler}
- />
-
-
- );
- }
-
-}
-
-// 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')
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 && (
)}
+
+
+ {({ opacity, scaleX, scaleY }) => (
+
+ {message}
+
+ )}
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/compose/containers/warning_container.js b/app/javascript/flavours/glitch/features/compose/containers/warning_container.js
new file mode 100644
index 000000000..fdd21f114
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/containers/warning_container.js
@@ -0,0 +1,44 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import Warning from '../components/warning';
+import PropTypes from 'prop-types';
+import { FormattedMessage } from 'react-intl';
+import { me } from 'flavours/glitch/util/initial_state';
+
+const APPROX_HASHTAG_RE = /(?:^|[^\/\)\w])#(\w*[a-zA-Z·]\w*)/i;
+
+const mapStateToProps = state => ({
+ needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', me, 'locked']),
+ hashtagWarning: state.getIn(['compose', 'privacy']) !== 'public' && APPROX_HASHTAG_RE.test(state.getIn(['compose', 'text'])),
+ directMessageWarning: state.getIn(['compose', 'privacy']) === 'direct',
+});
+
+const WarningWrapper = ({ needsLockWarning, hashtagWarning, directMessageWarning }) => {
+ if (needsLockWarning) {
+ return }} />} />;
+ }
+
+ if (hashtagWarning) {
+ return } />;
+ }
+
+ if (directMessageWarning) {
+ const message = (
+
+
+
+ );
+
+ return ;
+ }
+
+ return null;
+};
+
+WarningWrapper.propTypes = {
+ needsLockWarning: PropTypes.bool,
+ hashtagWarning: PropTypes.bool,
+ directMessageWarning: PropTypes.bool,
+};
+
+export default connect(mapStateToProps)(WarningWrapper);
diff --git a/app/javascript/flavours/glitch/features/composer/direct_warning/index.js b/app/javascript/flavours/glitch/features/composer/direct_warning/index.js
deleted file mode 100644
index 3b1369acd..000000000
--- a/app/javascript/flavours/glitch/features/composer/direct_warning/index.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import React from 'react';
-import Motion from 'flavours/glitch/util/optional_motion';
-import spring from 'react-motion/lib/spring';
-import { defineMessages, FormattedMessage } from 'react-intl';
-import { termsLink} from 'flavours/glitch/util/backend_links';
-
-// This is the spring used with our motion.
-const motionSpring = spring(1, { damping: 35, stiffness: 400 });
-
-// Messages.
-const messages = defineMessages({
- disclaimer: {
- defaultMessage: 'This toot will only be sent to all the mentioned users.',
- id: 'compose_form.direct_message_warning',
- },
- learn_more: {
- defaultMessage: 'Learn more',
- id: 'compose_form.direct_message_warning_learn_more'
- }
-});
-
-// The component.
-export default function ComposerDirectWarning () {
- return (
-
- {({ opacity, scaleX, scaleY }) => (
-
-
-
- { termsLink !== undefined && }
-
-
- )}
-
- );
-}
-
-ComposerDirectWarning.propTypes = {};
diff --git a/app/javascript/flavours/glitch/features/composer/hashtag_warning/index.js b/app/javascript/flavours/glitch/features/composer/hashtag_warning/index.js
deleted file mode 100644
index 716028e4c..000000000
--- a/app/javascript/flavours/glitch/features/composer/hashtag_warning/index.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import React from 'react';
-import Motion from 'flavours/glitch/util/optional_motion';
-import spring from 'react-motion/lib/spring';
-import { defineMessages, FormattedMessage } from 'react-intl';
-
-// This is the spring used with our motion.
-const motionSpring = spring(1, { damping: 35, stiffness: 400 });
-
-// Messages.
-const messages = defineMessages({
- disclaimer: {
- defaultMessage: 'This toot won\'t be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.',
- id: 'compose_form.hashtag_warning',
- },
-});
-
-// The component.
-export default function ComposerHashtagWarning () {
- return (
-
- {({ opacity, scaleX, scaleY }) => (
-
-
-
- )}
-
- );
-}
-
-ComposerHashtagWarning.propTypes = {};
diff --git a/app/javascript/flavours/glitch/features/composer/warning/index.js b/app/javascript/flavours/glitch/features/composer/warning/index.js
deleted file mode 100644
index 8be8acbce..000000000
--- a/app/javascript/flavours/glitch/features/composer/warning/index.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import React from 'react';
-import Motion from 'flavours/glitch/util/optional_motion';
-import spring from 'react-motion/lib/spring';
-import { defineMessages, FormattedMessage } from 'react-intl';
-import { profileLink } from 'flavours/glitch/util/backend_links';
-
-// This is the spring used with our motion.
-const motionSpring = spring(1, { damping: 35, stiffness: 400 });
-
-// Messages.
-const messages = defineMessages({
- disclaimer: {
- defaultMessage: 'Your account is not {locked}. Anyone can follow you to view your follower-only posts.',
- id: 'compose_form.lock_disclaimer',
- },
- locked: {
- defaultMessage: 'locked',
- id: 'compose_form.lock_disclaimer.lock',
- },
-});
-
-// The component.
-export default function ComposerWarning () {
- let lockedLink = ;
- if (profileLink !== undefined) {
- lockedLink = {lockedLink};
- }
- return (
-
- {({ opacity, scaleX, scaleY }) => (
-
-
-
- )}
-
- );
-}
-
-ComposerWarning.propTypes = {};
--
cgit
From 8fd599fb40a5a078f26b5f450d88cf12609d9c14 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sat, 20 Apr 2019 22:21:28 +0200
Subject: ComposerReply → ReplyIndicator
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../features/compose/components/compose_form.js | 14 +---
.../features/compose/components/reply_indicator.js | 86 +++++++++++++++++++
.../compose/containers/compose_form_container.js | 7 --
.../containers/reply_indicator_container.js | 22 +++++
.../glitch/features/composer/reply/index.js | 96 ----------------------
5 files changed, 110 insertions(+), 115 deletions(-)
create mode 100644 app/javascript/flavours/glitch/features/compose/components/reply_indicator.js
create mode 100644 app/javascript/flavours/glitch/features/compose/containers/reply_indicator_container.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/reply/index.js
(limited to 'app/javascript')
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 1f37a1da5..10b51d920 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -7,12 +7,12 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
// 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 WarningContainer from '../containers/warning_container';
+import ReplyIndicatorContainer from '../containers/reply_indicator_container';
// Utils.
import { countableText } from 'flavours/glitch/util/counter';
@@ -49,7 +49,6 @@ class ComposeForm extends ImmutablePureComponent {
preselectDate: PropTypes.instanceOf(Date),
privacy: PropTypes.string,
progress: PropTypes.number,
- inReplyTo: ImmutablePropTypes.map,
resetFileKey: PropTypes.number,
sideArm: PropTypes.string,
sensitive: PropTypes.bool,
@@ -65,7 +64,6 @@ class ComposeForm extends ImmutablePureComponent {
preselectOnReply: PropTypes.bool,
// Dispatch props.
- onCancelReply: PropTypes.func,
onChangeAdvancedOption: PropTypes.func,
onChangeDescription: PropTypes.func,
onChangeSensitivity: PropTypes.func,
@@ -283,7 +281,6 @@ class ComposeForm extends ImmutablePureComponent {
layout,
media,
poll,
- onCancelReply,
onChangeAdvancedOption,
onChangeDescription,
onChangeSensitivity,
@@ -301,7 +298,6 @@ class ComposeForm extends ImmutablePureComponent {
onUpload,
privacy,
progress,
- inReplyTo,
resetFileKey,
sensitive,
showSearch,
@@ -319,13 +315,7 @@ class ComposeForm extends ImmutablePureComponent {
- {inReplyTo && (
-
- )}
+
{
+ const { onCancel } = this.props;
+ if (onCancel) {
+ onCancel();
+ }
+ }
+
+ // Rendering.
+ render () {
+ const { status, intl } = this.props;
+
+ if (!status) {
+ return null;
+ }
+
+ const account = status.get('account');
+ const content = status.get('content');
+ const attachments = status.get('media_attachments');
+
+ // The result.
+ return (
+
+
+
+ {account && (
+
+ )}
+
+
+ {attachments.size > 0 && (
+
+ )}
+
+ );
+ }
+
+}
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
index 18fc31dce..3293cc226 100644
--- a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
+++ b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
@@ -1,7 +1,6 @@
import { connect } from 'react-redux';
import ComposeForm from '../components/compose_form';
import {
- cancelReplyCompose,
changeCompose,
changeComposeAdvancedOption,
changeComposeSensitivity,
@@ -68,9 +67,6 @@ function mapStateToProps (state) {
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']),
@@ -90,9 +86,6 @@ function mapStateToProps (state) {
// Dispatch mapping.
const mapDispatchToProps = (dispatch, { intl }) => ({
- onCancelReply() {
- dispatch(cancelReplyCompose());
- },
onChangeAdvancedOption(option, value) {
dispatch(changeComposeAdvancedOption(option, value));
},
diff --git a/app/javascript/flavours/glitch/features/compose/containers/reply_indicator_container.js b/app/javascript/flavours/glitch/features/compose/containers/reply_indicator_container.js
new file mode 100644
index 000000000..395a9aa5b
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/containers/reply_indicator_container.js
@@ -0,0 +1,22 @@
+import { connect } from 'react-redux';
+import { cancelReplyCompose } from 'flavours/glitch/actions/compose';
+import { makeGetStatus } from 'flavours/glitch/selectors';
+import ReplyIndicator from '../components/reply_indicator';
+
+function makeMapStateToProps (state) {
+ const inReplyTo = state.getIn(['compose', 'in_reply_to']);
+
+ return {
+ status: inReplyTo ? state.getIn(['statuses', inReplyTo]) : null,
+ };
+};
+
+const mapDispatchToProps = dispatch => ({
+
+ onCancel () {
+ dispatch(cancelReplyCompose());
+ },
+
+});
+
+export default connect(makeMapStateToProps, mapDispatchToProps)(ReplyIndicator);
diff --git a/app/javascript/flavours/glitch/features/composer/reply/index.js b/app/javascript/flavours/glitch/features/composer/reply/index.js
deleted file mode 100644
index 56e9e96a5..000000000
--- a/app/javascript/flavours/glitch/features/composer/reply/index.js
+++ /dev/null
@@ -1,96 +0,0 @@
-// Package imports.
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { defineMessages } from 'react-intl';
-
-// Components.
-import AccountContainer from 'flavours/glitch/containers/account_container';
-import IconButton from 'flavours/glitch/components/icon_button';
-import AttachmentList from 'flavours/glitch/components/attachment_list';
-
-// Utils.
-import { assignHandlers } from 'flavours/glitch/util/react_helpers';
-import { isRtl } from 'flavours/glitch/util/rtl';
-
-// Messages.
-const messages = defineMessages({
- cancel: {
- defaultMessage: 'Cancel',
- id: 'reply_indicator.cancel',
- },
-});
-
-// Handlers.
-const handlers = {
-
- // Handles a click on the "close" button.
- handleClick () {
- const { onCancel } = this.props;
- if (onCancel) {
- onCancel();
- }
- },
-};
-
-// The component.
-export default class ComposerReply extends React.PureComponent {
-
- // Constructor.
- constructor (props) {
- super(props);
- assignHandlers(this, handlers);
- }
-
- // Rendering.
- render () {
- const { handleClick } = this.handlers;
- const {
- status,
- intl,
- } = this.props;
-
- const account = status.get('account');
- const content = status.get('content');
- const attachments = status.get('media_attachments');
-
- // The result.
- return (
-
-
-
- {account && (
-
- )}
-
-
- {attachments.size > 0 && (
-
- )}
-
- );
- }
-
-}
-
-ComposerReply.propTypes = {
- status: ImmutablePropTypes.map.isRequired,
- intl: PropTypes.object.isRequired,
- onCancel: PropTypes.func,
-};
--
cgit
From f1a22e33e26f124cb1b3131e56678001b9e43bc3 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sat, 20 Apr 2019 23:02:09 +0200
Subject: Inline spoiler input
---
.../features/compose/components/compose_form.js | 51 +++++++---
.../glitch/features/composer/spoiler/index.js | 107 ---------------------
2 files changed, 38 insertions(+), 120 deletions(-)
delete mode 100644 app/javascript/flavours/glitch/features/composer/spoiler/index.js
(limited to 'app/javascript')
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 10b51d920..fdc4401f6 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -7,7 +7,6 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
// Components.
import ComposerOptions from '../../composer/options';
import ComposerPublisher from '../../composer/publisher';
-import ComposerSpoiler from '../../composer/spoiler';
import ComposerTextarea from '../../composer/textarea';
import ComposerUploadForm from '../../composer/upload_form';
import ComposerPollForm from '../../composer/poll_form';
@@ -23,6 +22,7 @@ const messages = defineMessages({
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' },
+ spoiler_placeholder: { id: 'compose_form.spoiler_placeholder', defaultMessage: 'Write your warning here' },
});
export default @injectIntl
@@ -124,6 +124,25 @@ class ComposeForm extends ImmutablePureComponent {
}
}
+ handleKeyDown = ({ ctrlKey, keyCode, metaKey, altKey }) => {
+ // We submit the status on control/meta + enter.
+ if (keyCode === 13 && (ctrlKey || metaKey)) {
+ handleSubmit();
+ }
+
+ // Submit the status with secondary visibility on alt + enter.
+ if (keyCode === 13 && altKey) {
+ handleSecondarySubmit();
+ }
+ }
+
+ // When the escape key is released, we focus the UI.
+ handleKeyUp = ({ key }) => {
+ if (key === 'Escape') {
+ document.querySelector('.ui').parentElement.focus();
+ }
+ }
+
// Submits the status.
handleSubmit = () => {
const { textarea: { value }, uploadForm } = this;
@@ -181,7 +200,7 @@ class ComposeForm extends ImmutablePureComponent {
// Sets a reference to the CW field.
handleRefSpoilerText = (spoilerComponent) => {
if (spoilerComponent) {
- this.spoilerText = spoilerComponent.spoilerText;
+ this.spoilerText = spoilerComponent;
}
}
@@ -260,14 +279,12 @@ class ComposeForm extends ImmutablePureComponent {
render () {
const {
- handleChangeSpoiler,
handleEmoji,
handleSecondarySubmit,
handleSelect,
handleSubmit,
handleRefUploadForm,
handleRefTextarea,
- handleRefSpoilerText,
} = this;
const {
acceptContentTypes,
@@ -317,15 +334,23 @@ class ComposeForm extends ImmutablePureComponent {
-
+
+
+
-
-
- );
- }
-
-}
-
-// Props.
-ComposerSpoiler.propTypes = {
- hidden: PropTypes.bool,
- intl: PropTypes.object.isRequired,
- onChange: PropTypes.func,
- onSubmit: PropTypes.func,
- onSecondarySubmit: PropTypes.func,
- text: PropTypes.string,
-};
--
cgit
From c5f49a92dce9157debf3a68487dd30b6f0af6c4a Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sat, 20 Apr 2019 23:27:20 +0200
Subject: Move PollForm from features/composer to features/compose
---
.../features/compose/components/compose_form.js | 6 +-
.../features/compose/components/poll_form.js | 135 +++++++++++++++++++++
.../compose/containers/poll_form_container.js | 29 +++++
.../composer/poll_form/components/poll_form.js | 135 ---------------------
.../glitch/features/composer/poll_form/index.js | 29 -----
5 files changed, 166 insertions(+), 168 deletions(-)
create mode 100644 app/javascript/flavours/glitch/features/compose/components/poll_form.js
create mode 100644 app/javascript/flavours/glitch/features/compose/containers/poll_form_container.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/poll_form/components/poll_form.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/poll_form/index.js
(limited to 'app/javascript')
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 fdc4401f6..ccbcba571 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -9,7 +9,7 @@ import ComposerOptions from '../../composer/options';
import ComposerPublisher from '../../composer/publisher';
import ComposerTextarea from '../../composer/textarea';
import ComposerUploadForm from '../../composer/upload_form';
-import ComposerPollForm from '../../composer/poll_form';
+import PollFormContainer from '../containers/poll_form_container';
import WarningContainer from '../containers/warning_container';
import ReplyIndicatorContainer from '../containers/reply_indicator_container';
@@ -382,9 +382,7 @@ class ComposeForm extends ImmutablePureComponent {
handleRef={handleRefUploadForm}
/>
) : null}
- {!!poll && (
-
- )}
+
{
+ 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) => )}
+ {options.size < pollLimits.max_options && (
+
+ )}
+
+
+
+
+
+
+
+
+ );
+ }
+
+}
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) => )}
- {options.size < pollLimits.max_options && (
-
- )}
-
-
-
-
-
-
-
-
- );
- }
-
-}
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')
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' && }
+
+
+
+
+
+
+ )}
+
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/compose/components/upload_form.js b/app/javascript/flavours/glitch/features/compose/components/upload_form.js
new file mode 100644
index 000000000..a126cc7e4
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/components/upload_form.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import UploadProgressContainer from '../containers/upload_progress_container';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import UploadContainer from '../containers/upload_container';
+
+export default class UploadForm extends ImmutablePureComponent {
+ static propTypes = {
+ mediaIds: ImmutablePropTypes.list.isRequired,
+ };
+
+ render () {
+ const { mediaIds } = this.props;
+
+ return (
+
+
+
+
+ {mediaIds.map(id => (
+
+ ))}
+
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/compose/components/upload_progress.js b/app/javascript/flavours/glitch/features/compose/components/upload_progress.js
new file mode 100644
index 000000000..264c563f2
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/components/upload_progress.js
@@ -0,0 +1,42 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import Motion from 'flavours/glitch/util/optional_motion';
+import spring from 'react-motion/lib/spring';
+import { FormattedMessage } from 'react-intl';
+import Icon from 'flavours/glitch/components/icon';
+
+export default class UploadProgress extends React.PureComponent {
+
+ static propTypes = {
+ active: PropTypes.bool,
+ progress: PropTypes.number,
+ };
+
+ render () {
+ const { active, progress } = this.props;
+
+ if (!active) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+
+
+
+ {({ width }) =>
+ ()
+ }
+
+
+
+
+ );
+ }
+
+}
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
index 3293cc226..4716d9435 100644
--- a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
+++ b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
@@ -7,14 +7,12 @@ import {
changeComposeSpoilerText,
changeComposeSpoilerness,
changeComposeVisibility,
- changeUploadCompose,
clearComposeSuggestions,
fetchComposeSuggestions,
insertEmojiCompose,
mountCompose,
selectComposeSuggestion,
submitCompose,
- undoUploadCompose,
unmountCompose,
uploadCompose,
} from 'flavours/glitch/actions/compose';
@@ -66,7 +64,6 @@ function mapStateToProps (state) {
media: state.getIn(['compose', 'media_attachments']),
preselectDate: state.getIn(['compose', 'preselectDate']),
privacy: state.getIn(['compose', 'privacy']),
- progress: state.getIn(['compose', 'progress']),
resetFileKey: state.getIn(['compose', 'resetFileKey']),
sideArm: sideArmPrivacy,
sensitive: state.getIn(['compose', 'sensitive']),
@@ -89,9 +86,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
onChangeAdvancedOption(option, value) {
dispatch(changeComposeAdvancedOption(option, value));
},
- onChangeDescription(id, description) {
- dispatch(changeUploadCompose(id, { description }));
- },
onChangeSensitivity() {
dispatch(changeComposeSensitivity());
},
@@ -137,9 +131,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
onOpenDoodleModal() {
dispatch(openModal('DOODLE', { noEsc: true }));
},
- onOpenFocalPointModal(id) {
- dispatch(openModal('FOCAL_POINT', { id }));
- },
onSelectSuggestion(position, token, suggestion) {
dispatch(selectComposeSuggestion(position, token, suggestion));
},
@@ -154,9 +145,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
onSubmit(routerHistory) {
dispatch(submitCompose(routerHistory));
},
- onUndoUpload(id) {
- dispatch(undoUploadCompose(id));
- },
onUnmount() {
dispatch(unmountCompose());
},
diff --git a/app/javascript/flavours/glitch/features/compose/containers/upload_container.js b/app/javascript/flavours/glitch/features/compose/containers/upload_container.js
new file mode 100644
index 000000000..d6bff63ac
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/containers/upload_container.js
@@ -0,0 +1,31 @@
+import { connect } from 'react-redux';
+import Upload from '../components/upload';
+import { undoUploadCompose, changeUploadCompose } from 'flavours/glitch/actions/compose';
+import { openModal } from 'flavours/glitch/actions/modal';
+import { submitCompose } from 'flavours/glitch/actions/compose';
+
+const mapStateToProps = (state, { id }) => ({
+ media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
+});
+
+const mapDispatchToProps = dispatch => ({
+
+ onUndo: id => {
+ dispatch(undoUploadCompose(id));
+ },
+
+ onDescriptionChange: (id, description) => {
+ dispatch(changeUploadCompose(id, { description }));
+ },
+
+ onOpenFocalPoint: id => {
+ dispatch(openModal('FOCAL_POINT', { id }));
+ },
+
+ onSubmit (router) {
+ dispatch(submitCompose(router));
+ },
+
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(Upload);
diff --git a/app/javascript/flavours/glitch/features/compose/containers/upload_form_container.js b/app/javascript/flavours/glitch/features/compose/containers/upload_form_container.js
new file mode 100644
index 000000000..a6798bf51
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/containers/upload_form_container.js
@@ -0,0 +1,8 @@
+import { connect } from 'react-redux';
+import UploadForm from '../components/upload_form';
+
+const mapStateToProps = state => ({
+ mediaIds: state.getIn(['compose', 'media_attachments']).map(item => item.get('id')),
+});
+
+export default connect(mapStateToProps)(UploadForm);
diff --git a/app/javascript/flavours/glitch/features/compose/containers/upload_progress_container.js b/app/javascript/flavours/glitch/features/compose/containers/upload_progress_container.js
new file mode 100644
index 000000000..0cfee96da
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/containers/upload_progress_container.js
@@ -0,0 +1,9 @@
+import { connect } from 'react-redux';
+import UploadProgress from '../components/upload_progress';
+
+const mapStateToProps = state => ({
+ active: state.getIn(['compose', 'is_uploading']),
+ progress: state.getIn(['compose', 'progress']),
+});
+
+export default connect(mapStateToProps)(UploadProgress);
diff --git a/app/javascript/flavours/glitch/features/composer/upload_form/index.js b/app/javascript/flavours/glitch/features/composer/upload_form/index.js
deleted file mode 100644
index c2ff66623..000000000
--- a/app/javascript/flavours/glitch/features/composer/upload_form/index.js
+++ /dev/null
@@ -1,60 +0,0 @@
-// Package imports.
-import classNames from 'classnames';
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-
-// Components.
-import ComposerUploadFormItem from './item';
-import ComposerUploadFormProgress from './progress';
-
-// The component.
-export default function ComposerUploadForm ({
- intl,
- media,
- onChangeDescription,
- onOpenFocalPointModal,
- onRemove,
- progress,
- uploading,
- handleRef,
-}) {
- const computedClass = classNames('composer--upload_form', { uploading });
-
- // The result.
- return (
-
- {uploading ?
: null}
- {media ? (
-
- {media.map(item => (
-
- ))}
-
- ) : null}
-
- );
-}
-
-// Props.
-ComposerUploadForm.propTypes = {
- intl: PropTypes.object.isRequired,
- media: ImmutablePropTypes.list,
- onChangeDescription: PropTypes.func.isRequired,
- onRemove: PropTypes.func.isRequired,
- progress: PropTypes.number,
- uploading: PropTypes.bool,
- handleRef: PropTypes.func,
-};
diff --git a/app/javascript/flavours/glitch/features/composer/upload_form/item/index.js b/app/javascript/flavours/glitch/features/composer/upload_form/item/index.js
deleted file mode 100644
index 4f5f66f04..000000000
--- a/app/javascript/flavours/glitch/features/composer/upload_form/item/index.js
+++ /dev/null
@@ -1,202 +0,0 @@
-// Package imports.
-import classNames from 'classnames';
-import PropTypes from 'prop-types';
-import React from 'react';
-import {
- FormattedMessage,
- defineMessages,
-} from 'react-intl';
-import spring from 'react-motion/lib/spring';
-
-// Components.
-import IconButton from 'flavours/glitch/components/icon_button';
-
-// Utils.
-import Motion from 'flavours/glitch/util/optional_motion';
-import { assignHandlers } from 'flavours/glitch/util/react_helpers';
-import { isUserTouching } from 'flavours/glitch/util/is_mobile';
-
-// Messages.
-const messages = defineMessages({
- undo: {
- defaultMessage: 'Undo',
- id: 'upload_form.undo',
- },
- description: {
- defaultMessage: 'Describe for the visually impaired',
- id: 'upload_form.description',
- },
- crop: {
- defaultMessage: 'Crop',
- id: 'upload_form.focus',
- },
-});
-
-// Handlers.
-const handlers = {
-
- // On blur, we save the description for the media item.
- handleBlur () {
- const {
- id,
- onChangeDescription,
- } = this.props;
- const { dirtyDescription } = this.state;
-
- this.setState({ dirtyDescription: null, focused: false });
-
- if (id && onChangeDescription && dirtyDescription !== null) {
- onChangeDescription(id, dirtyDescription);
- }
- },
-
- // When the value of our description changes, we store it in the
- // temp value `dirtyDescription` in our state.
- handleChange ({ target: { value } }) {
- this.setState({ dirtyDescription: value });
- },
-
- // Records focus on the media item.
- handleFocus () {
- this.setState({ focused: true });
- },
-
- // Records the start of a hover over the media item.
- handleMouseEnter () {
- this.setState({ hovered: true });
- },
-
- // Records the end of a hover over the media item.
- handleMouseLeave () {
- this.setState({ hovered: false });
- },
-
- // Removes the media item.
- handleRemove () {
- const {
- id,
- onRemove,
- } = this.props;
- if (id && onRemove) {
- onRemove(id);
- }
- },
-
- // Opens the focal point modal.
- handleFocalPointClick () {
- const {
- id,
- onOpenFocalPointModal,
- } = this.props;
- if (id && onOpenFocalPointModal) {
- onOpenFocalPointModal(id);
- }
- },
-};
-
-// The component.
-export default class ComposerUploadFormItem extends React.PureComponent {
-
- // Constructor.
- constructor (props) {
- super(props);
- assignHandlers(this, handlers);
- this.state = {
- hovered: false,
- focused: false,
- dirtyDescription: null,
- };
- }
-
- // Rendering.
- render () {
- const {
- handleBlur,
- handleChange,
- handleFocus,
- handleMouseEnter,
- handleMouseLeave,
- handleRemove,
- handleFocalPointClick,
- } = this.handlers;
- const {
- intl,
- preview,
- focusX,
- focusY,
- mediaType,
- } = this.props;
- const {
- focused,
- hovered,
- dirtyDescription,
- } = this.state;
- const active = hovered || focused || isUserTouching();
- const computedClass = classNames('composer--upload_form--item', { active });
- const x = ((focusX / 2) + .5) * 100;
- const y = ((focusY / -2) + .5) * 100;
- const description = dirtyDescription || (dirtyDescription !== '' && this.props.description) || '';
-
- // The result.
- return (
-
-
- {({ scale }) => (
-
-
-
- {mediaType === 'image' && }
-
-
-
- )}
-
-
- );
- }
-
-}
-
-// Props.
-ComposerUploadFormItem.propTypes = {
- description: PropTypes.string,
- id: PropTypes.string,
- intl: PropTypes.object.isRequired,
- onChangeDescription: PropTypes.func.isRequired,
- onOpenFocalPointModal: PropTypes.func.isRequired,
- onRemove: PropTypes.func.isRequired,
- focusX: PropTypes.number,
- focusY: PropTypes.number,
- mediaType: PropTypes.string,
- preview: PropTypes.string,
-};
diff --git a/app/javascript/flavours/glitch/features/composer/upload_form/progress/index.js b/app/javascript/flavours/glitch/features/composer/upload_form/progress/index.js
deleted file mode 100644
index 8c4b0eea6..000000000
--- a/app/javascript/flavours/glitch/features/composer/upload_form/progress/index.js
+++ /dev/null
@@ -1,52 +0,0 @@
-// Package imports.
-import PropTypes from 'prop-types';
-import React from 'react';
-import {
- defineMessages,
- FormattedMessage,
-} from 'react-intl';
-import spring from 'react-motion/lib/spring';
-
-// Components.
-import Icon from 'flavours/glitch/components/icon';
-
-// Utils.
-import Motion from 'flavours/glitch/util/optional_motion';
-
-// Messages.
-const messages = defineMessages({
- upload: {
- defaultMessage: 'Uploading...',
- id: 'upload_progress.label',
- },
-});
-
-// The component.
-export default function ComposerUploadFormProgress ({ progress }) {
-
- // The result.
- return (
-
-
-
-
-
-
- {({ width }) =>
- ()
- }
-
-
-
-
- );
-}
-
-// Props.
-ComposerUploadFormProgress.propTypes = { progress: PropTypes.number };
--
cgit
From 47faf47ed5a20d7d959110caefe6839d12343ec7 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sun, 21 Apr 2019 12:44:30 +0200
Subject: ComposerTextarea → AutosuggestTextarea
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../glitch/components/autosuggest_emoji.js | 42 +++
.../glitch/components/autosuggest_textarea.js | 224 +++++++++++++++
.../compose/components/autosuggest_account.js | 24 ++
.../features/compose/components/compose_form.js | 54 ++--
.../containers/autosuggest_account_container.js | 15 +
.../glitch/features/composer/textarea/index.js | 312 ---------------------
.../composer/textarea/suggestions/index.js | 43 ---
.../composer/textarea/suggestions/item/index.js | 118 --------
.../glitch/styles/components/composer.scss | 13 +-
.../glitch/styles/mastodon-light/diff.scss | 4 +-
10 files changed, 350 insertions(+), 499 deletions(-)
create mode 100644 app/javascript/flavours/glitch/components/autosuggest_emoji.js
create mode 100644 app/javascript/flavours/glitch/components/autosuggest_textarea.js
create mode 100644 app/javascript/flavours/glitch/features/compose/components/autosuggest_account.js
create mode 100644 app/javascript/flavours/glitch/features/compose/containers/autosuggest_account_container.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/textarea/index.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/textarea/suggestions/index.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/textarea/suggestions/item/index.js
(limited to 'app/javascript')
diff --git a/app/javascript/flavours/glitch/components/autosuggest_emoji.js b/app/javascript/flavours/glitch/components/autosuggest_emoji.js
new file mode 100644
index 000000000..c8609e48f
--- /dev/null
+++ b/app/javascript/flavours/glitch/components/autosuggest_emoji.js
@@ -0,0 +1,42 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import unicodeMapping from 'flavours/glitch/util/emoji/emoji_unicode_mapping_light';
+
+const assetHost = process.env.CDN_HOST || '';
+
+export default class AutosuggestEmoji extends React.PureComponent {
+
+ static propTypes = {
+ emoji: PropTypes.object.isRequired,
+ };
+
+ render () {
+ const { emoji } = this.props;
+ let url;
+
+ if (emoji.custom) {
+ url = emoji.imageUrl;
+ } else {
+ const mapping = unicodeMapping[emoji.native] || unicodeMapping[emoji.native.replace(/\uFE0F$/, '')];
+
+ if (!mapping) {
+ return null;
+ }
+
+ url = `${assetHost}/emoji/${mapping.filename}.svg`;
+ }
+
+ return (
+
+
+
+ {emoji.colons}
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/components/autosuggest_textarea.js b/app/javascript/flavours/glitch/components/autosuggest_textarea.js
new file mode 100644
index 000000000..af8fbe406
--- /dev/null
+++ b/app/javascript/flavours/glitch/components/autosuggest_textarea.js
@@ -0,0 +1,224 @@
+import React from 'react';
+import AutosuggestAccountContainer from 'flavours/glitch/features/compose/containers/autosuggest_account_container';
+import AutosuggestEmoji from './autosuggest_emoji';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import { isRtl } from 'flavours/glitch/util/rtl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import Textarea from 'react-textarea-autosize';
+import classNames from 'classnames';
+
+const textAtCursorMatchesToken = (str, caretPosition) => {
+ let word;
+
+ let left = str.slice(0, caretPosition).search(/[^\s\u200B]+$/);
+ let right = str.slice(caretPosition).search(/[\s\u200B]/);
+
+ if (right < 0) {
+ word = str.slice(left);
+ } else {
+ word = str.slice(left, right + caretPosition);
+ }
+
+ if (!word || word.trim().length < 3 || ['@', ':', '#'].indexOf(word[0]) === -1) {
+ return [null, null];
+ }
+
+ word = word.trim().toLowerCase();
+
+ if (word.length > 0) {
+ return [left, word];
+ } else {
+ return [null, null];
+ }
+};
+
+export default class AutosuggestTextarea extends ImmutablePureComponent {
+
+ static propTypes = {
+ value: PropTypes.string,
+ suggestions: ImmutablePropTypes.list,
+ disabled: PropTypes.bool,
+ placeholder: PropTypes.string,
+ onSuggestionSelected: PropTypes.func.isRequired,
+ onSuggestionsClearRequested: PropTypes.func.isRequired,
+ onSuggestionsFetchRequested: PropTypes.func.isRequired,
+ onChange: PropTypes.func.isRequired,
+ onKeyUp: PropTypes.func,
+ onKeyDown: PropTypes.func,
+ onPaste: PropTypes.func.isRequired,
+ autoFocus: PropTypes.bool,
+ };
+
+ static defaultProps = {
+ autoFocus: true,
+ };
+
+ state = {
+ suggestionsHidden: false,
+ selectedSuggestion: 0,
+ lastToken: null,
+ tokenStart: 0,
+ };
+
+ onChange = (e) => {
+ const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart);
+
+ if (token !== null && this.state.lastToken !== token) {
+ this.setState({ lastToken: token, selectedSuggestion: 0, tokenStart });
+ this.props.onSuggestionsFetchRequested(token);
+ } else if (token === null) {
+ this.setState({ lastToken: null });
+ this.props.onSuggestionsClearRequested();
+ }
+
+ this.props.onChange(e);
+ }
+
+ onKeyDown = (e) => {
+ const { suggestions, disabled } = this.props;
+ const { selectedSuggestion, suggestionsHidden } = this.state;
+
+ if (disabled) {
+ e.preventDefault();
+ return;
+ }
+
+ if (e.which === 229 || e.isComposing) {
+ // Ignore key events during text composition
+ // e.key may be a name of the physical key even in this case (e.x. Safari / Chrome on Mac)
+ return;
+ }
+
+ switch(e.key) {
+ case 'Escape':
+ if (suggestions.size === 0 || suggestionsHidden) {
+ document.querySelector('.ui').parentElement.focus();
+ } else {
+ e.preventDefault();
+ this.setState({ suggestionsHidden: true });
+ }
+
+ break;
+ case 'ArrowDown':
+ if (suggestions.size > 0 && !suggestionsHidden) {
+ e.preventDefault();
+ this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
+ }
+
+ break;
+ case 'ArrowUp':
+ if (suggestions.size > 0 && !suggestionsHidden) {
+ e.preventDefault();
+ this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) });
+ }
+
+ break;
+ case 'Enter':
+ case 'Tab':
+ // Select suggestion
+ if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) {
+ e.preventDefault();
+ e.stopPropagation();
+ this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
+ }
+
+ break;
+ }
+
+ if (e.defaultPrevented || !this.props.onKeyDown) {
+ return;
+ }
+
+ this.props.onKeyDown(e);
+ }
+
+ onBlur = () => {
+ this.setState({ suggestionsHidden: true });
+ }
+
+ onSuggestionClick = (e) => {
+ const suggestion = this.props.suggestions.get(e.currentTarget.getAttribute('data-index'));
+ e.preventDefault();
+ this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
+ this.textarea.focus();
+ }
+
+ componentWillReceiveProps (nextProps) {
+ if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden) {
+ this.setState({ suggestionsHidden: false });
+ }
+ }
+
+ setTextarea = (c) => {
+ this.textarea = c;
+ }
+
+ onPaste = (e) => {
+ if (e.clipboardData && e.clipboardData.files.length === 1) {
+ this.props.onPaste(e.clipboardData.files);
+ e.preventDefault();
+ }
+ }
+
+ renderSuggestion = (suggestion, i) => {
+ const { selectedSuggestion } = this.state;
+ let inner, key;
+
+ if (typeof suggestion === 'object') {
+ inner = ;
+ key = suggestion.id;
+ } else if (suggestion[0] === '#') {
+ inner = suggestion;
+ key = suggestion;
+ } else {
+ inner = ;
+ key = suggestion;
+ }
+
+ return (
+
+ {inner}
+
+ );
+ }
+
+ render () {
+ const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus } = this.props;
+ const { suggestionsHidden } = this.state;
+ const style = { direction: 'ltr' };
+
+ if (isRtl(value)) {
+ style.direction = 'rtl';
+ }
+
+ return (
+
+
+
+
+ {suggestions.map(this.renderSuggestion)}
+
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/compose/components/autosuggest_account.js b/app/javascript/flavours/glitch/features/compose/components/autosuggest_account.js
new file mode 100644
index 000000000..fb9bb5035
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/components/autosuggest_account.js
@@ -0,0 +1,24 @@
+import React from 'react';
+import Avatar from 'flavours/glitch/components/avatar';
+import DisplayName from 'flavours/glitch/components/display_name';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+export default class AutosuggestAccount extends ImmutablePureComponent {
+
+ static propTypes = {
+ account: ImmutablePropTypes.map.isRequired,
+ };
+
+ render () {
+ const { account } = this.props;
+
+ return (
+
+ );
+ }
+
+}
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 ecd1aed69..2ea137965 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -7,17 +7,20 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
// Components.
import ComposerOptions from '../../composer/options';
import ComposerPublisher from '../../composer/publisher';
-import ComposerTextarea from '../../composer/textarea';
+import ComposerTextareaIcons from '../../composer/textarea/icons';
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';
+import EmojiPicker from 'flavours/glitch/features/emoji_picker';
+import AutosuggestTextarea from '../../../components/autosuggest_textarea';
// Utils.
import { countableText } from 'flavours/glitch/util/counter';
import { isMobile } from 'flavours/glitch/util/is_mobile';
const messages = defineMessages({
+ placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
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',
@@ -183,7 +186,7 @@ class ComposeForm extends ImmutablePureComponent {
}
// Sets a reference to the textarea.
- handleRefTextarea = (textareaComponent) => {
+ setAutosuggestTextarea = (textareaComponent) => {
if (textareaComponent) {
this.textarea = textareaComponent.textarea;
}
@@ -269,6 +272,10 @@ class ComposeForm extends ImmutablePureComponent {
}
}
+ handleChange = (e) => {
+ this.props.onChangeText(e.target.value);
+ }
+
render () {
const {
handleEmoji,
@@ -339,27 +346,35 @@ class ComposeForm extends ImmutablePureComponent {
-
+
+
+
0)}
spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler}
/>
+
{
+ const getAccount = makeGetAccount();
+
+ const mapStateToProps = (state, { id }) => ({
+ account: getAccount(state, id),
+ });
+
+ return mapStateToProps;
+};
+
+export default connect(makeMapStateToProps)(AutosuggestAccount);
diff --git a/app/javascript/flavours/glitch/features/composer/textarea/index.js b/app/javascript/flavours/glitch/features/composer/textarea/index.js
deleted file mode 100644
index 50e46fc78..000000000
--- a/app/javascript/flavours/glitch/features/composer/textarea/index.js
+++ /dev/null
@@ -1,312 +0,0 @@
-// Package imports.
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import {
- defineMessages,
- FormattedMessage,
-} from 'react-intl';
-import Textarea from 'react-textarea-autosize';
-
-// Components.
-import EmojiPicker from 'flavours/glitch/features/emoji_picker';
-import ComposerTextareaIcons from './icons';
-import ComposerTextareaSuggestions from './suggestions';
-
-// Utils.
-import { isRtl } from 'flavours/glitch/util/rtl';
-import {
- assignHandlers,
- hiddenComponent,
-} from 'flavours/glitch/util/react_helpers';
-
-// Messages.
-const messages = defineMessages({
- placeholder: {
- defaultMessage: 'What is on your mind?',
- id: 'compose_form.placeholder',
- },
-});
-
-// Handlers.
-const handlers = {
-
- // When blurring the textarea, suggestions are hidden.
- handleBlur () {
- this.setState({ suggestionsHidden: true });
- },
-
- // When the contents of the textarea change, we have to pull up new
- // autosuggest suggestions if applicable, and also change the value
- // of the textarea in our store.
- handleChange ({
- target: {
- selectionStart,
- value,
- },
- }) {
- const {
- onChange,
- onSuggestionsFetchRequested,
- onSuggestionsClearRequested,
- } = this.props;
- const { lastToken } = this.state;
-
- // This gets the token at the caret location, if it begins with an
- // `@` (mentions) or `:` (shortcodes).
- const left = value.slice(0, selectionStart).search(/[^\s\u200B]+$/);
- const right = value.slice(selectionStart).search(/[\s\u200B]/);
- const token = function () {
- switch (true) {
- case left < 0 || !/[@:#]/.test(value[left]):
- return null;
- case right < 0:
- return value.slice(left);
- default:
- return value.slice(left, right + selectionStart).trim().toLowerCase();
- }
- }();
-
- // We only request suggestions for tokens which are at least 3
- // characters long.
- if (onSuggestionsFetchRequested && token && token.length >= 3) {
- if (lastToken !== token) {
- this.setState({
- lastToken: token,
- selectedSuggestion: 0,
- tokenStart: left,
- });
- onSuggestionsFetchRequested(token);
- }
- } else {
- this.setState({ lastToken: null });
- if (onSuggestionsClearRequested) {
- onSuggestionsClearRequested();
- }
- }
-
- // Updates the value of the textarea.
- if (onChange) {
- onChange(value);
- }
- },
-
- // Handles a click on an autosuggestion.
- handleClickSuggestion (index) {
- const { textarea } = this;
- const {
- onSuggestionSelected,
- suggestions,
- } = this.props;
- const {
- lastToken,
- tokenStart,
- } = this.state;
- onSuggestionSelected(tokenStart, lastToken, suggestions.get(index));
- textarea.focus();
- },
-
- // Handles a keypress. If the autosuggestions are visible, we need
- // to allow keypresses to navigate and sleect them.
- handleKeyDown (e) {
- const {
- disabled,
- onSubmit,
- onSecondarySubmit,
- onSuggestionSelected,
- suggestions,
- } = this.props;
- const {
- lastToken,
- suggestionsHidden,
- selectedSuggestion,
- tokenStart,
- } = this.state;
-
- // Keypresses do nothing if the composer is disabled.
- if (disabled) {
- e.preventDefault();
- return;
- }
-
- // We submit the status on control/meta + enter.
- if (onSubmit && e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
- onSubmit();
- }
-
- // Submit the status with secondary visibility on alt + enter.
- if (onSecondarySubmit && e.keyCode === 13 && e.altKey) {
- onSecondarySubmit();
- }
-
- // Switches over the pressed key.
- switch(e.key) {
-
- // On arrow down, we pick the next suggestion.
- case 'ArrowDown':
- if (suggestions && suggestions.size > 0 && !suggestionsHidden) {
- e.preventDefault();
- this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
- }
- return;
-
- // On arrow up, we pick the previous suggestion.
- case 'ArrowUp':
- if (suggestions && suggestions.size > 0 && !suggestionsHidden) {
- e.preventDefault();
- this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) });
- }
- return;
-
- // On enter or tab, we select the suggestion.
- case 'Enter':
- case 'Tab':
- if (onSuggestionSelected && lastToken !== null && suggestions && suggestions.size > 0 && !suggestionsHidden) {
- e.preventDefault();
- e.stopPropagation();
- onSuggestionSelected(tokenStart, lastToken, suggestions.get(selectedSuggestion));
- }
- return;
- }
- },
-
- // When the escape key is released, we either close the suggestions
- // window or focus the UI.
- handleKeyUp ({ key }) {
- const { suggestionsHidden } = this.state;
- if (key === 'Escape') {
- if (!suggestionsHidden) {
- this.setState({ suggestionsHidden: true });
- } else {
- document.querySelector('.ui').parentElement.focus();
- }
- }
- },
-
- // Handles the pasting of images into the composer.
- handlePaste (e) {
- const { onPaste } = this.props;
- let d;
- if (onPaste && (d = e.clipboardData) && (d = d.files).length === 1) {
- onPaste(d);
- e.preventDefault();
- }
- },
-
- // Saves a reference to the textarea.
- handleRefTextarea (textarea) {
- this.textarea = textarea;
- },
-};
-
-// The component.
-export default class ComposerTextarea extends React.Component {
-
- // Constructor.
- constructor (props) {
- super(props);
- assignHandlers(this, handlers);
- this.state = {
- suggestionsHidden: false,
- selectedSuggestion: 0,
- lastToken: null,
- tokenStart: 0,
- };
-
- // Instance variables.
- this.textarea = null;
- }
-
- // When we receive new suggestions, we unhide the suggestions window
- // if we didn't have any suggestions before.
- componentWillReceiveProps (nextProps) {
- const { suggestions } = this.props;
- const { suggestionsHidden } = this.state;
- if (nextProps.suggestions && nextProps.suggestions !== suggestions && nextProps.suggestions.size > 0 && suggestionsHidden) {
- this.setState({ suggestionsHidden: false });
- }
- }
-
- // Rendering.
- render () {
- const {
- handleBlur,
- handleChange,
- handleClickSuggestion,
- handleKeyDown,
- handleKeyUp,
- handlePaste,
- handleRefTextarea,
- } = this.handlers;
- const {
- advancedOptions,
- autoFocus,
- disabled,
- intl,
- onPickEmoji,
- suggestions,
- value,
- } = this.props;
- const {
- selectedSuggestion,
- suggestionsHidden,
- } = this.state;
-
- // The result.
- return (
-
-
-
-
-
- );
- }
-
-}
-
-// Props.
-ComposerTextarea.propTypes = {
- advancedOptions: ImmutablePropTypes.map,
- autoFocus: PropTypes.bool,
- disabled: PropTypes.bool,
- intl: PropTypes.object.isRequired,
- onChange: PropTypes.func,
- onPaste: PropTypes.func,
- onPickEmoji: PropTypes.func,
- onSubmit: PropTypes.func,
- onSecondarySubmit: PropTypes.func,
- onSuggestionsClearRequested: PropTypes.func,
- onSuggestionsFetchRequested: PropTypes.func,
- onSuggestionSelected: PropTypes.func,
- suggestions: ImmutablePropTypes.list,
- value: PropTypes.string,
-};
-
-// Default props.
-ComposerTextarea.defaultProps = { autoFocus: true };
diff --git a/app/javascript/flavours/glitch/features/composer/textarea/suggestions/index.js b/app/javascript/flavours/glitch/features/composer/textarea/suggestions/index.js
deleted file mode 100644
index dc72585f2..000000000
--- a/app/javascript/flavours/glitch/features/composer/textarea/suggestions/index.js
+++ /dev/null
@@ -1,43 +0,0 @@
-// Package imports.
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-
-// Components.
-import ComposerTextareaSuggestionsItem from './item';
-
-// The component.
-export default function ComposerTextareaSuggestions ({
- hidden,
- onSuggestionClick,
- suggestions,
- value,
-}) {
-
- // The result.
- return (
-
- {!hidden && suggestions ? suggestions.map(
- (suggestion, index) => (
-
- )
- ) : null}
-
- );
-}
-
-ComposerTextareaSuggestions.propTypes = {
- hidden: PropTypes.bool,
- onSuggestionClick: PropTypes.func,
- suggestions: ImmutablePropTypes.list,
- value: PropTypes.number,
-};
diff --git a/app/javascript/flavours/glitch/features/composer/textarea/suggestions/item/index.js b/app/javascript/flavours/glitch/features/composer/textarea/suggestions/item/index.js
deleted file mode 100644
index 1b7ae8904..000000000
--- a/app/javascript/flavours/glitch/features/composer/textarea/suggestions/item/index.js
+++ /dev/null
@@ -1,118 +0,0 @@
-// Package imports.
-import classNames from 'classnames';
-import PropTypes from 'prop-types';
-import React from 'react';
-
-// Components.
-import AccountContainer from 'flavours/glitch/containers/account_container';
-
-// Utils.
-import { unicodeMapping } from 'flavours/glitch/util/emoji';
-import { assignHandlers } from 'flavours/glitch/util/react_helpers';
-
-// Gets our asset host from the environment, if available.
-const assetHost = process.env.CDN_HOST || '';
-
-// Handlers.
-const handlers = {
-
- // Handles a click on a suggestion.
- handleClick (e) {
- const {
- index,
- onClick,
- } = this.props;
- if (onClick) {
- e.preventDefault();
- e.stopPropagation(); // Prevents following account links
- onClick(index);
- }
- },
-
- // This prevents the focus from changing, which would mess with
- // our suggestion code.
- handleMouseDown (e) {
- e.preventDefault();
- },
-};
-
-// The component.
-export default class ComposerTextareaSuggestionsItem extends React.Component {
-
- // Constructor.
- constructor (props) {
- super(props);
- assignHandlers(this, handlers);
- }
-
- // Rendering.
- render () {
- const {
- handleMouseDown,
- handleClick,
- } = this.handlers;
- const {
- selected,
- suggestion,
- } = this.props;
- const computedClass = classNames('composer--textarea--suggestions--item', { selected });
-
- // If the suggestion is an object, then we render an emoji.
- // Otherwise, we render a hashtag if it starts with #, or an account.
- let inner;
- if (typeof suggestion === 'object') {
- let url;
- if (suggestion.custom) {
- url = suggestion.imageUrl;
- } else {
- const mapping = unicodeMapping[suggestion.native] || unicodeMapping[suggestion.native.replace(/\uFE0F$/, '')];
- if (mapping) {
- url = `${assetHost}/emoji/${mapping.filename}.svg`;
- }
- }
- if (url) {
- inner = (
-
-
- {suggestion.colons}
-
- );
- }
- } else if (suggestion[0] === '#') {
- inner = suggestion;
- } else {
- inner = (
-
- );
- }
-
- // The result.
- return (
-
- { inner }
-
- );
- }
-
-}
-
-// Props.
-ComposerTextareaSuggestionsItem.propTypes = {
- index: PropTypes.number,
- onClick: PropTypes.func,
- selected: PropTypes.bool,
- suggestion: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
-};
diff --git a/app/javascript/flavours/glitch/styles/components/composer.scss b/app/javascript/flavours/glitch/styles/components/composer.scss
index f0729bedc..466b654de 100644
--- a/app/javascript/flavours/glitch/styles/components/composer.scss
+++ b/app/javascript/flavours/glitch/styles/components/composer.scss
@@ -131,8 +131,8 @@
.composer--textarea {
position: relative;
- & > label {
- .textarea {
+ label {
+ .autosuggest-textarea__textarea {
display: block;
box-sizing: border-box;
margin: 0;
@@ -186,7 +186,7 @@
}
}
-.composer--textarea--suggestions {
+.autosuggest-textarea__suggestions {
display: block;
position: absolute;
box-sizing: border-box;
@@ -199,11 +199,14 @@
box-shadow: 4px 4px 6px rgba($base-shadow-color, 0.4);
font-size: 14px;
z-index: 99;
+ display: none;
+}
- &[hidden] { display: none }
+.autosuggest-textarea__suggestions--visible {
+ display: block;
}
-.composer--textarea--suggestions--item {
+.autosuggest-textarea__suggestions__item {
display: flex;
flex-direction: row;
align-items: center;
diff --git a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
index 6f105d3fa..224272f24 100644
--- a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
+++ b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
@@ -196,11 +196,11 @@
border-color: $ui-base-color;
}
-.composer--textarea--suggestions {
+.autosuggest-textarea__suggestions {
background: lighten($ui-base-color, 10%)
}
-.composer--textarea--suggestions--item {
+.autosuggest-textarea__suggestions__item {
&:hover,
&:focus,
&:active,
--
cgit
From 4c6221929f608764c616c2b909eccdc8a17e8909 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sun, 21 Apr 2019 15:57:06 +0200
Subject: Move ComposerTextareaIcons to TextareaIcons
---
.../features/compose/components/compose_form.js | 7 +--
.../features/compose/components/textarea_icons.js | 59 +++++++++++++++++++++
.../features/composer/textarea/icons/index.js | 60 ----------------------
3 files changed, 61 insertions(+), 65 deletions(-)
create mode 100644 app/javascript/flavours/glitch/features/compose/components/textarea_icons.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/textarea/icons/index.js
(limited to 'app/javascript')
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 2ea137965..ae22f4d6d 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -7,7 +7,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
// Components.
import ComposerOptions from '../../composer/options';
import ComposerPublisher from '../../composer/publisher';
-import ComposerTextareaIcons from '../../composer/textarea/icons';
+import TextareaIcons from './textarea_icons';
import UploadFormContainer from '../containers/upload_form_container';
import PollFormContainer from '../containers/poll_form_container';
import WarningContainer from '../containers/warning_container';
@@ -347,10 +347,7 @@ class ComposeForm extends ImmutablePureComponent {
-
+
+ {advancedOptions ? iconMap.map(
+ ([key, icon, message]) => advancedOptions.get(key) ? (
+
+
+
+ ) : null
+ ) : null}
+
+ );
+ }
+}
diff --git a/app/javascript/flavours/glitch/features/composer/textarea/icons/index.js b/app/javascript/flavours/glitch/features/composer/textarea/icons/index.js
deleted file mode 100644
index 049cdd5cd..000000000
--- a/app/javascript/flavours/glitch/features/composer/textarea/icons/index.js
+++ /dev/null
@@ -1,60 +0,0 @@
-// Package imports.
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { defineMessages } from 'react-intl';
-
-// Components.
-import Icon from 'flavours/glitch/components/icon';
-
-// Messages.
-const messages = defineMessages({
- localOnly: {
- defaultMessage: 'This post is local-only',
- id: 'advanced_options.local-only.tooltip',
- },
- threadedMode: {
- defaultMessage: 'Threaded mode enabled',
- id: 'advanced_options.threaded_mode.tooltip',
- },
-});
-
-// We use an array of tuples here instead of an object because it
-// preserves order.
-const iconMap = [
- ['do_not_federate', 'home', messages.localOnly],
- ['threaded_mode', 'comments', messages.threadedMode],
-];
-
-// The component.
-export default function ComposerTextareaIcons ({
- advancedOptions,
- intl,
-}) {
-
- // The result. We just map every active option to its icon.
- return (
-
- {advancedOptions ? iconMap.map(
- ([key, icon, message]) => advancedOptions.get(key) ? (
-
-
-
- ) : null
- ) : null}
-
- );
-}
-
-// Props.
-ComposerTextareaIcons.propTypes = {
- advancedOptions: ImmutablePropTypes.map,
- intl: PropTypes.object.isRequired,
-};
--
cgit
From 14028655df1b94a1722d6e329174947db45db477 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sun, 21 Apr 2019 17:17:10 +0200
Subject: Move composer Dropdown from features/composer to features/compose
---
.../features/compose/components/compose_form.js | 5 +-
.../glitch/features/compose/components/dropdown.js | 229 +++++++++++++
.../features/compose/components/dropdown_menu.js | 219 ++++++++++++
.../glitch/features/compose/components/options.js | 352 +++++++++++++++++++
.../composer/options/dropdown/content/index.js | 146 --------
.../options/dropdown/content/item/index.js | 129 -------
.../features/composer/options/dropdown/index.js | 229 -------------
.../glitch/features/composer/options/index.js | 377 ---------------------
8 files changed, 802 insertions(+), 884 deletions(-)
create mode 100644 app/javascript/flavours/glitch/features/compose/components/dropdown.js
create mode 100644 app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js
create mode 100644 app/javascript/flavours/glitch/features/compose/components/options.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/options/dropdown/content/index.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/options/dropdown/content/item/index.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/options/dropdown/index.js
delete mode 100644 app/javascript/flavours/glitch/features/composer/options/index.js
(limited to 'app/javascript')
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 ae22f4d6d..a9be9c751 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -5,7 +5,7 @@ import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
// Components.
-import ComposerOptions from '../../composer/options';
+import Options from './options';
import ComposerPublisher from '../../composer/publisher';
import TextareaIcons from './textarea_icons';
import UploadFormContainer from '../containers/upload_form_container';
@@ -372,7 +372,7 @@ class ComposeForm extends ImmutablePureComponent {
-
({
+ ...rest,
+ active: value && name === value,
+ name,
+ onClick (e) {
+ e.preventDefault(); // Prevents focus from changing
+ onModalClose();
+ onChange(name);
+ },
+ onPassiveClick (e) {
+ e.preventDefault(); // Prevents focus from changing
+ onChange(name);
+ component.setState({ needsModalUpdate: true });
+ },
+ })
+ ),
+ };
+ },
+
+ // Toggles opening and closing the dropdown.
+ handleToggle ({ target }) {
+ const { handleMakeModal } = this.handlers;
+ const { onModalOpen } = this.props;
+ const { open } = this.state;
+
+ // If this is a touch device, we open a modal instead of the
+ // dropdown.
+ if (isUserTouching()) {
+
+ // This gets the modal to open.
+ const modal = handleMakeModal();
+
+ // If we can, we then open the modal.
+ if (modal && onModalOpen) {
+ onModalOpen(modal);
+ return;
+ }
+ }
+
+ const { top } = target.getBoundingClientRect();
+ this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' });
+ // Otherwise, we just set our state to open.
+ this.setState({ open: !open });
+ },
+
+ // If our modal is open and our props update, we need to also update
+ // the modal.
+ handleUpdate () {
+ const { handleMakeModal } = this.handlers;
+ const { onModalOpen } = this.props;
+ const { needsModalUpdate } = this.state;
+
+ // Gets our modal object.
+ const modal = handleMakeModal();
+
+ // Reopens the modal with the new object.
+ if (needsModalUpdate && modal && onModalOpen) {
+ onModalOpen(modal);
+ }
+ },
+};
+
+// The component.
+export default class ComposerOptionsDropdown extends React.PureComponent {
+
+ // Constructor.
+ constructor (props) {
+ super(props);
+ assignHandlers(this, handlers);
+ this.state = {
+ needsModalUpdate: false,
+ open: false,
+ placement: 'bottom',
+ };
+ }
+
+ // Updates our modal as necessary.
+ componentDidUpdate (prevProps) {
+ const { handleUpdate } = this.handlers;
+ const { items } = this.props;
+ const { needsModalUpdate } = this.state;
+ if (needsModalUpdate && items.find(
+ (item, i) => item.on !== prevProps.items[i].on
+ )) {
+ handleUpdate();
+ this.setState({ needsModalUpdate: false });
+ }
+ }
+
+ // Rendering.
+ render () {
+ const {
+ handleClose,
+ handleKeyDown,
+ handleToggle,
+ } = this.handlers;
+ const {
+ active,
+ disabled,
+ title,
+ icon,
+ items,
+ onChange,
+ value,
+ } = this.props;
+ const { open, placement } = this.state;
+ const computedClass = classNames('composer--options--dropdown', {
+ active,
+ open,
+ top: placement === 'top',
+ });
+
+ // The result.
+ return (
+
+
+
+
+
+
+ );
+ }
+
+}
+
+// Props.
+ComposerOptionsDropdown.propTypes = {
+ active: PropTypes.bool,
+ disabled: PropTypes.bool,
+ icon: PropTypes.string,
+ items: PropTypes.arrayOf(PropTypes.shape({
+ icon: PropTypes.string,
+ meta: PropTypes.node,
+ name: PropTypes.string.isRequired,
+ on: PropTypes.bool,
+ text: PropTypes.node,
+ })).isRequired,
+ onChange: PropTypes.func,
+ onModalClose: PropTypes.func,
+ onModalOpen: PropTypes.func,
+ title: PropTypes.string,
+ value: PropTypes.string,
+};
diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js b/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js
new file mode 100644
index 000000000..b4e2ec07b
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js
@@ -0,0 +1,219 @@
+// Package imports.
+import PropTypes from 'prop-types';
+import React from 'react';
+import spring from 'react-motion/lib/spring';
+import Toggle from 'react-toggle';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import classNames from 'classnames';
+
+// Components.
+import Icon from 'flavours/glitch/components/icon';
+
+// Utils.
+import { withPassive } from 'flavours/glitch/util/dom_helpers';
+import Motion from 'flavours/glitch/util/optional_motion';
+import { assignHandlers } from 'flavours/glitch/util/react_helpers';
+
+class ComposerOptionsDropdownContentItem extends ImmutablePureComponent {
+
+ static propTypes = {
+ active: PropTypes.bool,
+ name: PropTypes.string,
+ onChange: PropTypes.func,
+ onClose: PropTypes.func,
+ options: PropTypes.shape({
+ icon: PropTypes.string,
+ meta: PropTypes.node,
+ on: PropTypes.bool,
+ text: PropTypes.node,
+ }),
+ };
+
+ handleActivate = (e) => {
+ const {
+ name,
+ onChange,
+ onClose,
+ options: { on },
+ } = this.props;
+
+ // If the escape key was pressed, we close the dropdown.
+ if (e.key === 'Escape' && onClose) {
+ onClose();
+
+ // Otherwise, we both close the dropdown and change the value.
+ } else if (onChange && (!e.key || e.key === 'Enter')) {
+ e.preventDefault(); // Prevents change in focus on click
+ if ((on === null || typeof on === 'undefined') && onClose) {
+ onClose();
+ }
+ onChange(name);
+ }
+ }
+
+ // Rendering.
+ render () {
+ const {
+ active,
+ options: {
+ icon,
+ meta,
+ on,
+ text,
+ },
+ } = this.props;
+ const computedClass = classNames('composer--options--dropdown--content--item', {
+ active,
+ lengthy: meta,
+ 'toggled-off': !on && on !== null && typeof on !== 'undefined',
+ 'toggled-on': on,
+ 'with-icon': icon,
+ });
+
+ let prefix = null;
+
+ if (on !== null && typeof on !== 'undefined') {
+ prefix = ;
+ } else if (icon) {
+ prefix =
+ }
+
+ // The result.
+ return (
+
+ {prefix}
+
+
+ {text}
+ {meta ? meta : nil}
+
+
+ );
+ }
+
+};
+
+// The spring to use with our motion.
+const springMotion = spring(1, {
+ damping: 35,
+ stiffness: 400,
+});
+
+// The component.
+export default class ComposerOptionsDropdownContent extends React.PureComponent {
+
+ static propTypes = {
+ items: PropTypes.arrayOf(PropTypes.shape({
+ icon: PropTypes.string,
+ meta: PropTypes.node,
+ name: PropTypes.string.isRequired,
+ on: PropTypes.bool,
+ text: PropTypes.node,
+ })),
+ onChange: PropTypes.func,
+ onClose: PropTypes.func,
+ style: PropTypes.object,
+ value: PropTypes.string,
+ };
+
+ static defaultProps = {
+ style: {},
+ };
+
+ state = {
+ mounted: false,
+ };
+
+ // When the document is clicked elsewhere, we close the dropdown.
+ handleDocumentClick = ({ target }) => {
+ const { node } = this;
+ const { onClose } = this.props;
+ if (onClose && node && !node.contains(target)) {
+ onClose();
+ }
+ }
+
+ // Stores our node in `this.node`.
+ handleRef = (node) => {
+ this.node = node;
+ }
+
+ // On mounting, we add our listeners.
+ componentDidMount () {
+ document.addEventListener('click', this.handleDocumentClick, false);
+ document.addEventListener('touchend', this.handleDocumentClick, withPassive);
+ this.setState({ mounted: true });
+ }
+
+ // On unmounting, we remove our listeners.
+ componentWillUnmount () {
+ document.removeEventListener('click', this.handleDocumentClick, false);
+ document.removeEventListener('touchend', this.handleDocumentClick, withPassive);
+ }
+
+ // Rendering.
+ render () {
+ const { mounted } = this.state;
+ const {
+ items,
+ onChange,
+ onClose,
+ style,
+ value,
+ } = this.props;
+
+ // The result.
+ return (
+
+ {({ opacity, scaleX, scaleY }) => (
+ // It should not be transformed when mounting because the resulting
+ // size will be used to determine the coordinate of the menu by
+ // react-overlays
+
+ {items ? items.map(
+ ({
+ name,
+ ...rest
+ }) => (
+
+ )
+ ) : null}
+
+ )}
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/compose/components/options.js b/app/javascript/flavours/glitch/features/compose/components/options.js
new file mode 100644
index 000000000..8a760bd15
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/components/options.js
@@ -0,0 +1,352 @@
+// Package imports.
+import PropTypes from 'prop-types';
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
+import spring from 'react-motion/lib/spring';
+
+// Components.
+import IconButton from 'flavours/glitch/components/icon_button';
+import TextIconButton from 'flavours/glitch/components/text_icon_button';
+import Dropdown from './dropdown';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+// Utils.
+import Motion from 'flavours/glitch/util/optional_motion';
+import { pollLimits } from 'flavours/glitch/util/initial_state';
+
+// Messages.
+const messages = defineMessages({
+ advanced_options_icon_title: {
+ defaultMessage: 'Advanced options',
+ id: 'advanced_options.icon_title',
+ },
+ attach: {
+ defaultMessage: 'Attach...',
+ id: 'compose.attach',
+ },
+ change_privacy: {
+ defaultMessage: 'Adjust status privacy',
+ id: 'privacy.change',
+ },
+ direct_long: {
+ defaultMessage: 'Post to mentioned users only',
+ id: 'privacy.direct.long',
+ },
+ direct_short: {
+ defaultMessage: 'Direct',
+ id: 'privacy.direct.short',
+ },
+ doodle: {
+ defaultMessage: 'Draw something',
+ id: 'compose.attach.doodle',
+ },
+ local_only_long: {
+ defaultMessage: 'Do not post to other instances',
+ id: 'advanced_options.local-only.long',
+ },
+ local_only_short: {
+ defaultMessage: 'Local-only',
+ id: 'advanced_options.local-only.short',
+ },
+ private_long: {
+ defaultMessage: 'Post to followers only',
+ id: 'privacy.private.long',
+ },
+ private_short: {
+ defaultMessage: 'Followers-only',
+ id: 'privacy.private.short',
+ },
+ public_long: {
+ defaultMessage: 'Post to public timelines',
+ id: 'privacy.public.long',
+ },
+ public_short: {
+ defaultMessage: 'Public',
+ id: 'privacy.public.short',
+ },
+ sensitive: {
+ defaultMessage: 'Mark media as sensitive',
+ id: 'compose_form.sensitive',
+ },
+ spoiler: {
+ defaultMessage: 'Hide text behind warning',
+ id: 'compose_form.spoiler',
+ },
+ threaded_mode_long: {
+ defaultMessage: 'Automatically opens a reply on posting',
+ id: 'advanced_options.threaded_mode.long',
+ },
+ threaded_mode_short: {
+ defaultMessage: 'Threaded mode',
+ id: 'advanced_options.threaded_mode.short',
+ },
+ unlisted_long: {
+ defaultMessage: 'Do not show in public timelines',
+ id: 'privacy.unlisted.long',
+ },
+ unlisted_short: {
+ defaultMessage: 'Unlisted',
+ id: 'privacy.unlisted.short',
+ },
+ upload: {
+ defaultMessage: 'Upload a file',
+ id: 'compose.attach.upload',
+ },
+ add_poll: {
+ defaultMessage: 'Add a poll',
+ id: 'poll_button.add_poll',
+ },
+ remove_poll: {
+ defaultMessage: 'Remove poll',
+ id: 'poll_button.remove_poll',
+ },
+});
+
+export default @injectIntl
+class ComposerOptions extends ImmutablePureComponent {
+
+ static propTypes = {
+ acceptContentTypes: PropTypes.string,
+ advancedOptions: ImmutablePropTypes.map,
+ disabled: PropTypes.bool,
+ allowMedia: PropTypes.bool,
+ hasMedia: PropTypes.bool,
+ allowPoll: PropTypes.bool,
+ hasPoll: PropTypes.bool,
+ intl: PropTypes.object.isRequired,
+ onChangeAdvancedOption: PropTypes.func,
+ onChangeSensitivity: PropTypes.func,
+ onChangeVisibility: PropTypes.func,
+ onTogglePoll: PropTypes.func,
+ onDoodleOpen: PropTypes.func,
+ onModalClose: PropTypes.func,
+ onModalOpen: PropTypes.func,
+ onToggleSpoiler: PropTypes.func,
+ onUpload: PropTypes.func,
+ privacy: PropTypes.string,
+ resetFileKey: PropTypes.number,
+ sensitive: PropTypes.bool,
+ spoiler: PropTypes.bool,
+ };
+
+ // Handles file selection.
+ handleChangeFiles = ({ target: { files } }) => {
+ const { onUpload } = this.props;
+ if (files.length && onUpload) {
+ onUpload(files);
+ }
+ }
+
+ // Handles attachment clicks.
+ handleClickAttach = (name) => {
+ const { fileElement } = this;
+ const { onDoodleOpen } = this.props;
+
+ // We switch over the name of the option.
+ switch (name) {
+ case 'upload':
+ if (fileElement) {
+ fileElement.click();
+ }
+ return;
+ case 'doodle':
+ if (onDoodleOpen) {
+ onDoodleOpen();
+ }
+ return;
+ }
+ }
+
+ // Handles a ref to the file input.
+ handleRefFileElement = (fileElement) => {
+ this.fileElement = fileElement;
+ }
+
+ // Rendering.
+ render () {
+ const {
+ acceptContentTypes,
+ advancedOptions,
+ disabled,
+ allowMedia,
+ hasMedia,
+ allowPoll,
+ hasPoll,
+ intl,
+ onChangeAdvancedOption,
+ onChangeSensitivity,
+ onChangeVisibility,
+ onTogglePoll,
+ onModalClose,
+ onModalOpen,
+ onToggleSpoiler,
+ privacy,
+ resetFileKey,
+ sensitive,
+ spoiler,
+ } = this.props;
+
+ // We predefine our privacy items so that we can easily pick the
+ // dropdown icon later.
+ const privacyItems = {
+ direct: {
+ icon: 'envelope',
+ meta: ,
+ name: 'direct',
+ text: ,
+ },
+ private: {
+ icon: 'lock',
+ meta: ,
+ name: 'private',
+ text: ,
+ },
+ public: {
+ icon: 'globe',
+ meta: ,
+ name: 'public',
+ text: ,
+ },
+ unlisted: {
+ icon: 'unlock',
+ meta: ,
+ name: 'unlisted',
+ text: ,
+ },
+ };
+
+ // The result.
+ return (
+
+
+
,
+ },
+ {
+ icon: 'paint-brush',
+ name: 'doodle',
+ text:
,
+ },
+ ]}
+ onChange={this.handleClickAttach}
+ onModalClose={onModalClose}
+ onModalOpen={onModalOpen}
+ title={intl.formatMessage(messages.attach)}
+ />
+ {!!pollLimits && (
+
+ )}
+
+ {({ scale }) => (
+
+
+
+ )}
+
+
+
+ {onToggleSpoiler && (
+
+ )}
+
!!value)}
+ disabled={disabled}
+ icon='ellipsis-h'
+ items={advancedOptions ? [
+ {
+ meta: ,
+ name: 'do_not_federate',
+ on: advancedOptions.get('do_not_federate'),
+ text: ,
+ },
+ {
+ meta: ,
+ name: 'threaded_mode',
+ on: advancedOptions.get('threaded_mode'),
+ text: ,
+ },
+ ] : null}
+ onChange={onChangeAdvancedOption}
+ onModalClose={onModalClose}
+ onModalOpen={onModalOpen}
+ title={intl.formatMessage(messages.advanced_options_icon_title)}
+ />
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/features/composer/options/dropdown/content/index.js b/app/javascript/flavours/glitch/features/composer/options/dropdown/content/index.js
deleted file mode 100644
index b76410561..000000000
--- a/app/javascript/flavours/glitch/features/composer/options/dropdown/content/index.js
+++ /dev/null
@@ -1,146 +0,0 @@
-// Package imports.
-import PropTypes from 'prop-types';
-import React from 'react';
-import spring from 'react-motion/lib/spring';
-
-// Components.
-import ComposerOptionsDropdownContentItem from './item';
-
-// Utils.
-import { withPassive } from 'flavours/glitch/util/dom_helpers';
-import Motion from 'flavours/glitch/util/optional_motion';
-import { assignHandlers } from 'flavours/glitch/util/react_helpers';
-
-// Handlers.
-const handlers = {
- // When the document is clicked elsewhere, we close the dropdown.
- handleDocumentClick ({ target }) {
- const { node } = this;
- const { onClose } = this.props;
- if (onClose && node && !node.contains(target)) {
- onClose();
- }
- },
-
- // Stores our node in `this.node`.
- handleRef (node) {
- this.node = node;
- },
-};
-
-// The spring to use with our motion.
-const springMotion = spring(1, {
- damping: 35,
- stiffness: 400,
-});
-
-// The component.
-export default class ComposerOptionsDropdownContent extends React.PureComponent {
-
- // Constructor.
- constructor (props) {
- super(props);
- assignHandlers(this, handlers);
-
- // Instance variables.
- this.node = null;
-
- this.state = {
- mounted: false,
- };
- }
-
- // On mounting, we add our listeners.
- componentDidMount () {
- const { handleDocumentClick } = this.handlers;
- document.addEventListener('click', handleDocumentClick, false);
- document.addEventListener('touchend', handleDocumentClick, withPassive);
- this.setState({ mounted: true });
- }
-
- // On unmounting, we remove our listeners.
- componentWillUnmount () {
- const { handleDocumentClick } = this.handlers;
- document.removeEventListener('click', handleDocumentClick, false);
- document.removeEventListener('touchend', handleDocumentClick, withPassive);
- }
-
- // Rendering.
- render () {
- const { mounted } = this.state;
- const { handleRef } = this.handlers;
- const {
- items,
- onChange,
- onClose,
- style,
- value,
- } = this.props;
-
- // The result.
- return (
-
- {({ opacity, scaleX, scaleY }) => (
- // It should not be transformed when mounting because the resulting
- // size will be used to determine the coordinate of the menu by
- // react-overlays
-
- {items ? items.map(
- ({
- name,
- ...rest
- }) => (
-
- )
- ) : null}
-
- )}
-
- );
- }
-
-}
-
-// Props.
-ComposerOptionsDropdownContent.propTypes = {
- items: PropTypes.arrayOf(PropTypes.shape({
- icon: PropTypes.string,
- meta: PropTypes.node,
- name: PropTypes.string.isRequired,
- on: PropTypes.bool,
- text: PropTypes.node,
- })),
- onChange: PropTypes.func,
- onClose: PropTypes.func,
- style: PropTypes.object,
- value: PropTypes.string,
-};
-
-// Default props.
-ComposerOptionsDropdownContent.defaultProps = { style: {} };
diff --git a/app/javascript/flavours/glitch/features/composer/options/dropdown/content/item/index.js b/app/javascript/flavours/glitch/features/composer/options/dropdown/content/item/index.js
deleted file mode 100644
index 68a52083f..000000000
--- a/app/javascript/flavours/glitch/features/composer/options/dropdown/content/item/index.js
+++ /dev/null
@@ -1,129 +0,0 @@
-// Package imports.
-import classNames from 'classnames';
-import PropTypes from 'prop-types';
-import React from 'react';
-import Toggle from 'react-toggle';
-
-// Components.
-import Icon from 'flavours/glitch/components/icon';
-
-// Utils.
-import { assignHandlers } from 'flavours/glitch/util/react_helpers';
-
-// Handlers.
-const handlers = {
-
- // This function activates the dropdown item.
- handleActivate (e) {
- const {
- name,
- onChange,
- onClose,
- options: { on },
- } = this.props;
-
- // If the escape key was pressed, we close the dropdown.
- if (e.key === 'Escape' && onClose) {
- onClose();
-
- // Otherwise, we both close the dropdown and change the value.
- } else if (onChange && (!e.key || e.key === 'Enter')) {
- e.preventDefault(); // Prevents change in focus on click
- if ((on === null || typeof on === 'undefined') && onClose) {
- onClose();
- }
- onChange(name);
- }
- },
-};
-
-// The component.
-export default class ComposerOptionsDropdownContentItem extends React.PureComponent {
-
- // Constructor.
- constructor (props) {
- super(props);
- assignHandlers(this, handlers);
- }
-
- // Rendering.
- render () {
- const { handleActivate } = this.handlers;
- const {
- active,
- options: {
- icon,
- meta,
- on,
- text,
- },
- } = this.props;
- const computedClass = classNames('composer--options--dropdown--content--item', {
- active,
- lengthy: meta,
- 'toggled-off': !on && on !== null && typeof on !== 'undefined',
- 'toggled-on': on,
- 'with-icon': icon,
- });
-
- // The result.
- return (
-
- {function () {
-
- // We render a `
` if we were provided an `on`
- // property, and otherwise show an `` if available.
- switch (true) {
- case on !== null && typeof on !== 'undefined':
- return (
-
- );
- case !!icon:
- return (
-
- );
- default:
- return null;
- }
- }()}
- {meta ? (
-
- {text}
- {meta}
-
- ) :
-
- {text}
-
}
-
- );
- }
-
-};
-
-// Props.
-ComposerOptionsDropdownContentItem.propTypes = {
- active: PropTypes.bool,
- name: PropTypes.string,
- onChange: PropTypes.func,
- onClose: PropTypes.func,
- options: PropTypes.shape({
- icon: PropTypes.string,
- meta: PropTypes.node,
- on: PropTypes.bool,
- text: PropTypes.node,
- }),
-};
diff --git a/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js b/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js
deleted file mode 100644
index 7817cc964..000000000
--- a/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js
+++ /dev/null
@@ -1,229 +0,0 @@
-// Package imports.
-import classNames from 'classnames';
-import PropTypes from 'prop-types';
-import React from 'react';
-import Overlay from 'react-overlays/lib/Overlay';
-
-// Components.
-import IconButton from 'flavours/glitch/components/icon_button';
-import ComposerOptionsDropdownContent from './content';
-
-// Utils.
-import { isUserTouching } from 'flavours/glitch/util/is_mobile';
-import { assignHandlers } from 'flavours/glitch/util/react_helpers';
-
-// Handlers.
-const handlers = {
-
- // Closes the dropdown.
- handleClose () {
- this.setState({ open: false });
- },
-
- // The enter key toggles the dropdown's open state, and the escape
- // key closes it.
- handleKeyDown ({ key }) {
- const {
- handleClose,
- handleToggle,
- } = this.handlers;
- switch (key) {
- case 'Enter':
- handleToggle(key);
- break;
- case 'Escape':
- handleClose();
- break;
- }
- },
-
- // Creates an action modal object.
- handleMakeModal () {
- const component = this;
- const {
- items,
- onChange,
- onModalOpen,
- onModalClose,
- value,
- } = this.props;
-
- // Required props.
- if (!(onChange && onModalOpen && onModalClose && items)) {
- return null;
- }
-
- // The object.
- return {
- actions: items.map(
- ({
- name,
- ...rest
- }) => ({
- ...rest,
- active: value && name === value,
- name,
- onClick (e) {
- e.preventDefault(); // Prevents focus from changing
- onModalClose();
- onChange(name);
- },
- onPassiveClick (e) {
- e.preventDefault(); // Prevents focus from changing
- onChange(name);
- component.setState({ needsModalUpdate: true });
- },
- })
- ),
- };
- },
-
- // Toggles opening and closing the dropdown.
- handleToggle ({ target }) {
- const { handleMakeModal } = this.handlers;
- const { onModalOpen } = this.props;
- const { open } = this.state;
-
- // If this is a touch device, we open a modal instead of the
- // dropdown.
- if (isUserTouching()) {
-
- // This gets the modal to open.
- const modal = handleMakeModal();
-
- // If we can, we then open the modal.
- if (modal && onModalOpen) {
- onModalOpen(modal);
- return;
- }
- }
-
- const { top } = target.getBoundingClientRect();
- this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' });
- // Otherwise, we just set our state to open.
- this.setState({ open: !open });
- },
-
- // If our modal is open and our props update, we need to also update
- // the modal.
- handleUpdate () {
- const { handleMakeModal } = this.handlers;
- const { onModalOpen } = this.props;
- const { needsModalUpdate } = this.state;
-
- // Gets our modal object.
- const modal = handleMakeModal();
-
- // Reopens the modal with the new object.
- if (needsModalUpdate && modal && onModalOpen) {
- onModalOpen(modal);
- }
- },
-};
-
-// The component.
-export default class ComposerOptionsDropdown extends React.PureComponent {
-
- // Constructor.
- constructor (props) {
- super(props);
- assignHandlers(this, handlers);
- this.state = {
- needsModalUpdate: false,
- open: false,
- placement: 'bottom',
- };
- }
-
- // Updates our modal as necessary.
- componentDidUpdate (prevProps) {
- const { handleUpdate } = this.handlers;
- const { items } = this.props;
- const { needsModalUpdate } = this.state;
- if (needsModalUpdate && items.find(
- (item, i) => item.on !== prevProps.items[i].on
- )) {
- handleUpdate();
- this.setState({ needsModalUpdate: false });
- }
- }
-
- // Rendering.
- render () {
- const {
- handleClose,
- handleKeyDown,
- handleToggle,
- } = this.handlers;
- const {
- active,
- disabled,
- title,
- icon,
- items,
- onChange,
- value,
- } = this.props;
- const { open, placement } = this.state;
- const computedClass = classNames('composer--options--dropdown', {
- active,
- open,
- top: placement === 'top',
- });
-
- // The result.
- return (
-
-
-
-
-
-
- );
- }
-
-}
-
-// Props.
-ComposerOptionsDropdown.propTypes = {
- active: PropTypes.bool,
- disabled: PropTypes.bool,
- icon: PropTypes.string,
- items: PropTypes.arrayOf(PropTypes.shape({
- icon: PropTypes.string,
- meta: PropTypes.node,
- name: PropTypes.string.isRequired,
- on: PropTypes.bool,
- text: PropTypes.node,
- })).isRequired,
- onChange: PropTypes.func,
- onModalClose: PropTypes.func,
- onModalOpen: PropTypes.func,
- title: PropTypes.string,
- value: PropTypes.string,
-};
diff --git a/app/javascript/flavours/glitch/features/composer/options/index.js b/app/javascript/flavours/glitch/features/composer/options/index.js
deleted file mode 100644
index 7c7f01dc2..000000000
--- a/app/javascript/flavours/glitch/features/composer/options/index.js
+++ /dev/null
@@ -1,377 +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';
-
-// Components.
-import IconButton from 'flavours/glitch/components/icon_button';
-import TextIconButton from 'flavours/glitch/components/text_icon_button';
-import Dropdown from './dropdown';
-
-// Utils.
-import Motion from 'flavours/glitch/util/optional_motion';
-import {
- assignHandlers,
- hiddenComponent,
-} from 'flavours/glitch/util/react_helpers';
-import { pollLimits } from 'flavours/glitch/util/initial_state';
-
-// Messages.
-const messages = defineMessages({
- advanced_options_icon_title: {
- defaultMessage: 'Advanced options',
- id: 'advanced_options.icon_title',
- },
- attach: {
- defaultMessage: 'Attach...',
- id: 'compose.attach',
- },
- change_privacy: {
- defaultMessage: 'Adjust status privacy',
- id: 'privacy.change',
- },
- direct_long: {
- defaultMessage: 'Post to mentioned users only',
- id: 'privacy.direct.long',
- },
- direct_short: {
- defaultMessage: 'Direct',
- id: 'privacy.direct.short',
- },
- doodle: {
- defaultMessage: 'Draw something',
- id: 'compose.attach.doodle',
- },
- local_only_long: {
- defaultMessage: 'Do not post to other instances',
- id: 'advanced_options.local-only.long',
- },
- local_only_short: {
- defaultMessage: 'Local-only',
- id: 'advanced_options.local-only.short',
- },
- private_long: {
- defaultMessage: 'Post to followers only',
- id: 'privacy.private.long',
- },
- private_short: {
- defaultMessage: 'Followers-only',
- id: 'privacy.private.short',
- },
- public_long: {
- defaultMessage: 'Post to public timelines',
- id: 'privacy.public.long',
- },
- public_short: {
- defaultMessage: 'Public',
- id: 'privacy.public.short',
- },
- sensitive: {
- defaultMessage: 'Mark media as sensitive',
- id: 'compose_form.sensitive',
- },
- spoiler: {
- defaultMessage: 'Hide text behind warning',
- id: 'compose_form.spoiler',
- },
- threaded_mode_long: {
- defaultMessage: 'Automatically opens a reply on posting',
- id: 'advanced_options.threaded_mode.long',
- },
- threaded_mode_short: {
- defaultMessage: 'Threaded mode',
- id: 'advanced_options.threaded_mode.short',
- },
- unlisted_long: {
- defaultMessage: 'Do not show in public timelines',
- id: 'privacy.unlisted.long',
- },
- unlisted_short: {
- defaultMessage: 'Unlisted',
- id: 'privacy.unlisted.short',
- },
- upload: {
- defaultMessage: 'Upload a file',
- id: 'compose.attach.upload',
- },
- add_poll: {
- defaultMessage: 'Add a poll',
- id: 'poll_button.add_poll',
- },
- remove_poll: {
- defaultMessage: 'Remove poll',
- id: 'poll_button.remove_poll',
- },
-});
-
-// Handlers.
-const handlers = {
-
- // Handles file selection.
- handleChangeFiles ({ target: { files } }) {
- const { onUpload } = this.props;
- if (files.length && onUpload) {
- onUpload(files);
- }
- },
-
- // Handles attachment clicks.
- handleClickAttach (name) {
- const { fileElement } = this;
- const { onDoodleOpen } = this.props;
-
- // We switch over the name of the option.
- switch (name) {
- case 'upload':
- if (fileElement) {
- fileElement.click();
- }
- return;
- case 'doodle':
- if (onDoodleOpen) {
- onDoodleOpen();
- }
- return;
- }
- },
-
- // Handles a ref to the file input.
- handleRefFileElement (fileElement) {
- this.fileElement = fileElement;
- },
-};
-
-// The component.
-export default class ComposerOptions extends React.PureComponent {
-
- // Constructor.
- constructor (props) {
- super(props);
- assignHandlers(this, handlers);
-
- // Instance variables.
- this.fileElement = null;
- }
-
- // Rendering.
- render () {
- const {
- handleChangeFiles,
- handleClickAttach,
- handleRefFileElement,
- } = this.handlers;
- const {
- acceptContentTypes,
- advancedOptions,
- disabled,
- allowMedia,
- hasMedia,
- allowPoll,
- hasPoll,
- intl,
- onChangeAdvancedOption,
- onChangeSensitivity,
- onChangeVisibility,
- onTogglePoll,
- onModalClose,
- onModalOpen,
- onToggleSpoiler,
- privacy,
- resetFileKey,
- sensitive,
- spoiler,
- } = this.props;
-
- // We predefine our privacy items so that we can easily pick the
- // dropdown icon later.
- const privacyItems = {
- direct: {
- icon: 'envelope',
- meta: ,
- name: 'direct',
- text: ,
- },
- private: {
- icon: 'lock',
- meta: ,
- name: 'private',
- text: ,
- },
- public: {
- icon: 'globe',
- meta: ,
- name: 'public',
- text: ,
- },
- unlisted: {
- icon: 'unlock',
- meta: ,
- name: 'unlisted',
- text: ,
- },
- };
-
- // The result.
- return (
-
-
-
,
- },
- {
- icon: 'paint-brush',
- name: 'doodle',
- text:
,
- },
- ]}
- onChange={handleClickAttach}
- onModalClose={onModalClose}
- onModalOpen={onModalOpen}
- title={intl.formatMessage(messages.attach)}
- />
- {!!pollLimits && (
-
- )}
-
- {({ scale }) => (
-
-
-
- )}
-
-
-
- {onToggleSpoiler && (
-
- )}
-
!!value)}
- disabled={disabled}
- icon='ellipsis-h'
- items={advancedOptions ? [
- {
- meta: ,
- name: 'do_not_federate',
- on: advancedOptions.get('do_not_federate'),
- text: ,
- },
- {
- meta: ,
- name: 'threaded_mode',
- on: advancedOptions.get('threaded_mode'),
- text: ,
- },
- ] : null}
- onChange={onChangeAdvancedOption}
- onModalClose={onModalClose}
- onModalOpen={onModalOpen}
- title={intl.formatMessage(messages.advanced_options_icon_title)}
- />
-
- );
- }
-
-}
-
-// Props.
-ComposerOptions.propTypes = {
- acceptContentTypes: PropTypes.string,
- advancedOptions: ImmutablePropTypes.map,
- disabled: PropTypes.bool,
- allowMedia: PropTypes.bool,
- hasMedia: PropTypes.bool,
- allowPoll: PropTypes.bool,
- hasPoll: PropTypes.bool,
- intl: PropTypes.object.isRequired,
- onChangeAdvancedOption: PropTypes.func,
- onChangeSensitivity: PropTypes.func,
- onChangeVisibility: PropTypes.func,
- onTogglePoll: PropTypes.func,
- onDoodleOpen: PropTypes.func,
- onModalClose: PropTypes.func,
- onModalOpen: PropTypes.func,
- onToggleSpoiler: PropTypes.func,
- onUpload: PropTypes.func,
- privacy: PropTypes.string,
- resetFileKey: PropTypes.number,
- sensitive: PropTypes.bool,
- spoiler: PropTypes.bool,
-};
--
cgit
From df951c319c83d905307b8dc8abda837c651820e1 Mon Sep 17 00:00:00 2001
From: Thibaut Girka
Date: Sun, 21 Apr 2019 18:31:26 +0200
Subject: Add OptionsContainer
---
.../features/compose/components/compose_form.js | 34 +------------
.../compose/containers/compose_form_container.js | 31 ------------
.../compose/containers/options_container.js | 57 ++++++++++++++++++++++
3 files changed, 59 insertions(+), 63 deletions(-)
create mode 100644 app/javascript/flavours/glitch/features/compose/containers/options_container.js
(limited to 'app/javascript')
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 a9be9c751..76a5117bc 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -5,7 +5,7 @@ import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
// Components.
-import Options from './options';
+import OptionsContainer from '../containers/options_container';
import ComposerPublisher from '../../composer/publisher';
import TextareaIcons from './textarea_icons';
import UploadFormContainer from '../containers/upload_form_container';
@@ -39,7 +39,6 @@ class ComposeForm extends ImmutablePureComponent {
intl: PropTypes.object.isRequired,
// State props.
- acceptContentTypes: PropTypes.string,
advancedOptions: ImmutablePropTypes.map,
amUnlocked: PropTypes.bool,
focusDate: PropTypes.instanceOf(Date),
@@ -51,7 +50,6 @@ class ComposeForm extends ImmutablePureComponent {
media: ImmutablePropTypes.list,
preselectDate: PropTypes.instanceOf(Date),
privacy: PropTypes.string,
- resetFileKey: PropTypes.number,
sideArm: PropTypes.string,
sensitive: PropTypes.bool,
showSearch: PropTypes.bool,
@@ -66,19 +64,14 @@ class ComposeForm extends ImmutablePureComponent {
preselectOnReply: PropTypes.bool,
// Dispatch props.
- onChangeAdvancedOption: 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,
onUnmount: PropTypes.func,
@@ -285,7 +278,6 @@ class ComposeForm extends ImmutablePureComponent {
handleRefTextarea,
} = this;
const {
- acceptContentTypes,
advancedOptions,
amUnlocked,
anyMedia,
@@ -295,21 +287,13 @@ class ComposeForm extends ImmutablePureComponent {
isUploading,
layout,
media,
- poll,
- onChangeAdvancedOption,
- onChangeSensitivity,
onChangeSpoilerness,
onChangeText,
onChangeVisibility,
- onTogglePoll,
onClearSuggestions,
- onCloseModal,
onFetchSuggestions,
- onOpenActionsModal,
- onOpenDoodleModal,
onUpload,
privacy,
- resetFileKey,
sensitive,
showSearch,
sideArm,
@@ -372,27 +356,13 @@ class ComposeForm extends ImmutablePureComponent {