Threaded mode~
This commit is contained in:
parent
85ffb07656
commit
e5a10a1fd3
9 changed files with 157 additions and 48 deletions
|
@ -61,7 +61,7 @@ export function replyCompose(status, router) {
|
||||||
status: status,
|
status: status,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!getState().getIn(['compose', 'mounted'])) {
|
if (router && !getState().getIn(['compose', 'mounted'])) {
|
||||||
router.push('/statuses/new');
|
router.push('/statuses/new');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -118,6 +118,11 @@ export function submitCompose() {
|
||||||
}).then(function (response) {
|
}).then(function (response) {
|
||||||
dispatch(submitComposeSuccess({ ...response.data }));
|
dispatch(submitComposeSuccess({ ...response.data }));
|
||||||
|
|
||||||
|
// If the response has no data then we can't do anything else.
|
||||||
|
if (!response.data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// To make the app more responsive, immediately get the status into the columns
|
// To make the app more responsive, immediately get the status into the columns
|
||||||
|
|
||||||
const insertOrRefresh = (timelineId, refreshAction) => {
|
const insertOrRefresh = (timelineId, refreshAction) => {
|
||||||
|
@ -341,10 +346,11 @@ export function unmountCompose() {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function toggleComposeAdvancedOption(option) {
|
export function changeComposeAdvancedOption(option, value) {
|
||||||
return {
|
return {
|
||||||
|
option,
|
||||||
type: COMPOSE_ADVANCED_OPTIONS_CHANGE,
|
type: COMPOSE_ADVANCED_OPTIONS_CHANGE,
|
||||||
option: option,
|
value,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import {
|
import {
|
||||||
cancelReplyCompose,
|
cancelReplyCompose,
|
||||||
changeCompose,
|
changeCompose,
|
||||||
|
changeComposeAdvancedOption,
|
||||||
changeComposeSensitivity,
|
changeComposeSensitivity,
|
||||||
changeComposeSpoilerText,
|
changeComposeSpoilerText,
|
||||||
changeComposeSpoilerness,
|
changeComposeSpoilerness,
|
||||||
|
@ -18,7 +19,6 @@ import {
|
||||||
mountCompose,
|
mountCompose,
|
||||||
selectComposeSuggestion,
|
selectComposeSuggestion,
|
||||||
submitCompose,
|
submitCompose,
|
||||||
toggleComposeAdvancedOption,
|
|
||||||
undoUploadCompose,
|
undoUploadCompose,
|
||||||
unmountCompose,
|
unmountCompose,
|
||||||
uploadCompose,
|
uploadCompose,
|
||||||
|
@ -49,8 +49,8 @@ function mapStateToProps (state) {
|
||||||
const inReplyTo = state.getIn(['compose', 'in_reply_to']);
|
const inReplyTo = state.getIn(['compose', 'in_reply_to']);
|
||||||
return {
|
return {
|
||||||
acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']).toArray().join(','),
|
acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']).toArray().join(','),
|
||||||
|
advancedOptions: state.getIn(['compose', 'advanced_options']),
|
||||||
amUnlocked: !state.getIn(['accounts', me, 'locked']),
|
amUnlocked: !state.getIn(['accounts', me, 'locked']),
|
||||||
doNotFederate: state.getIn(['compose', 'advanced_options', 'do_not_federate']),
|
|
||||||
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']),
|
||||||
|
@ -76,6 +76,7 @@ function mapStateToProps (state) {
|
||||||
// Dispatch mapping.
|
// Dispatch mapping.
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
onCancelReply: cancelReplyCompose,
|
onCancelReply: cancelReplyCompose,
|
||||||
|
onChangeAdvancedOption: changeComposeAdvancedOption,
|
||||||
onChangeDescription: changeUploadCompose,
|
onChangeDescription: changeUploadCompose,
|
||||||
onChangeSensitivity: changeComposeSensitivity,
|
onChangeSensitivity: changeComposeSensitivity,
|
||||||
onChangeSpoilerText: changeComposeSpoilerText,
|
onChangeSpoilerText: changeComposeSpoilerText,
|
||||||
|
@ -91,7 +92,6 @@ const mapDispatchToProps = {
|
||||||
onOpenDoodleModal: openModal.bind(null, 'DOODLE', { noEsc: true }),
|
onOpenDoodleModal: openModal.bind(null, 'DOODLE', { noEsc: true }),
|
||||||
onSelectSuggestion: selectComposeSuggestion,
|
onSelectSuggestion: selectComposeSuggestion,
|
||||||
onSubmit: submitCompose,
|
onSubmit: submitCompose,
|
||||||
onToggleAdvancedOption: toggleComposeAdvancedOption,
|
|
||||||
onUndoUpload: undoUploadCompose,
|
onUndoUpload: undoUploadCompose,
|
||||||
onUnmount: unmountCompose,
|
onUnmount: unmountCompose,
|
||||||
onUpload: uploadCompose,
|
onUpload: uploadCompose,
|
||||||
|
@ -267,14 +267,15 @@ class Composer extends React.Component {
|
||||||
} = this.handlers;
|
} = this.handlers;
|
||||||
const {
|
const {
|
||||||
acceptContentTypes,
|
acceptContentTypes,
|
||||||
|
advancedOptions,
|
||||||
amUnlocked,
|
amUnlocked,
|
||||||
doNotFederate,
|
|
||||||
intl,
|
intl,
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
isUploading,
|
isUploading,
|
||||||
layout,
|
layout,
|
||||||
media,
|
media,
|
||||||
onCancelReply,
|
onCancelReply,
|
||||||
|
onChangeAdvancedOption,
|
||||||
onChangeDescription,
|
onChangeDescription,
|
||||||
onChangeSensitivity,
|
onChangeSensitivity,
|
||||||
onChangeSpoilerness,
|
onChangeSpoilerness,
|
||||||
|
@ -285,7 +286,6 @@ class Composer extends React.Component {
|
||||||
onFetchSuggestions,
|
onFetchSuggestions,
|
||||||
onOpenActionsModal,
|
onOpenActionsModal,
|
||||||
onOpenDoodleModal,
|
onOpenDoodleModal,
|
||||||
onToggleAdvancedOption,
|
|
||||||
onUndoUpload,
|
onUndoUpload,
|
||||||
onUpload,
|
onUpload,
|
||||||
privacy,
|
privacy,
|
||||||
|
@ -321,6 +321,7 @@ class Composer extends React.Component {
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<ComposerTextarea
|
<ComposerTextarea
|
||||||
|
advancedOptions={advancedOptions}
|
||||||
autoFocus={!showSearch && !isMobile(window.innerWidth, layout)}
|
autoFocus={!showSearch && !isMobile(window.innerWidth, layout)}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
intl={intl}
|
intl={intl}
|
||||||
|
@ -347,19 +348,19 @@ class Composer extends React.Component {
|
||||||
) : null}
|
) : null}
|
||||||
<ComposerOptions
|
<ComposerOptions
|
||||||
acceptContentTypes={acceptContentTypes}
|
acceptContentTypes={acceptContentTypes}
|
||||||
|
advancedOptions={advancedOptions}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
doNotFederate={doNotFederate}
|
|
||||||
full={media.size >= 4 || media.some(
|
full={media.size >= 4 || media.some(
|
||||||
item => item.get('type') === 'video'
|
item => item.get('type') === 'video'
|
||||||
)}
|
)}
|
||||||
hasMedia={!!media.size}
|
hasMedia={!!media.size}
|
||||||
intl={intl}
|
intl={intl}
|
||||||
|
onChangeAdvancedOption={onChangeAdvancedOption}
|
||||||
onChangeSensitivity={onChangeSensitivity}
|
onChangeSensitivity={onChangeSensitivity}
|
||||||
onChangeVisibility={onChangeVisibility}
|
onChangeVisibility={onChangeVisibility}
|
||||||
onDoodleOpen={onOpenDoodleModal}
|
onDoodleOpen={onOpenDoodleModal}
|
||||||
onModalClose={onCloseModal}
|
onModalClose={onCloseModal}
|
||||||
onModalOpen={onOpenActionsModal}
|
onModalOpen={onOpenActionsModal}
|
||||||
onToggleAdvancedOption={onToggleAdvancedOption}
|
|
||||||
onToggleSpoiler={onChangeSpoilerness}
|
onToggleSpoiler={onChangeSpoilerness}
|
||||||
onUpload={onUpload}
|
onUpload={onUpload}
|
||||||
privacy={privacy}
|
privacy={privacy}
|
||||||
|
@ -368,7 +369,7 @@ class Composer extends React.Component {
|
||||||
spoiler={spoiler}
|
spoiler={spoiler}
|
||||||
/>
|
/>
|
||||||
<ComposerPublisher
|
<ComposerPublisher
|
||||||
countText={`${spoilerText}${countableText(text)}${doNotFederate ? ' 👁️' : ''}`}
|
countText={`${spoilerText}${countableText(text)}${advancedOptions.get('do_not_federate') ? ' 👁️' : ''}`}
|
||||||
disabled={isSubmitting || isUploading || !!text.length && !text.trim().length}
|
disabled={isSubmitting || isUploading || !!text.length && !text.trim().length}
|
||||||
intl={intl}
|
intl={intl}
|
||||||
onSecondarySubmit={handleSecondarySubmit}
|
onSecondarySubmit={handleSecondarySubmit}
|
||||||
|
@ -388,8 +389,8 @@ Composer.propTypes = {
|
||||||
|
|
||||||
// State props.
|
// State props.
|
||||||
acceptContentTypes: PropTypes.string,
|
acceptContentTypes: PropTypes.string,
|
||||||
|
advancedOptions: ImmutablePropTypes.map,
|
||||||
amUnlocked: PropTypes.bool,
|
amUnlocked: 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,
|
||||||
|
@ -412,6 +413,7 @@ Composer.propTypes = {
|
||||||
|
|
||||||
// Dispatch props.
|
// Dispatch props.
|
||||||
onCancelReply: PropTypes.func,
|
onCancelReply: PropTypes.func,
|
||||||
|
onChangeAdvancedOption: PropTypes.func,
|
||||||
onChangeDescription: PropTypes.func,
|
onChangeDescription: PropTypes.func,
|
||||||
onChangeSensitivity: PropTypes.func,
|
onChangeSensitivity: PropTypes.func,
|
||||||
onChangeSpoilerText: PropTypes.func,
|
onChangeSpoilerText: PropTypes.func,
|
||||||
|
@ -427,7 +429,6 @@ Composer.propTypes = {
|
||||||
onOpenDoodleModal: PropTypes.func,
|
onOpenDoodleModal: PropTypes.func,
|
||||||
onSelectSuggestion: PropTypes.func,
|
onSelectSuggestion: PropTypes.func,
|
||||||
onSubmit: PropTypes.func,
|
onSubmit: PropTypes.func,
|
||||||
onToggleAdvancedOption: PropTypes.func,
|
|
||||||
onUndoUpload: PropTypes.func,
|
onUndoUpload: PropTypes.func,
|
||||||
onUnmount: PropTypes.func,
|
onUnmount: PropTypes.func,
|
||||||
onUpload: PropTypes.func,
|
onUpload: PropTypes.func,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Package imports.
|
// Package imports.
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import {
|
import {
|
||||||
FormattedMessage,
|
FormattedMessage,
|
||||||
defineMessages,
|
defineMessages,
|
||||||
|
@ -47,11 +48,11 @@ const messages = defineMessages({
|
||||||
},
|
},
|
||||||
local_only_long: {
|
local_only_long: {
|
||||||
defaultMessage: 'Do not post to other instances',
|
defaultMessage: 'Do not post to other instances',
|
||||||
id: 'advanced-options.local-only.long',
|
id: 'advanced_options.local-only.long',
|
||||||
},
|
},
|
||||||
local_only_short: {
|
local_only_short: {
|
||||||
defaultMessage: 'Local-only',
|
defaultMessage: 'Local-only',
|
||||||
id: 'advanced-options.local-only.short',
|
id: 'advanced_options.local-only.short',
|
||||||
},
|
},
|
||||||
private_long: {
|
private_long: {
|
||||||
defaultMessage: 'Post to followers only',
|
defaultMessage: 'Post to followers only',
|
||||||
|
@ -77,6 +78,14 @@ const messages = defineMessages({
|
||||||
defaultMessage: 'Hide text behind warning',
|
defaultMessage: 'Hide text behind warning',
|
||||||
id: 'compose_form.spoiler',
|
id: 'compose_form.spoiler',
|
||||||
},
|
},
|
||||||
|
threaded_mode_long: {
|
||||||
|
defaultMessage: 'Automatically opens a reply on posting',
|
||||||
|
id: 'advanced_options.threaded_mode.long',
|
||||||
|
},
|
||||||
|
threaded_mode_short: {
|
||||||
|
defaultMessage: 'Threaded mode',
|
||||||
|
id: 'advanced_options.threaded_mode.short',
|
||||||
|
},
|
||||||
unlisted_long: {
|
unlisted_long: {
|
||||||
defaultMessage: 'Do not show in public timelines',
|
defaultMessage: 'Do not show in public timelines',
|
||||||
id: 'privacy.unlisted.long',
|
id: 'privacy.unlisted.long',
|
||||||
|
@ -149,16 +158,16 @@ export default class ComposerOptions extends React.PureComponent {
|
||||||
} = this.handlers;
|
} = this.handlers;
|
||||||
const {
|
const {
|
||||||
acceptContentTypes,
|
acceptContentTypes,
|
||||||
|
advancedOptions,
|
||||||
disabled,
|
disabled,
|
||||||
doNotFederate,
|
|
||||||
full,
|
full,
|
||||||
hasMedia,
|
hasMedia,
|
||||||
intl,
|
intl,
|
||||||
|
onChangeAdvancedOption,
|
||||||
onChangeSensitivity,
|
onChangeSensitivity,
|
||||||
onChangeVisibility,
|
onChangeVisibility,
|
||||||
onModalClose,
|
onModalClose,
|
||||||
onModalOpen,
|
onModalOpen,
|
||||||
onToggleAdvancedOption,
|
|
||||||
onToggleSpoiler,
|
onToggleSpoiler,
|
||||||
privacy,
|
privacy,
|
||||||
resetFileKey,
|
resetFileKey,
|
||||||
|
@ -283,23 +292,31 @@ export default class ComposerOptions extends React.PureComponent {
|
||||||
onClick={onToggleSpoiler}
|
onClick={onToggleSpoiler}
|
||||||
title={intl.formatMessage(messages.spoiler)}
|
title={intl.formatMessage(messages.spoiler)}
|
||||||
/>
|
/>
|
||||||
|
{advancedOptions ? (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
active={doNotFederate}
|
active={advancedOptions.some(value => !!value)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
icon='home'
|
icon='ellipsis-h'
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
meta: <FormattedMessage {...messages.local_only_long} />,
|
meta: <FormattedMessage {...messages.local_only_long} />,
|
||||||
name: 'do_not_federate',
|
name: 'do_not_federate',
|
||||||
on: doNotFederate,
|
on: advancedOptions.get('do_not_federate'),
|
||||||
text: <FormattedMessage {...messages.local_only_short} />,
|
text: <FormattedMessage {...messages.local_only_short} />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
meta: <FormattedMessage {...messages.threaded_mode_long} />,
|
||||||
|
name: 'threaded_mode',
|
||||||
|
on: advancedOptions.get('threaded_mode'),
|
||||||
|
text: <FormattedMessage {...messages.threaded_mode_short} />,
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
onChange={onToggleAdvancedOption}
|
onChange={onChangeAdvancedOption}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
onModalOpen={onModalOpen}
|
onModalOpen={onModalOpen}
|
||||||
title={intl.formatMessage(messages.advanced_options_icon_title)}
|
title={intl.formatMessage(messages.advanced_options_icon_title)}
|
||||||
/>
|
/>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -309,17 +326,17 @@ export default class ComposerOptions extends React.PureComponent {
|
||||||
// Props.
|
// Props.
|
||||||
ComposerOptions.propTypes = {
|
ComposerOptions.propTypes = {
|
||||||
acceptContentTypes: PropTypes.string,
|
acceptContentTypes: PropTypes.string,
|
||||||
|
advancedOptions: ImmutablePropTypes.map,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
doNotFederate: PropTypes.bool,
|
|
||||||
full: PropTypes.bool,
|
full: PropTypes.bool,
|
||||||
hasMedia: PropTypes.bool,
|
hasMedia: PropTypes.bool,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
|
onChangeAdvancedOption: PropTypes.func,
|
||||||
onChangeSensitivity: PropTypes.func,
|
onChangeSensitivity: PropTypes.func,
|
||||||
onChangeVisibility: PropTypes.func,
|
onChangeVisibility: PropTypes.func,
|
||||||
onDoodleOpen: PropTypes.func,
|
onDoodleOpen: PropTypes.func,
|
||||||
onModalClose: PropTypes.func,
|
onModalClose: PropTypes.func,
|
||||||
onModalOpen: PropTypes.func,
|
onModalOpen: PropTypes.func,
|
||||||
onToggleAdvancedOption: PropTypes.func,
|
|
||||||
onToggleSpoiler: PropTypes.func,
|
onToggleSpoiler: PropTypes.func,
|
||||||
onUpload: PropTypes.func,
|
onUpload: PropTypes.func,
|
||||||
privacy: PropTypes.string,
|
privacy: PropTypes.string,
|
||||||
|
|
|
@ -10,6 +10,7 @@ import Textarea from 'react-textarea-autosize';
|
||||||
|
|
||||||
// Components.
|
// Components.
|
||||||
import EmojiPicker from 'flavours/glitch/features/emoji_picker';
|
import EmojiPicker from 'flavours/glitch/features/emoji_picker';
|
||||||
|
import ComposerTextareaIcons from './icons';
|
||||||
import ComposerTextareaSuggestions from './suggestions';
|
import ComposerTextareaSuggestions from './suggestions';
|
||||||
|
|
||||||
// Utils.
|
// Utils.
|
||||||
|
@ -232,6 +233,7 @@ export default class ComposerTextarea extends React.Component {
|
||||||
handleRefTextarea,
|
handleRefTextarea,
|
||||||
} = this.handlers;
|
} = this.handlers;
|
||||||
const {
|
const {
|
||||||
|
advancedOptions,
|
||||||
autoFocus,
|
autoFocus,
|
||||||
disabled,
|
disabled,
|
||||||
intl,
|
intl,
|
||||||
|
@ -249,6 +251,10 @@ export default class ComposerTextarea extends React.Component {
|
||||||
<div className='composer--textarea'>
|
<div className='composer--textarea'>
|
||||||
<label>
|
<label>
|
||||||
<span {...hiddenComponent}><FormattedMessage {...messages.placeholder} /></span>
|
<span {...hiddenComponent}><FormattedMessage {...messages.placeholder} /></span>
|
||||||
|
<ComposerTextareaIcons
|
||||||
|
advancedOptions={advancedOptions}
|
||||||
|
intl={intl}
|
||||||
|
/>
|
||||||
<Textarea
|
<Textarea
|
||||||
aria-autocomplete='list'
|
aria-autocomplete='list'
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
|
@ -280,6 +286,7 @@ export default class ComposerTextarea extends React.Component {
|
||||||
|
|
||||||
// Props.
|
// Props.
|
||||||
ComposerTextarea.propTypes = {
|
ComposerTextarea.propTypes = {
|
||||||
|
advancedOptions: ImmutablePropTypes.map,
|
||||||
autoFocus: PropTypes.bool,
|
autoFocus: PropTypes.bool,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
|
|
|
@ -52,9 +52,13 @@ const messages = {
|
||||||
'compose.attach.doodle': 'Draw something',
|
'compose.attach.doodle': 'Draw something',
|
||||||
'compose.attach': 'Attach...',
|
'compose.attach': 'Attach...',
|
||||||
|
|
||||||
'advanced-options.local-only.short': 'Local-only',
|
'advanced_options.local-only.short': 'Local-only',
|
||||||
'advanced-options.local-only.long': 'Do not post to other instances',
|
'advanced_options.local-only.long': 'Do not post to other instances',
|
||||||
|
'advanced_options.local-only.tooltip': 'This post is local-only',
|
||||||
'advanced_options.icon_title': 'Advanced options',
|
'advanced_options.icon_title': 'Advanced options',
|
||||||
|
'advanced_options.threaded_mode.short': 'Threaded mode',
|
||||||
|
'advanced_options.threaded_mode.long': 'Automatically opens a reply on posting',
|
||||||
|
'advanced_options.threaded_mode.tooltip': 'Threaded mode enabled',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.assign({}, inherited, messages);
|
export default Object.assign({}, inherited, messages);
|
||||||
|
|
|
@ -55,8 +55,8 @@ const messages = {
|
||||||
'compose.attach.doodle': '落書きをする',
|
'compose.attach.doodle': '落書きをする',
|
||||||
'compose.attach': 'アタッチ...',
|
'compose.attach': 'アタッチ...',
|
||||||
|
|
||||||
'advanced-options.local-only.short': 'ローカル限定',
|
'advanced_options.local-only.short': 'ローカル限定',
|
||||||
'advanced-options.local-only.long': '他のインスタンスには投稿されません',
|
'advanced_options.local-only.long': '他のインスタンスには投稿されません',
|
||||||
'advanced_options.icon_title': '高度な設定',
|
'advanced_options.icon_title': '高度な設定',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -33,11 +33,13 @@ import { STORE_HYDRATE } from 'flavours/glitch/actions/store';
|
||||||
import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
|
import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
|
||||||
import uuid from 'flavours/glitch/util/uuid';
|
import uuid from 'flavours/glitch/util/uuid';
|
||||||
import { me } from 'flavours/glitch/util/initial_state';
|
import { me } from 'flavours/glitch/util/initial_state';
|
||||||
|
import { overwrite } from 'flavours/glitch/util/js_helpers';
|
||||||
|
|
||||||
const initialState = ImmutableMap({
|
const initialState = ImmutableMap({
|
||||||
mounted: false,
|
mounted: false,
|
||||||
advanced_options: ImmutableMap({
|
advanced_options: ImmutableMap({
|
||||||
do_not_federate: false,
|
do_not_federate: false,
|
||||||
|
threaded_mode: false,
|
||||||
}),
|
}),
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
spoiler: false,
|
spoiler: false,
|
||||||
|
@ -55,6 +57,7 @@ const initialState = ImmutableMap({
|
||||||
suggestions: ImmutableList(),
|
suggestions: ImmutableList(),
|
||||||
default_advanced_options: ImmutableMap({
|
default_advanced_options: ImmutableMap({
|
||||||
do_not_federate: false,
|
do_not_federate: false,
|
||||||
|
threaded_mode: null, // Do not reset
|
||||||
}),
|
}),
|
||||||
default_privacy: 'public',
|
default_privacy: 'public',
|
||||||
default_sensitive: false,
|
default_sensitive: false,
|
||||||
|
@ -83,6 +86,20 @@ function statusToTextMentions(state, status) {
|
||||||
return set.union(status.get('mentions').filterNot(mention => mention.get('id') === me).map(mention => `@${mention.get('acct')} `)).join('');
|
return set.union(status.get('mentions').filterNot(mention => mention.get('id') === me).map(mention => `@${mention.get('acct')} `)).join('');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function apiStatusToTextMentions (state, status) {
|
||||||
|
let set = ImmutableOrderedSet([]);
|
||||||
|
|
||||||
|
if (status.account.id !== me) {
|
||||||
|
set = set.add(`@${status.account.acct} `);
|
||||||
|
}
|
||||||
|
|
||||||
|
return set.union(status.mentions.filter(
|
||||||
|
mention => mention.id !== me
|
||||||
|
).map(
|
||||||
|
mention => `@${mention.acct} `
|
||||||
|
)).join('');
|
||||||
|
}
|
||||||
|
|
||||||
function clearAll(state) {
|
function clearAll(state) {
|
||||||
return state.withMutations(map => {
|
return state.withMutations(map => {
|
||||||
map.set('text', '');
|
map.set('text', '');
|
||||||
|
@ -90,7 +107,10 @@ function clearAll(state) {
|
||||||
map.set('spoiler_text', '');
|
map.set('spoiler_text', '');
|
||||||
map.set('is_submitting', false);
|
map.set('is_submitting', false);
|
||||||
map.set('in_reply_to', null);
|
map.set('in_reply_to', null);
|
||||||
map.set('advanced_options', state.get('default_advanced_options'));
|
map.update(
|
||||||
|
'advanced_options',
|
||||||
|
map => map.mergeWith(overwrite, state.get('default_advanced_options'))
|
||||||
|
);
|
||||||
map.set('privacy', state.get('default_privacy'));
|
map.set('privacy', state.get('default_privacy'));
|
||||||
map.set('sensitive', false);
|
map.set('sensitive', false);
|
||||||
map.update('media_attachments', list => list.clear());
|
map.update('media_attachments', list => list.clear());
|
||||||
|
@ -98,6 +118,31 @@ function clearAll(state) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function continueThread (state, status) {
|
||||||
|
return state.withMutations(function (map) {
|
||||||
|
map.set('text', apiStatusToTextMentions(state, status));
|
||||||
|
if (status.spoiler_text) {
|
||||||
|
map.set('spoiler', true);
|
||||||
|
map.set('spoiler_text', status.spoiler_text);
|
||||||
|
} else {
|
||||||
|
map.set('spoiler', false);
|
||||||
|
map.set('spoiler_text', '');
|
||||||
|
}
|
||||||
|
map.set('is_submitting', false);
|
||||||
|
map.set('in_reply_to', status.id);
|
||||||
|
map.update(
|
||||||
|
'advanced_options',
|
||||||
|
map => map.merge(new ImmutableMap({ do_not_federate: /👁\ufe0f?\u200b?(?:<\/p>)?$/.test(status.content) }))
|
||||||
|
);
|
||||||
|
map.set('privacy', privacyPreference(status.visibility, state.get('default_privacy')));
|
||||||
|
map.set('sensitive', false);
|
||||||
|
map.update('media_attachments', list => list.clear());
|
||||||
|
map.set('idempotencyKey', uuid());
|
||||||
|
map.set('focusDate', new Date());
|
||||||
|
map.set('preselectDate', new Date());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function appendMedia(state, media) {
|
function appendMedia(state, media) {
|
||||||
const prevSize = state.get('media_attachments').size;
|
const prevSize = state.get('media_attachments').size;
|
||||||
|
|
||||||
|
@ -182,8 +227,7 @@ export default function compose(state = initialState, action) {
|
||||||
return state.set('mounted', false);
|
return state.set('mounted', false);
|
||||||
case COMPOSE_ADVANCED_OPTIONS_CHANGE:
|
case COMPOSE_ADVANCED_OPTIONS_CHANGE:
|
||||||
return state
|
return state
|
||||||
.set('advanced_options',
|
.set('advanced_options', state.get('advanced_options').set(action.option, !!overwrite(!state.getIn(['advanced_options', action.option]), action.value)))
|
||||||
state.get('advanced_options').set(action.option, !state.getIn(['advanced_options', action.option])))
|
|
||||||
.set('idempotencyKey', uuid());
|
.set('idempotencyKey', uuid());
|
||||||
case COMPOSE_SENSITIVITY_CHANGE:
|
case COMPOSE_SENSITIVITY_CHANGE:
|
||||||
return state.withMutations(map => {
|
return state.withMutations(map => {
|
||||||
|
@ -220,9 +264,10 @@ export default function compose(state = initialState, action) {
|
||||||
map.set('in_reply_to', action.status.get('id'));
|
map.set('in_reply_to', action.status.get('id'));
|
||||||
map.set('text', statusToTextMentions(state, action.status));
|
map.set('text', statusToTextMentions(state, action.status));
|
||||||
map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
|
map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
|
||||||
map.set('advanced_options', new ImmutableMap({
|
map.update(
|
||||||
do_not_federate: /👁\ufe0f?<\/p>$/.test(action.status.get('content')),
|
'advanced_options',
|
||||||
}));
|
map => map.merge(new ImmutableMap({ do_not_federate: /👁\ufe0f?\u200b?(?:<\/p>)?$/.test(action.status.get('content')) }))
|
||||||
|
);
|
||||||
map.set('focusDate', new Date());
|
map.set('focusDate', new Date());
|
||||||
map.set('preselectDate', new Date());
|
map.set('preselectDate', new Date());
|
||||||
map.set('idempotencyKey', uuid());
|
map.set('idempotencyKey', uuid());
|
||||||
|
@ -243,14 +288,17 @@ export default function compose(state = initialState, action) {
|
||||||
map.set('spoiler', false);
|
map.set('spoiler', false);
|
||||||
map.set('spoiler_text', '');
|
map.set('spoiler_text', '');
|
||||||
map.set('privacy', state.get('default_privacy'));
|
map.set('privacy', state.get('default_privacy'));
|
||||||
map.set('advanced_options', state.get('default_advanced_options'));
|
map.update(
|
||||||
|
'advanced_options',
|
||||||
|
map => map.mergeWith(overwrite, state.get('default_advanced_options'))
|
||||||
|
);
|
||||||
map.set('idempotencyKey', uuid());
|
map.set('idempotencyKey', uuid());
|
||||||
});
|
});
|
||||||
case COMPOSE_SUBMIT_REQUEST:
|
case COMPOSE_SUBMIT_REQUEST:
|
||||||
case COMPOSE_UPLOAD_CHANGE_REQUEST:
|
case COMPOSE_UPLOAD_CHANGE_REQUEST:
|
||||||
return state.set('is_submitting', true);
|
return state.set('is_submitting', true);
|
||||||
case COMPOSE_SUBMIT_SUCCESS:
|
case COMPOSE_SUBMIT_SUCCESS:
|
||||||
return clearAll(state);
|
return action.status && state.get('advanced_options', 'threaded_mode') ? continueThread(state, action.status) : clearAll(state);
|
||||||
case COMPOSE_SUBMIT_FAIL:
|
case COMPOSE_SUBMIT_FAIL:
|
||||||
case COMPOSE_UPLOAD_CHANGE_FAIL:
|
case COMPOSE_UPLOAD_CHANGE_FAIL:
|
||||||
return state.set('is_submitting', false);
|
return state.set('is_submitting', false);
|
||||||
|
|
|
@ -134,6 +134,27 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.composer--textarea--icons {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 29px;
|
||||||
|
right: 5px;
|
||||||
|
bottom: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
& > .textarea_icon {
|
||||||
|
display: block;
|
||||||
|
margin: 2px 0 0 2px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
color: darken($ui-primary-color, 24%);
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
opacity: .8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.composer--textarea--suggestions {
|
.composer--textarea--suggestions {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
5
app/javascript/flavours/glitch/util/js_helpers.js
Normal file
5
app/javascript/flavours/glitch/util/js_helpers.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// This function returns the new value unless it is `null` or
|
||||||
|
// `undefined`, in which case it returns the old one.
|
||||||
|
export function overwrite (oldVal, newVal) {
|
||||||
|
return newVal === null || typeof newVal === 'undefined' ? oldVal : newVal;
|
||||||
|
}
|
Loading…
Reference in a new issue