Skip to content

Commit

Permalink
Backend support for changelog markup
Browse files Browse the repository at this point in the history
  • Loading branch information
JasonBarnabe committed Dec 23, 2017
1 parent d1f7a38 commit d03de7e
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 170 deletions.
159 changes: 0 additions & 159 deletions app/helpers/application_helper.rb
@@ -1,6 +1,3 @@
require 'sanitize'
require 'redcarpet'

module ApplicationHelper

def title(page_title)
Expand Down Expand Up @@ -29,26 +26,6 @@ def relative_time(date)
return I18n.translate('helpers.application.time_ago.days', count: (diff_in_minutes / 1440.0).round)
end

def format_user_text(text, markup_type)
return '' if text.nil?
return format_user_text_html(text) if markup_type == 'html'
return format_user_text_markdown(text) if markup_type == 'markdown'
return ''
end

# Returns the plain text representation of the passed markup
def format_user_text_as_plain(text, markup_type)
Sanitize.clean(format_user_text(text, markup_type))
end

def format_user_text_html(text)
Sanitize.clean(text, get_html_sanitize_config).html_safe
end

def format_user_text_markdown(text)
Sanitize.clean(@@markdown.render(text), get_markdown_sanitize_config).html_safe
end

def discussion_class(discussion)
case discussion.Rating
when 0
Expand All @@ -64,100 +41,6 @@ def discussion_class(discussion)
end
end

def get_html_sanitize_config
if @@html_sanitize_config.nil?
@@html_sanitize_config = get_markdown_sanitize_config.dup
fix_whitespace = lambda do |env|
node = env[:node]
return unless node.text?
return if has_ancestor(node, 'pre')
node.content = node.content.lstrip if element_is_block(node.previous_sibling)
node.content = node.content.rstrip if element_is_block(node.next_sibling)
return if node.text.empty?
return unless node.text.include?("\n")
resulting_nodes = replace_text_with_node(node, "\n", Nokogiri::XML::Node.new('br', node.document))
end

@@html_sanitize_config[:transformers] << fix_whitespace
end
return @@html_sanitize_config
end

def get_markdown_sanitize_config
if @@markdown_sanitize_config.nil?
@@markdown_sanitize_config = Sanitize::Config::BASIC.dup
@@markdown_sanitize_config[:elements] = @@markdown_sanitize_config[:elements].dup
@@markdown_sanitize_config[:elements].concat(['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img', 'hr', 'del', 'ins', 'table', 'tr', 'th', 'td', 'thead', 'tbody', 'tfoot', 'span', 'div', 'tt', 'center', 'ruby', 'rt', 'rp', 'video', 'details', 'summary'])
@@markdown_sanitize_config[:attributes] = @@markdown_sanitize_config[:attributes].merge('img' => ['src', 'alt', 'height', 'width'], 'video' => ['src', 'poster', 'height', 'width'], 'details' => ['open'], :all => ['title', 'name'])
@@markdown_sanitize_config[:protocols] = @@markdown_sanitize_config[:protocols].merge('img' => {'src' => ['https']}, 'video' => {'src' => ['https']})
@@markdown_sanitize_config[:remove_contents] = ['script', 'style']
@@markdown_sanitize_config[:add_attributes] = @@markdown_sanitize_config[:add_attributes].merge('video' => {'controls' => 'controls'})

yes_follow = lambda do |env|
follow_domains = ['mozillazine.org', 'mozilla.org', 'mozilla.com', 'userscripts.org', 'userstyles.org', 'mozdev.org', 'photobucket.com', 'facebook.com', 'chrome.google.com', 'github.com', 'greasyfork.org', 'openuserjs.org']
return unless env[:node_name] == 'a'
node = env[:node]
href = nil
href = node['href'].downcase unless node['href'].nil?
follow = false
if href.nil?
# missing the href, we don't want a rel here
follow = true
elsif href =~ Sanitize::REGEX_PROTOCOL
# external link, let's figure out the domain if it's http or https
match = /https?:\/\/([^\/]+).*/.match(href)
# check domain against our list, including subdomains
if !match.nil?
follow_domains.each do |d|
if match[1] == d or match[1].ends_with?('.' + d)
follow = true
break
end
end
end
else
# internal link
follow = true
end
if follow
# take out any rel value the user may have provided
node.delete('rel')
else
node['rel'] = 'nofollow'
end

# make a config that allows the rel attribute and does not include this transformer
# do a deep copy of anything we're going to change
config_allows_rel = env[:config].dup
config_allows_rel[:attributes] = config_allows_rel[:attributes].dup
config_allows_rel[:attributes]['a'] = config_allows_rel[:attributes]['a'].dup
config_allows_rel[:attributes]['a'] << 'rel'
config_allows_rel[:add_attributes] = config_allows_rel[:add_attributes].dup
config_allows_rel[:add_attributes]['a'] = config_allows_rel[:add_attributes]['a'].dup
config_allows_rel[:add_attributes]['a'].delete('rel')
config_allows_rel[:transformers] = config_allows_rel[:transformers].dup
config_allows_rel[:transformers].delete(yes_follow)

