diff --git a/Gemfile b/Gemfile index 75e5aedf..c81935b6 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,7 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby "3.1.2" -gem "react_on_rails", "14.0.0" +gem "react_on_rails", path: '../react_on_rails' gem "shakapacker", "7.2.1" # Bundle edge Rails instead: gem "rails", github: "rails/rails" diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index d9b9934f..e0ca6a6c 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -33,6 +33,7 @@ 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 @@ -99,6 +100,10 @@ def inline_form end end + def test_1508 + @comment = Comment.new + end + private def set_comments diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index c9f6543e..6730abcb 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 diff --git a/app/views/comments/_form_1508.html.erb b/app/views/comments/_form_1508.html.erb new file mode 100644 index 00000000..ef4280bc --- /dev/null +++ b/app/views/comments/_form_1508.html.erb @@ -0,0 +1,30 @@ +<%= form_for(@comment, html: { class: "flex flex-col gap-4" }) do |f| %> + <% if @comment.errors.any? %> +
+

<%= pluralize(comment.errors.count, "error") %> prohibited this comment from being saved:

+ + +
+ <% end %> + +
+ <%= f.label :author, 'Your Name' %>
+ <%= f.text_field :author, class: "px-3 py-1 leading-4 border border-gray-300 rounded" %> +
+
+ <%= f.label :text, 'Say something using markdown...' %>
+ <%= f.text_area :text, class: "px-3 py-1 leading-4 border border-gray-300 rounded" %> +
+
+ <%= f.submit 'Post', class: "self-start px-3 py-1 font-semibold border-0 rounded text-sky-50 bg-sky-600 hover:bg-sky-800 cursor-pointer" %> +
+ +

Below is a react component which render inside a form (that will proof #1508 is fixed), You can turn-off the flag `force_load` and this below form will not show.

+
+ <%= react_component('HotwiredCommentForm', props: { }, prerender: false, force_load: true) %> +
+<% end %> diff --git a/app/views/comments/create.turbo_stream.erb b/app/views/comments/create.turbo_stream.erb new file mode 100644 index 00000000..3d93e4b7 --- /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 %> \ No newline at end of file diff --git a/app/views/comments/new.turbo_stream.erb b/app/views/comments/new.turbo_stream.erb new file mode 100644 index 00000000..844cb6dc --- /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 %> \ No newline at end of file diff --git a/app/views/comments/test_1508.turbo_stream.erb b/app/views/comments/test_1508.turbo_stream.erb new file mode 100644 index 00000000..fb704ace --- /dev/null +++ b/app/views/comments/test_1508.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.replace dom_id(@comment), target: '_top' do %> + <%= render partial: 'form_1508' %> +<% end %> \ No newline at end of file diff --git a/app/views/pages/hotwired.html.erb b/app/views/pages/hotwired.html.erb new file mode 100644 index 00000000..0a9db2a6 --- /dev/null +++ b/app/views/pages/hotwired.html.erb @@ -0,0 +1,8 @@ +

Demo React Over Hotwired

+ +<%= react_component('HotwiredCommentScreen', props: @props, prerender: false) %> + + +<%= turbo_frame_tag "new_comment" do %> + <%= link_to "proof that #1508 fixed", "/test_1508", data: {turbo_stream: true} %> +<% end %> \ No newline at end of file diff --git a/client/app/bundles/comments/components/CommentBox/CommentList/CommentList.jsx b/client/app/bundles/comments/components/CommentBox/CommentList/CommentList.jsx index 128f8878..cc74ebf3 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/HotwiredCommentForm.jsx b/client/app/bundles/comments/components/HotwiredCommentScreen/HotwiredCommentForm.jsx new file mode 100644 index 00000000..10e89092 --- /dev/null +++ b/client/app/bundles/comments/components/HotwiredCommentScreen/HotwiredCommentForm.jsx @@ -0,0 +1,120 @@ +// eslint-disable-next-line max-classes-per-file +import React from 'react'; +import request from 'axios'; +import Immutable from 'immutable'; +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 { defaultMessages, defaultLocale } from 'libs/i18n/default'; +import { translations } from 'libs/i18n/translations'; + +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'); + } + + componentDidMount() { + } + + handleCommentSubmit(comment) { + this.setState({ isSaving: true }); + + const requestConfig = { + responseType: 'text/vnd.turbo-stream.html', + headers: ReactOnRails.authenticityHeaders(), + }; + + return request + .post('comments.turbo_stream', { comment }, requestConfig) + .then(r => r.data) + .then(html => { + Turbo.renderStreamMessage(html) + }) + .then(() => { + const { $$comments } = this.state; + const $$comment = Immutable.fromJS(comment); + + this.setState({ + $$comments: $$comments.unshift($$comment), + submitCommentError: null, + isSaving: false, + }); + }) + .catch((error) => { + this.setState({ + submitCommentError: error, + isSaving: false, + }); + }); + } + + render() { + const { handleSetLocale, locale, intl } = 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/HotwiredCommentScreen.jsx b/client/app/bundles/comments/components/HotwiredCommentScreen/HotwiredCommentScreen.jsx new file mode 100644 index 00000000..94278b42 --- /dev/null +++ b/client/app/bundles/comments/components/HotwiredCommentScreen/HotwiredCommentScreen.jsx @@ -0,0 +1,87 @@ +// eslint-disable-next-line max-classes-per-file +import React from 'react'; +import Immutable from 'immutable'; +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 { defaultMessages, defaultLocale } from 'libs/i18n/default'; +import { translations } from 'libs/i18n/translations'; + +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), + }; + } + + componentDidMount() { + } + + 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/HotwiredCommentScreen/HotwiredCommentScreen.module.scss b/client/app/bundles/comments/components/HotwiredCommentScreen/HotwiredCommentScreen.module.scss new file mode 100644 index 00000000..c96d339f --- /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/NavigationBar/NavigationBar.jsx b/client/app/bundles/comments/components/NavigationBar/NavigationBar.jsx index 65dc3b40..63998104 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 + +
  • "/cable" + + + get "test_1508", to: "comments#test_1508", format: :turbo_stream end