diff --git a/Gemfile b/Gemfile index b9a9159def..31cf743a41 100644 --- a/Gemfile +++ b/Gemfile @@ -95,6 +95,8 @@ gem 'json-ld', '~> 3.0' gem 'json-ld-preloaded', '~> 3.0' gem 'rdf-normalize', '~> 0.3' +gem 'redcarpet', '~> 3.4' + group :development, :test do gem 'fabrication', '~> 2.20' gem 'fuubar', '~> 2.3' diff --git a/Gemfile.lock b/Gemfile.lock index 98cd2103dd..8e3f1faf7d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -480,6 +480,7 @@ GEM link_header (~> 0.0, >= 0.0.8) rdf-normalize (0.3.3) rdf (>= 2.2, < 4.0) + redcarpet (3.4.0) redis (4.1.1) redis-actionpack (5.0.2) actionpack (>= 4.0, < 6) @@ -745,6 +746,7 @@ DEPENDENCIES rails-i18n (~> 5.1) rails-settings-cached (~> 0.6) rdf-normalize (~> 0.3) + redcarpet (~> 3.4) redis (~> 4.1) redis-namespace (~> 1.5) redis-rails (~> 5.0) diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb index 8a1aad41a7..fe5b5b7b74 100644 --- a/app/lib/formatter.rb +++ b/app/lib/formatter.rb @@ -36,14 +36,52 @@ class Formatter html = raw_content html = "RT @#{prepend_reblog} #{html}" if prepend_reblog - html = encode_and_link_urls(html, linkable_accounts) + html = format_markdown(html) if status.content_type == 'text/markdown' + html = encode_and_link_urls(html, linkable_accounts, keep_html: status.content_type == 'text/markdown') html = encode_custom_emojis(html, status.emojis, options[:autoplay]) if options[:custom_emojify] - html = simple_format(html, {}, sanitize: false) + html = simple_format(html, {}, sanitize: false) unless status.content_type == 'text/markdown' html = html.delete("\n") html.html_safe # rubocop:disable Rails/OutputSafety end + def format_markdown(html) + extensions = { + autolink: false, + no_intra_emphasis: true, + fenced_code_blocks: true, + disable_indented_code_blocks: true, + strikethrough: true, + lax_spacing: true, + space_after_headers: true, + superscript: true, + underline: true, + highlight: true, + footnotes: true + } + + renderer = Redcarpet::Render::HTML.new({ + filter_html: false, + no_images: true, + no_styles: true, + safe_links_only: true, + hard_wrap: true, + link_attributes: { target: '_blank', rel: 'nofollow noopener' }, + }) + + markdown = Redcarpet::Markdown.new(renderer, extensions) + + html = reformat(markdown.render(html)) + html = html.gsub("\r\n", "\n").gsub("\r", "\n") + code_safe_strip(html) + end + + def code_safe_strip(html, char="\n") + html = html.split(/(].*?\/code>)/m) + html.each_slice(2) { |part| part[0].delete!(char) } + html.join + end + def reformat(html) sanitize(html, Sanitize::Config::MASTODON_STRICT) end @@ -116,7 +154,7 @@ class Formatter accounts = nil end - rewrite(html.dup, entities) do |entity| + rewrite(html.dup, entities, options[:keep_html]) do |entity| if entity[:url] link_to_url(entity, options) elsif entity[:hashtag] @@ -186,7 +224,7 @@ class Formatter html end - def rewrite(text, entities) + def rewrite(text, entities, keep_html = false) text = text.to_s # Sort by start index @@ -199,12 +237,12 @@ class Formatter last_index = entities.reduce(0) do |index, entity| indices = entity.respond_to?(:indices) ? entity.indices : entity[:indices] - result << encode(text[index...indices.first]) + result << (keep_html ? text[index...indices.first] : encode(text[index...indices.first])) result << yield(entity) indices.last end - result << encode(text[last_index..-1]) + result << (keep_html ? text[last_index..-1] : encode(text[last_index..-1])) result.flatten.join end