diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js index 5c8b54d2acfdaf8fcfbfc2cf65ee7b9093428281..e50dbba79d67faaedd1d9bee95f0ce5b08ab9fe7 100644 --- a/app/javascript/flavours/glitch/actions/compose.js +++ b/app/javascript/flavours/glitch/actions/compose.js @@ -42,6 +42,7 @@ export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE'; export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE'; export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE'; export const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE'; +export const COMPOSE_CONTENT_TYPE_CHANGE = 'COMPOSE_CONTENT_TYPE_CHANGE'; export const COMPOSE_EMOJI_INSERT = 'COMPOSE_EMOJI_INSERT'; @@ -125,6 +126,7 @@ export function submitCompose(routerHistory) { } api(getState).post('/api/v1/statuses', { status, + content_type: getState().getIn(['compose', 'content_type']), in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null), media_ids: media.map(item => item.get('id')), sensitive: getState().getIn(['compose', 'sensitive']) || (spoilerText.length > 0 && media.size !== 0), @@ -469,6 +471,13 @@ export function changeComposeVisibility(value) { }; }; +export function changeComposeContentType(value) { + return { + type: COMPOSE_CONTENT_TYPE_CHANGE, + value, + }; +}; + export function insertEmojiCompose(position, emoji) { return { type: COMPOSE_EMOJI_INSERT, diff --git a/app/javascript/flavours/glitch/features/composer/index.js b/app/javascript/flavours/glitch/features/composer/index.js index ec0e405a47a849b4ae1ef5339a7a7b967d587cd9..d81ef385c2a659e1267e77b94fc1fddb64c5d136 100644 --- a/app/javascript/flavours/glitch/features/composer/index.js +++ b/app/javascript/flavours/glitch/features/composer/index.js @@ -15,6 +15,7 @@ import { changeComposeSpoilerText, changeComposeSpoilerness, changeComposeVisibility, + changeComposeContentType, changeUploadCompose, clearComposeSuggestions, fetchComposeSuggestions, @@ -88,6 +89,7 @@ function mapStateToProps (state) { media: state.getIn(['compose', 'media_attachments']), preselectDate: state.getIn(['compose', 'preselectDate']), privacy: state.getIn(['compose', 'privacy']), + contentType: state.getIn(['compose', 'content_type']), progress: state.getIn(['compose', 'progress']), inReplyTo: inReplyTo ? state.getIn(['statuses', inReplyTo]) : null, replyAccount: inReplyTo ? state.getIn(['statuses', inReplyTo, 'account']) : null, @@ -116,6 +118,9 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ onChangeAdvancedOption(option, value) { dispatch(changeComposeAdvancedOption(option, value)); }, + onChangeContentType(value) { + dispatch(changeComposeContentType(value)); + }, onChangeDescription(id, description) { dispatch(changeUploadCompose(id, { description })); }, @@ -388,6 +393,7 @@ class Composer extends React.Component { advancedOptions, amUnlocked, anyMedia, + contentType, intl, isSubmitting, isChangingUpload, @@ -396,6 +402,7 @@ class Composer extends React.Component { media, onCancelReply, onChangeAdvancedOption, + onChangeContentType, onChangeDescription, onChangeSensitivity, onChangeSpoilerness, @@ -478,6 +485,7 @@ class Composer extends React.Component { <ComposerOptions acceptContentTypes={acceptContentTypes} advancedOptions={advancedOptions} + contentType={contentType} disabled={isSubmitting} full={media ? media.size >= 4 || media.some( item => item.get('type') === 'video' @@ -485,6 +493,7 @@ class Composer extends React.Component { hasMedia={media && !!media.size} intl={intl} onChangeAdvancedOption={onChangeAdvancedOption} + onChangeContentType={onChangeContentType} onChangeSensitivity={onChangeSensitivity} onChangeVisibility={onChangeVisibility} onDoodleOpen={onOpenDoodleModal} @@ -529,6 +538,7 @@ Composer.propTypes = { media: ImmutablePropTypes.list, preselectDate: PropTypes.instanceOf(Date), privacy: PropTypes.string, + contentType: PropTypes.string, progress: PropTypes.number, inReplyTo: ImmutablePropTypes.map, resetFileKey: PropTypes.number, @@ -548,6 +558,7 @@ Composer.propTypes = { // Dispatch props. onCancelReply: PropTypes.func, onChangeAdvancedOption: PropTypes.func, + onChangeContentType: PropTypes.func, onChangeDescription: PropTypes.func, onChangeSensitivity: PropTypes.func, onChangeSpoilerText: PropTypes.func, diff --git a/app/javascript/flavours/glitch/features/composer/options/index.js b/app/javascript/flavours/glitch/features/composer/options/index.js index 9fe3abc03e7ec86a2f2e189ac79f083b6987c00b..8a11b12dc5d1b56368a12c71a39f4e2fb3a6ae2b 100644 --- a/app/javascript/flavours/glitch/features/composer/options/index.js +++ b/app/javascript/flavours/glitch/features/composer/options/index.js @@ -34,6 +34,10 @@ const messages = defineMessages({ defaultMessage: 'Adjust status privacy', id: 'privacy.change', }, + content_type: { + defaultMessage: 'Content type', + id: 'content-type.change', + }, direct_long: { defaultMessage: 'Post to mentioned users only', id: 'privacy.direct.long', @@ -46,6 +50,10 @@ const messages = defineMessages({ defaultMessage: 'Draw something', id: 'compose.attach.doodle', }, + html: { + defaultMessage: 'HTML', + id: 'compose.content-type.html', + }, local_only_long: { defaultMessage: 'Do not post to other instances', id: 'advanced_options.local-only.long', @@ -54,6 +62,14 @@ const messages = defineMessages({ defaultMessage: 'Local-only', id: 'advanced_options.local-only.short', }, + markdown: { + defaultMessage: 'Markdown', + id: 'compose.content-type.markdown', + }, + plain: { + defaultMessage: 'Plain text', + id: 'compose.content-type.plain', + }, private_long: { defaultMessage: 'Post to followers only', id: 'privacy.private.long', @@ -159,6 +175,7 @@ export default class ComposerOptions extends React.PureComponent { const { acceptContentTypes, advancedOptions, + contentType, disabled, full, hasMedia, @@ -166,6 +183,7 @@ export default class ComposerOptions extends React.PureComponent { onChangeAdvancedOption, onChangeSensitivity, onChangeVisibility, + onChangeContentType, onModalClose, onModalOpen, onToggleSpoiler, @@ -204,6 +222,24 @@ export default class ComposerOptions extends React.PureComponent { }, }; + const contentTypeItems = { + plain: { + icon: 'file', + name: 'text/plain', + text: <FormattedMessage {...messages.plain} />, + }, + html: { + icon: 'file-text', + name: 'text/html', + text: <FormattedMessage {...messages.html} />, + }, + markdown: { + icon: 'file-text', + name: 'text/markdown', + text: <FormattedMessage {...messages.markdown} />, + }, + }; + // The result. return ( <div className='composer--options'> @@ -285,6 +321,19 @@ export default class ComposerOptions extends React.PureComponent { title={intl.formatMessage(messages.change_privacy)} value={privacy} /> + <Dropdown + icon="code" + items={[ + contentTypeItems.plain, + contentTypeItems.html, + contentTypeItems.markdown, + ]} + onChange={onChangeContentType} + onModalClose={onModalClose} + onModalOpen={onModalOpen} + title={intl.formatMessage(messages.content_type)} + value={contentType} + /> {onToggleSpoiler && ( <TextIconButton active={spoiler} @@ -327,6 +376,7 @@ export default class ComposerOptions extends React.PureComponent { ComposerOptions.propTypes = { acceptContentTypes: PropTypes.string, advancedOptions: ImmutablePropTypes.map, + contentType: PropTypes.string, disabled: PropTypes.bool, full: PropTypes.bool, hasMedia: PropTypes.bool, @@ -334,6 +384,7 @@ ComposerOptions.propTypes = { onChangeAdvancedOption: PropTypes.func, onChangeSensitivity: PropTypes.func, onChangeVisibility: PropTypes.func, + onChangeContentType: PropTypes.func, onDoodleOpen: PropTypes.func, onModalClose: PropTypes.func, onModalOpen: PropTypes.func, diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js index 7281cbd61e44054c66459fdca4a3a2eab0febd65..cfa6ac47aa773707897aa3cf5afad489c0d2e979 100644 --- a/app/javascript/flavours/glitch/reducers/compose.js +++ b/app/javascript/flavours/glitch/reducers/compose.js @@ -25,6 +25,7 @@ import { COMPOSE_SPOILERNESS_CHANGE, COMPOSE_SPOILER_TEXT_CHANGE, COMPOSE_VISIBILITY_CHANGE, + COMPOSE_CONTENT_TYPE_CHANGE, COMPOSE_EMOJI_INSERT, COMPOSE_UPLOAD_CHANGE_REQUEST, COMPOSE_UPLOAD_CHANGE_SUCCESS, @@ -60,6 +61,7 @@ const initialState = ImmutableMap({ spoiler: false, spoiler_text: '', privacy: null, + content_type: 'text/plain', text: '', focusDate: null, caretPosition: null, @@ -294,6 +296,10 @@ export default function compose(state = initialState, action) { return state .set('privacy', action.value) .set('idempotencyKey', uuid()); + case COMPOSE_CONTENT_TYPE_CHANGE: + return state + .set('content_type', action.value) + .set('idempotencyKey', uuid()); case COMPOSE_CHANGE: return state .set('text', action.text)