DEV: Switch `InlineUploads` to a regexp based implementation.

DEV: Switch InlineUploads to a regexp based implementation.

diff --git a/Gemfile b/Gemfile
index 1caacda..d0878a8 100644
--- a/Gemfile
+++ b/Gemfile
@@ -151,7 +151,6 @@ group :development do
   gem 'bullet', require: !!ENV['BULLET']
   gem 'better_errors'
   gem 'binding_of_caller'
-  gem 'diffy'
 
   # waiting on 2.7.5 per: https://github.com/ctran/annotate_models/pull/595
   if rails_master?
diff --git a/Gemfile.lock b/Gemfile.lock
index 0fa3580..db8907d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -90,7 +90,6 @@ GEM
     crass (1.0.4)
     debug_inspector (0.0.3)
     diff-lcs (1.3)
-    diffy (3.3.0)
     discourse-ember-source (3.8.0.1)
     discourse_image_optim (0.26.2)
       exifr (~> 1.2, >= 1.2.2)
@@ -436,7 +435,6 @@ DEPENDENCIES
   certified
   colored2
   cppjieba_rb
-  diffy
   discourse-ember-source (~> 3.8.0)
   discourse_image_optim
   email_reply_trimmer (~> 0.1)
diff --git a/app/assets/javascripts/pretty-text/engines/discourse-markdown/emoji.js.es6 b/app/assets/javascripts/pretty-text/engines/discourse-markdown/emoji.js.es6
index f1fc15e..ba2c971 100644
--- a/app/assets/javascripts/pretty-text/engines/discourse-markdown/emoji.js.es6
+++ b/app/assets/javascripts/pretty-text/engines/discourse-markdown/emoji.js.es6
@@ -236,7 +236,7 @@ function applyEmoji(
 
 export function setup(helper) {
   helper.registerOptions((opts, siteSettings, state) => {
-    opts.features.emoji = !!siteSettings.enable_emoji;
+    opts.features.emoji = !state.disableEmojis && !!siteSettings.enable_emoji;
     opts.features.emojiShortcuts = !!siteSettings.enable_emoji_shortcuts;
     opts.features.inlineEmoji = !!siteSettings.enable_inline_emoji_translation;
     opts.emojiSet = siteSettings.emoji_set || "";
diff --git a/app/assets/javascripts/pretty-text/pretty-text.js.es6 b/app/assets/javascripts/pretty-text/pretty-text.js.es6
index 33836cd..e656c9a 100644
--- a/app/assets/javascripts/pretty-text/pretty-text.js.es6
+++ b/app/assets/javascripts/pretty-text/pretty-text.js.es6
@@ -29,7 +29,8 @@ export function buildOptions(state) {
     lookupUploadUrls,
     previewing,
     linkify,
-    censoredWords
+    censoredWords,
+    disableEmojis
   } = state;
 
   let features = {
@@ -76,7 +77,8 @@ export function buildOptions(state) {
     markdownIt: true,
     injectLineNumbersToPreview:
       siteSettings.enable_advanced_editor_preview_sync,
-    previewing
+    previewing,
+    disableEmojis
   };
 
   // note, this will mutate options due to the way the API is designed
diff --git a/app/services/inline_uploads.rb b/app/services/inline_uploads.rb
index 4e3cece..a8ce2db 100644
--- a/app/services/inline_uploads.rb
+++ b/app/services/inline_uploads.rb
@@ -3,98 +3,147 @@
 require_dependency "pretty_text"
 
 class InlineUploads
+  PLACEHOLDER = "__replace__"
+  private_constant :PLACEHOLDER
+
+  UPLOAD_REGEXP_PATTERN = "/original/(\\dX/(?:[a-f0-9]/)*[a-f0-9]{40}[a-z0-9.]*)"
+  private_constant :UPLOAD_REGEXP_PATTERN
+
   def self.process(markdown, on_missing: nil)
     markdown = markdown.dup
-    cooked_fragment = Nokogiri::HTML::fragment(PrettyText.cook(markdown))
+    cooked_fragment = Nokogiri::HTML::fragment(PrettyText.cook(markdown, disable_emojis: true))
     link_occurences = []
 
     cooked_fragment.traverse do |node|
       if node.name == "img"
         # Do nothing
-      elsif !(node.children.count == 1 && (node.children[0].name != "img" && node.children[0].children.blank?))
+      elsif !(node.children.count == 1 && (node.children[0].name != "img" && node.children[0].children.blank?)) ||
+            !node.ancestors.all? { |parent| !parent.attributes["class"]&.value&.include?("quote") }
         next
       end
 
       if seen_link = matched_uploads(node).first
-        if actual_link = (node.attributes["href"]&.value || node.attributes["src"]&.value)
-          link_occurences << [actual_link, true]
-        else
-          link_occurences << [seen_link, false]
-        end
+        link_occurences <<
+          if (actual_link = (node.attributes["href"]&.value || node.attributes["src"]&.value))
+            { link: actual_link, is_valid: true }
+          else
+            { link: seen_link, is_valid: false }
+          end
       end
     end
 
-    raw_fragment = Nokogiri::HTML::fragment(markdown)
+    raw_matches = []
 
-    raw_fragment.traverse do |node|
-      if node.name == "img"
-        # Do nothing
-      elsif !(node.children.count == 0 || (node.children.count == 1 && node.children[0].children.blank?))
-        next
+    markdown.scan(/(\[img\]\s?(.+)\s?\[\/img\])/) do |match|
+      raw_matches << [match[0], match[1], +"![](#{PLACEHOLDER})", $~.offset(0)[0]]
+    end
+
+    markdown.scan(/(!?\[([^\[\]]+)\]\(([a-zA-z0-9\.\/:-]+)\))/) do |match|
+      if matched_uploads(match[2]).present?
+        raw_matches << [
+          match[0],
+          match[2],
+          +"#{match[0].start_with?("!") ? "!" : ""}[#{match[1]}](#{PLACEHOLDER})",
+          $~.offset(0)[0]
+        ]
       end
+    end
 
-      matches = matched_uploads(node)
-      next if matches.blank?
-      links = extract_links(node)
+    markdown.scan(/(<(?!img)[^<>]+\/?>)?(\n*)(([ ]*)<img ([^<>]+)>([ ]*))(\n*)/) do |match|
+      node = Nokogiri::HTML::fragment(match[2].strip).children[0]
+      src =  node.attributes["src"].value
 
-      matches.zip(links).each do |_match, link|
-        seen_link, is_valid = link_occurences.shift
-        next unless (link && is_valid)
+      if matched_uploads(src).present?
+        text = node.attributes["alt"]&.value
+        width = node.attributes["width"]&.value
+        height = node.attributes["height"]&.value
+        text = "#{text}|#{width}x#{height}" if width && height
+        after_html_tag = match[0].present?
 
-        if link.include?(seen_link)
-          begin
-            uri = URI(link)
-          rescue URI::Error
+        spaces_before =
+          if after_html_tag && !match[0].end_with?("/>")
+            (match[3].present? ? match[3] : "  ")
+          else
+            ""
           end
 
-          if !Discourse.store.external?
-            next if uri&.host && uri.host != Discourse.current_hostname
-          end
+        replacement = +"#{spaces_before}![#{text}](#{PLACEHOLDER})"
 
-          upload = Upload.get_from_url(link)
-
-          if upload
-            new_node =
-              case node.name
-              when 'a'
-                attachment_postfix =
-                  if node.attributes["class"]&.value&.split(" ")&.include?("attachment")
-                    "|attachment"
-                  else
-                    ""
-                  end
-
-                text = node.children.text.strip.gsub("\n", "").gsub(/ +/, " ")
-
-                markdown.sub!(
-                  node.to_s,
-                  "[#{text}#{attachment_postfix}](#{upload.short_url})"
-                )
-              when "img"
-                text = node.attributes["alt"]&.value
-                width = node.attributes["width"]&.value
-                height = node.attributes["height"]&.value
-                text = "#{text}|#{width}x#{height}" if width && height
-                markdown.sub!(node.to_s, "![#{text}](#{upload.short_url})")
-              else
-                if markdown =~ /\[img\]\s?#{link}\s?\[\/img\]/
-                  capture = Regexp.last_match[0]
-
-                  if capture
-                    markdown.sub!(capture, "![](#{upload.short_url})")
-                  end
-                elsif markdown =~ /(!?\[([a-z0-9|]+)\]\([a-zA-z0-9\.\/]+\))/
-                  capture = Regexp.last_match[0]
-
-                  if capture
-                    markdown.sub!(capture, "![#{Regexp.last_match[2]}](#{upload.short_url})")
-                  end
-                end
-              end
+        if after_html_tag && (num_newlines = match[1].length) <= 1
+          replacement.prepend("\n" * (num_newlines == 0 ? 2 : 1))
+        end
 
-          else
-            on_missing.call(link) if on_missing
-          end
+        if after_html_tag && !match[0].end_with?("/>") && (num_newlines = match[6].length) <= 1

[... diff too long, it was truncated ...]

GitHub sha: 1991af2a