In the first part of this short series on how to build a Medium site in Ruby on Rails, we setup the blog on an existing Rails app. We added the Post model and controller and showed a list of drafts and published posts. In this Part 2, we'll create and edit posts, using the Medium Editor library we installed on Part 1.
Creating the post
When users click on Write a post we want to take them to the form. When they start typing, we'll create a post and from then our new form becomes the edit form. We'll need to change the URL in the browser to point at the newly-created post.
Our edit.html.erb view looks like this:
<%= form_for [:blogr, @post], remote: true, html: { id: 'blogr-posts-form', class: 'blogr-form' } do |f| %>
<%= f.text_area :title, class: 'editable title', id: 'blogr-editable-title', placeholder: 'Title' %>
<%= f.text_area :body, class: 'editable body', id: 'blogr-editable-body', placeholder: 'Tell your story ...' %>
<% end %>
Notice how we use add the editable class to our elements, to help us with the Medium Editor. The form is going to be posted remotely, and auto-saved every second. Let's create a small object in CoffeeScript to help us with this:
# app/assets/javascripts/blogr.coffee
blogr =
start: ->
@_container = $("#posts-cont")
self = this
@_document = $(document)
@_document.on 'ajax:success', (e, data) ->
self._onLinkSuccess(e.target, data) if e.target.tagName == 'A'
self._onFormSuccess(e.target, data) if e.target.tagName == 'FORM'
@_document.on 'keyup', '#blogr-posts-form .editable', => @_create()
@_loadMedium()
Since the Write new post link was a remote link, we'll need to manipulate the response by handling the ajax:success
event triggered by Rails unobtrusive JavaScript (normally we should also handle ajax:error
event, removed here for brevity).
# app/assets/javascripts/blogr.coffee
_onLinkSuccess: (link, data) ->
history.pushState {}, null, link.href.replace('.html', '')
@_container.html data
@_loadMedium()
This function adds an entry to the browser's history and changes the container with the HTML coming from the server. It also instantiates the Medium Editor:
# app/assets/javascripts/blogr.coffee
_loadMedium: ->
$('.editable').each (_, el) ->
new MediumEditor("##{el.id}", placeholder: { text: el.placeholder })
Medium Editor has a multitude of options that you should check at their github page. We're using the placeholder option to show some text before creating the editors.
The new action of PostsController looks like this:
# app/controllers/blogr/posts_controller.rb
def new
@post = Post.new
render :edit
end
It renders the edit
template that we saw earlier.
When the user starts writing the post, we create a new post, via this line of CoffeScript we mentioned above:
# app/assets/javascripts/blogr.coffee
@_document.on 'keyup', '#blogr-posts-form .editable', => @_create()
Let's now write the _create
handler:
# app/assets/javascripts/blogr.coffee
_create: ->
$('#blogr-posts-form').submit()
@_document.off 'keyup'
We submit the form and stop listening to the keyup
event. The form goes to the server via AJAX, and is handled by the create
action:
# app/controllers/blogr/posts_controller.rb
def create
@post = Post.new(post_params)
@post.save
render json: {
edit_url: edit_blogr_post_path(@post),
update_url: blogr_post_path(@post)
}
end
Here we save the post and send back the edit and update URLS, so that we can replace in the editing form. When the response is returned by the server, the function _onFormSuccess
is called as we stated above:
# app/assets/javascripts/blogr.coffee
_onFormSuccess: (form, data) ->
if location.pathname.match(/new$/)
form = $('#blogr-posts-form')
form.prop 'action', data.update_url
form.prepend('<input type="hidden" name="_method" value="patch">')
history.pushState {}, null, data.edit_url
@_autoSave()
When we are creating a new post, and the URL ends in new, we change the form's action with the update URL and also the browser's URL with the edit URL. This way we enter the edit mode and auto save the post every minute.
Editing the post
This is done automatically, without a save button:
# app/assets/javascripts/blogr.coffee
_autoSave: ->
clearTimeout(@_saveTimeout) if @_saveTimeout
fn = -> $('#blogr-posts-form').submit()
@_saveTimeout = setTimeout(fn, 1000)
As usual, we clear an existing timeout before calling a new one, and submit the form. This is now an editing form, and will call the update action on the server:
# app/controllers/blogr/posts_controller.rb
def update
@post.update(post_params)
end
The post_params
are simple:
# app/controllers/blogr/posts_controller.rb
def post_params
params.require(:post).permit(:title, :body)
end
Publish a post
We can publish a post by clicking on the publish link. This posts to our controller action:
# app/controllers/blogr/posts_controller.rb
def publish
@post.update(published_at: Time.current)
redirect_to public_blogr_posts_url, notice: 'Post was successfully published.'
end
The unpublish does the opposite:
# app/controllers/blogr/posts_controller.rb
def unpublish
@post.update(published_at: nil)
redirect_to blogr_posts_url, notice: 'Post was successfully unpublished.'
end
Delete a post
Deleting the post is simple:
# app/controllers/blogr/posts_controller.rb
def destroy
@post.destroy
redirect_to blogr_posts_url, notice: 'Post was successfully deleted.'
end
The styles
Let's add a nice paint to our blogr, as per the classes we added to the HTML:
.blogr {
font-family: sans-serif;
a {
border: none;
}
.blogr-cont {
max-width: 960px;
width: 95%;
margin: 0 auto;
}
.posts-title {
h1 {
float: left;
font-size: 2.5em;
margin-bottom: 1em;
}
a {
float: right;
display: inline-block;
color: #CE0A0A;
border: 1px solid;
border-radius: 2em;
padding: .5em 1.4em;
margin-top: 1em;
}
}
.posts-nav {
border-top: 1px solid #ddd;
margin-bottom: 2em;
li {
display: inline-block;
line-height: 5;
text-transform: uppercase;
font-size: .8em;
letter-spacing: 2px;
font-weight: 700;
margin-right: 2em;
margin-top: -1px;
color: #222;
}
span {
display: block;
border-top: 1px solid #666;
}
a {
color: #999;
}
}
.blogr-posts {
.summary {
margin-bottom: 3em;
h3 {
a {
font-family: serif;
font-size: 2em;
color: #222;
}
}
.desc {
color: #aaa;
a {
color: #aaa;
}
}
.dot {
position: relative;
top: -.2em;
display: inline-block;
padding: 0 .3em;
}
}
}
.blogr-form {
font-family: serif;
max-width: 50em;
textarea {
display: none;
}
.title {
font-size: 2em;
color: #222;
font-weight: bold;
margin-bottom: 1em;
}
.body {
font-size: 1.3em;
}
}
}
Conclusion
In this short series we saw how easy it is to add a blog to our existing Rails app using the Medium Editor. We could enrich it by adding and formatting links, images and other buttons and functionality that the Editor offers.
- Rewrite Medium in Ruby on Rails, Part 1: The Setup
- Rewrite Medium in Ruby on Rails, Part 2: Creating the posts