about summary refs log tree commit diff
diff options
context:
space:
mode:
authorThibaut Girka <thib@sitedethib.com>2019-05-12 20:20:05 +0200
committerThibG <thib@sitedethib.com>2019-05-17 23:51:14 +0200
commit94aef563b9abbc449028f44c4aac84ef2e072d89 (patch)
treea73bab047aadc4be878e81eb795cc1ff3c6bb611
parentbfc509f44ac5506d1d675eb01aeda1324d4cdb6b (diff)
Add support for markdown-formatted toots
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock2
-rw-r--r--app/lib/formatter.rb50
3 files changed, 48 insertions, 6 deletions
diff --git a/Gemfile b/Gemfile
index b9a9159de..31cf743a4 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 98cd2103d..8e3f1faf7 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 8a1aad41a..fe5b5b7b7 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[ >].*?\/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