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()}
-
+
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 (
+
+
+
+ );
+ }
+}
+
+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
+
+