Sanitize.clean_node!(node, config_allows_rel)

# whitelist so the initial clean call doesn't strip the rel
return {:node_whitelist => [node]}
end
linkify_urls = lambda do |env|
node = env[:node]
return unless node.text?
return if has_ancestor(node, 'a')
return if has_ancestor(node, 'pre')
url_reference = node.text.match(/(\s|^|\()(https?:\/\/[^\s\)\]]*)/i)
return if url_reference.nil?
resulting_nodes = replace_text_with_link(node, url_reference[2], url_reference[2], url_reference[2])
end

@@markdown_sanitize_config[:transformers] = [linkify_urls, yes_follow]
end
return @@markdown_sanitize_config
end

def self_link(name, text)
"<span id=\"#{name}\">#{link_to('§', {:anchor => name}, {:class => 'self-link'})} #{text}</span>".html_safe
end
Expand Down Expand Up @@ -195,48 +78,6 @@ def asset_path_if_exists(path)
return asset_path(path) if asset_exists?(path)
end

private

@@markdown_sanitize_config = nil
@@html_sanitize_config = nil

def replace_text_with_link(node, original_text, link_text, url)
# the text itself becomes a link
link = Nokogiri::XML::Node.new('a', node.document)
link['href'] = url
link.add_child(Nokogiri::XML::Text.new(link_text, node.document))
return replace_text_with_node(node, original_text, link)
end

def replace_text_with_node(node, text, node_to_insert)
original_content = node.text
start = node.text.index(text)
# the stuff before stays in the current node
node.content = original_content[0, start]
# add the new node
node.add_next_sibling(node_to_insert)
# the stuff after becomes a new text node
node_to_insert.add_next_sibling(Nokogiri::XML::Text.new(original_content[start + text.size, original_content.size], node.document))
return [node, node.next_sibling, node.next_sibling.next_sibling]
end

def has_ancestor(node, ancestor_node_name)
until node.nil?
return true if node.name == ancestor_node_name
node = node.parent
end
return false
end

def element_is_block(node)
return false if node.nil?
# https://github.com/rgrove/sanitize/issues/108
d = Nokogiri::HTML::ElementDescription[node.name]
return !d.nil? && d.block?
end

@@markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML.new({:link_attributes => {:rel => 'nofollow'}}), :fenced_code_blocks => true, :lax_spacing => true)

TOP_SCRIPTS_PERCENTAGE = 0.2
TOP_SCRIPTS_COUNT = 5

Expand Down
163 changes: 163 additions & 0 deletions app/helpers/user_text_helper.rb
@@ -0,0 +1,163 @@
require 'sanitize'
require 'redcarpet'

module UserTextHelper

def format_user_text(text, markup_type)
return '' if text.nil?
return text if markup_type == 'text'
return format_user_text_html(text) if markup_type == 'html'
return format_user_text_markdown(text) if markup_type == 'markdown'
return ''
end

# Returns the plain text representation of the passed markup
def format_user_text_as_plain(text, markup_type)
Sanitize.clean(format_user_text(text, markup_type))
end

private

def format_user_text_html(text)
Sanitize.clean(text, get_html_sanitize_config).html_safe
end

def format_user_text_markdown(text)
Sanitize.clean(@@markdown.render(text), get_markdown_sanitize_config).html_safe
end

def get_html_sanitize_config
if @@html_sanitize_config.nil?
@@html_sanitize_config = get_markdown_sanitize_config.dup
fix_whitespace = lambda do |env|
node = env[:node]
return unless node.text?
return if has_ancestor(node, 'pre')
node.content = node.content.lstrip if element_is_block(node.previous_sibling)
node.content = node.content.rstrip if element_is_block(node.next_sibling)
return if node.text.empty?
return unless node.text.include?("\n")
resulting_nodes = replace_text_with_node(node, "\n", Nokogiri::XML::Node.new('br', node.document))
end

@@html_sanitize_config[:transformers] << fix_whitespace
end
return @@html_sanitize_config
end

def get_markdown_sanitize_config
if @@markdown_sanitize_config.nil?
@@markdown_sanitize_config = Sanitize::Config::BASIC.dup
@@markdown_sanitize_config[:elements] = @@markdown_sanitize_config[:elements].dup
@@markdown_sanitize_config[:elements].concat(['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img', 'hr', 'del', 'ins', 'table', 'tr', 'th', 'td', 'thead', 'tbody', 'tfoot', 'span', 'div', 'tt', 'center', 'ruby', 'rt', 'rp', 'video', 'details', 'summary'])
@@markdown_sanitize_config[:attributes] = @@markdown_sanitize_config[:attributes].merge('img' => ['src', 'alt', 'height', 'width'], 'video' => ['src', 'poster', 'height', 'width'], 'details' => ['open'], :all => ['title', 'name'])
@@markdown_sanitize_config[:protocols] = @@markdown_sanitize_config[:protocols].merge('img' => {'src' => ['https']}, 'video' => {'src' => ['https']})
@@markdown_sanitize_config[:remove_contents] = ['script', 'style']
@@markdown_sanitize_config[:add_attributes] = @@markdown_sanitize_config[:add_attributes].merge('video' => {'controls' => 'controls'})

