Browse Source

Fix emoji substitution not applying only to text nodes in backend code

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
Claire 1 year ago
parent
commit
0ce2b4d358
1 changed files with 31 additions and 34 deletions
  1. 31 34
      app/lib/formatter.rb

+ 31 - 34
app/lib/formatter.rb

@@ -8,6 +8,8 @@ class Formatter
 
   include ActionView::Helpers::TextHelper
 
+  DISALLOWED_BOUNDING_REGEX = /[[:alnum:]:]/.freeze
+
   def format(status, **options)
     if status.reblog?
       prepend_reblog = status.reblog.account.acct
@@ -140,54 +142,49 @@ class Formatter
 
     emoji_map = emojis.each_with_object({}) { |e, h| h[e.shortcode] = [full_asset_url(e.image.url), full_asset_url(e.image.url(:static))] }
 
-    i                     = -1
-    tag_open_index        = nil
-    inside_shortname      = false
-    shortname_start_index = -1
-    invisible_depth       = 0
+    tree = Nokogiri::HTML.fragment(html)
+    tree.xpath('./text()|.//text()[not(ancestor[@class="invisible"])]').to_a.each do |node|
+      i                     = -1
+      inside_shortname      = false
+      shortname_start_index = -1
+      last_index            = 0
+      text                  = node.content
+      result                = Nokogiri::XML::NodeSet.new(tree.document)
+
+      while i + 1 < text.size
+        i += 1
 
-    while i + 1 < html.size
-      i += 1
+        if inside_shortname && text[i] == ':'
+          inside_shortname = false
+          shortcode = text[shortname_start_index + 1..i - 1]
+          char_after = text[i + 1]
 
-      if invisible_depth.zero? && inside_shortname && html[i] == ':'
-        shortcode = html[shortname_start_index + 1..i - 1]
-        emoji     = emoji_map[shortcode]
+          next unless (char_after.nil? || !DISALLOWED_BOUNDING_REGEX.match?(char_after)) && (emoji = emoji_map[shortcode])
 
-        if emoji
           original_url, static_url = emoji
-          replacement = begin
+
+          result << Nokogiri::XML::Text.new(text[last_index..shortname_start_index - 1], tree.document) if shortname_start_index.positive?
+
+          result << Nokogiri::HTML.fragment(
             if animate
               image_tag(original_url, draggable: false, class: 'emojione', alt: ":#{shortcode}:", title: ":#{shortcode}:")
             else
               image_tag(original_url, draggable: false, class: 'emojione custom-emoji', alt: ":#{shortcode}:", title: ":#{shortcode}:", data: { original: original_url, static: static_url })
             end
-          end
-          before_html = shortname_start_index.positive? ? html[0..shortname_start_index - 1] : ''
-          html        = before_html + replacement + html[i + 1..-1]
-          i          += replacement.size - (shortcode.size + 2) - 1
-        else
-          i -= 1
-        end
+          )
 
-        inside_shortname = false
-      elsif tag_open_index && html[i] == '>'
-        tag = html[tag_open_index..i]
-        tag_open_index = nil
-        if invisible_depth.positive?
-          invisible_depth += count_tag_nesting(tag)
-        elsif tag == '<span class="invisible">'
-          invisible_depth = 1
+          last_index = i + 1
+        elsif text[i] == ':' && (i.zero? || !DISALLOWED_BOUNDING_REGEX.match?(text[i - 1]))
+          inside_shortname = true
+          shortname_start_index = i
         end
-      elsif html[i] == '<'
-        tag_open_index   = i
-        inside_shortname = false
-      elsif !tag_open_index && html[i] == ':'
-        inside_shortname      = true
-        shortname_start_index = i
       end
+
+      result << Nokogiri::XML::Text.new(text[last_index..-1], tree.document)
+      node.replace(result)
     end
 
-    html
+    tree.to_html
   end
   # rubocop:enable Metrics/BlockNesting