Skip to content

Commit

Permalink
Rewrite Comment Editor with React, install Webpack and React (publicl…
Browse files Browse the repository at this point in the history
…ab#9176)

* git rebase

* take ?react=true parameter publiclab#9175

* create & display hello world component publiclab#9175

* delete HelloWorld component

* run Rails methods on comments state object

* create components for comments, comment forms, & comment container

* create react props for comment editor publiclab#9175

* add author pic, username, time posted, border, reply link publiclab#9175

* add userCommentedText prop publiclab#9175

* run views methods on react props publiclab#9175

* create comment toolbar publiclab#9175

* return nodeAuthorID and currentUser publiclab#9175

* toggle reply form; attach User Context publiclab#9175

* flesh out comment form publiclab#9175

* create User Context publiclab#9175

* create textarea, publish, & preview buttons publiclab#9175

* bundle text props as elementText object publiclab#9175

* destructure props with ES6 publiclab#9175

* destructure props with ES6 publiclab#9175

* create handlers for form submission and textarea input publiclab#9175

* explanatory comments publiclab#9175

* make AJAX request on form submission publiclab#9175

* rewrite get_react_comments so it doesn't return nil values publiclab#9175

* fix undefined ID bug: change commentId to formId publiclab#9175

* add webpacker startup to github actions runs

* add webpack-dev-server start to gitpod.yml

* switch to 'public/lib' instead of 'node_modules'

* Update .gitpod.yml

* Update .gitpod.yml

* delete get_comment_react_props method publiclab#9175

* move get_react_comments to comment_helper.rb publiclab#9175

* render JSON for React comment editors publiclab#9175

* create state for comments; insert freshly posted comments into state publiclab#9175

* handle reply submission publiclab#9175

* create CommentsHeader component publiclab#9175

* extract comment author slug into its own component; create edit comment form publiclab#9175

* extract CommentReplies into its own component publiclab#9175

* rename to CommentAuthorSlug publiclab#9175

* change Comment key

* handle edit form toggle publiclab#9175

* appease eslint

* extract CommentDisplay and CommentHeader into separate components publiclab#9175

* extract CommentDisplay into its own component publiclab#9175

* consolidate CommentAuthorSlug into new CommentHeader component publiclab#9175

* pass linter publiclab#9175

* move formId calculation to CommentsContainer publiclab#9175

* break out CommentsList into its own component publiclab#9175

* generate default textAreaValues for edit forms publiclab#9175

* reset edit form textAreaValue when canceling edit publiclab#9175

* move elementText instantiation to controller publiclab#9175

* add makeDeepCopy function; extract everything to helpers.js publiclab#9175

* flash notification on comment post publiclab#9175

* dangerouslySetInnerHTML to display comment text publiclab#9175

* create edit comment functionality publiclab#9175

* rename parameter to getting_replies for clarity publiclab#9175

* change submit button's data- attributes publiclab#9175

* nest nodeId and nodeAuthor inside node publiclab#9175

* create StaticPropsContext publiclab#9175

* create handleDeleteComment function publiclab#9175

* newline at end of file publiclab#9175

* move all static props to StaticPropsContext publiclab#9175

* move commentFormVisibility state into CommentsContainer; close forms on submission publiclab#9175

* Update .gitpod.yml

* Update .gitpod.yml

* add rails generate commands for webpack

* bundle exec

* add functionality for deleting comments publiclab#9175

* remove webpack-dev-server line

* remove webpacker-dev-server

* add mysql env vars

* added rails env

* Update tests.yml

* added snake_case, changed hash syntax to pass codeclimate publiclab#9175

* syntax tweaks to pass codeclimate publiclab#9175

* create readme file publiclab#9175

* undo reply nesting for comment state publiclab#9175

* remove is_reply parameter publiclab#9175

* set reply & edit isFormVisible, textAreaValue for new comments publiclab#9175

* add comments publiclab#9175

* stop passing down setTextAreaValues & replies as props publiclab#9175

* create CommentReplies in CommentsList publiclab#9175

* wrap CommentsContainer in new App component publiclab#9175

* break out handleFormSubmit into commentCreate and commentUpdate functions; pass parameters instead of using data attributes publiclab#9175

* upgrade node to v12 from v8 publiclab#9175

* setup guest browsing publiclab#9175

* replace replyCommentForm with link to login for anonymous browsing publiclab#9175

* add separate routes for React comments publiclab#9175

* fixes for codeclimate publiclab#9175

* add rails generate commandds for React & webpacker publiclab#9175

* remove rails generate webpacker/React commands publiclab#9175

* add rails g commands for webpacker & React publiclab#9175

* remove rails generate commands publiclab#9175

* add rails g commands for webpacker & React publiclab#9175

* add rails g commands for webpacker & React to deploy-container publiclab#9175

* remove rails generate react & webpacker commands from redeploy-container publiclab#9175

* re-add the command to redeploy-container; switch the order of statements publiclab#9175

* change webpack to webpacker in Makefile 🤦 publiclab#9175

* added the CORRECT webpacker & React commands to Makefile publiclab#9175

Co-authored-by: Jeffrey Warren <[email protected]>
  • Loading branch information
noi5e and jywarren authored Mar 23, 2021
1 parent 3c7e548 commit c188b97
Show file tree
Hide file tree
Showing 44 changed files with 7,381 additions and 149 deletions.
1 change: 1 addition & 0 deletions .browserslistrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
defaults
18 changes: 18 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ jobs:
- uses: ./.github/actions/setup-test-environment
- name: Install JavaScript dependencies with Yarn
run: yarn check || yarn install --frozen-lockfile;
- name: Run webpacker setup
env:
RAILS_ENV: test
DB_PASSWORD: root
DB_PORT: ${{ job.services.mysql.ports[3306] }}
run: bundle exec rails g webpack:install && bundle exec rails g webpack:install:react && bundle exec rails g react:install
- name: "Functional Tests"
env:
RAILS_ENV: test
Expand All @@ -59,6 +65,12 @@ jobs:
- uses: ./.github/actions/setup-test-environment
- name: Install JavaScript dependencies with Yarn
run: yarn check || yarn install --frozen-lockfile;
- name: Run webpacker setup
env:
RAILS_ENV: test
DB_PASSWORD: root
DB_PORT: ${{ job.services.mysql.ports[3306] }}
run: bundle exec rails g webpack:install && bundle exec rails g webpack:install:react && bundle exec rails g react:install
- name: "Integration Tests"
env:
RAILS_ENV: test
Expand Down Expand Up @@ -90,6 +102,12 @@ jobs:
- uses: ./.github/actions/setup-test-environment
- name: Install JavaScript dependencies with Yarn
run: yarn check || yarn install --frozen-lockfile;
- name: Run webpacker setup
env:
RAILS_ENV: test
DB_PASSWORD: root
DB_PORT: ${{ job.services.mysql.ports[3306] }}
run: bundle exec rails g webpack:install && bundle exec rails g webpack:install:react && bundle exec rails g react:install
- name: "System Tests"
env:
RAILS_ENV: test
Expand Down
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,10 @@ vendor/bundle
spec/TEST-Teaspoon-Result.xml
yarn.lock
yarn-error.log

/public/packs
/public/packs-test
/node_modules
/yarn-error.log
yarn-debug.log*
.yarn-integrity
2 changes: 2 additions & 0 deletions .gitpod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ tasks:
yarn install
rails g webpack:install && rails g webpack:install:react && rails g react:install
command: >
passenger start
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ RUN echo \
'deb http://ftp.ca.debian.org/debian/ stretch main\n \
deb http://ftp.ca.debian.org/debian/ stretch-updates main\n \
deb http://security.debian.org stretch/updates main\n \
deb http://deb.nodesource.com/node_8.x stretch main\n' \
deb http://deb.nodesource.com/node_12.x stretch main\n' \
> /etc/apt/sources.list

# Install dependencies
Expand Down
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ gem 'rails-i18n', '~> 5.1.3'
gem 'rails_autolink'
gem 'rb-readline'
gem 'rdiscount', '~> 2.2'
gem 'react-rails'
gem "recaptcha", require: "recaptcha/rails"
gem 'responders', '~> 3.0'
gem 'rubocop', '~> 1.11.0', require: false
Expand All @@ -58,6 +59,7 @@ gem 'skylight' # performance tracking via skylight.io
gem 'turbolinks', '~> 5'
gem 'tzinfo-data', platforms: %i(mingw mswin x64_mingw jruby)
gem 'unicode-emoji'
gem 'webpacker'
gem 'whenever', require: false
gem 'will_paginate', '>= 3.0.6'
gem 'will_paginate-bootstrap4'
Expand Down
20 changes: 20 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ GEM
scrypt (>= 1.2, < 4.0)
authlogic-oid (1.0.4)
authlogic
babel-source (5.8.35)
babel-transpiler (0.7.0)
babel-source (>= 4.0, < 6)
execjs (~> 2.0)
bindex (0.6.0)
buftok (0.2.0)
builder (3.2.4)
Expand Down Expand Up @@ -370,6 +374,8 @@ GEM
ruby-openid (>= 2.1.8)
rack-protection (2.0.8.1)
rack
rack-proxy (0.6.5)
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (5.2.3)
Expand Down Expand Up @@ -413,6 +419,12 @@ GEM
ffi (~> 1.0)
rb-readline (0.5.5)
rdiscount (2.2.0.2)
react-rails (2.6.1)
babel-transpiler (>= 0.7.0)
connection_pool
execjs
railties (>= 3.2)
tilt
recaptcha (4.14.0)
json
redis (4.1.4)
Expand Down Expand Up @@ -480,6 +492,7 @@ GEM
selenium-webdriver (3.142.7)
childprocess (>= 0.5, < 4.0)
rubyzip (>= 1.2.2)
semantic_range (2.3.1)
sentry-raven (3.1.1)
faraday (>= 1.0)
sidekiq (5.2.9)
Expand Down Expand Up @@ -556,6 +569,11 @@ GEM
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
webpacker (5.2.1)
activesupport (>= 5.2)
rack-proxy (>= 0.6.1)
railties (>= 5.2)
semantic_range (>= 2.3.0)
websocket-driver (0.7.1)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
Expand Down Expand Up @@ -643,6 +661,7 @@ DEPENDENCIES
rake (~> 13.0.3)
rb-readline
rdiscount (~> 2.2)
react-rails
recaptcha
responders (~> 3.0)
rest-client
Expand Down Expand Up @@ -670,6 +689,7 @@ DEPENDENCIES
unicode-emoji
web-console (>= 3.3.0)
webmock (~> 3.11)
webpacker
whenever
will_paginate (>= 3.0.6)
will_paginate-bootstrap4
Expand Down
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ redeploy-container:
docker-compose build --pull
docker-compose run --rm web yarn install
docker-compose run --rm web bash -c "bundle exec rake db:migrate && bundle exec rake assets:precompile && bundle exec rake tmp:cache:clear"
docker-compose run --rm web bash -c "bundle exec rails webpacker:install && bundle exec rails webpacker:install:react && bundle exec rails g react:install"
docker-compose down --remove-orphans
docker-compose up -d
docker-compose exec -T web bash -c "echo 172.17.0.1 smtp >> /etc/hosts"
Expand All @@ -24,6 +25,7 @@ automated-redeploy: pull-from-stable redeploy-container
deploy-container:
docker-compose run --rm web yarn install
docker-compose run --rm web bash -c "sleep 5 && bundle exec rake db:migrate && bundle exec rake assets:precompile"
docker-compose run --rm web bash -c "sleep 5 && bundle exec rails webpacker:install && bundle exec rails webpacker:install:react && bundle exec rails g react:install"
docker-compose up -d
docker-compose exec -T web bash -c "echo 172.17.0.1 smtp >> /etc/hosts"
docker-compose exec -T mailman bash -c "echo 172.17.0.1 smtp >> /etc/hosts"
Expand Down
63 changes: 63 additions & 0 deletions app/controllers/comment_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,67 @@ def like_comment
end
end
end

def react_create
@node = Node.find params[:id]
@body = params[:body]
@user = current_user

begin
@comment = create_comment(@node, @user, @body)

if params[:reply_to].present?
@comment.reply_to = params[:reply_to].to_i
@comment.save
end

new_comment = helpers.get_react_comments([@comment])
render json: { comment: new_comment }
rescue CommentError
flash.now[:error] = 'The comment could not be saved.'
render plain: 'failure', status: :bad_request
end
end

def react_delete
@comment = Comment.find params[:id]

comments_node_and_path

if current_user.uid == @node.uid ||
@comment.uid == current_user.uid ||
logged_in_as(%w(admin moderator))

if @comment.destroy
render json: { success: true }
return
else
flash[:error] = 'The comment could not be deleted.'
render plain: 'failure'
end
else
prompt_login 'Only the comment or post author can delete this comment'
end
end

def react_update
@comment = Comment.find params[:id]

comments_node_and_path

if @comment.uid == current_user.uid
# should abstract ".comment" to ".body" for future migration to native db
@comment.comment = params[:body]
if @comment.save
new_comment = helpers.get_react_comments([@comment])
render json: { comment: new_comment }
else
flash[:error] = 'The comment could not be updated.'
redirect_to @path
end
else
flash[:error] = 'Only the author of the comment can edit it.'
redirect_to @path
end
end
end
38 changes: 38 additions & 0 deletions app/controllers/notes_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,45 @@ def show
@tags = @node.tags
@tagnames = @tags.collect(&:name)
@preview = false
@react = params[:react]

if params[:react]
# query everything we need in the comments state object
comments_record = @node
.comments_viewable_by(current_user)
.includes(%i(replied_comments node))
.order('timestamp ASC')

comments = helpers.get_react_comments(comments_record)

current_user_json = nil

if current_user
current_user_json = {
canModerate: current_user.can_moderate?,
id: current_user[:id],
role: current_user[:role],
status: current_user[:status]
}
end

@react_props = {
currentUser: current_user_json,
comments: comments,
elementText: {
commentFormPlaceholder: I18n.t('notes._comments.post_placeholder'),
commentsHeaderText: helpers.translation('notes._comments.comments'),
commentPreviewText: helpers.translation('comments._form.preview'),
commentPublishText: helpers.translation('comments._form.publish'),
userCommentedText: helpers.translation('notes._comment.commented')
},
node: {
nodeId: @node.id,
nodeAuthorId: @node.uid
},
user: current_user
}
end
else
page_not_found
end
Expand Down
23 changes: 23 additions & 0 deletions app/helpers/comment_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,27 @@ def create_comment(node, user, body)
raise CommentError
end
end

# takes an activerecord query, returns a plain array
# used in notes_controller.rb and comment_controller.rb
def get_react_comments(comments_record)
comments = []
comments_record.each do |comment|
comment_json = {}
comment_json[:authorId] = comment.uid
comment_json[:authorPicFilename] = comment.author.photo_file_name
comment_json[:authorPicUrl] = comment.author.photo_path(:thumb)
comment_json[:authorUsername] = comment.author.username
comment_json[:commentId] = comment.cid
comment_json[:commentName] = comment.name
comment_json[:createdAt] = comment.created_at
comment_json[:htmlCommentText] = raw insert_extras(filtered_comment_body(comment.render_body))
comment_json[:rawCommentText] = comment.comment
comment_json[:replyTo] = comment.reply_to
time_created_string = distance_of_time_in_words(comment.created_at, Time.current, include_seconds: false, scope: 'datetime.time_ago_in_words')
comment_json[:timeCreatedString] = time_created_string
comments << comment_json
end
comments
end
end
Empty file added app/javascript/components/.keep
Empty file.
61 changes: 61 additions & 0 deletions app/javascript/components/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from "react";
import PropTypes from "prop-types";

import { UserContext } from "./user-context";
import { StaticPropsContext } from "./static-props-context";
import { getEditTextAreaValues, getInitialCommentFormToggleState } from "./helpers";

import CommentsContainer from "./CommentsContainer";

const App = ({
// ES6 destructure the props
// so we can simply refer to initialComments instead of this.props.initialComments
currentUser,
elementText,
node,
node: {
nodeId
},
initialComments
}) => {
// process the initialComments object and create initial state that is passed down to CommentsContainer.js to make React Hooks

// this is an object containing boolean values like: { "reply-33": false, "edit-1": true }
// this is used as the initial state showing whether or not an edit or reply comment form is shown or hidden
// false means the comment form is closed, true means open
const initialCommentFormToggleState = getInitialCommentFormToggleState(initialComments);

// this is used as initial state for the content of <textarea>s inside comment forms
// main and reply comment forms are an empty string
// edit forms must contain the raw comment text to be edited
// this is an object that holds the contents of multiple text forms:
// eg. { main: "foo", reply-123: "bar" }
const initialTextAreaValues = {
"main": "",
...getEditTextAreaValues(initialComments)
};

// React Context ensures that all components below this one can access certain props, without having to pass down component to component
// currentUser is passed down, as well as static props that do not change (like header text)
return (
<UserContext.Provider value={currentUser}>
<StaticPropsContext.Provider value={{ node, elementText }}>
<CommentsContainer
initialCommentFormToggleState={initialCommentFormToggleState}
initialComments={initialComments}
initialTextAreaValues={initialTextAreaValues}
nodeId={nodeId}
/>
</StaticPropsContext.Provider>
</UserContext.Provider>
);
}

App.propTypes = {
currentUser: PropTypes.object,
elementText: PropTypes.object.isRequired,
initialComments: PropTypes.array.isRequired,
node: PropTypes.object.isRequired
};

export default App;
Loading

0 comments on commit c188b97

Please sign in to comment.