forked from treehouse/mastodon
WIP <Compose> Refactor; 1000 tiny edits
parent
b4a3792201
commit
42f50049ff
|
@ -134,11 +134,12 @@ export default class Dropdown extends React.PureComponent {
|
||||||
this.props.onModalOpen({
|
this.props.onModalOpen({
|
||||||
status,
|
status,
|
||||||
actions: items.map(
|
actions: items.map(
|
||||||
(item, i) => ({
|
(item, i) => item ? {
|
||||||
...item,
|
...item,
|
||||||
name: `${item.text}-${i}`,
|
name: `${item.text}-${i}`,
|
||||||
onClick: this.handleItemClick.bind(i),
|
onClick: this.handleItemClick.bind(i),
|
||||||
}),
|
} : null
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -45,7 +45,7 @@ export default class Link extends React.PureComponent {
|
||||||
title,
|
title,
|
||||||
...rest
|
...rest
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const computedClass = classNames('link', className, role);
|
const computedClass = classNames('link', className, `role-${role}`);
|
||||||
|
|
||||||
// We assume that our `onClick` is a routing function and give it
|
// We assume that our `onClick` is a routing function and give it
|
||||||
// the qualities of a link even if no `href` is provided. However,
|
// the qualities of a link even if no `href` is provided. However,
|
||||||
|
|
|
@ -52,6 +52,7 @@ function mapStateToProps (state) {
|
||||||
focusDate: state.getIn(['compose', 'focusDate']),
|
focusDate: state.getIn(['compose', 'focusDate']),
|
||||||
isSubmitting: state.getIn(['compose', 'is_submitting']),
|
isSubmitting: state.getIn(['compose', 'is_submitting']),
|
||||||
isUploading: state.getIn(['compose', 'is_uploading']),
|
isUploading: state.getIn(['compose', 'is_uploading']),
|
||||||
|
layout: state.getIn(['local_settings', 'layout']),
|
||||||
media: state.getIn(['compose', 'media_attachments']),
|
media: state.getIn(['compose', 'media_attachments']),
|
||||||
preselectDate: state.getIn(['compose', 'preselectDate']),
|
preselectDate: state.getIn(['compose', 'preselectDate']),
|
||||||
privacy: state.getIn(['compose', 'privacy']),
|
privacy: state.getIn(['compose', 'privacy']),
|
||||||
|
@ -71,132 +72,96 @@ function mapStateToProps (state) {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Dispatch mapping.
|
// Dispatch mapping.
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = {
|
||||||
cancelReply () {
|
onCancelReply: cancelReplyCompose,
|
||||||
dispatch(cancelReplyCompose());
|
onChangeDescription: changeUploadCompose,
|
||||||
},
|
onChangeSensitivity: changeComposeSensitivity,
|
||||||
changeDescription (mediaId, description) {
|
onChangeSpoilerText: changeComposeSpoilerText,
|
||||||
dispatch(changeUploadCompose(mediaId, description));
|
onChangeSpoilerness: changeComposeSpoilerness,
|
||||||
},
|
onChangeText: changeCompose,
|
||||||
changeSensitivity () {
|
onChangeVisibility: changeComposeVisibility,
|
||||||
dispatch(changeComposeSensitivity());
|
onClearSuggestions: clearComposeSuggestions,
|
||||||
},
|
onCloseModal: closeModal,
|
||||||
changeSpoilerText (checked) {
|
onFetchSuggestions: fetchComposeSuggestions,
|
||||||
dispatch(changeComposeSpoilerText(checked));
|
onInsertEmoji: insertEmojiCompose,
|
||||||
},
|
onOpenActionsModal: openModal.bind(null, 'ACTIONS'),
|
||||||
changeSpoilerness () {
|
onOpenDoodleModal: openModal.bind(null, 'DOODLE', { noEsc: true }),
|
||||||
dispatch(changeComposeSpoilerness());
|
onSelectSuggestion: selectComposeSuggestion,
|
||||||
},
|
onSubmit: submitCompose,
|
||||||
changeText (text) {
|
onToggleAdvancedOption: toggleComposeAdvancedOption,
|
||||||
dispatch(changeCompose(text));
|
onUndoUpload: undoUploadCompose,
|
||||||
},
|
onUpload: uploadCompose,
|
||||||
changeVisibility (value) {
|
};
|
||||||
dispatch(changeComposeVisibility(value));
|
|
||||||
},
|
|
||||||
clearSuggestions () {
|
|
||||||
dispatch(clearComposeSuggestions());
|
|
||||||
},
|
|
||||||
closeModal () {
|
|
||||||
dispatch(closeModal());
|
|
||||||
},
|
|
||||||
fetchSuggestions (token) {
|
|
||||||
dispatch(fetchComposeSuggestions(token));
|
|
||||||
},
|
|
||||||
insertEmoji (position, data) {
|
|
||||||
dispatch(insertEmojiCompose(position, data));
|
|
||||||
},
|
|
||||||
openActionsModal (data) {
|
|
||||||
dispatch(openModal('ACTIONS', data));
|
|
||||||
},
|
|
||||||
openDoodleModal () {
|
|
||||||
dispatch(openModal('DOODLE', { noEsc: true }));
|
|
||||||
},
|
|
||||||
selectSuggestion (position, token, accountId) {
|
|
||||||
dispatch(selectComposeSuggestion(position, token, accountId));
|
|
||||||
},
|
|
||||||
submit () {
|
|
||||||
dispatch(submitCompose());
|
|
||||||
},
|
|
||||||
toggleAdvancedOption (option) {
|
|
||||||
dispatch(toggleComposeAdvancedOption(option));
|
|
||||||
},
|
|
||||||
undoUpload (mediaId) {
|
|
||||||
dispatch(undoUploadCompose(mediaId));
|
|
||||||
},
|
|
||||||
upload (files) {
|
|
||||||
dispatch(uploadCompose(files));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handlers.
|
// Handlers.
|
||||||
const handlers = {
|
const handlers = {
|
||||||
|
|
||||||
// Changes the text value of the spoiler.
|
// Changes the text value of the spoiler.
|
||||||
changeSpoiler ({ target: { value } }) {
|
handleChangeSpoiler ({ target: { value } }) {
|
||||||
const { dispatch: { changeSpoilerText } } = this.props;
|
const { onChangeSpoilerText } = this.props;
|
||||||
if (changeSpoilerText) {
|
if (onChangeSpoilerText) {
|
||||||
changeSpoilerText(value);
|
onChangeSpoilerText(value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Inserts an emoji at the caret.
|
// Inserts an emoji at the caret.
|
||||||
emoji (data) {
|
handleEmoji (data) {
|
||||||
const { textarea: { selectionStart } } = this;
|
const { textarea: { selectionStart } } = this;
|
||||||
const { dispatch: { insertEmoji } } = this.props;
|
const { onInsertEmoji } = this.props;
|
||||||
this.caretPos = selectionStart + data.native.length + 1;
|
this.caretPos = selectionStart + data.native.length + 1;
|
||||||
if (insertEmoji) {
|
if (onInsertEmoji) {
|
||||||
insertEmoji(selectionStart, data);
|
onInsertEmoji(selectionStart, data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Handles the secondary submit button.
|
// Handles the secondary submit button.
|
||||||
secondarySubmit () {
|
handleSecondarySubmit () {
|
||||||
const { submit } = this.handlers;
|
const { handleSubmit } = this.handlers;
|
||||||
const {
|
const {
|
||||||
dispatch: { changeVisibility },
|
onChangeVisibility,
|
||||||
side_arm,
|
sideArm,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
if (changeVisibility) {
|
if (sideArm !== 'none' && onChangeVisibility) {
|
||||||
changeVisibility(side_arm);
|
onChangeVisibility(sideArm);
|
||||||
}
|
}
|
||||||
submit();
|
handleSubmit();
|
||||||
},
|
},
|
||||||
|
|
||||||
// Selects a suggestion from the autofill.
|
// Selects a suggestion from the autofill.
|
||||||
select (tokenStart, token, value) {
|
handleSelect (tokenStart, token, value) {
|
||||||
const { dispatch: { selectSuggestion } } = this.props;
|
const { onSelectSuggestion } = this.props;
|
||||||
this.caretPos = null;
|
this.caretPos = null;
|
||||||
if (selectSuggestion) {
|
if (onSelectSuggestion) {
|
||||||
selectSuggestion(tokenStart, token, value);
|
onSelectSuggestion(tokenStart, token, value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Submits the status.
|
// Submits the status.
|
||||||
submit () {
|
handleSubmit () {
|
||||||
const { textarea: { value } } = this;
|
const { textarea: { value } } = this;
|
||||||
const {
|
const {
|
||||||
dispatch: {
|
onChangeText,
|
||||||
changeText,
|
onSubmit,
|
||||||
submit,
|
text,
|
||||||
},
|
|
||||||
state: { text },
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
// If something changes inside the textarea, then we update the
|
// If something changes inside the textarea, then we update the
|
||||||
// state before submitting.
|
// state before submitting.
|
||||||
if (changeText && text !== value) {
|
if (onChangeText && text !== value) {
|
||||||
changeText(value);
|
onChangeText(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Submits the status.
|
// Submits the status.
|
||||||
if (submit) {
|
if (onSubmit) {
|
||||||
submit();
|
onSubmit();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Sets a reference to the textarea.
|
// Sets a reference to the textarea.
|
||||||
refTextarea ({ textarea }) {
|
handleRefTextarea (textareaComponent) {
|
||||||
this.textarea = textarea;
|
if (textareaComponent) {
|
||||||
|
this.textarea = textareaComponent.textarea;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -216,10 +181,10 @@ class Composer extends React.Component {
|
||||||
// If this is the update where we've finished uploading,
|
// If this is the update where we've finished uploading,
|
||||||
// save the last caret position so we can restore it below!
|
// save the last caret position so we can restore it below!
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
const { textarea: { selectionStart } } = this;
|
const { textarea } = this;
|
||||||
const { state: { isUploading } } = this.props;
|
const { isUploading } = this.props;
|
||||||
if (isUploading && !nextProps.state.isUploading) {
|
if (textarea && isUploading && !nextProps.isUploading) {
|
||||||
this.caretPos = selectionStart;
|
this.caretPos = textarea.selectionStart;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,20 +204,18 @@ class Composer extends React.Component {
|
||||||
textarea,
|
textarea,
|
||||||
} = this;
|
} = this;
|
||||||
const {
|
const {
|
||||||
state: {
|
|
||||||
focusDate,
|
focusDate,
|
||||||
isUploading,
|
isUploading,
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
preselectDate,
|
preselectDate,
|
||||||
text,
|
text,
|
||||||
},
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
let selectionEnd, selectionStart;
|
let selectionEnd, selectionStart;
|
||||||
|
|
||||||
// Caret/selection handling.
|
// Caret/selection handling.
|
||||||
if (focusDate !== prevProps.state.focusDate || (prevProps.state.isUploading && !isUploading && !isNaN(caretPos) && caretPos !== null)) {
|
if (focusDate !== prevProps.focusDate || (prevProps.isUploading && !isUploading && !isNaN(caretPos) && caretPos !== null)) {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case preselectDate !== prevProps.state.preselectDate:
|
case preselectDate !== prevProps.preselectDate:
|
||||||
selectionStart = text.search(/\s/) + 1;
|
selectionStart = text.search(/\s/) + 1;
|
||||||
selectionEnd = text.length;
|
selectionEnd = text.length;
|
||||||
break;
|
break;
|
||||||
|
@ -262,49 +225,50 @@ class Composer extends React.Component {
|
||||||
default:
|
default:
|
||||||
selectionStart = selectionEnd = text.length;
|
selectionStart = selectionEnd = text.length;
|
||||||
}
|
}
|
||||||
|
if (textarea) {
|
||||||
textarea.setSelectionRange(selectionStart, selectionEnd);
|
textarea.setSelectionRange(selectionStart, selectionEnd);
|
||||||
textarea.focus();
|
textarea.focus();
|
||||||
|
}
|
||||||
|
|
||||||
// Refocuses the textarea after submitting.
|
// Refocuses the textarea after submitting.
|
||||||
} else if (prevProps.state.isSubmitting && !isSubmitting) {
|
} else if (textarea && prevProps.isSubmitting && !isSubmitting) {
|
||||||
textarea.focus();
|
textarea.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
changeSpoiler,
|
handleChangeSpoiler,
|
||||||
emoji,
|
handleEmoji,
|
||||||
secondarySubmit,
|
handleSecondarySubmit,
|
||||||
select,
|
handleSelect,
|
||||||
submit,
|
handleSubmit,
|
||||||
refTextarea,
|
handleRefTextarea,
|
||||||
} = this.handlers;
|
} = this.handlers;
|
||||||
const { history } = this.context;
|
const { history } = this.context;
|
||||||
const {
|
const {
|
||||||
dispatch: {
|
|
||||||
cancelReply,
|
|
||||||
changeDescription,
|
|
||||||
changeSensitivity,
|
|
||||||
changeText,
|
|
||||||
changeVisibility,
|
|
||||||
clearSuggestions,
|
|
||||||
closeModal,
|
|
||||||
fetchSuggestions,
|
|
||||||
openActionsModal,
|
|
||||||
openDoodleModal,
|
|
||||||
toggleAdvancedOption,
|
|
||||||
undoUpload,
|
|
||||||
upload,
|
|
||||||
},
|
|
||||||
intl,
|
|
||||||
state: {
|
|
||||||
acceptContentTypes,
|
acceptContentTypes,
|
||||||
amUnlocked,
|
amUnlocked,
|
||||||
doNotFederate,
|
doNotFederate,
|
||||||
|
intl,
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
isUploading,
|
isUploading,
|
||||||
|
layout,
|
||||||
media,
|
media,
|
||||||
|
onCancelReply,
|
||||||
|
onChangeDescription,
|
||||||
|
onChangeSensitivity,
|
||||||
|
onChangeSpoilerness,
|
||||||
|
onChangeText,
|
||||||
|
onChangeVisibility,
|
||||||
|
onClearSuggestions,
|
||||||
|
onCloseModal,
|
||||||
|
onFetchSuggestions,
|
||||||
|
onOpenActionsModal,
|
||||||
|
onOpenDoodleModal,
|
||||||
|
onToggleAdvancedOption,
|
||||||
|
onUndoUpload,
|
||||||
|
onUpload,
|
||||||
privacy,
|
privacy,
|
||||||
progress,
|
progress,
|
||||||
replyAccount,
|
replyAccount,
|
||||||
|
@ -317,16 +281,15 @@ class Composer extends React.Component {
|
||||||
spoilerText,
|
spoilerText,
|
||||||
suggestions,
|
suggestions,
|
||||||
text,
|
text,
|
||||||
},
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='compose'>
|
<div className='composer'>
|
||||||
<ComposerSpoiler
|
<ComposerSpoiler
|
||||||
hidden={!spoiler}
|
hidden={!spoiler}
|
||||||
intl={intl}
|
intl={intl}
|
||||||
onChange={changeSpoiler}
|
onChange={handleChangeSpoiler}
|
||||||
onSubmit={submit}
|
onSubmit={handleSubmit}
|
||||||
text={spoilerText}
|
text={spoilerText}
|
||||||
/>
|
/>
|
||||||
{privacy === 'private' && amUnlocked ? <ComposerWarning /> : null}
|
{privacy === 'private' && amUnlocked ? <ComposerWarning /> : null}
|
||||||
|
@ -336,32 +299,32 @@ class Composer extends React.Component {
|
||||||
content={replyContent}
|
content={replyContent}
|
||||||
history={history}
|
history={history}
|
||||||
intl={intl}
|
intl={intl}
|
||||||
onCancel={cancelReply}
|
onCancel={onCancelReply}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<ComposerTextarea
|
<ComposerTextarea
|
||||||
autoFocus={!showSearch && !isMobile(window.innerWidth)}
|
autoFocus={!showSearch && !isMobile(window.innerWidth, layout)}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
intl={intl}
|
intl={intl}
|
||||||
onChange={changeText}
|
onChange={onChangeText}
|
||||||
onPaste={upload}
|
onPaste={onUpload}
|
||||||
onPickEmoji={emoji}
|
onPickEmoji={handleEmoji}
|
||||||
onSubmit={submit}
|
onSubmit={handleSubmit}
|
||||||
onSuggestionsClearRequested={clearSuggestions}
|
onSuggestionsClearRequested={onClearSuggestions}
|
||||||
onSuggestionsFetchRequested={fetchSuggestions}
|
onSuggestionsFetchRequested={onFetchSuggestions}
|
||||||
onSuggestionSelected={select}
|
onSuggestionSelected={handleSelect}
|
||||||
ref={refTextarea}
|
ref={handleRefTextarea}
|
||||||
suggestions={suggestions}
|
suggestions={suggestions}
|
||||||
value={text}
|
value={text}
|
||||||
/>
|
/>
|
||||||
{media && media.size ? (
|
{isUploading || media && media.size ? (
|
||||||
<ComposerUploadForm
|
<ComposerUploadForm
|
||||||
active={isUploading}
|
|
||||||
intl={intl}
|
intl={intl}
|
||||||
media={media}
|
media={media}
|
||||||
onChangeDescription={changeDescription}
|
onChangeDescription={onChangeDescription}
|
||||||
onRemove={undoUpload}
|
onRemove={onUndoUpload}
|
||||||
progress={progress}
|
progress={progress}
|
||||||
|
uploading={isUploading}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<ComposerOptions
|
<ComposerOptions
|
||||||
|
@ -373,13 +336,14 @@ class Composer extends React.Component {
|
||||||
)}
|
)}
|
||||||
hasMedia={!!media.size}
|
hasMedia={!!media.size}
|
||||||
intl={intl}
|
intl={intl}
|
||||||
onChangeSensitivity={changeSensitivity}
|
onChangeSensitivity={onChangeSensitivity}
|
||||||
onChangeVisibility={changeVisibility}
|
onChangeVisibility={onChangeVisibility}
|
||||||
onDoodleOpen={openDoodleModal}
|
onDoodleOpen={onOpenDoodleModal}
|
||||||
onModalClose={closeModal}
|
onModalClose={onCloseModal}
|
||||||
onModalOpen={openActionsModal}
|
onModalOpen={onOpenActionsModal}
|
||||||
onToggleAdvancedOption={toggleAdvancedOption}
|
onToggleAdvancedOption={onToggleAdvancedOption}
|
||||||
onUpload={upload}
|
onToggleSpoiler={onChangeSpoilerness}
|
||||||
|
onUpload={onUpload}
|
||||||
privacy={privacy}
|
privacy={privacy}
|
||||||
resetFileKey={resetFileKey}
|
resetFileKey={resetFileKey}
|
||||||
sensitive={sensitive}
|
sensitive={sensitive}
|
||||||
|
@ -387,10 +351,10 @@ class Composer extends React.Component {
|
||||||
/>
|
/>
|
||||||
<ComposerPublisher
|
<ComposerPublisher
|
||||||
countText={`${spoilerText}${countableText(text)}${doNotFederate ? ' 👁️' : ''}`}
|
countText={`${spoilerText}${countableText(text)}${doNotFederate ? ' 👁️' : ''}`}
|
||||||
disabled={isSubmitting || isUploading || text.length && text.trim().length === 0}
|
disabled={isSubmitting || isUploading || !!text.length && !text.trim().length}
|
||||||
intl={intl}
|
intl={intl}
|
||||||
onSecondarySubmit={secondarySubmit}
|
onSecondarySubmit={handleSecondarySubmit}
|
||||||
onSubmit={submit}
|
onSubmit={handleSubmit}
|
||||||
privacy={privacy}
|
privacy={privacy}
|
||||||
sideArm={sideArm}
|
sideArm={sideArm}
|
||||||
/>
|
/>
|
||||||
|
@ -407,22 +371,23 @@ Composer.contextTypes = {
|
||||||
|
|
||||||
// Props.
|
// Props.
|
||||||
Composer.propTypes = {
|
Composer.propTypes = {
|
||||||
dispatch: PropTypes.objectOf(PropTypes.func).isRequired,
|
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
state: PropTypes.shape({
|
|
||||||
|
// State props.
|
||||||
acceptContentTypes: PropTypes.string,
|
acceptContentTypes: PropTypes.string,
|
||||||
amUnlocked: PropTypes.bool,
|
amUnlocked: PropTypes.bool,
|
||||||
doNotFederate: PropTypes.bool,
|
doNotFederate: PropTypes.bool,
|
||||||
focusDate: PropTypes.instanceOf(Date),
|
focusDate: PropTypes.instanceOf(Date),
|
||||||
isSubmitting: PropTypes.bool,
|
isSubmitting: PropTypes.bool,
|
||||||
isUploading: PropTypes.bool,
|
isUploading: PropTypes.bool,
|
||||||
media: PropTypes.list,
|
layout: PropTypes.string,
|
||||||
|
media: ImmutablePropTypes.list,
|
||||||
preselectDate: PropTypes.instanceOf(Date),
|
preselectDate: PropTypes.instanceOf(Date),
|
||||||
privacy: PropTypes.string,
|
privacy: PropTypes.string,
|
||||||
progress: PropTypes.number,
|
progress: PropTypes.number,
|
||||||
replyAccount: ImmutablePropTypes.map,
|
replyAccount: ImmutablePropTypes.map,
|
||||||
replyContent: PropTypes.string,
|
replyContent: PropTypes.string,
|
||||||
resetFileKey: PropTypes.string,
|
resetFileKey: PropTypes.number,
|
||||||
sideArm: PropTypes.string,
|
sideArm: PropTypes.string,
|
||||||
sensitive: PropTypes.bool,
|
sensitive: PropTypes.bool,
|
||||||
showSearch: PropTypes.bool,
|
showSearch: PropTypes.bool,
|
||||||
|
@ -431,13 +396,26 @@ Composer.propTypes = {
|
||||||
suggestionToken: PropTypes.string,
|
suggestionToken: PropTypes.string,
|
||||||
suggestions: ImmutablePropTypes.list,
|
suggestions: ImmutablePropTypes.list,
|
||||||
text: PropTypes.string,
|
text: PropTypes.string,
|
||||||
}).isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Default props.
|
// Dispatch props.
|
||||||
Composer.defaultProps = {
|
onCancelReply: PropTypes.func,
|
||||||
dispatch: {},
|
onChangeDescription: PropTypes.func,
|
||||||
state: {},
|
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,
|
||||||
|
onOpenActionsModal: PropTypes.func,
|
||||||
|
onOpenDoodleModal: PropTypes.func,
|
||||||
|
onSelectSuggestion: PropTypes.func,
|
||||||
|
onSubmit: PropTypes.func,
|
||||||
|
onToggleAdvancedOption: PropTypes.func,
|
||||||
|
onUndoUpload: PropTypes.func,
|
||||||
|
onUpload: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Connecting and export.
|
// Connecting and export.
|
||||||
|
|
|
@ -0,0 +1,138 @@
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// On mounting, we add our listeners.
|
||||||
|
componentDidMount () {
|
||||||
|
const { handleDocumentClick } = this.handlers;
|
||||||
|
document.addEventListener('click', handleDocumentClick, false);
|
||||||
|
document.addEventListener('touchend', handleDocumentClick, withPassive);
|
||||||
|
}
|
||||||
|
|
||||||
|
// On unmounting, we remove our listeners.
|
||||||
|
componentWillUnmount () {
|
||||||
|
const { handleDocumentClick } = this.handlers;
|
||||||
|
document.removeEventListener('click', handleDocumentClick, false);
|
||||||
|
document.removeEventListener('touchend', handleDocumentClick, withPassive);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rendering.
|
||||||
|
render () {
|
||||||
|
const { handleRef } = this.handlers;
|
||||||
|
const {
|
||||||
|
items,
|
||||||
|
onChange,
|
||||||
|
onClose,
|
||||||
|
style,
|
||||||
|
value,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
// The result.
|
||||||
|
return (
|
||||||
|
<Motion
|
||||||
|
defaultStyle={{
|
||||||
|
opacity: 0,
|
||||||
|
scaleX: 0.85,
|
||||||
|
scaleY: 0.75,
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
opacity: springMotion,
|
||||||
|
scaleX: springMotion,
|
||||||
|
scaleY: springMotion,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ opacity, scaleX, scaleY }) => (
|
||||||
|
<div
|
||||||
|
className='composer--options--dropdown--content'
|
||||||
|
ref={handleRef}
|
||||||
|
style={{
|
||||||
|
...style,
|
||||||
|
opacity: opacity,
|
||||||
|
transform: `scale(${scaleX}, ${scaleY})`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{items.map(
|
||||||
|
({
|
||||||
|
name,
|
||||||
|
...rest
|
||||||
|
}) => (
|
||||||
|
<ComposerOptionsDropdownContentItem
|
||||||
|
active={name === value}
|
||||||
|
key={name}
|
||||||
|
name={name}
|
||||||
|
onChange={onChange}
|
||||||
|
onClose={onClose}
|
||||||
|
options={rest}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Motion>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Props.
|
||||||
|
ComposerOptionsDropdownContent.propTypes = {
|
||||||
|
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,
|
||||||
|
onClose: PropTypes.func,
|
||||||
|
style: PropTypes.object,
|
||||||
|
value: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Default props.
|
||||||
|
ComposerOptionsDropdownContent.defaultProps = { style: {} };
|
|
@ -14,7 +14,7 @@ import { assignHandlers } from 'flavours/glitch/util/react_helpers';
|
||||||
const handlers = {
|
const handlers = {
|
||||||
|
|
||||||
// This function activates the dropdown item.
|
// This function activates the dropdown item.
|
||||||
activate (e) {
|
handleActivate (e) {
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
onChange,
|
onChange,
|
||||||
|
@ -35,11 +35,10 @@ const handlers = {
|
||||||
onChange(name);
|
onChange(name);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// The component.
|
// The component.
|
||||||
export default class ComposerOptionsDropdownItem extends React.PureComponent {
|
export default class ComposerOptionsDropdownContentItem extends React.PureComponent {
|
||||||
|
|
||||||
// Constructor.
|
// Constructor.
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
|
@ -49,7 +48,7 @@ export default class ComposerOptionsDropdownItem extends React.PureComponent {
|
||||||
|
|
||||||
// Rendering.
|
// Rendering.
|
||||||
render () {
|
render () {
|
||||||
const { activate } = this.handlers;
|
const { handleActivate } = this.handlers;
|
||||||
const {
|
const {
|
||||||
active,
|
active,
|
||||||
options: {
|
options: {
|
||||||
|
@ -59,7 +58,7 @@ export default class ComposerOptionsDropdownItem extends React.PureComponent {
|
||||||
text,
|
text,
|
||||||
},
|
},
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const computedClass = classNames('composer--options--dropdown_item', {
|
const computedClass = classNames('composer--options--dropdown--content--item', {
|
||||||
active,
|
active,
|
||||||
lengthy: meta,
|
lengthy: meta,
|
||||||
'toggled-off': !on && on !== null && typeof on !== 'undefined',
|
'toggled-off': !on && on !== null && typeof on !== 'undefined',
|
||||||
|
@ -71,8 +70,8 @@ export default class ComposerOptionsDropdownItem extends React.PureComponent {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={computedClass}
|
className={computedClass}
|
||||||
onClick={activate}
|
onClick={handleActivate}
|
||||||
onKeyDown={activate}
|
onKeyDown={handleActivate}
|
||||||
role='button'
|
role='button'
|
||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
>
|
>
|
||||||
|
@ -85,7 +84,7 @@ export default class ComposerOptionsDropdownItem extends React.PureComponent {
|
||||||
return (
|
return (
|
||||||
<Toggle
|
<Toggle
|
||||||
checked={on}
|
checked={on}
|
||||||
onChange={activate}
|
onChange={handleActivate}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case !!icon:
|
case !!icon:
|
||||||
|
@ -113,7 +112,7 @@ export default class ComposerOptionsDropdownItem extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Props.
|
// Props.
|
||||||
ComposerOptionsDropdownItem.propTypes = {
|
ComposerOptionsDropdownContentItem.propTypes = {
|
||||||
active: PropTypes.bool,
|
active: PropTypes.bool,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
|
@ -2,77 +2,59 @@
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import spring from 'react-motion/lib/spring';
|
|
||||||
import Overlay from 'react-overlays/lib/Overlay';
|
import Overlay from 'react-overlays/lib/Overlay';
|
||||||
|
|
||||||
// Components.
|
// Components.
|
||||||
import IconButton from 'flavours/glitch/components/icon_button';
|
import IconButton from 'flavours/glitch/components/icon_button';
|
||||||
import ComposerOptionsDropdownItem from './item';
|
import ComposerOptionsDropdownContent from './content';
|
||||||
|
|
||||||
// Utils.
|
// Utils.
|
||||||
import { withPassive } from 'flavours/glitch/util/dom_helpers';
|
|
||||||
import { isUserTouching } from 'flavours/glitch/util/is_mobile';
|
import { isUserTouching } from 'flavours/glitch/util/is_mobile';
|
||||||
import Motion from 'flavours/glitch/util/optional_motion';
|
|
||||||
import { assignHandlers } from 'flavours/glitch/util/react_helpers';
|
import { assignHandlers } from 'flavours/glitch/util/react_helpers';
|
||||||
|
|
||||||
// We'll use this to define our various transitions.
|
|
||||||
const springMotion = spring(1, {
|
|
||||||
damping: 35,
|
|
||||||
stiffness: 400,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handlers.
|
// Handlers.
|
||||||
const handlers = {
|
const handlers = {
|
||||||
|
|
||||||
// Closes the dropdown.
|
// Closes the dropdown.
|
||||||
close () {
|
handleClose () {
|
||||||
this.setState({ open: false });
|
this.setState({ open: false });
|
||||||
},
|
},
|
||||||
|
|
||||||
// When the document is clicked elsewhere, we close the dropdown.
|
|
||||||
documentClick ({ target }) {
|
|
||||||
const { node } = this;
|
|
||||||
const { onClose } = this.props;
|
|
||||||
if (onClose && node && !node.contains(target)) {
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// The enter key toggles the dropdown's open state, and the escape
|
// The enter key toggles the dropdown's open state, and the escape
|
||||||
// key closes it.
|
// key closes it.
|
||||||
keyDown ({ key }) {
|
handleKeyDown ({ key }) {
|
||||||
const {
|
const {
|
||||||
close,
|
handleClose,
|
||||||
toggle,
|
handleToggle,
|
||||||
} = this.handlers;
|
} = this.handlers;
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
toggle();
|
handleToggle();
|
||||||
break;
|
break;
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
close();
|
handleClose();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Toggles opening and closing the dropdown.
|
// Creates an action modal object.
|
||||||
toggle () {
|
handleMakeModal () {
|
||||||
|
const component = this;
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
onChange,
|
onChange,
|
||||||
onModalClose,
|
|
||||||
onModalOpen,
|
onModalOpen,
|
||||||
|
onModalClose,
|
||||||
value,
|
value,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { open } = this.state;
|
|
||||||
|
|
||||||
// If this is a touch device, we open a modal instead of the
|
// Required props.
|
||||||
// dropdown.
|
if (!(onChange && onModalOpen && onModalClose && items)) {
|
||||||
if (onModalClose && isUserTouching()) {
|
return null;
|
||||||
if (open) {
|
}
|
||||||
onModalClose();
|
|
||||||
} else if (onChange && onModalOpen) {
|
// The object.
|
||||||
onModalOpen({
|
return {
|
||||||
actions: items.map(
|
actions: items.map(
|
||||||
({
|
({
|
||||||
name,
|
name,
|
||||||
|
@ -89,21 +71,51 @@ const handlers = {
|
||||||
onPassiveClick (e) {
|
onPassiveClick (e) {
|
||||||
e.preventDefault(); // Prevents focus from changing
|
e.preventDefault(); // Prevents focus from changing
|
||||||
onChange(name);
|
onChange(name);
|
||||||
|
component.setState({ needsModalUpdate: true });
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
});
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// Toggles opening and closing the dropdown.
|
||||||
|
handleToggle () {
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, we just set our state to open.
|
// Otherwise, we just set our state to open.
|
||||||
} else {
|
|
||||||
this.setState({ open: !open });
|
this.setState({ open: !open });
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Stores our node in `this.node`.
|
// If our modal is open and our props update, we need to also update
|
||||||
ref (node) {
|
// the modal.
|
||||||
this.node = node;
|
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);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -114,33 +126,31 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props);
|
super(props);
|
||||||
assignHandlers(this, handlers);
|
assignHandlers(this, handlers);
|
||||||
this.state = { open: false };
|
this.state = {
|
||||||
|
needsModalUpdate: false,
|
||||||
// Instance variables.
|
open: false,
|
||||||
this.node = null;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// On mounting, we add our listeners.
|
// Updates our modal as necessary.
|
||||||
componentDidMount () {
|
componentDidUpdate (prevProps) {
|
||||||
const { documentClick } = this.handlers;
|
const { handleUpdate } = this.handlers;
|
||||||
document.addEventListener('click', documentClick, false);
|
const { items } = this.props;
|
||||||
document.addEventListener('touchend', documentClick, withPassive);
|
const { needsModalUpdate } = this.state;
|
||||||
|
if (needsModalUpdate && items.find(
|
||||||
|
(item, i) => item.on !== prevProps.items[i].on
|
||||||
|
)) {
|
||||||
|
handleUpdate();
|
||||||
|
this.setState({ needsModalUpdate: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
// On unmounting, we remove our listeners.
|
|
||||||
componentWillUnmount () {
|
|
||||||
const { documentClick } = this.handlers;
|
|
||||||
document.removeEventListener('click', documentClick, false);
|
|
||||||
document.removeEventListener('touchend', documentClick, withPassive);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rendering.
|
// Rendering.
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
close,
|
handleClose,
|
||||||
keyDown,
|
handleKeyDown,
|
||||||
ref,
|
handleToggle,
|
||||||
toggle,
|
|
||||||
} = this.handlers;
|
} = this.handlers;
|
||||||
const {
|
const {
|
||||||
active,
|
active,
|
||||||
|
@ -154,22 +164,21 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
||||||
const { open } = this.state;
|
const { open } = this.state;
|
||||||
const computedClass = classNames('composer--options--dropdown', {
|
const computedClass = classNames('composer--options--dropdown', {
|
||||||
active,
|
active,
|
||||||
open: open || active,
|
open,
|
||||||
});
|
});
|
||||||
|
|
||||||
// The result.
|
// The result.
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={computedClass}
|
className={computedClass}
|
||||||
onKeyDown={keyDown}
|
onKeyDown={handleKeyDown}
|
||||||
ref={ref}
|
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
active={open || active}
|
active={open || active}
|
||||||
className='value'
|
className='value'
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
icon={icon}
|
icon={icon}
|
||||||
onClick={toggle}
|
onClick={handleToggle}
|
||||||
size={18}
|
size={18}
|
||||||
style={{
|
style={{
|
||||||
height: null,
|
height: null,
|
||||||
|
@ -178,49 +187,17 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
||||||
title={title}
|
title={title}
|
||||||
/>
|
/>
|
||||||
<Overlay
|
<Overlay
|
||||||
|
containerPadding={20}
|
||||||
placement='bottom'
|
placement='bottom'
|
||||||
show={open}
|
show={open}
|
||||||
target={this}
|
target={this}
|
||||||
>
|
>
|
||||||
<Motion
|
<ComposerOptionsDropdownContent
|
||||||
defaultStyle={{
|
items={items}
|
||||||
opacity: 0,
|
|
||||||
scaleX: 0.85,
|
|
||||||
scaleY: 0.75,
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
opacity: springMotion,
|
|
||||||
scaleX: springMotion,
|
|
||||||
scaleY: springMotion,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{({ opacity, scaleX, scaleY }) => (
|
|
||||||
<div
|
|
||||||
className='composer--options--dropdown__dropdown'
|
|
||||||
ref={this.setRef}
|
|
||||||
style={{
|
|
||||||
opacity: opacity,
|
|
||||||
transform: `scale(${scaleX}, ${scaleY})`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{items.map(
|
|
||||||
({
|
|
||||||
name,
|
|
||||||
...rest
|
|
||||||
}) => (
|
|
||||||
<ComposerOptionsDropdownItem
|
|
||||||
active={name === value}
|
|
||||||
key={name}
|
|
||||||
name={name}
|
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onClose={close}
|
onClose={handleClose}
|
||||||
options={rest}
|
value={value}
|
||||||
/>
|
/>
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Motion>
|
|
||||||
</Overlay>
|
</Overlay>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -95,7 +95,7 @@ const messages = defineMessages({
|
||||||
const handlers = {
|
const handlers = {
|
||||||
|
|
||||||
// Handles file selection.
|
// Handles file selection.
|
||||||
changeFiles ({ target: { files } }) {
|
handleChangeFiles ({ target: { files } }) {
|
||||||
const { onUpload } = this.props;
|
const { onUpload } = this.props;
|
||||||
if (files.length && onUpload) {
|
if (files.length && onUpload) {
|
||||||
onUpload(files);
|
onUpload(files);
|
||||||
|
@ -103,7 +103,7 @@ const handlers = {
|
||||||
},
|
},
|
||||||
|
|
||||||
// Handles attachment clicks.
|
// Handles attachment clicks.
|
||||||
clickAttach (name) {
|
handleClickAttach (name) {
|
||||||
const { fileElement } = this;
|
const { fileElement } = this;
|
||||||
const { onDoodleOpen } = this.props;
|
const { onDoodleOpen } = this.props;
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@ const handlers = {
|
||||||
},
|
},
|
||||||
|
|
||||||
// Handles a ref to the file input.
|
// Handles a ref to the file input.
|
||||||
refFileElement (fileElement) {
|
handleRefFileElement (fileElement) {
|
||||||
this.fileElement = fileElement;
|
this.fileElement = fileElement;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -143,9 +143,9 @@ export default class ComposerOptions extends React.PureComponent {
|
||||||
// Rendering.
|
// Rendering.
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
changeFiles,
|
handleChangeFiles,
|
||||||
clickAttach,
|
handleClickAttach,
|
||||||
refFileElement,
|
handleRefFileElement,
|
||||||
} = this.handlers;
|
} = this.handlers;
|
||||||
const {
|
const {
|
||||||
acceptContentTypes,
|
acceptContentTypes,
|
||||||
|
@ -159,6 +159,7 @@ export default class ComposerOptions extends React.PureComponent {
|
||||||
onModalClose,
|
onModalClose,
|
||||||
onModalOpen,
|
onModalOpen,
|
||||||
onToggleAdvancedOption,
|
onToggleAdvancedOption,
|
||||||
|
onToggleSpoiler,
|
||||||
privacy,
|
privacy,
|
||||||
resetFileKey,
|
resetFileKey,
|
||||||
sensitive,
|
sensitive,
|
||||||
|
@ -201,8 +202,8 @@ export default class ComposerOptions extends React.PureComponent {
|
||||||
accept={acceptContentTypes}
|
accept={acceptContentTypes}
|
||||||
disabled={disabled || full}
|
disabled={disabled || full}
|
||||||
key={resetFileKey}
|
key={resetFileKey}
|
||||||
onChange={changeFiles}
|
onChange={handleChangeFiles}
|
||||||
ref={refFileElement}
|
ref={handleRefFileElement}
|
||||||
type='file'
|
type='file'
|
||||||
{...hiddenComponent}
|
{...hiddenComponent}
|
||||||
/>
|
/>
|
||||||
|
@ -221,10 +222,10 @@ export default class ComposerOptions extends React.PureComponent {
|
||||||
text: <FormattedMessage {...messages.doodle} />,
|
text: <FormattedMessage {...messages.doodle} />,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
onChange={clickAttach}
|
onChange={handleClickAttach}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
onModalOpen={onModalOpen}
|
onModalOpen={onModalOpen}
|
||||||
title={messages.attach}
|
title={intl.formatMessage(messages.attach)}
|
||||||
/>
|
/>
|
||||||
<Motion
|
<Motion
|
||||||
defaultStyle={{ scale: 0.87 }}
|
defaultStyle={{ scale: 0.87 }}
|
||||||
|
@ -279,6 +280,7 @@ export default class ComposerOptions extends React.PureComponent {
|
||||||
active={spoiler}
|
active={spoiler}
|
||||||
ariaControls='glitch.composer.spoiler.input'
|
ariaControls='glitch.composer.spoiler.input'
|
||||||
label='CW'
|
label='CW'
|
||||||
|
onClick={onToggleSpoiler}
|
||||||
title={intl.formatMessage(messages.spoiler)}
|
title={intl.formatMessage(messages.spoiler)}
|
||||||
/>
|
/>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
|
@ -318,9 +320,10 @@ ComposerOptions.propTypes = {
|
||||||
onModalClose: PropTypes.func,
|
onModalClose: PropTypes.func,
|
||||||
onModalOpen: PropTypes.func,
|
onModalOpen: PropTypes.func,
|
||||||
onToggleAdvancedOption: PropTypes.func,
|
onToggleAdvancedOption: PropTypes.func,
|
||||||
|
onToggleSpoiler: PropTypes.func,
|
||||||
onUpload: PropTypes.func,
|
onUpload: PropTypes.func,
|
||||||
privacy: PropTypes.string,
|
privacy: PropTypes.string,
|
||||||
resetFileKey: PropTypes.string,
|
resetFileKey: PropTypes.number,
|
||||||
sensitive: PropTypes.bool,
|
sensitive: PropTypes.bool,
|
||||||
spoiler: PropTypes.bool,
|
spoiler: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
|
@ -46,10 +46,13 @@ export default function ComposerPublisher ({
|
||||||
// The result.
|
// The result.
|
||||||
return (
|
return (
|
||||||
<div className={computedClass}>
|
<div className={computedClass}>
|
||||||
<span class='count'>{diff}</span>
|
<span className='count'>{diff}</span>
|
||||||
{sideArm && sideArm !== 'none' ? (
|
{sideArm && sideArm !== 'none' ? (
|
||||||
<Button
|
<Button
|
||||||
className='side_arm'
|
className='side_arm'
|
||||||
|
disabled={disabled || diff < 0}
|
||||||
|
onClick={onSecondarySubmit}
|
||||||
|
style={{ padding: null }}
|
||||||
text={
|
text={
|
||||||
<span>
|
<span>
|
||||||
<Icon
|
<Icon
|
||||||
|
@ -63,8 +66,6 @@ export default function ComposerPublisher ({
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
title={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${sideArm}.short` })}`}
|
title={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${sideArm}.short` })}`}
|
||||||
onClick={onSecondarySubmit}
|
|
||||||
disabled={disabled || diff < 0}
|
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -25,7 +25,7 @@ const messages = defineMessages({
|
||||||
const handlers = {
|
const handlers = {
|
||||||
|
|
||||||
// Handles a click on the "close" button.
|
// Handles a click on the "close" button.
|
||||||
click () {
|
handleClick () {
|
||||||
const { onCancel } = this.props;
|
const { onCancel } = this.props;
|
||||||
if (onCancel) {
|
if (onCancel) {
|
||||||
onCancel();
|
onCancel();
|
||||||
|
@ -33,7 +33,7 @@ const handlers = {
|
||||||
},
|
},
|
||||||
|
|
||||||
// Handles a click on the status's account.
|
// Handles a click on the status's account.
|
||||||
clickAccount () {
|
handleClickAccount () {
|
||||||
const {
|
const {
|
||||||
account,
|
account,
|
||||||
history,
|
history,
|
||||||
|
@ -56,8 +56,8 @@ export default class ComposerReply extends React.PureComponent {
|
||||||
// Rendering.
|
// Rendering.
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
click,
|
handleClick,
|
||||||
clickAccount,
|
handleClickAccount,
|
||||||
} = this.handlers;
|
} = this.handlers;
|
||||||
const {
|
const {
|
||||||
account,
|
account,
|
||||||
|
@ -72,14 +72,14 @@ export default class ComposerReply extends React.PureComponent {
|
||||||
<IconButton
|
<IconButton
|
||||||
className='cancel'
|
className='cancel'
|
||||||
icon='times'
|
icon='times'
|
||||||
onClick={click}
|
onClick={handleClick}
|
||||||
title={intl.formatMessage(messages.cancel)}
|
title={intl.formatMessage(messages.cancel)}
|
||||||
/>
|
/>
|
||||||
{account ? (
|
{account ? (
|
||||||
<a
|
<a
|
||||||
className='account'
|
className='account'
|
||||||
href={account.get('url')}
|
href={account.get('url')}
|
||||||
onClick={clickAccount}
|
onClick={handleClickAccount}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
account={account}
|
account={account}
|
||||||
|
|
|
@ -24,7 +24,7 @@ const messages = defineMessages({
|
||||||
const handlers = {
|
const handlers = {
|
||||||
|
|
||||||
// Handles a keypress.
|
// Handles a keypress.
|
||||||
keyDown ({
|
handleKeyDown ({
|
||||||
ctrlKey,
|
ctrlKey,
|
||||||
keyCode,
|
keyCode,
|
||||||
metaKey,
|
metaKey,
|
||||||
|
@ -49,7 +49,7 @@ export default class ComposerSpoiler extends React.PureComponent {
|
||||||
|
|
||||||
// Rendering.
|
// Rendering.
|
||||||
render () {
|
render () {
|
||||||
const { keyDown } = this.handlers;
|
const { handleKeyDown } = this.handlers;
|
||||||
const {
|
const {
|
||||||
hidden,
|
hidden,
|
||||||
intl,
|
intl,
|
||||||
|
@ -70,7 +70,7 @@ export default class ComposerSpoiler extends React.PureComponent {
|
||||||
<input
|
<input
|
||||||
id='glitch.composer.spoiler.input'
|
id='glitch.composer.spoiler.input'
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onKeyDown={keyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder={intl.formatMessage(messages.placeholder)}
|
placeholder={intl.formatMessage(messages.placeholder)}
|
||||||
type='text'
|
type='text'
|
||||||
value={text}
|
value={text}
|
||||||
|
|
|
@ -31,14 +31,14 @@ const messages = defineMessages({
|
||||||
const handlers = {
|
const handlers = {
|
||||||
|
|
||||||
// When blurring the textarea, suggestions are hidden.
|
// When blurring the textarea, suggestions are hidden.
|
||||||
blur () {
|
handleBlur () {
|
||||||
this.setState({ suggestionsHidden: true });
|
this.setState({ suggestionsHidden: true });
|
||||||
},
|
},
|
||||||
|
|
||||||
// When the contents of the textarea change, we have to pull up new
|
// When the contents of the textarea change, we have to pull up new
|
||||||
// autosuggest suggestions if applicable, and also change the value
|
// autosuggest suggestions if applicable, and also change the value
|
||||||
// of the textarea in our store.
|
// of the textarea in our store.
|
||||||
change ({
|
handleChange ({
|
||||||
target: {
|
target: {
|
||||||
selectionStart,
|
selectionStart,
|
||||||
value,
|
value,
|
||||||
|
@ -91,7 +91,7 @@ const handlers = {
|
||||||
},
|
},
|
||||||
|
|
||||||
// Handles a click on an autosuggestion.
|
// Handles a click on an autosuggestion.
|
||||||
clickSuggestion (index) {
|
handleClickSuggestion (index) {
|
||||||
const { textarea } = this;
|
const { textarea } = this;
|
||||||
const {
|
const {
|
||||||
onSuggestionSelected,
|
onSuggestionSelected,
|
||||||
|
@ -107,7 +107,7 @@ const handlers = {
|
||||||
|
|
||||||
// Handles a keypress. If the autosuggestions are visible, we need
|
// Handles a keypress. If the autosuggestions are visible, we need
|
||||||
// to allow keypresses to navigate and sleect them.
|
// to allow keypresses to navigate and sleect them.
|
||||||
keyDown (e) {
|
handleKeyDown (e) {
|
||||||
const {
|
const {
|
||||||
disabled,
|
disabled,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
|
@ -165,7 +165,7 @@ const handlers = {
|
||||||
|
|
||||||
// When the escape key is released, we either close the suggestions
|
// When the escape key is released, we either close the suggestions
|
||||||
// window or focus the UI.
|
// window or focus the UI.
|
||||||
keyUp ({ key }) {
|
handleKeyUp ({ key }) {
|
||||||
const { suggestionsHidden } = this.state;
|
const { suggestionsHidden } = this.state;
|
||||||
if (key === 'Escape') {
|
if (key === 'Escape') {
|
||||||
if (!suggestionsHidden) {
|
if (!suggestionsHidden) {
|
||||||
|
@ -177,7 +177,7 @@ const handlers = {
|
||||||
},
|
},
|
||||||
|
|
||||||
// Handles the pasting of images into the composer.
|
// Handles the pasting of images into the composer.
|
||||||
paste (e) {
|
handlePaste (e) {
|
||||||
const { onPaste } = this.props;
|
const { onPaste } = this.props;
|
||||||
let d;
|
let d;
|
||||||
if (onPaste && (d = e.clipboardData) && (d = d.files).length === 1) {
|
if (onPaste && (d = e.clipboardData) && (d = d.files).length === 1) {
|
||||||
|
@ -187,7 +187,7 @@ const handlers = {
|
||||||
},
|
},
|
||||||
|
|
||||||
// Saves a reference to the textarea.
|
// Saves a reference to the textarea.
|
||||||
refTextarea (textarea) {
|
handleRefTextarea (textarea) {
|
||||||
this.textarea = textarea;
|
this.textarea = textarea;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -223,13 +223,13 @@ export default class ComposerTextarea extends React.Component {
|
||||||
// Rendering.
|
// Rendering.
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
blur,
|
handleBlur,
|
||||||
change,
|
handleChange,
|
||||||
clickSuggestion,
|
handleClickSuggestion,
|
||||||
keyDown,
|
handleKeyDown,
|
||||||
keyUp,
|
handleKeyUp,
|
||||||
paste,
|
handlePaste,
|
||||||
refTextarea,
|
handleRefTextarea,
|
||||||
} = this.handlers;
|
} = this.handlers;
|
||||||
const {
|
const {
|
||||||
autoFocus,
|
autoFocus,
|
||||||
|
@ -254,12 +254,12 @@ export default class ComposerTextarea extends React.Component {
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
className='textarea'
|
className='textarea'
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
inputRef={refTextarea}
|
inputRef={handleRefTextarea}
|
||||||
onBlur={blur}
|
onBlur={handleBlur}
|
||||||
onChange={change}
|
onChange={handleChange}
|
||||||
onKeyDown={keyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onKeyUp={keyUp}
|
onKeyUp={handleKeyUp}
|
||||||
onPaste={paste}
|
onPaste={handlePaste}
|
||||||
placeholder={intl.formatMessage(messages.placeholder)}
|
placeholder={intl.formatMessage(messages.placeholder)}
|
||||||
value={value}
|
value={value}
|
||||||
style={{ direction: isRtl(value) ? 'rtl' : 'ltr' }}
|
style={{ direction: isRtl(value) ? 'rtl' : 'ltr' }}
|
||||||
|
@ -268,7 +268,7 @@ export default class ComposerTextarea extends React.Component {
|
||||||
<EmojiPicker onPickEmoji={onPickEmoji} />
|
<EmojiPicker onPickEmoji={onPickEmoji} />
|
||||||
<ComposerTextareaSuggestions
|
<ComposerTextareaSuggestions
|
||||||
hidden={suggestionsHidden}
|
hidden={suggestionsHidden}
|
||||||
onSuggestionClick={clickSuggestion}
|
onSuggestionClick={handleClickSuggestion}
|
||||||
suggestions={suggestions}
|
suggestions={suggestions}
|
||||||
value={selectedSuggestion}
|
value={selectedSuggestion}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -18,9 +18,9 @@ export default function ComposerTextareaSuggestions ({
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className='composer--textarea--suggestions'
|
className='composer--textarea--suggestions'
|
||||||
hidden={hidden || suggestions.isEmpty()}
|
hidden={hidden || !suggestions || suggestions.isEmpty()}
|
||||||
>
|
>
|
||||||
{!hidden ? suggestions.map(
|
{!hidden && suggestions ? suggestions.map(
|
||||||
(suggestion, index) => (
|
(suggestion, index) => (
|
||||||
<ComposerTextareaSuggestionsItem
|
<ComposerTextareaSuggestionsItem
|
||||||
index={index}
|
index={index}
|
||||||
|
@ -39,5 +39,5 @@ ComposerTextareaSuggestions.propTypes = {
|
||||||
hidden: PropTypes.bool,
|
hidden: PropTypes.bool,
|
||||||
onSuggestionClick: PropTypes.func,
|
onSuggestionClick: PropTypes.func,
|
||||||
suggestions: ImmutablePropTypes.list,
|
suggestions: ImmutablePropTypes.list,
|
||||||
value: PropTypes.string,
|
value: PropTypes.number,
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,7 +17,7 @@ const assetHost = ((process || {}).env || {}).CDN_HOST || '';
|
||||||
const handlers = {
|
const handlers = {
|
||||||
|
|
||||||
// Handles a click on a suggestion.
|
// Handles a click on a suggestion.
|
||||||
click (e) {
|
handleClick (e) {
|
||||||
const {
|
const {
|
||||||
index,
|
index,
|
||||||
onClick,
|
onClick,
|
||||||
|
@ -40,7 +40,7 @@ export default class ComposerTextareaSuggestionsItem extends React.Component {
|
||||||
|
|
||||||
// Rendering.
|
// Rendering.
|
||||||
render () {
|
render () {
|
||||||
const { click } = this.handlers;
|
const { handleClick } = this.handlers;
|
||||||
const {
|
const {
|
||||||
selected,
|
selected,
|
||||||
suggestion,
|
suggestion,
|
||||||
|
@ -51,7 +51,7 @@ export default class ComposerTextareaSuggestionsItem extends React.Component {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={computedClass}
|
className={computedClass}
|
||||||
onMouseDown={click}
|
onMouseDown={handleClick}
|
||||||
role='button'
|
role='button'
|
||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
>
|
>
|
||||||
|
|
|
@ -10,24 +10,21 @@ import ComposerUploadFormProgress from './progress';
|
||||||
|
|
||||||
// The component.
|
// The component.
|
||||||
export default function ComposerUploadForm ({
|
export default function ComposerUploadForm ({
|
||||||
active,
|
|
||||||
intl,
|
intl,
|
||||||
media,
|
media,
|
||||||
onChangeDescription,
|
onChangeDescription,
|
||||||
onRemove,
|
onRemove,
|
||||||
progress,
|
progress,
|
||||||
|
uploading,
|
||||||
}) {
|
}) {
|
||||||
const computedClass = classNames('composer--upload_form', { uploading: active });
|
const computedClass = classNames('composer--upload_form', { uploading });
|
||||||
|
|
||||||
// We need `media` in order to be able to render.
|
|
||||||
if (!media) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The result.
|
// The result.
|
||||||
return (
|
return (
|
||||||
<div className={computedClass}>
|
<div className={computedClass}>
|
||||||
{active ? <ComposerUploadFormProgress progress={progress} /> : null}
|
{uploading ? <ComposerUploadFormProgress progress={progress} /> : null}
|
||||||
|
{media ? (
|
||||||
|
<div className='content'>
|
||||||
{media.map(item => (
|
{media.map(item => (
|
||||||
<ComposerUploadFormItem
|
<ComposerUploadFormItem
|
||||||
description={item.get('description')}
|
description={item.get('description')}
|
||||||
|
@ -40,15 +37,17 @@ export default function ComposerUploadForm ({
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Props.
|
// Props.
|
||||||
ComposerUploadForm.propTypes = {
|
ComposerUploadForm.propTypes = {
|
||||||
active: PropTypes.bool,
|
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
media: ImmutablePropTypes.list,
|
media: ImmutablePropTypes.list,
|
||||||
onChangeDescription: PropTypes.func,
|
onChangeDescription: PropTypes.func,
|
||||||
onRemove: PropTypes.func,
|
onRemove: PropTypes.func,
|
||||||
progress: PropTypes.number,
|
progress: PropTypes.number,
|
||||||
|
uploading: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
|
@ -31,7 +31,7 @@ const messages = defineMessages({
|
||||||
const handlers = {
|
const handlers = {
|
||||||
|
|
||||||
// On blur, we save the description for the media item.
|
// On blur, we save the description for the media item.
|
||||||
blur () {
|
handleBlur () {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
onChangeDescription,
|
onChangeDescription,
|
||||||
|
@ -48,27 +48,27 @@ const handlers = {
|
||||||
|
|
||||||
// When the value of our description changes, we store it in the
|
// When the value of our description changes, we store it in the
|
||||||
// temp value `dirtyDescription` in our state.
|
// temp value `dirtyDescription` in our state.
|
||||||
change ({ target: { value } }) {
|
handleChange ({ target: { value } }) {
|
||||||
this.setState({ dirtyDescription: value });
|
this.setState({ dirtyDescription: value });
|
||||||
},
|
},
|
||||||
|
|
||||||
// Records focus on the media item.
|
// Records focus on the media item.
|
||||||
focus () {
|
handleFocus () {
|
||||||
this.setState({ focused: true });
|
this.setState({ focused: true });
|
||||||
},
|
},
|
||||||
|
|
||||||
// Records the start of a hover over the media item.
|
// Records the start of a hover over the media item.
|
||||||
mouseEnter () {
|
handleMouseEnter () {
|
||||||
this.setState({ hovered: true });
|
this.setState({ hovered: true });
|
||||||
},
|
},
|
||||||
|
|
||||||
// Records the end of a hover over the media item.
|
// Records the end of a hover over the media item.
|
||||||
mouseLeave () {
|
handleMouseLeave () {
|
||||||
this.setState({ hovered: false });
|
this.setState({ hovered: false });
|
||||||
},
|
},
|
||||||
|
|
||||||
// Removes the media item.
|
// Removes the media item.
|
||||||
remove () {
|
handleRemove () {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
onRemove,
|
onRemove,
|
||||||
|
@ -85,7 +85,7 @@ export default class ComposerUploadFormItem extends React.PureComponent {
|
||||||
// Constructor.
|
// Constructor.
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props);
|
super(props);
|
||||||
assignHandlers(handlers);
|
assignHandlers(this, handlers);
|
||||||
this.state = {
|
this.state = {
|
||||||
hovered: false,
|
hovered: false,
|
||||||
focused: false,
|
focused: false,
|
||||||
|
@ -96,12 +96,12 @@ export default class ComposerUploadFormItem extends React.PureComponent {
|
||||||
// Rendering.
|
// Rendering.
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
blur,
|
handleBlur,
|
||||||
change,
|
handleChange,
|
||||||
focus,
|
handleFocus,
|
||||||
mouseEnter,
|
handleMouseEnter,
|
||||||
mouseLeave,
|
handleMouseLeave,
|
||||||
remove,
|
handleRemove,
|
||||||
} = this.handlers;
|
} = this.handlers;
|
||||||
const {
|
const {
|
||||||
description,
|
description,
|
||||||
|
@ -119,8 +119,8 @@ export default class ComposerUploadFormItem extends React.PureComponent {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={computedClass}
|
className={computedClass}
|
||||||
onMouseEnter={mouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
onMouseLeave={mouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
>
|
>
|
||||||
<Motion
|
<Motion
|
||||||
defaultStyle={{ scale: 0.8 }}
|
defaultStyle={{ scale: 0.8 }}
|
||||||
|
@ -141,7 +141,7 @@ export default class ComposerUploadFormItem extends React.PureComponent {
|
||||||
<IconButton
|
<IconButton
|
||||||
className='close'
|
className='close'
|
||||||
icon='times'
|
icon='times'
|
||||||
onClick={remove}
|
onClick={handleRemove}
|
||||||
size={36}
|
size={36}
|
||||||
title={intl.formatMessage(messages.undo)}
|
title={intl.formatMessage(messages.undo)}
|
||||||
/>
|
/>
|
||||||
|
@ -149,9 +149,9 @@ export default class ComposerUploadFormItem extends React.PureComponent {
|
||||||
<span style={{ display: 'none' }}><FormattedMessage {...messages.description} /></span>
|
<span style={{ display: 'none' }}><FormattedMessage {...messages.description} /></span>
|
||||||
<input
|
<input
|
||||||
maxLength={420}
|
maxLength={420}
|
||||||
onBlur={blur}
|
onBlur={handleBlur}
|
||||||
onChange={change}
|
onChange={handleChange}
|
||||||
onFocus={focus}
|
onFocus={handleFocus}
|
||||||
placeholder={intl.formatMessage(messages.description)}
|
placeholder={intl.formatMessage(messages.description)}
|
||||||
type='text'
|
type='text'
|
||||||
value={dirtyDescription || description || ''}
|
value={dirtyDescription || description || ''}
|
||||||
|
@ -169,7 +169,7 @@ export default class ComposerUploadFormItem extends React.PureComponent {
|
||||||
// Props.
|
// Props.
|
||||||
ComposerUploadFormItem.propTypes = {
|
ComposerUploadFormItem.propTypes = {
|
||||||
description: PropTypes.string,
|
description: PropTypes.string,
|
||||||
id: PropTypes.number,
|
id: PropTypes.string,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
onChangeDescription: PropTypes.func,
|
onChangeDescription: PropTypes.func,
|
||||||
onRemove: PropTypes.func,
|
onRemove: PropTypes.func,
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default function DrawerAccount ({ account }) {
|
||||||
// We need an account to render.
|
// We need an account to render.
|
||||||
if (!account) {
|
if (!account) {
|
||||||
return (
|
return (
|
||||||
<div className='drawer--pager--account'>
|
<div className='drawer--account'>
|
||||||
<a
|
<a
|
||||||
className='edit'
|
className='edit'
|
||||||
href='/settings/profile'
|
href='/settings/profile'
|
||||||
|
@ -40,7 +40,7 @@ export default function DrawerAccount ({ account }) {
|
||||||
|
|
||||||
// The result.
|
// The result.
|
||||||
return (
|
return (
|
||||||
<div className='drawer--pager--account'>
|
<div className='drawer--account'>
|
||||||
<Permalink
|
<Permalink
|
||||||
className='avatar'
|
className='avatar'
|
||||||
href={account.get('url')}
|
href={account.get('url')}
|
||||||
|
@ -67,4 +67,5 @@ export default function DrawerAccount ({ account }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Props.
|
||||||
DrawerAccount.propTypes = { account: ImmutablePropTypes.map };
|
DrawerAccount.propTypes = { account: ImmutablePropTypes.map };
|
||||||
|
|
|
@ -51,7 +51,7 @@ export default function DrawerHeader ({
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
// Only renders the component if the column isn't being shown.
|
// Only renders the component if the column isn't being shown.
|
||||||
const renderForColumn = conditionalRender.bind(
|
const renderForColumn = conditionalRender.bind(null,
|
||||||
columnId => !columns || !columns.some(
|
columnId => !columns || !columns.some(
|
||||||
column => column.get('id') === columnId
|
column => column.get('id') === columnId
|
||||||
)
|
)
|
||||||
|
@ -110,6 +110,7 @@ export default function DrawerHeader ({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Props.
|
||||||
DrawerHeader.propTypes = {
|
DrawerHeader.propTypes = {
|
||||||
columns: ImmutablePropTypes.list,
|
columns: ImmutablePropTypes.list,
|
||||||
intl: PropTypes.object,
|
intl: PropTypes.object,
|
||||||
|
|
|
@ -34,23 +34,13 @@ const mapStateToProps = state => ({
|
||||||
});
|
});
|
||||||
|
|
||||||
// Dispatch mapping.
|
// Dispatch mapping.
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = {
|
||||||
change (value) {
|
onChange: changeSearch,
|
||||||
dispatch(changeSearch(value));
|
onClear: clearSearch,
|
||||||
},
|
onShow: showSearch,
|
||||||
clear () {
|
onSubmit: submitSearch,
|
||||||
dispatch(clearSearch());
|
onOpenSettings: openModal.bind(null, 'SETTINGS', {}),
|
||||||
},
|
};
|
||||||
show () {
|
|
||||||
dispatch(showSearch());
|
|
||||||
},
|
|
||||||
submit () {
|
|
||||||
dispatch(submitSearch());
|
|
||||||
},
|
|
||||||
openSettings () {
|
|
||||||
dispatch(openModal('SETTINGS', {}));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// The component.
|
// The component.
|
||||||
class Drawer extends React.Component {
|
class Drawer extends React.Component {
|
||||||
|
@ -63,23 +53,19 @@ class Drawer extends React.Component {
|
||||||
// Rendering.
|
// Rendering.
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
dispatch: {
|
|
||||||
change,
|
|
||||||
clear,
|
|
||||||
openSettings,
|
|
||||||
show,
|
|
||||||
submit,
|
|
||||||
},
|
|
||||||
intl,
|
|
||||||
multiColumn,
|
|
||||||
state: {
|
|
||||||
account,
|
account,
|
||||||
columns,
|
columns,
|
||||||
|
intl,
|
||||||
|
multiColumn,
|
||||||
|
onChange,
|
||||||
|
onClear,
|
||||||
|
onOpenSettings,
|
||||||
|
onShow,
|
||||||
|
onSubmit,
|
||||||
results,
|
results,
|
||||||
searchHidden,
|
searchHidden,
|
||||||
searchValue,
|
searchValue,
|
||||||
submitted,
|
submitted,
|
||||||
},
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
// The result.
|
// The result.
|
||||||
|
@ -89,15 +75,15 @@ class Drawer extends React.Component {
|
||||||
<DrawerHeader
|
<DrawerHeader
|
||||||
columns={columns}
|
columns={columns}
|
||||||
intl={intl}
|
intl={intl}
|
||||||
onSettingsClick={openSettings}
|
onSettingsClick={onOpenSettings}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<DrawerSearch
|
<DrawerSearch
|
||||||
intl={intl}
|
intl={intl}
|
||||||
onChange={change}
|
onChange={onChange}
|
||||||
onClear={clear}
|
onClear={onClear}
|
||||||
onShow={show}
|
onShow={onShow}
|
||||||
onSubmit={submit}
|
onSubmit={onSubmit}
|
||||||
submitted={submitted}
|
submitted={submitted}
|
||||||
value={searchValue}
|
value={searchValue}
|
||||||
/>
|
/>
|
||||||
|
@ -117,23 +103,23 @@ class Drawer extends React.Component {
|
||||||
|
|
||||||
// Props.
|
// Props.
|
||||||
Drawer.propTypes = {
|
Drawer.propTypes = {
|
||||||
dispatch: PropTypes.func.isRequired,
|
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
multiColumn: PropTypes.bool,
|
multiColumn: PropTypes.bool,
|
||||||
state: PropTypes.shape({
|
|
||||||
|
// State props.
|
||||||
account: ImmutablePropTypes.map,
|
account: ImmutablePropTypes.map,
|
||||||
columns: ImmutablePropTypes.list,
|
columns: ImmutablePropTypes.list,
|
||||||
results: ImmutablePropTypes.map,
|
results: ImmutablePropTypes.map,
|
||||||
searchHidden: PropTypes.bool,
|
searchHidden: PropTypes.bool,
|
||||||
searchValue: PropTypes.string,
|
searchValue: PropTypes.string,
|
||||||
submitted: PropTypes.bool,
|
submitted: PropTypes.bool,
|
||||||
}).isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Default props.
|
// Dispatch props.
|
||||||
Drawer.defaultProps = {
|
onChange: PropTypes.func,
|
||||||
dispatch: {},
|
onClear: PropTypes.func,
|
||||||
state: {},
|
onShow: PropTypes.func,
|
||||||
|
onSubmit: PropTypes.func,
|
||||||
|
onOpenSettings: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Connecting and export.
|
// Connecting and export.
|
||||||
|
|
|
@ -25,7 +25,7 @@ const messages = defineMessages({
|
||||||
});
|
});
|
||||||
|
|
||||||
// The component.
|
// The component.
|
||||||
export default function DrawerPager ({
|
export default function DrawerResults ({
|
||||||
results,
|
results,
|
||||||
visible,
|
visible,
|
||||||
}) {
|
}) {
|
||||||
|
@ -33,6 +33,7 @@ export default function DrawerPager ({
|
||||||
const statuses = results ? results.get('statuses') : null;
|
const statuses = results ? results.get('statuses') : null;
|
||||||
const hashtags = results ? results.get('hashtags') : null;
|
const hashtags = results ? results.get('hashtags') : null;
|
||||||
|
|
||||||
|
// This gets the total number of items.
|
||||||
const count = [accounts, statuses, hashtags].reduce(function (size, item) {
|
const count = [accounts, statuses, hashtags].reduce(function (size, item) {
|
||||||
if (item && item.size) {
|
if (item && item.size) {
|
||||||
return size + item.size;
|
return size + item.size;
|
||||||
|
@ -108,7 +109,8 @@ export default function DrawerPager ({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawerPager.propTypes = {
|
// Props.
|
||||||
|
DrawerResults.propTypes = {
|
||||||
results: ImmutablePropTypes.map,
|
results: ImmutablePropTypes.map,
|
||||||
visible: PropTypes.bool,
|
visible: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
|
@ -30,18 +30,18 @@ const messages = defineMessages({
|
||||||
// Handlers.
|
// Handlers.
|
||||||
const handlers = {
|
const handlers = {
|
||||||
|
|
||||||
blur () {
|
handleBlur () {
|
||||||
this.setState({ expanded: false });
|
this.setState({ expanded: false });
|
||||||
},
|
},
|
||||||
|
|
||||||
change ({ target: { value } }) {
|
handleChange ({ target: { value } }) {
|
||||||
const { onChange } = this.props;
|
const { onChange } = this.props;
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
onChange(value);
|
onChange(value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
clear (e) {
|
handleClear (e) {
|
||||||
const {
|
const {
|
||||||
onClear,
|
onClear,
|
||||||
submitted,
|
submitted,
|
||||||
|
@ -53,7 +53,7 @@ const handlers = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
focus () {
|
handleFocus () {
|
||||||
const { onShow } = this.props;
|
const { onShow } = this.props;
|
||||||
this.setState({ expanded: true });
|
this.setState({ expanded: true });
|
||||||
if (onShow) {
|
if (onShow) {
|
||||||
|
@ -61,7 +61,7 @@ const handlers = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
keyUp (e) {
|
handleKeyUp (e) {
|
||||||
const { onSubmit } = this.props;
|
const { onSubmit } = this.props;
|
||||||
switch (e.key) {
|
switch (e.key) {
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
|
@ -78,19 +78,21 @@ const handlers = {
|
||||||
// The component.
|
// The component.
|
||||||
export default class DrawerSearch extends React.PureComponent {
|
export default class DrawerSearch extends React.PureComponent {
|
||||||
|
|
||||||
|
// Constructor.
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props);
|
super(props);
|
||||||
assignHandlers(this, handlers);
|
assignHandlers(this, handlers);
|
||||||
this.state = { expanded: false };
|
this.state = { expanded: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rendering.
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
blur,
|
handleBlur,
|
||||||
change,
|
handleChange,
|
||||||
clear,
|
handleClear,
|
||||||
focus,
|
handleFocus,
|
||||||
keyUp,
|
handleKeyUp,
|
||||||
} = this.handlers;
|
} = this.handlers;
|
||||||
const {
|
const {
|
||||||
intl,
|
intl,
|
||||||
|
@ -110,23 +112,22 @@ export default class DrawerSearch extends React.PureComponent {
|
||||||
type='text'
|
type='text'
|
||||||
placeholder={intl.formatMessage(messages.placeholder)}
|
placeholder={intl.formatMessage(messages.placeholder)}
|
||||||
value={value || ''}
|
value={value || ''}
|
||||||
onChange={change}
|
onChange={handleChange}
|
||||||
onKeyUp={keyUp}
|
onKeyUp={handleKeyUp}
|
||||||
onFocus={focus}
|
onFocus={handleFocus}
|
||||||
onBlur={blur}
|
onBlur={handleBlur}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
aria-label={intl.formatMessage(messages.placeholder)}
|
aria-label={intl.formatMessage(messages.placeholder)}
|
||||||
className='icon'
|
className='icon'
|
||||||
onClick={clear}
|
onClick={handleClear}
|
||||||
role='button'
|
role='button'
|
||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
>
|
>
|
||||||
<Icon icon='search' />
|
<Icon icon='search' />
|
||||||
<Icon icon='fa-times-circle' />
|
<Icon icon='fa-times-circle' />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Overlay
|
<Overlay
|
||||||
placement='bottom'
|
placement='bottom'
|
||||||
show={expanded && !(value || '').length && !submitted}
|
show={expanded && !(value || '').length && !submitted}
|
||||||
|
@ -138,6 +139,7 @@ export default class DrawerSearch extends React.PureComponent {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Props.
|
||||||
DrawerSearch.propTypes = {
|
DrawerSearch.propTypes = {
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
submitted: PropTypes.bool,
|
submitted: PropTypes.bool,
|
||||||
|
|
|
@ -34,9 +34,13 @@ const messages = defineMessages({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// The spring used by our motion.
|
||||||
const motionSpring = spring(1, { damping: 35, stiffness: 400 });
|
const motionSpring = spring(1, { damping: 35, stiffness: 400 });
|
||||||
|
|
||||||
|
// The component.
|
||||||
export default function DrawerSearchPopout ({ style }) {
|
export default function DrawerSearchPopout ({ style }) {
|
||||||
|
|
||||||
|
// The result.
|
||||||
return (
|
return (
|
||||||
<Motion
|
<Motion
|
||||||
defaultStyle={{
|
defaultStyle={{
|
||||||
|
|
|
@ -50,7 +50,7 @@ export default class ActionsModal extends ImmutablePureComponent {
|
||||||
<Link
|
<Link
|
||||||
className={classNames('link', { active })}
|
className={classNames('link', { active })}
|
||||||
href={href}
|
href={href}
|
||||||
onClick={onClick}
|
onClick={on !== null && typeof on !== 'undefined' && onPassiveClick || onClick}
|
||||||
role={onClick ? 'button' : null}
|
role={onClick ? 'button' : null}
|
||||||
>
|
>
|
||||||
{function () {
|
{function () {
|
||||||
|
|
|
@ -11,13 +11,13 @@ import BundleContainer from '../containers/bundle_container';
|
||||||
import ColumnLoading from './column_loading';
|
import ColumnLoading from './column_loading';
|
||||||
import DrawerLoading from './drawer_loading';
|
import DrawerLoading from './drawer_loading';
|
||||||
import BundleColumnError from './bundle_column_error';
|
import BundleColumnError from './bundle_column_error';
|
||||||
import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses, ListTimeline } from 'flavours/glitch/util/async-components';
|
import { Drawer, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses, ListTimeline } from 'flavours/glitch/util/async-components';
|
||||||
|
|
||||||
import detectPassiveEvents from 'detect-passive-events';
|
import detectPassiveEvents from 'detect-passive-events';
|
||||||
import { scrollRight } from 'flavours/glitch/util/scroll';
|
import { scrollRight } from 'flavours/glitch/util/scroll';
|
||||||
|
|
||||||
const componentMap = {
|
const componentMap = {
|
||||||
'COMPOSE': Compose,
|
'COMPOSE': Drawer,
|
||||||
'HOME': HomeTimeline,
|
'HOME': HomeTimeline,
|
||||||
'NOTIFICATIONS': Notifications,
|
'NOTIFICATIONS': Notifications,
|
||||||
'PUBLIC': PublicTimeline,
|
'PUBLIC': PublicTimeline,
|
||||||
|
|
|
@ -17,7 +17,7 @@ import UploadArea from './components/upload_area';
|
||||||
import ColumnsAreaContainer from './containers/columns_area_container';
|
import ColumnsAreaContainer from './containers/columns_area_container';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {
|
import {
|
||||||
Compose,
|
Drawer,
|
||||||
Status,
|
Status,
|
||||||
GettingStarted,
|
GettingStarted,
|
||||||
KeyboardShortcuts,
|
KeyboardShortcuts,
|
||||||
|
@ -56,7 +56,6 @@ const messages = defineMessages({
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
isComposing: state.getIn(['compose', 'is_composing']),
|
|
||||||
hasComposingText: state.getIn(['compose', 'text']) !== '',
|
hasComposingText: state.getIn(['compose', 'text']) !== '',
|
||||||
layout: state.getIn(['local_settings', 'layout']),
|
layout: state.getIn(['local_settings', 'layout']),
|
||||||
isWide: state.getIn(['local_settings', 'stretch']),
|
isWide: state.getIn(['local_settings', 'stretch']),
|
||||||
|
@ -120,9 +119,9 @@ export default class UI extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
handleBeforeUnload = (e) => {
|
handleBeforeUnload = (e) => {
|
||||||
const { intl, isComposing, hasComposingText } = this.props;
|
const { intl, hasComposingText } = this.props;
|
||||||
|
|
||||||
if (isComposing && hasComposingText) {
|
if (hasComposingText) {
|
||||||
// Setting returnValue to any string causes confirmation dialog.
|
// Setting returnValue to any string causes confirmation dialog.
|
||||||
// Many browsers no longer display this text to users,
|
// Many browsers no longer display this text to users,
|
||||||
// but we set user-friendly message for other browsers, e.g. Edge.
|
// but we set user-friendly message for other browsers, e.g. Edge.
|
||||||
|
@ -227,9 +226,8 @@ export default class UI extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate (nextProps) {
|
shouldComponentUpdate (nextProps) {
|
||||||
if (nextProps.isComposing !== this.props.isComposing) {
|
if (nextProps.navbarUnder !== this.props.navbarUnder) {
|
||||||
// Avoid expensive update just to toggle a class
|
// Avoid expensive update just to toggle a class
|
||||||
this.node.classList.toggle('is-composing', nextProps.isComposing);
|
|
||||||
this.node.classList.toggle('navbar-under', nextProps.navbarUnder);
|
this.node.classList.toggle('navbar-under', nextProps.navbarUnder);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -427,7 +425,7 @@ export default class UI extends React.Component {
|
||||||
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
|
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
|
||||||
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
|
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
|
||||||
|
|
||||||
<WrappedRoute path='/statuses/new' component={Compose} content={children} />
|
<WrappedRoute path='/statuses/new' component={Drawer} content={children} />
|
||||||
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
|
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
|
||||||
<WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
|
<WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
|
||||||
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
|
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
|
||||||
|
|
|
@ -47,7 +47,6 @@ const initialState = ImmutableMap({
|
||||||
focusDate: null,
|
focusDate: null,
|
||||||
preselectDate: null,
|
preselectDate: null,
|
||||||
in_reply_to: null,
|
in_reply_to: null,
|
||||||
is_composing: false,
|
|
||||||
is_submitting: false,
|
is_submitting: false,
|
||||||
is_uploading: false,
|
is_uploading: false,
|
||||||
progress: 0,
|
progress: 0,
|
||||||
|
@ -180,9 +179,7 @@ export default function compose(state = initialState, action) {
|
||||||
case COMPOSE_MOUNT:
|
case COMPOSE_MOUNT:
|
||||||
return state.set('mounted', true);
|
return state.set('mounted', true);
|
||||||
case COMPOSE_UNMOUNT:
|
case COMPOSE_UNMOUNT:
|
||||||
return state
|
return state.set('mounted', false)
|
||||||
.set('mounted', false)
|
|
||||||
.set('is_composing', false);
|
|
||||||
case COMPOSE_ADVANCED_OPTIONS_CHANGE:
|
case COMPOSE_ADVANCED_OPTIONS_CHANGE:
|
||||||
return state
|
return state
|
||||||
.set('advanced_options',
|
.set('advanced_options',
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
.composer { padding: 10px }
|
.composer { padding: 10px }
|
||||||
|
|
||||||
.composer--spoiler {
|
.composer--spoiler {
|
||||||
|
input {
|
||||||
display: block;
|
display: block;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -18,6 +19,7 @@
|
||||||
&:focus { outline: 0 }
|
&:focus { outline: 0 }
|
||||||
@include single-column('screen and (max-width: 630px)') { font-size: 16px }
|
@include single-column('screen and (max-width: 630px)') { font-size: 16px }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.composer--warning {
|
.composer--warning {
|
||||||
color: darken($ui-secondary-color, 65%);
|
color: darken($ui-secondary-color, 65%);
|
||||||
|
@ -116,12 +118,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.composer--textarea {
|
.composer--textarea {
|
||||||
background: $simple-background-color;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&:disabled { background: $ui-secondary-color }
|
& > label {
|
||||||
|
.textarea {
|
||||||
& > .textarea {
|
|
||||||
display: block;
|
display: block;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -137,6 +137,7 @@
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
resize: none;
|
resize: none;
|
||||||
|
|
||||||
|
&:disabled { background: $ui-secondary-color }
|
||||||
&:focus { outline: 0 }
|
&:focus { outline: 0 }
|
||||||
@include single-column('screen and (max-width: 630px)') { font-size: 16px }
|
@include single-column('screen and (max-width: 630px)') { font-size: 16px }
|
||||||
|
|
||||||
|
@ -146,6 +147,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.composer--textarea--suggestions {
|
.composer--textarea--suggestions {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -192,16 +194,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.composer--upload_form {
|
.composer--upload_form {
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
color: $ui-base-color;
|
color: $ui-base-color;
|
||||||
background: $simple-background-color;
|
background: $simple-background-color;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
||||||
|
& > .content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.composer--upload_form--item {
|
.composer--upload_form--item {
|
||||||
flex: 1 1 0;
|
flex: 1 1 0;
|
||||||
|
@ -254,17 +259,61 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.composer--upload_form--progress {
|
||||||
|
display: flex;
|
||||||
|
padding: 10px;
|
||||||
|
color: $ui-base-lighter-color;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
& > .fa {
|
||||||
|
font-size: 34px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .message {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
|
||||||
|
& > span {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .backdrop {
|
||||||
|
position: relative;
|
||||||
|
margin-top: 5px;
|
||||||
|
border-radius: 6px;
|
||||||
|
width: 100%;
|
||||||
|
height: 6px;
|
||||||
|
background: $ui-base-lighter-color;
|
||||||
|
|
||||||
|
& > .tracker {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: $ui-highlight-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.composer--options {
|
.composer--options {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background: darken($simple-background-color, 8%);
|
background: darken($simple-background-color, 8%);
|
||||||
box-shadow: inset 0 5px 5px rgba($base-shadow-color, 0.05);
|
box-shadow: inset 0 5px 5px rgba($base-shadow-color, 0.05);
|
||||||
border-radius: 0 0 4px 4px;
|
border-radius: 0 0 4px 4px;
|
||||||
|
height: 27px;
|
||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
padding: 0 3px;
|
padding: 0 3px;
|
||||||
|
height: 27px;
|
||||||
line-height: 27px;
|
line-height: 27px;
|
||||||
|
vertical-align: bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > hr {
|
& > hr {
|
||||||
|
@ -274,26 +323,26 @@
|
||||||
border-style: none none none solid;
|
border-style: none none none solid;
|
||||||
border-color: transparent transparent transparent darken($simple-background-color, 24%);
|
border-color: transparent transparent transparent darken($simple-background-color, 24%);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 27px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.composer--options--dropdown {
|
.composer--options--dropdown {
|
||||||
& > .value { transition: none }
|
&.open {
|
||||||
|
|
||||||
&.active {
|
|
||||||
& > .value {
|
& > .value {
|
||||||
border-radius: 4px 4px 0 0;
|
border-radius: 4px 4px 0 0;
|
||||||
box-shadow: 0 -4px 4px rgba($base-shadow-color, 0.1);
|
box-shadow: 0 -4px 4px rgba($base-shadow-color, 0.1);
|
||||||
color: $primary-text-color;
|
color: $primary-text-color;
|
||||||
background: $ui-highlight-color;
|
background: $ui-highlight-color;
|
||||||
|
transition: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.composer--options--dropdown__dropdown {
|
.composer--options--dropdown--content {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-left: 40px;
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
|
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
|
||||||
background: $simple-background-color;
|
background: $simple-background-color;
|
||||||
|
@ -301,11 +350,12 @@
|
||||||
transform-origin: 50% 0;
|
transform-origin: 50% 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.composer--options--dropdown--item {
|
.composer--options--dropdown--content--item {
|
||||||
color: $ui-base-color;
|
|
||||||
padding: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px;
|
||||||
|
color: $ui-base-color;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
& > .content {
|
& > .content {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
@ -344,7 +394,6 @@
|
||||||
& > .count {
|
& > .count {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0 16px 0 8px;
|
margin: 0 16px 0 8px;
|
||||||
padding-top: 10px;
|
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 36px;
|
line-height: 36px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 10px 5px;
|
padding: 10px 5px;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
flex: 1 1 100%;
|
flex: none;
|
||||||
contain: strict;
|
contain: strict;
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
|
@ -15,10 +15,10 @@
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include multi-columns('screen and (max-width: 630px)') {
|
@include single-column('screen and (max-width: 630px)') { flex: auto }
|
||||||
&, &:first-child, &:last-child {
|
|
||||||
padding: 0;
|
@include limited-single-column('screen and (max-width: 630px)') {
|
||||||
}
|
&, &:first-child, &:last-child { padding: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
.wide & {
|
.wide & {
|
||||||
|
@ -27,8 +27,28 @@
|
||||||
flex: 1 1 200px;
|
flex: 1 1 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-swipeable-view-container & {
|
@include single-column('screen and (max-width: 630px)') {
|
||||||
|
:root & { // Overrides `.wide` for single-column view
|
||||||
|
flex: auto;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-swipeable-view-container & { height: 100% }
|
||||||
|
|
||||||
|
& > .contents {
|
||||||
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
background: lighten($ui-base-color, 13%);
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
contain: strict;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer--header {
|
.drawer--header {
|
||||||
|
@ -69,9 +89,8 @@
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
flex: none;
|
flex: none;
|
||||||
|
|
||||||
@include limited-single-column('screen and (max-width: 360px)') {
|
@include limited-single-column('screen and (max-width: 360px)') { margin-bottom: 0 }
|
||||||
margin-bottom: 0;
|
@include single-column('screen and (max-width: 630px)') { font-size: 16px }
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
input {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -121,9 +140,7 @@
|
||||||
transform: rotate(-90deg);
|
transform: rotate(-90deg);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover { color: $primary-text-color }
|
||||||
color: $primary-text-color;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
|
@ -141,16 +158,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& > .contents {
|
|
||||||
position: relative;
|
|
||||||
padding: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: lighten($ui-base-color, 13%);
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
contain: strict;
|
|
||||||
|
|
||||||
.drawer--account {
|
.drawer--account {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
color: $ui-primary-color;
|
color: $ui-primary-color;
|
||||||
|
@ -214,21 +221,3 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:root { // Overrides .wide stylings for mobile view
|
|
||||||
@include single-column('screen and (max-width: 630px)', $parent: null) {
|
|
||||||
.drawer {
|
|
||||||
flex: auto;
|
|
||||||
width: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
max-width: none;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
.drawer--search input {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -2704,47 +2704,6 @@
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-progress {
|
|
||||||
padding: 10px;
|
|
||||||
color: $ui-base-lighter-color;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.fa {
|
|
||||||
font-size: 34px;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-size: 12px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-weight: 500;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-progess__message {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-progress__backdrop {
|
|
||||||
width: 100%;
|
|
||||||
height: 6px;
|
|
||||||
border-radius: 6px;
|
|
||||||
background: $ui-base-lighter-color;
|
|
||||||
position: relative;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-progress__tracker {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
height: 6px;
|
|
||||||
background: $ui-highlight-color;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-button {
|
.emoji-button {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
|
@ -3339,6 +3298,7 @@
|
||||||
max-width: 80vw;
|
max-width: 80vw;
|
||||||
|
|
||||||
strong {
|
strong {
|
||||||
|
display: block;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3368,6 +3328,7 @@
|
||||||
color: $primary-text-color;
|
color: $primary-text-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& > .react-toggle,
|
||||||
& > .icon {
|
& > .icon {
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,8 @@ pack:
|
||||||
home:
|
home:
|
||||||
filename: packs/home.js
|
filename: packs/home.js
|
||||||
preload:
|
preload:
|
||||||
|
- flavours/glitch/async/drawer
|
||||||
- flavours/glitch/async/getting_started
|
- flavours/glitch/async/getting_started
|
||||||
- flavours/glitch/async/compose
|
|
||||||
- flavours/glitch/async/home_timeline
|
- flavours/glitch/async/home_timeline
|
||||||
- flavours/glitch/async/notifications
|
- flavours/glitch/async/notifications
|
||||||
modal:
|
modal:
|
||||||
|
|
|
@ -2,8 +2,8 @@ export function EmojiPicker () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/emoji_picker" */'flavours/glitch/util/emoji/emoji_picker');
|
return import(/* webpackChunkName: "flavours/glitch/async/emoji_picker" */'flavours/glitch/util/emoji/emoji_picker');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Compose () {
|
export function Drawer () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/compose" */'flavours/glitch/features/compose');
|
return import(/* webpackChunkName: "flavours/glitch/async/drawer" */'flavours/glitch/features/drawer');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Notifications () {
|
export function Notifications () {
|
||||||
|
|
|
@ -6,8 +6,8 @@ export function assignHandlers (target, handlers) {
|
||||||
|
|
||||||
// We just bind each handler to the `target`.
|
// We just bind each handler to the `target`.
|
||||||
const handle = target.handlers = {};
|
const handle = target.handlers = {};
|
||||||
handlers.keys().forEach(
|
Object.keys(handlers).forEach(
|
||||||
key => handle.key = key.bind(target)
|
key => handle[key] = handlers[key].bind(target)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,8 @@
|
||||||
import { injectIntl } from 'react-intl';
|
import { injectIntl } from 'react-intl';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
// Merges react-redux props.
|
|
||||||
export function mergeProps (stateProps, dispatchProps, ownProps) {
|
|
||||||
Object.assign({}, ownProps, {
|
|
||||||
dispatch: Object.assign({}, dispatchProps, ownProps.dispatch || {}),
|
|
||||||
state: Object.assign({}, stateProps, ownProps.state || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connects a component.
|
// Connects a component.
|
||||||
export function wrap (Component, mapStateToProps, mapDispatchToProps, options) {
|
export function wrap (Component, mapStateToProps, mapDispatchToProps, options) {
|
||||||
const withIntl = typeof options === 'object' ? options.withIntl : !!options;
|
const withIntl = typeof options === 'object' ? options.withIntl : !!options;
|
||||||
return (withIntl ? injectIntl : i => i)(connect(mapStateToProps, mapDispatchToProps, mergeProps)(Component));
|
return (withIntl ? injectIntl : i => i)(connect(mapStateToProps, mapDispatchToProps)(Component));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue