discourse/app/services/inline_uploads.rb

152 lines
4.7 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
require_dependency "pretty_text"
class InlineUploads
def self.process(markdown, on_missing: nil)
markdown = markdown.dup
cooked_fragment = Nokogiri::HTML::fragment(PrettyText.cook(markdown))
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?))
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
end
end
raw_fragment = Nokogiri::HTML::fragment(markdown)
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
end
matches = matched_uploads(node)
next if matches.blank?
links = extract_links(node)
matches.zip(links).each do |_match, link|
seen_link, is_valid = link_occurences.shift
next unless (link && is_valid)
if link.include?(seen_link)
begin
uri = URI(link)
rescue URI::Error
end
if !Discourse.store.external?
next if uri&.host && uri.host != Discourse.current_hostname
end
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
else
on_missing.call(link) if on_missing
end
end
end
end
markdown
end
def self.matched_uploads(node)
matches = []
regexps = [
/(upload:\/\/([a-zA-Z0-9]+)[a-z0-9\.]*)/,
/(\/uploads\/short-url\/([a-zA-Z0-9]+)[a-z0-9\.]*)/,
]
db = RailsMultisite::ConnectionManagement.current_db
if Discourse.store.external?
if Rails.configuration.multisite
regexps << /(#{SiteSetting.Upload.s3_base_url}\/uploads\/#{db}\/original\/(\dX\/(?:[a-f0-9]\/)*[a-f0-9]{40}[a-z0-9\.]*))/
regexps << /(#{SiteSetting.Upload.s3_cdn_url}\/uploads\/#{db}\/original\/(\dX\/(?:[a-f0-9]\/)*[a-f0-9]{40}[a-z0-9\.]*))/
else
regexps << /(#{SiteSetting.Upload.s3_base_url}\/original\/(\dX\/(?:[a-f0-9]\/)*[a-f0-9]{40}[a-z0-9\.]*))/
regexps << /(#{SiteSetting.Upload.s3_cdn_url}\/original\/(\dX\/(?:[a-f0-9]\/)*[a-f0-9]{40}[a-z0-9\.]*))/
regexps << /(\/uploads\/#{db}\/original\/(\dX\/(?:[a-f0-9]\/)*[a-f0-9]{40}[a-z0-9\.]*))/
end
else
regexps << /(\/uploads\/#{db}\/original\/(\dX\/(?:[a-f0-9]\/)*[a-f0-9]{40}[a-z0-9\.]*))/
end
node = node.to_s
regexps.each do |regexp|
node.scan(regexp).each do |matched|
matches << matched[0]
end
end
matches
end
private_class_method :matched_uploads
def self.extract_links(node)
links = []
links << node.attributes["href"]&.value
links << node.attributes["src"]&.value
links = links.concat(node.to_s.scan(/\[img\]\s?(.+)\s?\[\/img\]/))
links = links.concat(node.to_s.scan(/!?\[[a-z0-9|]+\]\(([a-zA-z0-9\.\/]+)\)/))
links.flatten!
links.compact!
links
end
private_class_method :extract_links
end