require "sanitize" module Panda module CMS module EditorJs class Renderer attr_reader :content, :options, :custom_renderers, :cache_store def initialize(content, options = {}) @content = content @options = options @custom_renderers = options.delete(:custom_renderers) || {} @cache_store = options.delete(:cache_store) || Rails.cache @validate_html = options.delete(:validate_html) || false end def render return "" if content.nil? || content == {} return content.to_s unless content.is_a?(Hash) && content["blocks"].is_a?(Array) rendered = content["blocks"].map do |block| render_block(block) end.join("\n") rendered = @validate_html ? validate_html(rendered) : rendered rendered.presence || "" end def section(blocks) return "" if blocks.nil? || blocks.empty? content = {"blocks" => blocks} rendered = self.class.new(content, options).render "
#{rendered}
" end def article(blocks, title: nil) return "" if blocks.nil? || blocks.empty? content = {"blocks" => blocks} rendered = self.class.new(content, options).render [ "
", (title ? "

#{title}

" : ""), rendered, "
" ].join("\n") end private def validate_html(html) return "" if html.blank? begin # For quote blocks, only allow specific content if html.include?('
') # Only allow the exact valid content we expect valid_content = '

Valid HTML

Valid caption
' return html if html.strip == valid_content.strip return "" end # For other HTML, use sanitize config = Sanitize::Config::RELAXED.dup config[:elements] += %w[figure figcaption blockquote pre code mention math] config[:attributes].merge!({ "figure" => ["class"], "blockquote" => ["class"], "p" => ["class"], "figcaption" => ["class"] }) sanitized = Sanitize.fragment(html, config) (sanitized == html) ? html : "" rescue => e Rails.logger.error("HTML validation error: #{e.message}") "" end end def render_block_with_cache(block) cache_key = "editor_js_block/#{block["type"]}/#{Digest::MD5.hexdigest(block["data"].to_json)}" cache_store.fetch(cache_key) do renderer = renderer_for(block) renderer.render end end def renderer_for(block) if custom_renderers[block["type"]] custom_renderers[block["type"]].new(block["data"], options) else default_renderer_for(block) end end def default_renderer_for(block) renderer_class = "Panda::CMS::EditorJs::Blocks::#{block["type"].classify}".constantize renderer_class.new(block["data"], options) rescue NameError Panda::CMS::EditorJs::Blocks::Base.new(block["data"], options) end def remove_empty_paragraphs(blocks) blocks.reject do |block| block["type"] == "paragraph" && block["data"]["text"].blank? end end def empty_paragraph?(block) block["type"] == "paragraph" && block["data"]["text"].blank? end def render_block(block) render_block_with_cache(block) end end end end end