diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 13f9cabb4..c620a23a2 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -33,13 +33,11 @@ def create format.html { redirect_to @comment, notice: I18n.t(:comment_was_successfully_created) } end format.json { render :show, status: :created, location: @comment } + format.turbo_stream else - if turbo_frame_request? - format.html - else - format.html { render :new } - end + format.html { render :new, status: :unprocessable_entity } format.json { render json: @comment.errors, status: :unprocessable_entity } + format.turbo_stream { render :new, status: :unprocessable_entity } end end end diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index 507cc6cf7..818c99c32 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -38,6 +38,10 @@ def simple; end def rescript; end + def hotwired + @props = comments_json_string + end + private def set_comments @@ -46,7 +50,7 @@ def set_comments def comments_json_string render_to_string(template: "/comments/index", - locals: { comments: Comment.all }, formats: :json) + locals: { comments: @comments }, formats: :json) end def render_html diff --git a/app/views/comments/create.turbo_stream.erb b/app/views/comments/create.turbo_stream.erb new file mode 100644 index 000000000..167be8d04 --- /dev/null +++ b/app/views/comments/create.turbo_stream.erb @@ -0,0 +1,10 @@ +<%= turbo_stream.prepend "comments" do %> +
+

<%= @comment.author %>

+ <%= markdown_to_html(@comment.text) %> +
+<% end %> + +<%= turbo_stream.update "comment-form" do %> + <%= link_to "New Comment", new_comment_path, data: { turbo_stream: true } %> +<% end %> diff --git a/app/views/comments/new.turbo_stream.erb b/app/views/comments/new.turbo_stream.erb new file mode 100644 index 000000000..438dcae38 --- /dev/null +++ b/app/views/comments/new.turbo_stream.erb @@ -0,0 +1,7 @@ +<%= turbo_stream.update "comment-form" do %> +
+

New Comment

+ + <%= react_component("HotwiredCommentForm", props: {}, prerender: false, force_load: true) %> +
+<% end %> diff --git a/app/views/pages/hotwired.html.erb b/app/views/pages/hotwired.html.erb new file mode 100644 index 000000000..5cbaa3869 --- /dev/null +++ b/app/views/pages/hotwired.html.erb @@ -0,0 +1,3 @@ +

Demo React Over Hotwired

