diff --git a/Gemfile b/Gemfile
index ec339981..387d5508 100644
--- a/Gemfile
+++ b/Gemfile
@@ -94,6 +94,8 @@ gem 'json-ld', '~> 3.0'
gem 'json-ld-preloaded', '~> 3.0'
gem 'rdf-normalize', '~> 0.3'
+gem 'redcarpet', '~> 3.4'
+
group :development, :test do
gem 'fabrication', '~> 2.20'
gem 'fuubar', '~> 2.3'
diff --git a/Gemfile.lock b/Gemfile.lock
index e64ff643..5d095708 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -479,6 +479,7 @@ GEM
link_header (~> 0.0, >= 0.0.8)
rdf-normalize (0.3.3)
rdf (>= 2.2, < 4.0)
+ redcarpet (3.4.0)
redis (4.1.2)
redis-actionpack (5.0.2)
actionpack (>= 4.0, < 6)
@@ -740,6 +741,7 @@ DEPENDENCIES
rails-i18n (~> 5.1)
rails-settings-cached (~> 0.6)
rdf-normalize (~> 0.3)
+ redcarpet (~> 3.4)
redis (~> 4.1)
redis-namespace (~> 1.5)
redis-rails (~> 5.0)
diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb
index e800bff4..cdee103e 100644
--- a/app/controllers/api/v1/statuses_controller.rb
+++ b/app/controllers/api/v1/statuses_controller.rb
@@ -52,9 +52,10 @@ class Api::V1::StatusesController < Api::BaseController
end
def create
+ markdown = status_params[:markdown] unless status_params[:markdown] === status_params[:status]
@status = PostStatusService.new.call(current_user.account,
text: status_params[:status],
- markdown: status_params[:markdown],
+ markdown: markdown,
thread: status_params[:in_reply_to_id].blank? ? nil : Status.find(status_params[:in_reply_to_id]),
media_ids: status_params[:media_ids],
sensitive: status_params[:sensitive],
@@ -72,9 +73,10 @@ class Api::V1::StatusesController < Api::BaseController
def update
authorize @status, :update?
-
+ markdown = status_params[:markdown] unless status_params[:markdown] === status_params[:status]
@status = EditStatusService.new.call(@status,
text: status_params[:status],
+ markdown: markdown,
media_ids: status_params[:media_ids],
sensitive: status_params[:sensitive],
spoiler_text: status_params[:spoiler_text],
diff --git a/app/javascript/gabsocial/actions/compose.js b/app/javascript/gabsocial/actions/compose.js
index f476da49..9745469f 100644
--- a/app/javascript/gabsocial/actions/compose.js
+++ b/app/javascript/gabsocial/actions/compose.js
@@ -263,12 +263,12 @@ export function submitCompose(group, replyToId = null, router, isStandalone) {
if (!me) return;
let status = getState().getIn(['compose', 'text'], '');
- const markdown = getState().getIn(['compose', 'markdown'], '');
+ let markdown = getState().getIn(['compose', 'markdown'], '');
const media = getState().getIn(['compose', 'media_attachments']);
// : hack :
//Prepend http:// to urls in status that don't have protocol
- status = status.replace(urlRegex, (match, a, b, c) =>{
+ status = `${status}`.replace(urlRegex, (match, a, b, c) =>{
const hasProtocol = match.startsWith('https://') || match.startsWith('http://')
//Make sure not a remote mention like @someone@somewhere.com
if (!hasProtocol) {
@@ -276,15 +276,20 @@ export function submitCompose(group, replyToId = null, router, isStandalone) {
}
return hasProtocol ? match : `http://${match}`
})
- // markdown = statusMarkdown.replace(urlRegex, (match) =>{
- // const hasProtocol = match.startsWith('https://') || match.startsWith('http://')
- // return hasProtocol ? match : `http://${match}`
- // })
+ markdown = !!markdown ? markdown.replace(urlRegex, (match) =>{
+ const hasProtocol = match.startsWith('https://') || match.startsWith('http://')
+ if (!hasProtocol) {
+ if (status.indexOf(`@${match}`) > -1) return match
+ }
+ return hasProtocol ? match : `http://${match}`
+ }) : undefined
+
+ if (status === markdown) {
+ markdown = undefined
+ }
const inReplyToId = getState().getIn(['compose', 'in_reply_to'], null) || replyToId
- // console.log("markdown:", markdown)
-
dispatch(submitComposeRequest());
dispatch(closeModal());
@@ -706,9 +711,9 @@ export function changeScheduledAt(date) {
};
};
-export function changeRichTextEditorControlsVisibility(status) {
+export function changeRichTextEditorControlsVisibility(open) {
return {
type: COMPOSE_RICH_TEXT_EDITOR_CONTROLS_VISIBILITY,
- status: status,
+ open,
}
}
\ No newline at end of file
diff --git a/app/javascript/gabsocial/actions/importer/normalizer.js b/app/javascript/gabsocial/actions/importer/normalizer.js
index eaa4ca17..1de5700f 100644
--- a/app/javascript/gabsocial/actions/importer/normalizer.js
+++ b/app/javascript/gabsocial/actions/importer/normalizer.js
@@ -1,9 +1,9 @@
-import escapeTextContentForBrowser from 'escape-html';
-import emojify from '../../components/emoji/emoji';
-import { unescapeHTML } from '../../utils/html';
-import { expandSpoilers } from '../../initial_state';
+import escapeTextContentForBrowser from 'escape-html'
+import emojify from '../../components/emoji/emoji'
+import { unescapeHTML } from '../../utils/html'
+import { expandSpoilers } from '../../initial_state'
-const domParser = new DOMParser();
+const domParser = new DOMParser()
const makeEmojiMap = record => record.emojis.reduce((obj, emoji) => {
obj[`:${emoji.shortcode}:`] = emoji;
@@ -86,3 +86,21 @@ export function normalizePoll(poll) {
return normalPoll;
}
+
+
+//
attention!
+// #test @bob #nice https://bob.com http://techcrunch.com strike it
+// https://twitter.com
+// @bobitalic
+// jonincode
+
+// # attention!
+// #test @bob #nice https://bob.com http://techcrunch.com ~~strike it~~
+
+// ~~https://twitter.com~~
+
+// _@bobitalic_
+
+// ```
+// jonincode
+// ```
\ No newline at end of file
diff --git a/app/javascript/gabsocial/components/autosuggest_textbox.js b/app/javascript/gabsocial/components/autosuggest_textbox.js
index 0576c7d1..f4aff23f 100644
--- a/app/javascript/gabsocial/components/autosuggest_textbox.js
+++ b/app/javascript/gabsocial/components/autosuggest_textbox.js
@@ -15,6 +15,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
static propTypes = {
value: PropTypes.string,
+ valueMarkdown: PropTypes.string,
suggestions: ImmutablePropTypes.list,
disabled: PropTypes.bool,
placeholder: PropTypes.string,
@@ -45,11 +46,12 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
tokenStart: 0,
}
- onChange = (e, value, selectionStart, markdown) => {
+ onChange = (e, value, markdown, selectionStart) => {
if (!isObject(e)) {
e = {
target: {
value,
+ markdown,
selectionStart,
},
}
@@ -65,7 +67,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
this.props.onSuggestionsClearRequested();
}
- this.props.onChange(e, markdown);
+ this.props.onChange(e);
}
onKeyDown = (e) => {
@@ -191,7 +193,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
}
setTextbox = (c) => {
- this.textbox = c;
+ this.textbox = c
}
render() {
@@ -203,6 +205,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
placeholder,
onKeyUp,
children,
+ valueMarkdown,
id,
} = this.props
@@ -246,29 +249,14 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
-
- {/**/}
+ />
{children}
diff --git a/app/javascript/gabsocial/components/composer.js b/app/javascript/gabsocial/components/composer.js
index 88e1d1d9..05d5946c 100644
--- a/app/javascript/gabsocial/components/composer.js
+++ b/app/javascript/gabsocial/components/composer.js
@@ -7,8 +7,8 @@ import {
convertFromRaw,
ContentState,
} from 'draft-js'
-import { draftToMarkdown } from 'markdown-draft-js'
-// import draftToMarkdown from 'draftjs-to-markdown'
+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'
@@ -30,11 +30,11 @@ function handleStrategy(contentBlock, callback, contentState) {
findWithRegex(HANDLE_REGEX, contentBlock, callback)
}
-function hashtagStrategy (contentBlock, callback, contentState) {
+function hashtagStrategy(contentBlock, callback, contentState) {
findWithRegex(HASHTAG_REGEX, contentBlock, callback)
}
-function urlStrategy (contentBlock, callback, contentState) {
+function urlStrategy(contentBlock, callback, contentState) {
findWithRegex(urlRegex, contentBlock, callback)
}
@@ -73,24 +73,17 @@ const compositeDecorator = new CompositeDecorator([
}
])
-const HANDLE_REGEX = /\@[\w]+/g;
-const HASHTAG_REGEX = /\#[\w\u0590-\u05ff]+/g;
+const HANDLE_REGEX = /\@[\w]+/g
+const HASHTAG_REGEX = /\#[\w\u0590-\u05ff]+/g
-
-const mapDispatchToProps = (dispatch) => ({
-
-})
-
-export default
-@connect(null, mapDispatchToProps)
-class Composer extends PureComponent {
+export default class Composer extends PureComponent {
static propTypes = {
inputRef: PropTypes.func,
disabled: PropTypes.bool,
placeholder: PropTypes.string,
- autoFocus: PropTypes.bool,
value: PropTypes.string,
+ valueMarkdown: PropTypes.string,
onChange: PropTypes.func,
onKeyDown: PropTypes.func,
onKeyUp: PropTypes.func,
@@ -101,59 +94,69 @@ class Composer extends PureComponent {
}
state = {
- markdownText: '',
- plainText: '',
editorState: EditorState.createEmpty(compositeDecorator),
- }
-
- static getDerivedStateFromProps(nextProps, prevState) {
- // if (!nextProps.isHidden && nextProps.isIntersecting && !prevState.fetched) {
- // return {
- // fetched: true
- // }
- // }
-
- return null
+ plainText: this.props.value,
}
- componentDidUpdate (prevProps) {
- // console.log("this.props.value:", this.props.value)
- if (prevProps.value !== this.props.value) {
- // const editorState = EditorState.push(this.state.editorState, ContentState.createFromText(this.props.value));
- // this.setState({ editorState })
+ 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,
+ })
}
}
- // EditorState.createWithContent(ContentState.createFromText('Hello'))
-
- onChange = (editorState) => {
- this.setState({ editorState })
- const content = this.state.editorState.getCurrentContent();
- const text = content.getPlainText('\u0001')
-
- // const selectionState = editorState.getSelection()
- // const selectionStart = selectionState.getStartOffset()
-
- // const rawObject = convertToRaw(content);
- // const markdownString = draftToMarkdown(rawObject);
- // const markdownString = draftToMarkdown(rawObject, {
- // trigger: '#',
- // separator: ' ',
- // });
-
- // console.log("text:", text, this.props.value)
-
- this.props.onChange(null, text, selectionStart, markdownString)
+ componentDidUpdate() {
+ if (this.state.plainText !== this.props.value) {
+ let editorState
+ if (!this.props.value) {
+ editorState = EditorState.createEmpty(compositeDecorator)
+ } else {
+ editorState = EditorState.push(this.state.editorState, ContentState.createFromText(this.props.value))
+ }
+ this.setState({
+ editorState,
+ plainText: this.props.value,
+ })
+ }
}
- // **bold**
- // *italic*
- // __underline__
- // ~strikethrough~
- // # title
- // > quote
- // `code`
- // ```code```
+ onChange = (editorState) => {
+ const content = editorState.getCurrentContent()
+ const plainText = content.getPlainText('\u0001')
+
+ this.setState({ editorState, plainText })
+
+ const selectionState = editorState.getSelection()
+ const selectionStart = selectionState.getStartOffset()
+
+ const rawObject = convertToRaw(content)
+ const markdownString = draftToMarkdown(rawObject, {
+ escapeMarkdownCharacters: false,
+ preserveNewlines: false,
+ remarkablePreset: 'commonmark',
+ remarkableOptions: {
+ disable: {
+ block: ['table']
+ },
+ enable: {
+ inline: ['del', 'ins'],
+ }
+ }
+ })
+
+ this.props.onChange(null, plainText, markdownString, selectionStart)
+ }
focus = () => {
this.textbox.editor.focus()
@@ -171,27 +174,24 @@ class Composer extends PureComponent {
return false
}
- handleOnTogglePopoutEditor = () => {
- //
- }
-
onTab = (e) => {
const maxDepth = 4
this.onChange(RichUtils.onTab(e, this.state.editorState, maxDepth))
}
setRef = (n) => {
- this.textbox = n
+ try {
+ this.textbox = n
+ this.props.inputRef(n)
+ } catch (error) {
+ //
+ }
}
render() {
const {
- inputRef,
disabled,
placeholder,
- autoFocus,
- value,
- onChange,
onKeyDown,
onKeyUp,
onFocus,
@@ -211,15 +211,13 @@ class Composer extends PureComponent {
pt15: !small,
px15: !small,
px10: small,
- pt5: small,
- pb5: small,
pb10: !small,
})
return (
- { /** : todo : */
+ {
!small &&
diff --git a/app/javascript/gabsocial/components/modal/pro_upgrade_modal.js b/app/javascript/gabsocial/components/modal/pro_upgrade_modal.js
index 235545c4..a130cb05 100644
--- a/app/javascript/gabsocial/components/modal/pro_upgrade_modal.js
+++ b/app/javascript/gabsocial/components/modal/pro_upgrade_modal.js
@@ -42,6 +42,7 @@ class ProUpgradeModal extends ImmutablePureComponent {
• Larger Video and Image Uploads
• Receive the PRO Badge
• Remove in-feed promotions
+ • Compose Rich Text posts (Bold, Italic, Underline and more)