diff --git a/app/assets/javascripts/utilities/buildArticleHTML.js b/app/assets/javascripts/utilities/buildArticleHTML.js index acde8cd6c..0c5451327 100644 --- a/app/assets/javascripts/utilities/buildArticleHTML.js +++ b/app/assets/javascripts/utilities/buildArticleHTML.js @@ -32,8 +32,8 @@ function buildArticleHTML(article) { currentTag = JSON.parse(container.dataset.params).tag; } if (article.flare_tag && currentTag !== article.flare_tag.name) { - flareTag = ` # @@ -170,6 +170,8 @@ function buildArticleHTML(article) { // We only show profile preview cards for Posts var isArticle = article.class_name === 'Article'; + // We need to be able to set the data-info hash attribute with escaped characters. + var name = article.user.name.replace(/[\\"']/g, '\\$&'); var previewCardContent = `
@@ -182,7 +184,7 @@ function buildArticleHTML(article) {
diff --git a/app/decorators/article_decorator.rb b/app/decorators/article_decorator.rb index 2007e9a5f..00d456e20 100644 --- a/app/decorators/article_decorator.rb +++ b/app/decorators/article_decorator.rb @@ -1,6 +1,15 @@ class ArticleDecorator < ApplicationDecorator LONG_MARKDOWN_THRESHOLD = 900 + # @return [String] JSON formatted string. + # + # @example + # > Article.last.decorate.user_data_info_to_json + # => "{\"user_id\":1,\"className\":\"User\",\"style\":\"full\",\"name\":\"Duane \\\"The Rock\\\" Johnson\"}" + def user_data_info_to_json + DataInfo.to_json(object: cached_user, class_name: "User", id: user_id, style: "full") + end + def current_state_path published ? "/#{username}/#{slug}" : "/#{username}/#{slug}?preview=#{password}" end diff --git a/app/decorators/notification_decorator.rb b/app/decorators/notification_decorator.rb index 4c40cbd78..7f3ad73b4 100644 --- a/app/decorators/notification_decorator.rb +++ b/app/decorators/notification_decorator.rb @@ -3,6 +3,9 @@ class NotificationDecorator < ApplicationDecorator def class Struct.new(:name).new(name) end + + # @see ApplicationRecord#class_name + alias_method :class_name, :name end.freeze # returns a stub notifiable object with name and id diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 366f4c024..5247e0641 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -112,12 +112,7 @@ module ApplicationHelper return if followable == Users::DeletedUser user_follow = followable.instance_of?(User) ? "follow-user" : "" - followable_type = if followable.respond_to?(:decorated?) && followable.decorated? - followable.object.class.name - else - followable.class.name - end - + followable_type = followable.class_name followable_name = followable.name tag.button( @@ -126,12 +121,7 @@ module ApplicationHelper name: :button, type: :button, data: { - info: { - id: followable.id, - className: followable_type, - name: followable_name, - style: style - } + info: DataInfo.to_json(object: followable, className: followable_type, style: style) }, class: "crayons-btn follow-action-button whitespace-nowrap #{classes} #{user_follow}", aria: { diff --git a/app/models/application_record.rb b/app/models/application_record.rb index cd08562a9..8dd6494d9 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -35,6 +35,16 @@ class ApplicationRecord < ActiveRecord::Base false end + # In our view objects, we often ask "What's this object's class's name?" + # + # We can either first check "Are you decorated?" If so, ask for the decorated object's class + # name. Or we can add a helper method for that very thing. + # + # @return [String] + def class_name + self.class.name + end + # Decorate collection with appropriate decorator def self.decorate decorator_class.decorate_collection(all) diff --git a/app/models/tag.rb b/app/models/tag.rb index b31f9a6c2..da1147015 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -82,6 +82,13 @@ class Tag < ActsAsTaggableOn::Tag scope :eager_load_serialized_data, -> {} scope :supported, -> { where(supported: true) } + # @return [String] + # + # @see ApplicationRecord#class_name + def class_name + self.class.name + end + # possible social previews templates for articles with a particular tag def self.social_preview_templates Rails.root.join("app/views/social_previews/articles").children.map { |ch| File.basename(ch, ".html.erb") } diff --git a/app/models/users/deleted_user.rb b/app/models/users/deleted_user.rb index 8ec043034..72824bcd0 100644 --- a/app/models/users/deleted_user.rb +++ b/app/models/users/deleted_user.rb @@ -24,6 +24,13 @@ module Users def self.path() = nil def self.tag_line() = nil + # @return [String] + # + # @see ApplicationRecord#class_name + def self.class_name + User.name + end + def self.decorate self end diff --git a/app/view_objects/data_info.rb b/app/view_objects/data_info.rb new file mode 100644 index 000000000..db3329575 --- /dev/null +++ b/app/view_objects/data_info.rb @@ -0,0 +1,36 @@ +# Throughout Forem, we encode JSON information in our Ruby templates. +# By convention, we often have a data-info attribute that is a Hash. +# +# This module provides some much needed character escaping to ensure +# that our JS that parses the data-info attribute doesn't choke. +# +# @see ./app/javascript/packs/followButtons.js for parsing +module DataInfo + # A convenience method to ensure properly escape JSON data attributes. + # + # @param object [#class, #id, #name] responds to `#class`, `#id`, + # and `#name`, though you can "cheat" and pass the specific + # values to allow for over-riding (or when you have not quite + # well formed objects; looking at ArticleDecorator's + # cached_user. It's not quite a valid object.) + # @param id [String, Integer] + # @param class_name [String] + # @param name [String] + # @param kwargs [Hash] any additional attributes to include in the + # JSON object. In some cases we include a "style" attribute. + # + # @return [String] JSON formatted string. + # + # @example + # > DataInfo.to_json(object: User.first, name: "Duane \"The Rock\" Johnson", id: 1, style: "full") + # => "{\"user_id\":1,\"className\":\"User\",\"style\":\"full\",\"name\":\"Duane \\\"The Rock\\\" Johnson\"}" + def self.to_json(object:, id: object.id, class_name: object.class_name, name: object.name, **kwargs) + kwargs.merge( + { + id: id, + className: class_name, + name: name + }, + ).to_json + end +end diff --git a/app/views/articles/_single_story.html.erb b/app/views/articles/_single_story.html.erb index 991852b1d..1f444ac11 100644 --- a/app/views/articles/_single_story.html.erb +++ b/app/views/articles/_single_story.html.erb @@ -65,7 +65,7 @@ diff --git a/app/views/organizations/_header.html.erb b/app/views/organizations/_header.html.erb index f99b3e2f5..47749acaf 100644 --- a/app/views/organizations/_header.html.erb +++ b/app/views/organizations/_header.html.erb @@ -20,7 +20,7 @@ diff --git a/app/views/podcast_episodes/index.html.erb b/app/views/podcast_episodes/index.html.erb index 49fb77ee7..45338addd 100644 --- a/app/views/podcast_episodes/index.html.erb +++ b/app/views/podcast_episodes/index.html.erb @@ -20,7 +20,7 @@ id="user-follow-butt" class="crayons-btn crayons-btn--inverted follow-action-button" style="min-width: 100px;" - data-info='{"id":<%= @podcast.id %>,"className":"<%= @podcast.class.name %>", "name": "<%= @podcast.name %>"}'> + data-info='<%= DataInfo.to_json(object: @podcast) %>'>  

diff --git a/app/views/podcast_episodes/show.html.erb b/app/views/podcast_episodes/show.html.erb index ad898040c..ab022922c 100644 --- a/app/views/podcast_episodes/show.html.erb +++ b/app/views/podcast_episodes/show.html.erb @@ -38,7 +38,7 @@ diff --git a/app/views/stories/tagged_articles/index.html.erb b/app/views/stories/tagged_articles/index.html.erb index 6b36b016a..22880954b 100644 --- a/app/views/stories/tagged_articles/index.html.erb +++ b/app/views/stories/tagged_articles/index.html.erb @@ -24,7 +24,7 @@ <% end %> diff --git a/app/views/tags/index.html.erb b/app/views/tags/index.html.erb index 0398413c3..93667d6dc 100644 --- a/app/views/tags/index.html.erb +++ b/app/views/tags/index.html.erb @@ -45,7 +45,7 @@
diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 3582ca097..41c514b27 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -32,7 +32,7 @@
- +