+ +<%= react_component("HotwiredCommentScreen", props: @props, prerender: false) %> diff --git a/client/app/bundles/comments/components/CommentBox/CommentList/CommentList.jsx b/client/app/bundles/comments/components/CommentBox/CommentList/CommentList.jsx index 128f8878b..cc74ebf3c 100644 --- a/client/app/bundles/comments/components/CommentBox/CommentList/CommentList.jsx +++ b/client/app/bundles/comments/components/CommentBox/CommentList/CommentList.jsx @@ -78,7 +78,7 @@ export default class CommentList extends BaseComponent {
{this.errorWarning()} - + {commentNodes}
diff --git a/client/app/bundles/comments/components/HotwiredCommentScreen/HotwiredCommentScreen.module.scss b/client/app/bundles/comments/components/HotwiredCommentScreen/HotwiredCommentScreen.module.scss new file mode 100644 index 000000000..c96d339f3 --- /dev/null +++ b/client/app/bundles/comments/components/HotwiredCommentScreen/HotwiredCommentScreen.module.scss @@ -0,0 +1,17 @@ +.elementEnter { + opacity: 0.01; + + &.elementEnterActive { + opacity: 1; + transition: opacity $animation-duration ease-in; + } +} + +.elementLeave { + opacity: 1; + + &.elementLeaveActive { + opacity: 0.01; + transition: opacity $animation-duration ease-in; + } +} diff --git a/client/app/bundles/comments/components/HotwiredCommentScreen/ror_components/HotwiredCommentForm.jsx b/client/app/bundles/comments/components/HotwiredCommentScreen/ror_components/HotwiredCommentForm.jsx new file mode 100644 index 000000000..7fa3fdbe8 --- /dev/null +++ b/client/app/bundles/comments/components/HotwiredCommentScreen/ror_components/HotwiredCommentForm.jsx @@ -0,0 +1,112 @@ +// eslint-disable-next-line max-classes-per-file +import React from 'react'; +import request from 'axios'; +import _ from 'lodash'; +import ReactOnRails from 'react-on-rails'; +import { IntlProvider, injectIntl } from 'react-intl'; +import BaseComponent from 'libs/components/BaseComponent'; +import SelectLanguage from 'libs/i18n/selectLanguage'; +import { defaultLocale } from 'libs/i18n/default'; +import { translations } from 'libs/i18n/translations'; +import { COMMENTS_TURBO_STREAM_PATH } from '../../../constants/paths'; + +import { Turbo } from '@hotwired/turbo-rails'; +import CommentForm from '../../CommentBox/CommentForm/CommentForm'; +import css from '../HotwiredCommentScreen.module.scss'; + +class HotwiredCommentForm extends BaseComponent { + constructor(props) { + super(props); + + this.state = { + isSaving: false, + submitCommentError: null, + }; + + _.bindAll(this, 'handleCommentSubmit'); + } + + handleCommentSubmit(comment) { + this.setState({ isSaving: true }); + + const requestConfig = { + responseType: 'text/vnd.turbo-stream.html', + headers: ReactOnRails.authenticityHeaders(), + }; + + return request + .post(COMMENTS_TURBO_STREAM_PATH, { comment }, requestConfig) + .then(r => r.data) + .then(html => { + Turbo.renderStreamMessage(html); + }) + .then(() => { + this.setState({ + submitCommentError: null, + isSaving: false, + }); + }) + .catch((error) => { + this.setState({ + submitCommentError: error, + isSaving: false, + }); + }); + } + + render() { + const { handleSetLocale, locale } = this.props; + const cssTransitionGroupClassNames = { + enter: css.elementEnter, + enterActive: css.elementEnterActive, + exit: css.elementLeave, + exitActive: css.elementLeaveActive, + }; + + return ( +
+ {SelectLanguage(handleSetLocale, locale)} + + +
+ ); + } +} + +export default class I18nWrapper extends BaseComponent { + constructor(props) { + super(props); + + this.state = { + locale: defaultLocale, + }; + + _.bindAll(this, 'handleSetLocale'); + } + + handleSetLocale(locale) { + this.setState({ locale }); + } + + render() { + const { locale } = this.state; + const messages = translations[locale]; + const InjectedHotwiredCommentForm = injectIntl(HotwiredCommentForm); + + return ( + + + + ); + } +} diff --git a/client/app/bundles/comments/components/HotwiredCommentScreen/ror_components/HotwiredCommentScreen.jsx b/client/app/bundles/comments/components/HotwiredCommentScreen/ror_components/HotwiredCommentScreen.jsx new file mode 100644 index 000000000..61717785c --- /dev/null +++ b/client/app/bundles/comments/components/HotwiredCommentScreen/ror_components/HotwiredCommentScreen.jsx @@ -0,0 +1,85 @@ +// eslint-disable-next-line max-classes-per-file +import React from 'react'; +import Immutable from 'immutable'; +import _ from 'lodash'; +import { IntlProvider, injectIntl } from 'react-intl'; +import BaseComponent from 'libs/components/BaseComponent'; +import SelectLanguage from 'libs/i18n/selectLanguage'; +import { defaultMessages, defaultLocale } from 'libs/i18n/default'; +import { translations } from 'libs/i18n/translations'; +import { NEW_COMMENT_PATH } from '../../../constants/paths'; + +import CommentList from '../../CommentBox/CommentList/CommentList'; +import css from '../HotwiredCommentScreen.module.scss'; + +class HotwiredCommentScreen extends BaseComponent { + constructor(props) { + super(props); + + this.state = { + $$comments: Immutable.fromJS(props.comments), + }; + } + + render() { + const { handleSetLocale, locale, intl } = this.props; + const { formatMessage } = intl; + const cssTransitionGroupClassNames = { + enter: css.elementEnter, + enterActive: css.elementEnterActive, + exit: css.elementLeave, + exitActive: css.elementLeaveActive, + }; + + return ( +
+ +

{formatMessage(defaultMessages.comments)}

+ {SelectLanguage(handleSetLocale, locale)} + + + + +
+
+ ); + } +} + +export default class I18nWrapper extends BaseComponent { + constructor(props) { + super(props); + + this.state = { + locale: defaultLocale, + }; + + _.bindAll(this, 'handleSetLocale'); + } + + handleSetLocale(locale) { + this.setState({ locale }); + } + + render() { + const { locale } = this.state; + const messages = translations[locale]; + const InjectedHotwiredCommentScreen = injectIntl(HotwiredCommentScreen); + + return ( + + + + ); + } +} diff --git a/client/app/bundles/comments/components/NavigationBar/NavigationBar.jsx b/client/app/bundles/comments/components/NavigationBar/NavigationBar.jsx index 65dc3b402..639981041 100644 --- a/client/app/bundles/comments/components/NavigationBar/NavigationBar.jsx +++ b/client/app/bundles/comments/components/NavigationBar/NavigationBar.jsx @@ -81,6 +81,14 @@ function NavigationBar(props) { Simple React +
  • + + HotWired + +