Rich Text Editor (WIP) x4
This commit is contained in:
parent
1a506327db
commit
b93ecc7095
|
@ -17,6 +17,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
|||
|
||||
static propTypes = {
|
||||
value: PropTypes.string,
|
||||
valueMarkdown: PropTypes.string,
|
||||
suggestions: ImmutablePropTypes.list,
|
||||
disabled: PropTypes.bool,
|
||||
placeholder: PropTypes.string,
|
||||
|
@ -214,6 +215,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
|||
onKeyUp,
|
||||
autoFocus,
|
||||
children,
|
||||
valueMarkdown,
|
||||
className,
|
||||
id,
|
||||
maxLength,
|
||||
|
@ -280,6 +282,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
|||
placeholder={placeholder}
|
||||
autoFocus={autoFocus}
|
||||
value={value}
|
||||
valueMarkdown={valueMarkdown}
|
||||
onChange={this.onChange}
|
||||
onKeyDown={this.onKeyDown}
|
||||
onKeyUp={onKeyUp}
|
||||
|
|
|
@ -4,9 +4,11 @@ import {
|
|||
CompositeDecorator,
|
||||
RichUtils,
|
||||
convertToRaw,
|
||||
convertFromRaw,
|
||||
ContentState,
|
||||
} from 'draft-js'
|
||||
import draftToMarkdown from '../features/ui/util/draft-to-markdown'
|
||||
import markdownToDraft from '../features/ui/util/markdown-to-draft'
|
||||
import { urlRegex } from '../features/ui/util/url_regex'
|
||||
import classNames from 'classnames/bind'
|
||||
import RichTextEditorBar from './rich_text_editor_bar'
|
||||
|
@ -81,6 +83,7 @@ export default class Composer extends PureComponent {
|
|||
disabled: PropTypes.bool,
|
||||
placeholder: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
valueMarkdown: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
onKeyDown: PropTypes.func,
|
||||
onKeyUp: PropTypes.func,
|
||||
|
@ -95,6 +98,24 @@ export default class Composer extends PureComponent {
|
|||
plainText: this.props.value,
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.valueMarkdown) {
|
||||
const rawData = markdownToDraft(this.props.valueMarkdown)
|
||||
const contentState = convertFromRaw(rawData)
|
||||
const editorState = EditorState.createWithContent(contentState)
|
||||
this.setState({
|
||||
editorState,
|
||||
plainText: this.props.value,
|
||||
})
|
||||
} else if (this.props.value) {
|
||||
editorState = EditorState.push(this.state.editorState, ContentState.createFromText(this.props.value))
|
||||
this.setState({
|
||||
editorState,
|
||||
plainText: this.props.value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.state.plainText !== this.props.value) {
|
||||
let editorState
|
||||
|
|
|
@ -45,7 +45,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
}
|
||||
|
||||
|
||||
state = {
|
||||
composeFocused: false,
|
||||
}
|
||||
|
@ -55,6 +55,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
edit: PropTypes.bool,
|
||||
isMatch: PropTypes.bool,
|
||||
text: PropTypes.string.isRequired,
|
||||
markdown: PropTypes.string,
|
||||
suggestions: ImmutablePropTypes.list,
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
status: ImmutablePropTypes.map,
|
||||
|
@ -133,9 +134,9 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
|
||||
handleSubmit = () => {
|
||||
// if (this.props.text !== this.autosuggestTextarea.textbox.value) {
|
||||
// Something changed the text inside the textarea (e.g. browser extensions like Grammarly)
|
||||
// Update the state to match the current text
|
||||
// this.props.onChange(this.autosuggestTextarea.textbox.value);
|
||||
// Something changed the text inside the textarea (e.g. browser extensions like Grammarly)
|
||||
// Update the state to match the current text
|
||||
// this.props.onChange(this.autosuggestTextarea.textbox.value);
|
||||
// }
|
||||
|
||||
// Submit disabled:
|
||||
|
@ -244,6 +245,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
isSubmitting,
|
||||
selectedGifSrc,
|
||||
} = this.props
|
||||
|
||||
const disabled = isSubmitting
|
||||
const text = [this.props.spoilerText, countableText(this.props.text)].join('');
|
||||
const disabledButton = disabled || isUploading || isChangingUpload || length(text) > MAX_POST_CHARACTER_COUNT || (length(text) !== 0 && length(text.trim()) === 0 && !anyMedia);
|
||||
|
@ -325,9 +327,9 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
<div className={actionsContainerClasses}>
|
||||
<div className={[_s.default, _s.flexRow, _s.mrAuto].join(' ')}>
|
||||
|
||||
{ /* <EmojiPickerButton small={shouldCondense} isMatch={isMatch} /> */ }
|
||||
{ /* <EmojiPickerButton small={shouldCondense} isMatch={isMatch} /> */}
|
||||
|
||||
{ /* <UploadButton small={shouldCondense} /> */ }
|
||||
{ /* <UploadButton small={shouldCondense} /> */}
|
||||
|
||||
<div className={commentPublishBtnClasses}>
|
||||
<Button
|
||||
|
@ -400,6 +402,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
placeholder={intl.formatMessage((shouldCondense || !!reduxReplyToId) && isMatch ? messages.commentPlaceholder : messages.placeholder)}
|
||||
disabled={disabled}
|
||||
value={this.props.text}
|
||||
valueMarkdown={this.props.markdown}
|
||||
onChange={this.handleChange}
|
||||
suggestions={this.props.suggestions}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
|
|
|
@ -39,6 +39,8 @@ const mapStateToProps = (state, props) => {
|
|||
// console.log("isMatch:", isMatch, reduxReplyToId, replyToId, state.getIn(['compose', 'text']))
|
||||
// console.log("reduxReplyToId:", reduxReplyToId, isModalOpen, isStandalone)
|
||||
|
||||
const edit = state.getIn(['compose', 'id'])
|
||||
|
||||
if (!isMatch) {
|
||||
return {
|
||||
isMatch,
|
||||
|
@ -46,6 +48,7 @@ const mapStateToProps = (state, props) => {
|
|||
reduxReplyToId,
|
||||
edit: null,
|
||||
text: '',
|
||||
markdown: null,
|
||||
suggestions: ImmutableList(),
|
||||
spoiler: false,
|
||||
spoilerText: '',
|
||||
|
@ -65,13 +68,14 @@ const mapStateToProps = (state, props) => {
|
|||
selectedGifSrc: null,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
isMatch,
|
||||
isModalOpen,
|
||||
reduxReplyToId,
|
||||
edit: state.getIn(['compose', 'id']) !== null,
|
||||
text: state.getIn(['compose', 'text']),
|
||||
markdown: state.getIn(['compose', 'markdown']),
|
||||
suggestions: state.getIn(['compose', 'suggestions']),
|
||||
spoiler: state.getIn(['compose', 'spoiler']),
|
||||
spoilerText: state.getIn(['compose', 'spoiler_text']),
|
||||
|
|
|
@ -0,0 +1,312 @@
|
|||
// https://raw.githubusercontent.com/Rosey/markdown-draft-js/main/src/markdown-to-draft.js
|
||||
import { Remarkable } from 'remarkable';
|
||||
|
||||
const TRAILING_NEW_LINE = /\n$/;
|
||||
|
||||
// In DraftJS, string lengths are calculated differently than in JS itself (due
|
||||
// to surrogate pairs). Instead of importing the entire UnicodeUtils file from
|
||||
// FBJS, we use a simpler alternative, in the form of `Array.from`.
|
||||
//
|
||||
// Alternative: const { strlen } = require('fbjs/lib/UnicodeUtils');
|
||||
function strlen(str) {
|
||||
return Array.from(str).length;
|
||||
}
|
||||
|
||||
// Block level items, key is Remarkable's key for them, value returned is
|
||||
// A function that generates the raw draftjs key and block data.
|
||||
//
|
||||
// Why a function? Because in some cases (headers) we need additional information
|
||||
// before we can determine the exact key to return. And blocks may also return data
|
||||
const DefaultBlockTypes = {
|
||||
paragraph_open: function (item) {
|
||||
return {
|
||||
type: 'unstyled',
|
||||
text: '',
|
||||
entityRanges: [],
|
||||
inlineStyleRanges: []
|
||||
};
|
||||
},
|
||||
|
||||
blockquote_open: function (item) {
|
||||
return {
|
||||
type: 'blockquote',
|
||||
text: ''
|
||||
};
|
||||
},
|
||||
|
||||
ordered_list_item_open: function () {
|
||||
return {
|
||||
type: 'ordered-list-item',
|
||||
text: ''
|
||||
};
|
||||
},
|
||||
|
||||
unordered_list_item_open: function () {
|
||||
return {
|
||||
type: 'unordered-list-item',
|
||||
text: ''
|
||||
};
|
||||
},
|
||||
|
||||
fence: function (item) {
|
||||
return {
|
||||
type: 'code-block',
|
||||
data: {
|
||||
language: item.params || ''
|
||||
},
|
||||
text: (item.content || '').replace(TRAILING_NEW_LINE, ''), // remarkable seems to always append an erronious trailing newline to its codeblock content, so we need to trim it out.
|
||||
entityRanges: [],
|
||||
inlineStyleRanges: []
|
||||
};
|
||||
},
|
||||
|
||||
heading_open: function (item) {
|
||||
var type = 'header-' + ({
|
||||
1: 'one',
|
||||
2: 'two',
|
||||
3: 'three',
|
||||
4: 'four',
|
||||
5: 'five',
|
||||
6: 'six'
|
||||
})[item.hLevel];
|
||||
|
||||
return {
|
||||
type: type,
|
||||
text: ''
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Entity types. These are things like links or images that require
|
||||
// additional data and will be added to the `entityMap`
|
||||
// again. In this case, key is remarkable key, value is
|
||||
// meethod that returns the draftjs key + any data needed.
|
||||
const DefaultBlockEntities = {
|
||||
link_open: function (item) {
|
||||
return {
|
||||
type: 'LINK',
|
||||
mutability: 'MUTABLE',
|
||||
data: {
|
||||
url: item.href,
|
||||
href: item.href
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Entity styles. Simple Inline styles that aren't added to entityMap
|
||||
// key is remarkable key, value is draftjs raw key
|
||||
const DefaultBlockStyles = {
|
||||
strong_open: 'BOLD',
|
||||
em_open: 'ITALIC',
|
||||
code: 'CODE'
|
||||
};
|
||||
|
||||
// Key generator for entityMap items
|
||||
var idCounter = -1;
|
||||
function generateUniqueKey() {
|
||||
idCounter++;
|
||||
return idCounter;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle inline content in a block level item
|
||||
* parses for BlockEntities (links, images) and BlockStyles (em, strong)
|
||||
* doesn't handle block level items (blockquote, ordered list, etc)
|
||||
*
|
||||
* @param <Object> inlineItem - single object from remarkable data representation of markdown
|
||||
* @param <Object> BlockEntities - key-value object of mappable block entity items. Passed in as param so users can include their own custom stuff
|
||||
* @param <Object> BlockStyles - key-value object of mappable block styles items. Passed in as param so users can include their own custom stuff
|
||||
*
|
||||
* @return <Object>
|
||||
* content: Entire text content for the inline item,
|
||||
* blockEntities: New block eneities to be added to global block entity map
|
||||
* blockEntityRanges: block-level representation of block entities including key to access the block entity from the global map
|
||||
* blockStyleRanges: block-level representation of styles (eg strong, em)
|
||||
*/
|
||||
function parseInline(inlineItem, BlockEntities, BlockStyles) {
|
||||
var content = '', blockEntities = {}, blockEntityRanges = [], blockInlineStyleRanges = [];
|
||||
inlineItem.children.forEach(function (child) {
|
||||
if (child.type === 'text') {
|
||||
content += child.content;
|
||||
} else if (child.type === 'softbreak') {
|
||||
content += '\n';
|
||||
} else if (child.type === 'hardbreak') {
|
||||
content += '\n';
|
||||
} else if (BlockStyles[child.type]) {
|
||||
var key = generateUniqueKey();
|
||||
var styleBlock = {
|
||||
offset: strlen(content) || 0,
|
||||
length: 0,
|
||||
style: BlockStyles[child.type]
|
||||
};
|
||||
|
||||
// Edge case hack because code items don't have inline content or open/close, unlike everything else
|
||||
if (child.type === 'code') {
|
||||
styleBlock.length = strlen(child.content);
|
||||
content += child.content;
|
||||
}
|
||||
|
||||
blockInlineStyleRanges.push(styleBlock);
|
||||
} else if (BlockEntities[child.type]) {
|
||||
var key = generateUniqueKey();
|
||||
|
||||
blockEntities[key] = BlockEntities[child.type](child);
|
||||
|
||||
blockEntityRanges.push({
|
||||
offset: strlen(content) || 0,
|
||||
length: 0,
|
||||
key: key
|
||||
});
|
||||
} else if (child.type.indexOf('_close') !== -1 && BlockEntities[child.type.replace('_close', '_open')]) {
|
||||
blockEntityRanges[blockEntityRanges.length - 1].length = strlen(content) - blockEntityRanges[blockEntityRanges.length - 1].offset;
|
||||
} else if (child.type.indexOf('_close') !== -1 && BlockStyles[child.type.replace('_close', '_open')]) {
|
||||
var type = BlockStyles[child.type.replace('_close', '_open')]
|
||||
blockInlineStyleRanges = blockInlineStyleRanges
|
||||
.map(style => {
|
||||
if (style.length === 0 && style.style === type) {
|
||||
style.length = strlen(content) - style.offset;
|
||||
}
|
||||
return style;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {content, blockEntities, blockEntityRanges, blockInlineStyleRanges};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert markdown into raw draftjs object
|
||||
*
|
||||
* @param {String} markdown - markdown to convert into raw draftjs object
|
||||
* @param {Object} options - optional additional data, see readme for what options can be passed in.
|
||||
*
|
||||
* @return {Object} rawDraftObject
|
||||
**/
|
||||
function markdownToDraft(string, options = {}) {
|
||||
const remarkablePreset = options.remarkablePreset || options.remarkableOptions;
|
||||
const remarkableOptions = typeof options.remarkableOptions === 'object' ? options.remarkableOptions : null;
|
||||
const md = new Remarkable(remarkablePreset, remarkableOptions);
|
||||
|
||||
// if tables are not explicitly enabled, disable them by default
|
||||
if (
|
||||
!remarkableOptions ||
|
||||
!remarkableOptions.enable ||
|
||||
!remarkableOptions.enable.block ||
|
||||
remarkableOptions.enable.block !== 'table' ||
|
||||
remarkableOptions.enable.block.includes('table') === false
|
||||
) {
|
||||
md.block.ruler.disable('table');
|
||||
}
|
||||
|
||||
// disable the specified rules
|
||||
if (remarkableOptions && remarkableOptions.disable) {
|
||||
for (let [key, value] of Object.entries(remarkableOptions.disable)) {
|
||||
md[key].ruler.disable(value);
|
||||
}
|
||||
}
|
||||
|
||||
// enable the specified rules
|
||||
if (remarkableOptions && remarkableOptions.enable) {
|
||||
for (let [key, value] of Object.entries(remarkableOptions.enable)) {
|
||||
md[key].ruler.enable(value);
|
||||
}
|
||||
}
|
||||
|
||||
// If users want to define custom remarkable plugins for custom markdown, they can be added here
|
||||
if (options.remarkablePlugins) {
|
||||
options.remarkablePlugins.forEach(function (plugin) {
|
||||
md.use(plugin, {});
|
||||
});
|
||||
}
|
||||
|
||||
var blocks = []; // blocks will be returned as part of the final draftjs raw object
|
||||
var entityMap = {}; // entitymap will be returned as part of the final draftjs raw object
|
||||
var parsedData = md.parse(string, {}); // remarkable js takes markdown and makes it an array of style objects for us to easily parse
|
||||
var currentListType = null; // Because of how remarkable's data is formatted, we need to cache what kind of list we're currently dealing with
|
||||
var previousBlockEndingLine = 0;
|
||||
|
||||
// Allow user to define custom BlockTypes and Entities if they so wish
|
||||
const BlockTypes = Object.assign({}, DefaultBlockTypes, options.blockTypes || {});
|
||||
const BlockEntities = Object.assign({}, DefaultBlockEntities, options.blockEntities || {});
|
||||
const BlockStyles = Object.assign({}, DefaultBlockStyles, options.blockStyles || {});
|
||||
|
||||
parsedData.forEach(function (item) {
|
||||
// Because of how remarkable's data is formatted, we need to cache what kind of list we're currently dealing with
|
||||
if (item.type === 'bullet_list_open') {
|
||||
currentListType = 'unordered_list_item_open';
|
||||
} else if (item.type === 'ordered_list_open') {
|
||||
currentListType = 'ordered_list_item_open';
|
||||
}
|
||||
|
||||
var itemType = item.type;
|
||||
if (itemType === 'list_item_open') {
|
||||
itemType = currentListType;
|
||||
}
|
||||
|
||||
if (itemType === 'inline') {
|
||||
// Parse inline content and apply it to the most recently created block level item,
|
||||
// which is where the inline content will belong.
|
||||
var {content, blockEntities, blockEntityRanges, blockInlineStyleRanges} = parseInline(item, BlockEntities, BlockStyles);
|
||||
var blockToModify = blocks[blocks.length - 1];
|
||||
blockToModify.text = content;
|
||||
blockToModify.inlineStyleRanges = blockInlineStyleRanges;
|
||||
blockToModify.entityRanges = blockEntityRanges;
|
||||
|
||||
// The entity map is a master object separate from the block so just add any entities created for this block to the master object
|
||||
Object.assign(entityMap, blockEntities);
|
||||
} else if ((itemType.indexOf('_open') !== -1 || itemType === 'fence' || itemType === 'hr') && BlockTypes[itemType]) {
|
||||
var depth = 0;
|
||||
var block;
|
||||
|
||||
if (item.level > 0) {
|
||||
depth = Math.floor(item.level / 2);
|
||||
}
|
||||
|
||||
// Draftjs only supports 1 level of blocks, hence the item.level === 0 check
|
||||
// List items will always be at least `level==1` though so we need a separate check for that
|
||||
// If there’s nested block level items deeper than that, we need to make sure we capture this by cloning the topmost block
|
||||
// otherwise we’ll accidentally overwrite its text. (eg if there's a blockquote with 3 nested paragraphs with inline text, without this check, only the last paragraph would be reflected)
|
||||
if (item.level === 0 || item.type === 'list_item_open') {
|
||||
block = Object.assign({
|
||||
depth: depth
|
||||
}, BlockTypes[itemType](item));
|
||||
} else if (item.level > 0 && blocks[blocks.length - 1].text) {
|
||||
block = Object.assign({}, blocks[blocks.length - 1]);
|
||||
}
|
||||
|
||||
if (block && options.preserveNewlines) {
|
||||
// Re: previousBlockEndingLine.... omg.
|
||||
// So remarkable strips out empty newlines and doesn't make any entities to parse to restore them
|
||||
// the only solution I could find is that there's a 2-value array on each block item called "lines" which is the start and end line of the block element.
|
||||
// by keeping track of the PREVIOUS block element ending line and the NEXT block element starting line, we can find the difference between the new lines and insert
|
||||
// an appropriate number of extra paragraphs to re-create those newlines in draftjs.
|
||||
// This is probably my least favourite thing in this file, but not sure what could be better.
|
||||
var totalEmptyParagraphsToCreate = item.lines[0] - previousBlockEndingLine;
|
||||
for (var i = 0; i < totalEmptyParagraphsToCreate; i++) {
|
||||
blocks.push(DefaultBlockTypes.paragraph_open());
|
||||
}
|
||||
}
|
||||
|
||||
if (block) {
|
||||
previousBlockEndingLine = item.lines[1];
|
||||
blocks.push(block);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// EditorState.createWithContent will error if there's no blocks defined
|
||||
// Remarkable returns an empty array though. So we have to generate a 'fake'
|
||||
// empty block in this case. 😑
|
||||
if (!blocks.length) {
|
||||
blocks.push(DefaultBlockTypes.paragraph_open());
|
||||
}
|
||||
|
||||
return {
|
||||
entityMap,
|
||||
blocks
|
||||
};
|
||||
}
|
||||
|
||||
export default markdownToDraft;
|
|
@ -366,9 +366,11 @@ export default function compose(state = initialState, action) {
|
|||
return item;
|
||||
}));
|
||||
case STATUS_EDIT:
|
||||
const hasMarkdown = !!action.status.get('plain_markdown')
|
||||
return state.withMutations(map => {
|
||||
map.set('id', action.status.get('id'));
|
||||
map.set('text', unescapeHTML(expandMentions(action.status)));
|
||||
map.set('markdown', action.status.get('plain_markdown'));
|
||||
map.set('in_reply_to', action.status.get('in_reply_to_id'));
|
||||
map.set('quote_of_id', action.status.get('quote_of_id'));
|
||||
map.set('privacy', action.status.get('visibility'));
|
||||
|
@ -376,7 +378,7 @@ export default function compose(state = initialState, action) {
|
|||
map.set('focusDate', new Date());
|
||||
map.set('caretPosition', null);
|
||||
map.set('idempotencyKey', uuid());
|
||||
map.set('rte_controls_visible', false);
|
||||
map.set('rte_controls_visible', hasMarkdown);
|
||||
|
||||
if (action.status.get('spoiler_text').length > 0) {
|
||||
map.set('spoiler', true);
|
||||
|
|
|
@ -13,6 +13,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||
|
||||
attribute :content, unless: :source_requested?
|
||||
attribute :rich_content, unless: :source_requested?
|
||||
attribute :plain_markdown, unless: :source_requested?
|
||||
attribute :text, if: :source_requested?
|
||||
|
||||
belongs_to :reblog, serializer: REST::StatusSerializer
|
||||
|
@ -76,6 +77,10 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||
Formatter.instance.format(object, use_markdown: true).strip
|
||||
end
|
||||
|
||||
def plain_markdown
|
||||
object.markdown
|
||||
end
|
||||
|
||||
def url
|
||||
TagManager.instance.url_for(object)
|
||||
end
|
||||
|
|
|
@ -7,6 +7,7 @@ class EditStatusService < BaseService
|
|||
# @param [Status] status Status being edited
|
||||
# @param [Hash] options
|
||||
# @option [String] :text Message
|
||||
# @option [String] :markdown Optional message in markdown
|
||||
# @option [Boolean] :sensitive
|
||||
# @option [String] :visibility
|
||||
# @option [String] :spoiler_text
|
||||
|
@ -20,6 +21,7 @@ class EditStatusService < BaseService
|
|||
@account = status.account
|
||||
@options = options
|
||||
@text = @options[:text] || ''
|
||||
@markdown = @options[:markdown] if @account.is_pro
|
||||
|
||||
return idempotency_duplicate if idempotency_given? && idempotency_duplicate?
|
||||
|
||||
|
@ -119,6 +121,7 @@ class EditStatusService < BaseService
|
|||
{
|
||||
revised_at: Time.now,
|
||||
text: @text,
|
||||
markdown: @markdown,
|
||||
media_attachments: @media || [],
|
||||
sensitive: (@options[:sensitive].nil? ? @account.user&.setting_default_sensitive : @options[:sensitive]) || @options[:spoiler_text].present?,
|
||||
spoiler_text: @options[:spoiler_text] || '',
|
||||
|
|
|
@ -26,7 +26,7 @@ class PostStatusService < BaseService
|
|||
@account = account
|
||||
@options = options
|
||||
@text = @options[:text] || ''
|
||||
@markdown = @options[:markdown]
|
||||
@markdown = @options[:markdown] if @account.is_pro
|
||||
@in_reply_to = @options[:thread]
|
||||
|
||||
return idempotency_duplicate if idempotency_given? && idempotency_duplicate?
|
||||
|
|
|
@ -152,6 +152,7 @@
|
|||
"redux": "^4.0.1",
|
||||
"redux-immutable": "^4.0.0",
|
||||
"redux-thunk": "^2.2.0",
|
||||
"remarkable": "^2.0.1",
|
||||
"requestidlecallback": "^0.3.0",
|
||||
"reselect": "^4.0.0",
|
||||
"rimraf": "^2.6.3",
|
||||
|
|
22
yarn.lock
22
yarn.lock
|
@ -1472,7 +1472,7 @@ are-we-there-yet@~1.1.2:
|
|||
delegates "^1.0.0"
|
||||
readable-stream "^2.0.6"
|
||||
|
||||
argparse@^1.0.7:
|
||||
argparse@^1.0.10, argparse@^1.0.7:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
|
||||
integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
|
||||
|
@ -1640,6 +1640,13 @@ atob@^2.1.2:
|
|||
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
||||
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
||||
|
||||
autolinker@^3.11.0:
|
||||
version "3.14.1"
|
||||
resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-3.14.1.tgz#6ae4b812b6eaf42d4d68138b9e67757cbf2bc1e4"
|
||||
integrity sha512-yvsRHIaY51EYDml6MGlbqyJGfl4n7zezGYf+R7gvM8c5LNpRGc4SISkvgAswSS8SWxk/OrGCylKV9mJyVstz7w==
|
||||
dependencies:
|
||||
tslib "^1.9.3"
|
||||
|
||||
autoprefixer@^9.5.1:
|
||||
version "9.7.6"
|
||||
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.6.tgz#63ac5bbc0ce7934e6997207d5bb00d68fa8293a4"
|
||||
|
@ -8211,6 +8218,14 @@ regjsparser@^0.6.4:
|
|||
dependencies:
|
||||
jsesc "~0.5.0"
|
||||
|
||||
remarkable@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/remarkable/-/remarkable-2.0.1.tgz#280ae6627384dfb13d98ee3995627ca550a12f31"
|
||||
integrity sha512-YJyMcOH5lrR+kZdmB0aJJ4+93bEojRZ1HGDn9Eagu6ibg7aVZhc3OWbbShRid+Q5eAfsEqWxpe+g5W5nYNfNiA==
|
||||
dependencies:
|
||||
argparse "^1.0.10"
|
||||
autolinker "^3.11.0"
|
||||
|
||||
remove-trailing-separator@^1.0.1:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
|
||||
|
@ -9425,6 +9440,11 @@ tslib@^1.9.0:
|
|||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35"
|
||||
integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==
|
||||
|
||||
tslib@^1.9.3:
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
|
||||
integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
|
||||
|
||||
tty-browserify@0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
|
||||
|
|
Loading…
Reference in New Issue