From 7a993324342d5542dcb40902eed097f7eaac3f1b Mon Sep 17 00:00:00 2001 From: Javan Makhmali Date: Wed, 3 Oct 2018 12:46:05 -0400 Subject: WIP: Image gallery support --- .../_attachment_gallery.html.erb | 3 + lib/action_text.rb | 1 + lib/action_text/attachment_gallery.rb | 62 +++++++++++++++++++++ lib/action_text/content.rb | 65 +++++++++++++++++++--- 4 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 app/views/action_text/attachment_galleries/_attachment_gallery.html.erb create mode 100644 lib/action_text/attachment_gallery.rb diff --git a/app/views/action_text/attachment_galleries/_attachment_gallery.html.erb b/app/views/action_text/attachment_galleries/_attachment_gallery.html.erb new file mode 100644 index 0000000000..6bc8674dc5 --- /dev/null +++ b/app/views/action_text/attachment_galleries/_attachment_gallery.html.erb @@ -0,0 +1,3 @@ + diff --git a/lib/action_text.rb b/lib/action_text.rb index 0559dce2ba..4182cacb8a 100644 --- a/lib/action_text.rb +++ b/lib/action_text.rb @@ -8,6 +8,7 @@ module ActionText mattr_accessor(:renderer) autoload :Attachable + autoload :AttachmentGallery autoload :Attachment autoload :Attribute autoload :Content diff --git a/lib/action_text/attachment_gallery.rb b/lib/action_text/attachment_gallery.rb new file mode 100644 index 0000000000..6f690551e6 --- /dev/null +++ b/lib/action_text/attachment_gallery.rb @@ -0,0 +1,62 @@ +module ActionText + class AttachmentGallery + include ActiveModel::Model + + class << self + def fragment_by_canonicalizing_attachment_galleries(content) + fragment_by_replacing_attachment_gallery_nodes(content) do |node| + "<#{TAG_NAME}>#{node.inner_html}" + end + end + + def fragment_by_replacing_attachment_gallery_nodes(content) + Fragment.wrap(content).update do |source| + find_attachment_gallery_nodes(source).each do |node| + node.replace(yield(node).to_s) + end + end + end + + def find_attachment_gallery_nodes(content) + Fragment.wrap(content).find_all(SELECTOR).select do |node| + node.children.all? do |child| + if child.text? + child.text =~ /\A(\n|\ )*\z/ + else + child.matches? ATTACHMENT_SELECTOR + end + end + end + end + + def from_node(node) + new(node) + end + end + + attr_reader :node + + def initialize(node) + @node = node + end + + def attachments + @attachments ||= node.css(ATTACHMENT_SELECTOR).map do |node| + ActionText::Attachment.from_node(node).with_full_attributes + end + end + + def size + attachments.size + end + + def inspect + "#<#{self.class.name} size=#{size.inspect}>" + end + + private + TAG_NAME = "div" + ATTACHMENT_SELECTOR = "#{ActionText::Attachment::SELECTOR}[presentation=gallery]" + SELECTOR = "#{TAG_NAME}:has(#{ATTACHMENT_SELECTOR} + #{ATTACHMENT_SELECTOR})" + end +end diff --git a/lib/action_text/content.rb b/lib/action_text/content.rb index 22b52cff1c..71d9e98657 100644 --- a/lib/action_text/content.rb +++ b/lib/action_text/content.rb @@ -6,8 +6,22 @@ module ActionText delegate :blank?, :empty?, :html_safe, :present?, to: :to_html # Delegating to to_html to avoid including the layout - def initialize(content = nil) - @fragment = ActionText::Attachment.fragment_by_canonicalizing_attachments(content) + class << self + def fragment_by_canonicalizing_content(content) + fragment = ActionText::Attachment.fragment_by_canonicalizing_attachments(content) + fragment = ActionText::AttachmentGallery.fragment_by_canonicalizing_attachment_galleries(fragment) + fragment + end + end + + def initialize(content = nil, options = {}) + options.with_defaults! canonicalize: true + + if options[:canonicalize] + @fragment = self.class.fragment_by_canonicalizing_content(content) + else + @fragment = ActionText::Fragment.wrap(content) + end end def links @@ -20,6 +34,16 @@ module ActionText end end + def attachment_galleries + @attachment_galleries ||= attachment_gallery_nodes.map do |node| + attachment_gallery_for_node(node) + end + end + + def gallery_attachments + @gallery_attachments ||= attachment_galleries.flat_map(&:attachments) + end + def attachables @attachables ||= attachment_nodes.map do |node| ActionText::Attachable.from_node(node) @@ -32,13 +56,21 @@ module ActionText end def render_attachments(**options, &block) - fragment.replace(ActionText::Attachment::SELECTOR) do |node| + content = fragment.replace(ActionText::Attachment::SELECTOR) do |node| block.call(attachment_for_node(node, **options)) end + self.class.new(content, canonicalize: false) + end + + def render_attachment_galleries(**options, &block) + content = ActionText::AttachmentGallery.fragment_by_replacing_attachment_gallery_nodes(fragment) do |node| + block.call(attachment_gallery_for_node(node, **options)) + end + self.class.new(content, canonicalize: false) end def to_plain_text - render_attachments(with_full_attributes: false, &:to_plain_text).to_plain_text + render_attachments(with_full_attributes: false, &:to_plain_text).fragment.to_plain_text end def to_trix_html @@ -46,19 +78,30 @@ module ActionText end def to_html + fragment.to_html + end + + def to_rendered_html render_attachments do |attachment| attachment.node.tap do |node| node.inner_html = ActionText.renderer.render(attachment) end + end.render_attachment_galleries do |attachment_gallery| + ActionText.renderer.render(layout: attachment_gallery, object: attachment_gallery, formats: "html") do + attachment_gallery.attachments.map do |attachment| + attachment.node.inner_html = ActionText.renderer.render(attachment) + attachment.to_html + end.join("").html_safe + end end.to_html end - def to_html_with_layout - ActionText.renderer.render(partial: "action_text/content/layout", locals: { document: to_html }) + def to_rendered_html_with_layout + ActionText.renderer.render(partial: "action_text/content/layout", locals: { document: to_rendered_html }) end def to_s - to_html_with_layout + to_rendered_html_with_layout end def as_json(*) @@ -80,9 +123,17 @@ module ActionText @attachment_nodes ||= fragment.find_all(ActionText::Attachment::SELECTOR) end + def attachment_gallery_nodes + @attachment_gallery_nodes ||= ActionText::AttachmentGallery.find_attachment_gallery_nodes(fragment) + end + def attachment_for_node(node, with_full_attributes: true) attachment = ActionText::Attachment.from_node(node) with_full_attributes ? attachment.with_full_attributes : attachment end + + def attachment_gallery_for_node(node, **options) + ActionText::AttachmentGallery.from_node(node) + end end end -- cgit v1.2.3