Reduce composer differences with upstream and simplify code (#2518)
parent
18856371be
commit
3d3fa75c81
|
@ -251,9 +251,9 @@ export function submitCompose(routerHistory) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// To make the app more responsive, immediately get the status into the columns
|
// To make the app more responsive, immediately push the status
|
||||||
|
// into the columns
|
||||||
const insertIfOnline = (timelineId) => {
|
const insertIfOnline = timelineId => {
|
||||||
const timeline = getState().getIn(['timelines', timelineId]);
|
const timeline = getState().getIn(['timelines', timelineId]);
|
||||||
|
|
||||||
if (timeline && timeline.get('items').size > 0 && timeline.getIn(['items', 0]) !== null && timeline.get('online')) {
|
if (timeline && timeline.get('items').size > 0 && timeline.getIn(['items', 0]) !== null && timeline.get('online')) {
|
||||||
|
@ -662,8 +662,9 @@ export function selectComposeSuggestion(position, token, suggestion, path) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
let completion;
|
let completion;
|
||||||
if (suggestion.type === 'emoji') {
|
if (suggestion.type === 'emoji') {
|
||||||
dispatch(useEmoji(suggestion));
|
|
||||||
completion = suggestion.native || suggestion.colons;
|
completion = suggestion.native || suggestion.colons;
|
||||||
|
|
||||||
|
dispatch(useEmoji(suggestion));
|
||||||
} else if (suggestion.type === 'hashtag') {
|
} else if (suggestion.type === 'hashtag') {
|
||||||
completion = `#${suggestion.name}`;
|
completion = `#${suggestion.name}`;
|
||||||
} else if (suggestion.type === 'account') {
|
} else if (suggestion.type === 'account') {
|
||||||
|
|
|
@ -56,14 +56,14 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
isChangingUpload: PropTypes.bool,
|
isChangingUpload: PropTypes.bool,
|
||||||
isEditing: PropTypes.bool,
|
isEditing: PropTypes.bool,
|
||||||
isUploading: PropTypes.bool,
|
isUploading: PropTypes.bool,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func.isRequired,
|
||||||
onSubmit: PropTypes.func,
|
onSubmit: PropTypes.func.isRequired,
|
||||||
onClearSuggestions: PropTypes.func,
|
onClearSuggestions: PropTypes.func.isRequired,
|
||||||
onFetchSuggestions: PropTypes.func,
|
onFetchSuggestions: PropTypes.func.isRequired,
|
||||||
onSuggestionSelected: PropTypes.func,
|
onSuggestionSelected: PropTypes.func.isRequired,
|
||||||
onChangeSpoilerText: PropTypes.func,
|
onChangeSpoilerText: PropTypes.func.isRequired,
|
||||||
onPaste: PropTypes.func,
|
onPaste: PropTypes.func.isRequired,
|
||||||
onPickEmoji: PropTypes.func,
|
onPickEmoji: PropTypes.func.isRequired,
|
||||||
showSearch: PropTypes.bool,
|
showSearch: PropTypes.bool,
|
||||||
anyMedia: PropTypes.bool,
|
anyMedia: PropTypes.bool,
|
||||||
isInReply: PropTypes.bool,
|
isInReply: PropTypes.bool,
|
||||||
|
@ -77,9 +77,9 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
spoilersAlwaysOn: PropTypes.bool,
|
spoilersAlwaysOn: PropTypes.bool,
|
||||||
mediaDescriptionConfirmation: PropTypes.bool,
|
mediaDescriptionConfirmation: PropTypes.bool,
|
||||||
preselectOnReply: PropTypes.bool,
|
preselectOnReply: PropTypes.bool,
|
||||||
onChangeSpoilerness: PropTypes.func,
|
onChangeSpoilerness: PropTypes.func.isRequired,
|
||||||
onChangeVisibility: PropTypes.func,
|
onChangeVisibility: PropTypes.func.isRequired,
|
||||||
onMediaDescriptionConfirm: PropTypes.func,
|
onMediaDescriptionConfirm: PropTypes.func.isRequired,
|
||||||
...WithOptionalRouterPropTypes
|
...WithOptionalRouterPropTypes
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -100,6 +100,16 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
this.props.onChange(e.target.value);
|
this.props.onChange(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleKeyDown = (e) => {
|
||||||
|
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
|
||||||
|
this.handleSubmit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.keyCode === 13 && e.altKey) {
|
||||||
|
this.handleSecondarySubmit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
getFulltextForCharacterCounting = () => {
|
getFulltextForCharacterCounting = () => {
|
||||||
return [
|
return [
|
||||||
this.props.spoiler? this.props.spoilerText: '',
|
this.props.spoiler? this.props.spoilerText: '',
|
||||||
|
@ -116,14 +126,6 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSubmit = (overriddenVisibility = null) => {
|
handleSubmit = (overriddenVisibility = null) => {
|
||||||
const {
|
|
||||||
onSubmit,
|
|
||||||
media,
|
|
||||||
mediaDescriptionConfirmation,
|
|
||||||
onMediaDescriptionConfirm,
|
|
||||||
onChangeVisibility,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
if (this.props.text !== this.textareaRef.current.value) {
|
if (this.props.text !== this.textareaRef.current.value) {
|
||||||
// Something changed the text inside the textarea (e.g. browser extensions like Grammarly)
|
// Something changed the text inside the textarea (e.g. browser extensions like Grammarly)
|
||||||
// Update the state to match the current text
|
// Update the state to match the current text
|
||||||
|
@ -135,35 +137,14 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Submit unless there are media with missing descriptions
|
// Submit unless there are media with missing descriptions
|
||||||
if (mediaDescriptionConfirmation && onMediaDescriptionConfirm && media && media.some(item => !item.get('description'))) {
|
if (this.props.mediaDescriptionConfirmation && this.props.media && this.props.media.some(item => !item.get('description'))) {
|
||||||
const firstWithoutDescription = media.find(item => !item.get('description'));
|
const firstWithoutDescription = this.props.media.find(item => !item.get('description'));
|
||||||
onMediaDescriptionConfirm(this.props.history || null, firstWithoutDescription.get('id'), overriddenVisibility);
|
this.props.onMediaDescriptionConfirm(this.props.history || null, firstWithoutDescription.get('id'), overriddenVisibility);
|
||||||
} else if (onSubmit) {
|
} else {
|
||||||
if (onChangeVisibility && overriddenVisibility) {
|
if (overriddenVisibility) {
|
||||||
onChangeVisibility(overriddenVisibility);
|
this.props.onChangeVisibility(overriddenVisibility);
|
||||||
}
|
}
|
||||||
onSubmit(this.props.history || null);
|
this.props.onSubmit(this.props.history || null);
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Changes the text value of the spoiler.
|
|
||||||
handleChangeSpoiler = ({ target: { value } }) => {
|
|
||||||
const { onChangeSpoilerText } = this.props;
|
|
||||||
if (onChangeSpoilerText) {
|
|
||||||
onChangeSpoilerText(value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
setRef = c => {
|
|
||||||
this.composeForm = c;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Inserts an emoji at the caret.
|
|
||||||
handleEmojiPick = (data) => {
|
|
||||||
const position = this.textareaRef.current.selectionStart;
|
|
||||||
|
|
||||||
if (this.props.onPickEmoji) {
|
|
||||||
this.props.onPickEmoji(position, data);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -175,30 +156,24 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
this.handleSubmit(sideArm === 'none' ? null : sideArm);
|
this.handleSubmit(sideArm === 'none' ? null : sideArm);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Selects a suggestion from the autofill.
|
onSuggestionsClearRequested = () => {
|
||||||
handleSuggestionSelected = (tokenStart, token, value) => {
|
this.props.onClearSuggestions();
|
||||||
|
};
|
||||||
|
|
||||||
|
onSuggestionsFetchRequested = (token) => {
|
||||||
|
this.props.onFetchSuggestions(token);
|
||||||
|
};
|
||||||
|
|
||||||
|
onSuggestionSelected = (tokenStart, token, value) => {
|
||||||
this.props.onSuggestionSelected(tokenStart, token, value, ['text']);
|
this.props.onSuggestionSelected(tokenStart, token, value, ['text']);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSpoilerSuggestionSelected = (tokenStart, token, value) => {
|
onSpoilerSuggestionSelected = (tokenStart, token, value) => {
|
||||||
this.props.onSuggestionSelected(tokenStart, token, value, ['spoiler_text']);
|
this.props.onSuggestionSelected(tokenStart, token, value, ['spoiler_text']);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleKeyDown = (e) => {
|
handleChangeSpoilerText = (e) => {
|
||||||
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
|
this.props.onChangeSpoilerText(e.target.value);
|
||||||
this.handleSubmit();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.keyCode === 13 && e.altKey) {
|
|
||||||
this.handleSecondarySubmit();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Sets a reference to the CW field.
|
|
||||||
handleRefSpoilerText = (spoilerComponent) => {
|
|
||||||
if (spoilerComponent) {
|
|
||||||
this.spoilerText = spoilerComponent.input;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
handleFocus = () => {
|
handleFocus = () => {
|
||||||
|
@ -222,120 +197,99 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
this._updateFocusAndSelection(prevProps);
|
this._updateFocusAndSelection(prevProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_updateFocusAndSelection = (prevProps) => {
|
||||||
// This statement does several things:
|
// This statement does several things:
|
||||||
// - If we're beginning a reply, and,
|
// - If we're beginning a reply, and,
|
||||||
// - Replying to zero or one users, places the cursor at the end
|
// - Replying to zero or one users, places the cursor at the end of the textbox.
|
||||||
// of the textbox.
|
// - Replying to more than one user, selects any usernames past the first;
|
||||||
// - Replying to more than one user, selects any usernames past
|
// this provides a convenient shortcut to drop everyone else from the conversation.
|
||||||
// the first; this provides a convenient shortcut to drop
|
if (this.props.focusDate && this.props.focusDate !== prevProps.focusDate) {
|
||||||
// everyone else from the conversation.
|
|
||||||
_updateFocusAndSelection = (prevProps) => {
|
|
||||||
const {
|
|
||||||
spoilerText,
|
|
||||||
} = this;
|
|
||||||
const {
|
|
||||||
focusDate,
|
|
||||||
caretPosition,
|
|
||||||
isSubmitting,
|
|
||||||
preselectDate,
|
|
||||||
text,
|
|
||||||
preselectOnReply,
|
|
||||||
singleColumn,
|
|
||||||
} = this.props;
|
|
||||||
let selectionEnd, selectionStart;
|
let selectionEnd, selectionStart;
|
||||||
|
|
||||||
// Caret/selection handling.
|
if (this.props.preselectDate !== prevProps.preselectDate && this.props.isInReply && this.props.preselectOnReply) {
|
||||||
if (focusDate !== prevProps.focusDate) {
|
selectionEnd = this.props.text.length;
|
||||||
switch (true) {
|
selectionStart = this.props.text.search(/\s/) + 1;
|
||||||
case preselectDate !== prevProps.preselectDate && this.props.isInReply && preselectOnReply:
|
} else if (typeof this.props.caretPosition === 'number') {
|
||||||
selectionStart = text.search(/\s/) + 1;
|
selectionStart = this.props.caretPosition;
|
||||||
selectionEnd = text.length;
|
selectionEnd = this.props.caretPosition;
|
||||||
break;
|
} else {
|
||||||
case !isNaN(caretPosition) && caretPosition !== null:
|
selectionEnd = this.props.text.length;
|
||||||
selectionStart = selectionEnd = caretPosition;
|
selectionStart = selectionEnd;
|
||||||
break;
|
|
||||||
default:
|
|
||||||
selectionStart = selectionEnd = text.length;
|
|
||||||
}
|
}
|
||||||
if (this.textareaRef.current) {
|
|
||||||
// Because of the wicg-inert polyfill, the activeElement may not be
|
// Because of the wicg-inert polyfill, the activeElement may not be
|
||||||
// immediately selectable, we have to wait for observers to run, as
|
// immediately selectable, we have to wait for observers to run, as
|
||||||
// described in https://github.com/WICG/inert#performance-and-gotchas
|
// described in https://github.com/WICG/inert#performance-and-gotchas
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
this.textareaRef.current.setSelectionRange(selectionStart, selectionEnd);
|
this.textareaRef.current.setSelectionRange(selectionStart, selectionEnd);
|
||||||
this.textareaRef.current.focus();
|
this.textareaRef.current.focus();
|
||||||
if (!singleColumn) this.textareaRef.current.scrollIntoView();
|
if (!this.props.singleColumn) this.textareaRef.current.scrollIntoView();
|
||||||
this.setState({ highlighted: true });
|
this.setState({ highlighted: true });
|
||||||
this.timeout = setTimeout(() => this.setState({ highlighted: false }), 700);
|
this.timeout = setTimeout(() => this.setState({ highlighted: false }), 700);
|
||||||
}).catch(console.error);
|
}).catch(console.error);
|
||||||
}
|
} else if(prevProps.isSubmitting && !this.props.isSubmitting) {
|
||||||
|
|
||||||
// Refocuses the textarea after submitting.
|
|
||||||
} else if (this.textareaRef.current && prevProps.isSubmitting && !isSubmitting) {
|
|
||||||
this.textareaRef.current.focus();
|
this.textareaRef.current.focus();
|
||||||
} else if (this.props.spoiler !== prevProps.spoiler) {
|
} else if (this.props.spoiler !== prevProps.spoiler) {
|
||||||
if (this.props.spoiler) {
|
if (this.props.spoiler) {
|
||||||
if (spoilerText) {
|
this.spoilerText.input.focus();
|
||||||
spoilerText.focus();
|
} else if (prevProps.spoiler) {
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this.textareaRef.current) {
|
|
||||||
this.textareaRef.current.focus();
|
this.textareaRef.current.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setSpoilerText = (c) => {
|
||||||
|
this.spoilerText = c;
|
||||||
|
};
|
||||||
|
|
||||||
|
setRef = c => {
|
||||||
|
this.composeForm = c;
|
||||||
|
};
|
||||||
|
|
||||||
|
handleEmojiPick = (data) => {
|
||||||
|
const position = this.textareaRef.current.selectionStart;
|
||||||
|
|
||||||
|
this.props.onPickEmoji(position, data);
|
||||||
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
handleEmojiPick,
|
|
||||||
handleSecondarySubmit,
|
|
||||||
handleSubmit,
|
|
||||||
} = this;
|
|
||||||
const {
|
|
||||||
advancedOptions,
|
|
||||||
intl,
|
intl,
|
||||||
|
advancedOptions,
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
layout,
|
layout,
|
||||||
onChangeSpoilerness,
|
onChangeSpoilerness,
|
||||||
onClearSuggestions,
|
|
||||||
onFetchSuggestions,
|
|
||||||
onPaste,
|
onPaste,
|
||||||
privacy,
|
privacy,
|
||||||
sensitive,
|
sensitive,
|
||||||
showSearch,
|
showSearch,
|
||||||
sideArm,
|
sideArm,
|
||||||
spoiler,
|
|
||||||
spoilerText,
|
|
||||||
suggestions,
|
|
||||||
spoilersAlwaysOn,
|
spoilersAlwaysOn,
|
||||||
isEditing,
|
isEditing,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { highlighted } = this.state;
|
const { highlighted } = this.state;
|
||||||
|
const disabled = this.props.isSubmitting;
|
||||||
const countText = this.getFulltextForCharacterCounting();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='compose-form'>
|
<form className='compose-form' onSubmit={this.handleSubmit}>
|
||||||
<WarningContainer />
|
<WarningContainer />
|
||||||
|
|
||||||
<ReplyIndicatorContainer />
|
<ReplyIndicatorContainer />
|
||||||
|
|
||||||
<div className={`spoiler-input ${spoiler ? 'spoiler-input--visible' : ''}`} ref={this.setRef} aria-hidden={!this.props.spoiler}>
|
<div className={`spoiler-input ${this.props.spoiler ? 'spoiler-input--visible' : ''}`} ref={this.setRef} aria-hidden={!this.props.spoiler}>
|
||||||
<AutosuggestInput
|
<AutosuggestInput
|
||||||
placeholder={intl.formatMessage(messages.spoiler_placeholder)}
|
placeholder={intl.formatMessage(messages.spoiler_placeholder)}
|
||||||
value={spoilerText}
|
value={this.props.spoilerText}
|
||||||
onChange={this.handleChangeSpoiler}
|
onChange={this.handleChangeSpoilerText}
|
||||||
onKeyDown={this.handleKeyDown}
|
onKeyDown={this.handleKeyDown}
|
||||||
disabled={!spoiler}
|
disabled={!this.props.spoiler}
|
||||||
ref={this.handleRefSpoilerText}
|
ref={this.setSpoilerText}
|
||||||
suggestions={suggestions}
|
suggestions={this.props.suggestions}
|
||||||
onSuggestionsFetchRequested={onFetchSuggestions}
|
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
|
||||||
onSuggestionsClearRequested={onClearSuggestions}
|
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
||||||
onSuggestionSelected={this.handleSpoilerSuggestionSelected}
|
onSuggestionSelected={this.onSpoilerSuggestionSelected}
|
||||||
searchTokens={[':']}
|
searchTokens={[':']}
|
||||||
id='glitch.composer.spoiler.input'
|
id='cw-spoiler-input'
|
||||||
className='spoiler-input__input'
|
className='spoiler-input__input'
|
||||||
lang={this.props.lang}
|
lang={this.props.lang}
|
||||||
autoFocus={false}
|
autoFocus={false}
|
||||||
|
@ -347,15 +301,15 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
<AutosuggestTextarea
|
<AutosuggestTextarea
|
||||||
ref={this.textareaRef}
|
ref={this.textareaRef}
|
||||||
placeholder={intl.formatMessage(messages.placeholder)}
|
placeholder={intl.formatMessage(messages.placeholder)}
|
||||||
disabled={isSubmitting}
|
disabled={disabled}
|
||||||
value={this.props.text}
|
value={this.props.text}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
onKeyDown={this.handleKeyDown}
|
suggestions={this.props.suggestions}
|
||||||
suggestions={suggestions}
|
|
||||||
onFocus={this.handleFocus}
|
onFocus={this.handleFocus}
|
||||||
onSuggestionsFetchRequested={onFetchSuggestions}
|
onKeyDown={this.handleKeyDown}
|
||||||
onSuggestionsClearRequested={onClearSuggestions}
|
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
|
||||||
onSuggestionSelected={this.handleSuggestionSelected}
|
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
||||||
|
onSuggestionSelected={this.onSuggestionSelected}
|
||||||
onPaste={onPaste}
|
onPaste={onPaste}
|
||||||
autoFocus={!showSearch && !isMobile(window.innerWidth, layout)}
|
autoFocus={!showSearch && !isMobile(window.innerWidth, layout)}
|
||||||
lang={this.props.lang}
|
lang={this.props.lang}
|
||||||
|
@ -366,34 +320,33 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
<PollFormContainer />
|
<PollFormContainer />
|
||||||
</div>
|
</div>
|
||||||
</AutosuggestTextarea>
|
</AutosuggestTextarea>
|
||||||
<EmojiPickerDropdown onPickEmoji={handleEmojiPick} />
|
<EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />
|
||||||
|
|
||||||
<div className='compose-form__buttons-wrapper'>
|
<div className='compose-form__buttons-wrapper'>
|
||||||
<OptionsContainer
|
<OptionsContainer
|
||||||
advancedOptions={advancedOptions}
|
advancedOptions={advancedOptions}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
onToggleSpoiler={spoilersAlwaysOn ? null : onChangeSpoilerness}
|
onToggleSpoiler={this.props.spoilersAlwaysOn ? null : onChangeSpoilerness}
|
||||||
onUpload={onPaste}
|
onUpload={onPaste}
|
||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
sensitive={sensitive || (spoilersAlwaysOn && spoilerText && spoilerText.length > 0)}
|
sensitive={sensitive || (spoilersAlwaysOn && this.props.spoilerText && this.props.spoilerText.length > 0)}
|
||||||
spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler}
|
spoiler={spoilersAlwaysOn ? (this.props.spoilerText && this.props.spoilerText.length > 0) : this.props.spoiler}
|
||||||
/>
|
/>
|
||||||
<div className='character-counter__wrapper'>
|
<div className='character-counter__wrapper'>
|
||||||
<CharacterCounter text={countText} max={maxChars} />
|
<CharacterCounter max={maxChars} text={this.getFulltextForCharacterCounting()} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Publisher
|
<Publisher
|
||||||
countText={countText}
|
|
||||||
disabled={!this.canSubmit()}
|
disabled={!this.canSubmit()}
|
||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
onSecondarySubmit={handleSecondarySubmit}
|
onSecondarySubmit={this.handleSecondarySubmit}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={this.handleSubmit}
|
||||||
privacy={privacy}
|
privacy={privacy}
|
||||||
sideArm={sideArm}
|
sideArm={sideArm}
|
||||||
/>
|
/>
|
||||||
</div>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,9 +30,11 @@ export default class NavigationBar extends ImmutablePureComponent {
|
||||||
</Permalink>
|
</Permalink>
|
||||||
|
|
||||||
<div className='navigation-bar__profile'>
|
<div className='navigation-bar__profile'>
|
||||||
<Permalink className='acct' href={this.props.account.get('url')} to={`/@${this.props.account.get('acct')}`}>
|
<span>
|
||||||
|
<Permalink className='acct' href={this.props.account.get('url')} to={`/@${username}`}>
|
||||||
<strong className='navigation-bar__profile-account'>@{username}</strong>
|
<strong className='navigation-bar__profile-account'>@{username}</strong>
|
||||||
</Permalink>
|
</Permalink>
|
||||||
|
</span>
|
||||||
|
|
||||||
{ profileLink !== undefined && (
|
{ profileLink !== undefined && (
|
||||||
<a
|
<a
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
// Package imports.
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
@ -9,8 +8,6 @@ import { connect } from 'react-redux';
|
||||||
|
|
||||||
import Toggle from 'react-toggle';
|
import Toggle from 'react-toggle';
|
||||||
|
|
||||||
|
|
||||||
// Components.
|
|
||||||
import { IconButton } from 'flavours/glitch/components/icon_button';
|
import { IconButton } from 'flavours/glitch/components/icon_button';
|
||||||
import { pollLimits } from 'flavours/glitch/initial_state';
|
import { pollLimits } from 'flavours/glitch/initial_state';
|
||||||
|
|
||||||
|
@ -20,11 +17,6 @@ import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
|
||||||
|
|
||||||
import TextIconButton from './text_icon_button';
|
import TextIconButton from './text_icon_button';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Utils.
|
|
||||||
|
|
||||||
// Messages.
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
advanced_options_icon_title: {
|
advanced_options_icon_title: {
|
||||||
defaultMessage: 'Advanced options',
|
defaultMessage: 'Advanced options',
|
||||||
|
@ -133,12 +125,12 @@ class ComposerOptions extends ImmutablePureComponent {
|
||||||
allowPoll: PropTypes.bool,
|
allowPoll: PropTypes.bool,
|
||||||
hasPoll: PropTypes.bool,
|
hasPoll: PropTypes.bool,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
onChangeAdvancedOption: PropTypes.func,
|
onChangeAdvancedOption: PropTypes.func.isRequired,
|
||||||
onChangeContentType: PropTypes.func,
|
onChangeContentType: PropTypes.func.isRequired,
|
||||||
onTogglePoll: PropTypes.func,
|
onTogglePoll: PropTypes.func.isRequired,
|
||||||
onDoodleOpen: PropTypes.func,
|
onDoodleOpen: PropTypes.func.isRequired,
|
||||||
onToggleSpoiler: PropTypes.func,
|
onToggleSpoiler: PropTypes.func,
|
||||||
onUpload: PropTypes.func,
|
onUpload: PropTypes.func.isRequired,
|
||||||
contentType: PropTypes.string,
|
contentType: PropTypes.string,
|
||||||
resetFileKey: PropTypes.number,
|
resetFileKey: PropTypes.number,
|
||||||
spoiler: PropTypes.bool,
|
spoiler: PropTypes.bool,
|
||||||
|
@ -146,20 +138,17 @@ class ComposerOptions extends ImmutablePureComponent {
|
||||||
isEditing: PropTypes.bool,
|
isEditing: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handles file selection.
|
|
||||||
handleChangeFiles = ({ target: { files } }) => {
|
handleChangeFiles = ({ target: { files } }) => {
|
||||||
const { onUpload } = this.props;
|
const { onUpload } = this.props;
|
||||||
if (files.length && onUpload) {
|
if (files.length) {
|
||||||
onUpload(files);
|
onUpload(files);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handles attachment clicks.
|
|
||||||
handleClickAttach = (name) => {
|
handleClickAttach = (name) => {
|
||||||
const { fileElement } = this;
|
const { fileElement } = this;
|
||||||
const { onDoodleOpen } = this.props;
|
const { onDoodleOpen } = this.props;
|
||||||
|
|
||||||
// We switch over the name of the option.
|
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case 'upload':
|
case 'upload':
|
||||||
if (fileElement) {
|
if (fileElement) {
|
||||||
|
@ -167,14 +156,11 @@ class ComposerOptions extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case 'doodle':
|
case 'doodle':
|
||||||
if (onDoodleOpen) {
|
|
||||||
onDoodleOpen();
|
onDoodleOpen();
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handles a ref to the file input.
|
|
||||||
handleRefFileElement = (fileElement) => {
|
handleRefFileElement = (fileElement) => {
|
||||||
this.fileElement = fileElement;
|
this.fileElement = fileElement;
|
||||||
};
|
};
|
||||||
|
@ -186,7 +172,6 @@ class ComposerOptions extends ImmutablePureComponent {
|
||||||
return <ToggleOption name={name} text={text} meta={meta} onChangeAdvancedOption={onChangeAdvancedOption} />;
|
return <ToggleOption name={name} text={text} meta={meta} onChangeAdvancedOption={onChangeAdvancedOption} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Rendering.
|
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
acceptContentTypes,
|
acceptContentTypes,
|
||||||
|
@ -290,7 +275,7 @@ class ComposerOptions extends ImmutablePureComponent {
|
||||||
{onToggleSpoiler && (
|
{onToggleSpoiler && (
|
||||||
<TextIconButton
|
<TextIconButton
|
||||||
active={spoiler}
|
active={spoiler}
|
||||||
ariaControls='glitch.composer.spoiler.input'
|
ariaControls='cw-spoiler-input'
|
||||||
label='CW'
|
label='CW'
|
||||||
onClick={onToggleSpoiler}
|
onClick={onToggleSpoiler}
|
||||||
title={formatMessage(messages.spoiler)}
|
title={formatMessage(messages.spoiler)}
|
||||||
|
|
|
@ -2,15 +2,10 @@ import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
|
||||||
import { length } from 'stringz';
|
|
||||||
|
|
||||||
import { Button } from 'flavours/glitch/components/button';
|
import { Button } from 'flavours/glitch/components/button';
|
||||||
import { Icon } from 'flavours/glitch/components/icon';
|
import { Icon } from 'flavours/glitch/components/icon';
|
||||||
import { maxChars } from 'flavours/glitch/initial_state';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
publish: {
|
publish: {
|
||||||
|
@ -31,7 +26,6 @@ const messages = defineMessages({
|
||||||
class Publisher extends ImmutablePureComponent {
|
class Publisher extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
countText: PropTypes.string,
|
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
onSecondarySubmit: PropTypes.func,
|
onSecondarySubmit: PropTypes.func,
|
||||||
|
@ -46,13 +40,7 @@ class Publisher extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { countText, disabled, intl, onSecondarySubmit, privacy, sideArm, isEditing } = this.props;
|
const { disabled, intl, onSecondarySubmit, privacy, sideArm, isEditing } = this.props;
|
||||||
|
|
||||||
const diff = maxChars - length(countText || '');
|
|
||||||
const computedClass = classNames('compose-form__publish', {
|
|
||||||
disabled: disabled,
|
|
||||||
over: diff < 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
const privacyIcons = { direct: 'envelope', private: 'lock', public: 'globe', unlisted: 'unlock' };
|
const privacyIcons = { direct: 'envelope', private: 'lock', public: 'globe', unlisted: 'unlock' };
|
||||||
|
|
||||||
|
@ -78,8 +66,8 @@ class Publisher extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={computedClass}>
|
<div className='compose-form__publish'>
|
||||||
{sideArm && !isEditing && sideArm !== 'none' ? (
|
{sideArm && !isEditing && sideArm !== 'none' && (
|
||||||
<div className='compose-form__publish-button-wrapper'>
|
<div className='compose-form__publish-button-wrapper'>
|
||||||
<Button
|
<Button
|
||||||
className='side_arm'
|
className='side_arm'
|
||||||
|
@ -90,7 +78,7 @@ class Publisher extends ImmutablePureComponent {
|
||||||
title={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage(privacyNames[sideArm])}`}
|
title={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage(privacyNames[sideArm])}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
)}
|
||||||
<div className='compose-form__publish-button-wrapper'>
|
<div className='compose-form__publish-button-wrapper'>
|
||||||
<Button
|
<Button
|
||||||
className='primary'
|
className='primary'
|
||||||
|
|
|
@ -18,13 +18,11 @@ export default class UploadForm extends ImmutablePureComponent {
|
||||||
<div className='compose-form__upload-wrapper'>
|
<div className='compose-form__upload-wrapper'>
|
||||||
<UploadProgressContainer />
|
<UploadProgressContainer />
|
||||||
|
|
||||||
{mediaIds.size > 0 && (
|
|
||||||
<div className='compose-form__uploads-wrapper'>
|
<div className='compose-form__uploads-wrapper'>
|
||||||
{mediaIds.map(id => (
|
{mediaIds.map(id => (
|
||||||
<UploadContainer id={id} key={id} />
|
<UploadContainer id={id} key={id} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{!mediaIds.isEmpty() && <SensitiveButtonContainer />}
|
{!mediaIds.isEmpty() && <SensitiveButtonContainer />}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -37,9 +37,7 @@ const messages = defineMessages({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// State mapping.
|
const sideArmPrivacy = state => {
|
||||||
function mapStateToProps (state) {
|
|
||||||
const spoilersAlwaysOn = state.getIn(['local_settings', 'always_show_spoilers_field']);
|
|
||||||
const inReplyTo = state.getIn(['compose', 'in_reply_to']);
|
const inReplyTo = state.getIn(['compose', 'in_reply_to']);
|
||||||
const replyPrivacy = inReplyTo ? state.getIn(['statuses', inReplyTo, 'visibility']) : null;
|
const replyPrivacy = inReplyTo ? state.getIn(['statuses', inReplyTo, 'visibility']) : null;
|
||||||
const sideArmBasePrivacy = state.getIn(['local_settings', 'side_arm']);
|
const sideArmBasePrivacy = state.getIn(['local_settings', 'side_arm']);
|
||||||
|
@ -53,44 +51,44 @@ function mapStateToProps (state) {
|
||||||
sideArmPrivacy = sideArmRestrictedPrivacy;
|
sideArmPrivacy = sideArmRestrictedPrivacy;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
sideArmPrivacy = sideArmPrivacy || sideArmBasePrivacy;
|
return sideArmPrivacy || sideArmBasePrivacy;
|
||||||
return {
|
};
|
||||||
advancedOptions: state.getIn(['compose', 'advanced_options']),
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
text: state.getIn(['compose', 'text']),
|
||||||
|
suggestions: state.getIn(['compose', 'suggestions']),
|
||||||
|
spoiler: state.getIn(['local_settings', 'always_show_spoilers_field']) || state.getIn(['compose', 'spoiler']),
|
||||||
|
spoilerText: state.getIn(['compose', 'spoiler_text']),
|
||||||
|
privacy: state.getIn(['compose', 'privacy']),
|
||||||
focusDate: state.getIn(['compose', 'focusDate']),
|
focusDate: state.getIn(['compose', 'focusDate']),
|
||||||
caretPosition: state.getIn(['compose', 'caretPosition']),
|
caretPosition: state.getIn(['compose', 'caretPosition']),
|
||||||
|
preselectDate: state.getIn(['compose', 'preselectDate']),
|
||||||
isSubmitting: state.getIn(['compose', 'is_submitting']),
|
isSubmitting: state.getIn(['compose', 'is_submitting']),
|
||||||
isEditing: state.getIn(['compose', 'id']) !== null,
|
isEditing: state.getIn(['compose', 'id']) !== null,
|
||||||
isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
|
isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
|
||||||
isUploading: state.getIn(['compose', 'is_uploading']),
|
isUploading: state.getIn(['compose', 'is_uploading']),
|
||||||
layout: state.getIn(['local_settings', 'layout']),
|
|
||||||
media: state.getIn(['compose', 'media_attachments']),
|
|
||||||
preselectDate: state.getIn(['compose', 'preselectDate']),
|
|
||||||
privacy: state.getIn(['compose', 'privacy']),
|
|
||||||
sideArm: sideArmPrivacy,
|
|
||||||
sensitive: state.getIn(['compose', 'sensitive']),
|
|
||||||
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
|
|
||||||
spoiler: spoilersAlwaysOn || state.getIn(['compose', 'spoiler']),
|
|
||||||
spoilerText: state.getIn(['compose', 'spoiler_text']),
|
|
||||||
suggestions: state.getIn(['compose', 'suggestions']),
|
|
||||||
text: state.getIn(['compose', 'text']),
|
|
||||||
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
|
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
|
||||||
spoilersAlwaysOn: spoilersAlwaysOn,
|
|
||||||
mediaDescriptionConfirmation: state.getIn(['local_settings', 'confirm_missing_media_description']),
|
|
||||||
preselectOnReply: state.getIn(['local_settings', 'preselect_on_reply']),
|
|
||||||
isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
|
isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
|
||||||
lang: state.getIn(['compose', 'language']),
|
lang: state.getIn(['compose', 'language']),
|
||||||
};
|
advancedOptions: state.getIn(['compose', 'advanced_options']),
|
||||||
}
|
layout: state.getIn(['local_settings', 'layout']),
|
||||||
|
media: state.getIn(['compose', 'media_attachments']),
|
||||||
|
sideArm: sideArmPrivacy(state),
|
||||||
|
sensitive: state.getIn(['compose', 'sensitive']),
|
||||||
|
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
|
||||||
|
spoilersAlwaysOn: state.getIn(['local_settings', 'always_show_spoilers_field']),
|
||||||
|
mediaDescriptionConfirmation: state.getIn(['local_settings', 'confirm_missing_media_description']),
|
||||||
|
preselectOnReply: state.getIn(['local_settings', 'preselect_on_reply']),
|
||||||
|
});
|
||||||
|
|
||||||
// Dispatch mapping.
|
|
||||||
const mapDispatchToProps = (dispatch, { intl }) => ({
|
const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||||
|
|
||||||
onChange (text) {
|
onChange (text) {
|
||||||
dispatch(changeCompose(text));
|
dispatch(changeCompose(text));
|
||||||
},
|
},
|
||||||
|
|
||||||
onSubmit(routerHistory) {
|
onSubmit (router) {
|
||||||
dispatch(submitCompose(routerHistory));
|
dispatch(submitCompose(router));
|
||||||
},
|
},
|
||||||
|
|
||||||
onClearSuggestions () {
|
onClearSuggestions () {
|
||||||
|
|
Loading…
Reference in New Issue