yes_follow = lambda do |env|
follow_domains = ['mozillazine.org', 'mozilla.org', 'mozilla.com', 'userscripts.org', 'userstyles.org', 'mozdev.org', 'photobucket.com', 'facebook.com', 'chrome.google.com', 'github.com', 'greasyfork.org', 'openuserjs.org']
return unless env[:node_name] == 'a'
node = env[:node]
href = nil
href = node['href'].downcase unless node['href'].nil?
follow = false
if href.nil?
# missing the href, we don't want a rel here
follow = true
elsif href =~ Sanitize::REGEX_PROTOCOL
# external link, let's figure out the domain if it's http or https
match = /https?:\/\/([^\/]+).*/.match(href)
# check domain against our list, including subdomains
if !match.nil?
follow_domains.each do |d|
if match[1] == d or match[1].ends_with?('.' + d)
follow = true
break
end
end
end
else
# internal link
follow = true
end
if follow
# take out any rel value the user may have provided
node.delete('rel')
else
node['rel'] = 'nofollow'
end

# make a config that allows the rel attribute and does not include this transformer
# do a deep copy of anything we're going to change
config_allows_rel = env[:config].dup
config_allows_rel[:attributes] = config_allows_rel[:attributes].dup
config_allows_rel[:attributes]['a'] = config_allows_rel[:attributes]['a'].dup
config_allows_rel[:attributes]['a'] << 'rel'
config_allows_rel[:add_attributes] = config_allows_rel[:add_attributes].dup
config_allows_rel[:add_attributes]['a'] = config_allows_rel[:add_attributes]['a'].dup
config_allows_rel[:add_attributes]['a'].delete('rel')
config_allows_rel[:transformers] = config_allows_rel[:transformers].dup
config_allows_rel[:transformers].delete(yes_follow)

Sanitize.clean_node!(node, config_allows_rel)

# whitelist so the initial clean call doesn't strip the rel
return {:node_whitelist => [node]}
end
linkify_urls = lambda do |env|
node = env[:node]
return unless node.text?
return if has_ancestor(node, 'a')
return if has_ancestor(node, 'pre')
url_reference = node.text.match(/(\s|^|\()(https?:\/\/[^\s\)\]]*)/i)
return if url_reference.nil?
resulting_nodes = replace_text_with_link(node, url_reference[2], url_reference[2], url_reference[2])
end

@@markdown_sanitize_config[:transformers] = [linkify_urls, yes_follow]
end
return @@markdown_sanitize_config
end

@@markdown_sanitize_config = nil
@@html_sanitize_config = nil

def replace_text_with_link(node, original_text, link_text, url)
# the text itself becomes a link
link = Nokogiri::XML::Node.new('a', node.document)
link['href'] = url
link.add_child(Nokogiri::XML::Text.new(link_text, node.document))
return replace_text_with_node(node, original_text, link)
end

def replace_text_with_node(node, text, node_to_insert)
original_content = node.text
start = node.text.index(text)
# the stuff before stays in the current node
node.content = original_content[0, start]
# add the new node
node.add_next_sibling(node_to_insert)
# the stuff after becomes a new text node
node_to_insert.add_next_sibling(Nokogiri::XML::Text.new(original_content[start + text.size, original_content.size], node.document))
return [node, node.next_sibling, node.next_sibling.next_sibling]
end

def has_ancestor(node, ancestor_node_name)
until node.nil?
return true if node.name == ancestor_node_name
node = node.parent
end
return false
end

def element_is_block(node)
return false if node.nil?
# https://github.com/rgrove/sanitize/issues/108
d = Nokogiri::HTML::ElementDescription[node.name]
return !d.nil? && d.block?
end

@@markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML.new({:link_attributes => {:rel => 'nofollow'}}), :fenced_code_blocks => true, :lax_spacing => true)

end
2 changes: 1 addition & 1 deletion app/views/script_versions/index.html.erb
Expand Up @@ -33,7 +33,7 @@ end %>
<% end %>
<%=markup_date(sv.created_at)%>
<% if !sv.changelog.nil? and !sv.changelog.empty? %>
- <%=sv.changelog%>
- <%=format_user_text(sv.changelog, sv.changelog_markup)%>
<% end %>
</li>
<% end
Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20171223173317_add_markup_to_changelog.rb
@@ -0,0 +1,5 @@
class AddMarkupToChangelog < ActiveRecord::Migration[5.1]
def change
add_column :script_versions, :changelog_markup, :string, limit: 10, null: false, default: 'text', after: 'changelog'
end
end

0 comments on commit d03de7e

Please sign in to comment.