From 1a506327db88a96568b2a5daff528b1b1050c6fc Mon Sep 17 00:00:00 2001 From: mgabdev <> Date: Wed, 17 Jun 2020 13:25:10 -0400 Subject: [PATCH] Rich Text Editor (WIP) x3 --- app/controllers/api/v1/statuses_controller.rb | 6 +- app/javascript/gabsocial/actions/compose.js | 16 ++-- app/javascript/gabsocial/actions/statuses.js | 1 - .../components/autosuggest_textbox.js | 2 +- .../gabsocial/components/composer.js | 93 +++++++------------ .../components/modal/pro_upgrade_modal.js | 1 + .../components/rich_text_editor_bar.js | 50 +++++----- .../components/timeline_compose_block.js | 2 +- .../compose/components/compose_form.js | 4 +- .../components/rich_text_editor_button.js | 21 ++++- .../containers/compose_form_container.js | 12 ++- app/javascript/gabsocial/reducers/compose.js | 8 +- app/javascript/styles/global.css | 7 +- 13 files changed, 113 insertions(+), 110 deletions(-) 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 0aecfcaa..9745469f 100644 --- a/app/javascript/gabsocial/actions/compose.js +++ b/app/javascript/gabsocial/actions/compose.js @@ -268,7 +268,7 @@ export function submitCompose(group, replyToId = null, router, isStandalone) { // : 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,18 +276,20 @@ export function submitCompose(group, replyToId = null, router, isStandalone) { } return hasProtocol ? match : `http://${match}` }) - markdown = markdown.replace(urlRegex, (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()); @@ -709,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/statuses.js b/app/javascript/gabsocial/actions/statuses.js index 60af0990..d22b6df1 100644 --- a/app/javascript/gabsocial/actions/statuses.js +++ b/app/javascript/gabsocial/actions/statuses.js @@ -109,7 +109,6 @@ export function fetchStatus(id) { }).then(() => { dispatch(fetchStatusSuccess(skipLoading)); }, () => api(getState).get(`/api/v1/statuses/${id}`).then(response => { - console.log("response.data:", response.data) dispatch(importFetchedStatus(response.data)); dispatch(fetchStatusSuccess(skipLoading)); })).catch(error => { diff --git a/app/javascript/gabsocial/components/autosuggest_textbox.js b/app/javascript/gabsocial/components/autosuggest_textbox.js index 40e945a6..9bbc2ca0 100644 --- a/app/javascript/gabsocial/components/autosuggest_textbox.js +++ b/app/javascript/gabsocial/components/autosuggest_textbox.js @@ -201,7 +201,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent { } setTextbox = (c) => { - this.textbox = c; + this.textbox = c } render() { diff --git a/app/javascript/gabsocial/components/composer.js b/app/javascript/gabsocial/components/composer.js index 7be61d27..90691bac 100644 --- a/app/javascript/gabsocial/components/composer.js +++ b/app/javascript/gabsocial/components/composer.js @@ -4,6 +4,7 @@ import { CompositeDecorator, RichUtils, convertToRaw, + ContentState, } from 'draft-js' import draftToMarkdown from '../features/ui/util/draft-to-markdown' import { urlRegex } from '../features/ui/util/url_regex' @@ -27,11 +28,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) } @@ -70,22 +71,15 @@ 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, onChange: PropTypes.func, onKeyDown: PropTypes.func, @@ -97,40 +91,35 @@ 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) { - if (prevProps.value !== this.props.value) { - // const editorState = EditorState.push(this.state.editorState, ContentState.createFromText(this.props.value)); - // this.setState({ editorState }) + 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, + }) } } - // EditorState.createWithContent(ContentState.createFromText('Hello')) + onChange = (editorState) => { + const content = editorState.getCurrentContent() + const plainText = content.getPlainText('\u0001') - onChange = (editorState, b, c, d) => { - this.setState({ editorState }) + this.setState({ editorState, plainText }) - const content = editorState.getCurrentContent(); - const text = content.getPlainText('\u0001') - const selectionState = editorState.getSelection() const selectionStart = selectionState.getStartOffset() - const rawObject = convertToRaw(content); + const rawObject = convertToRaw(content) const markdownString = draftToMarkdown(rawObject, { escapeMarkdownCharacters: false, preserveNewlines: false, @@ -143,24 +132,11 @@ class Composer extends PureComponent { inline: ['del', 'ins'], } } - }); + }) - console.log("text:", markdownString) - // console.log("html:", html) - - this.props.onChange(null, text, markdownString, selectionStart) + this.props.onChange(null, plainText, markdownString, selectionStart) } - // **bold** - // *italic* - // __underline__ - // ~~strike~~ - // # header - // > quote - // ``` - // code - // ``` - focus = () => { this.textbox.editor.focus() } @@ -177,27 +153,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, @@ -217,8 +190,6 @@ class Composer extends PureComponent { pt15: !small, px15: !small, px10: small, - pt5: small, - pb5: small, pb10: !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)