embeddable_content 0.1.19
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rubocop.yml +62 -0
- data/.ruby-version +1 -0
- data/Gemfile +15 -0
- data/MIT-LICENSE +20 -0
- data/README.md +49 -0
- data/Rakefile +22 -0
- data/app/assets/config/embeddable_content_manifest.js +0 -0
- data/app/assets/images/embeddable_content/.keep +0 -0
- data/app/assets/javascripts/desmos/support.js +12 -0
- data/app/assets/javascripts/geogebra/support.js +14 -0
- data/app/assets/stylesheets/embeddable_content/.keep +0 -0
- data/app/controllers/.keep +0 -0
- data/app/controllers/concerns/cms/embeddable_content_controller.rb +14 -0
- data/app/controllers/concerns/embeddable_content_controller.rb +85 -0
- data/app/helpers/.keep +0 -0
- data/app/mailers/.keep +0 -0
- data/app/models/.keep +0 -0
- data/app/models/concerns/provides_embeddable_content.rb +59 -0
- data/app/models/embeddable_model_config.rb +55 -0
- data/app/models/embedder_config.rb +45 -0
- data/app/models/embedding.rb +25 -0
- data/app/services/embeddable_content/desmos_files/doc_processor.rb +5 -0
- data/app/services/embeddable_content/desmos_files/node_processor.rb +7 -0
- data/app/services/embeddable_content/doc_processor.rb +63 -0
- data/app/services/embeddable_content/embedded_tag_info.rb +50 -0
- data/app/services/embeddable_content/embedded_tags.rb +30 -0
- data/app/services/embeddable_content/embedder.rb +89 -0
- data/app/services/embeddable_content/embedder_base.rb +68 -0
- data/app/services/embeddable_content/fragment_embedder.rb +30 -0
- data/app/services/embeddable_content/geogebra_files/doc_processor.rb +5 -0
- data/app/services/embeddable_content/geogebra_files/node_processor.rb +11 -0
- data/app/services/embeddable_content/html_tags/doc_processor.rb +11 -0
- data/app/services/embeddable_content/html_tags/node_processor.rb +26 -0
- data/app/services/embeddable_content/images/attributions_processor.rb +60 -0
- data/app/services/embeddable_content/images/doc_processor.rb +54 -0
- data/app/services/embeddable_content/images/image_downloader.rb +60 -0
- data/app/services/embeddable_content/images/img_tag_attributes.rb +125 -0
- data/app/services/embeddable_content/images/modal_dialog.rb +74 -0
- data/app/services/embeddable_content/images/node_processor.rb +91 -0
- data/app/services/embeddable_content/images/shared.rb +11 -0
- data/app/services/embeddable_content/node_processor.rb +75 -0
- data/app/services/embeddable_content/presentation_tags/doc_processor.rb +5 -0
- data/app/services/embeddable_content/presentation_tags/node_processor.rb +35 -0
- data/app/services/embeddable_content/record_node_processor.rb +90 -0
- data/app/services/embeddable_content/replacement_template_manager.rb +21 -0
- data/app/services/embeddable_content/sad_embedded_tags.rb +29 -0
- data/app/services/embeddable_content/scrubber.rb +32 -0
- data/app/services/embeddable_content/template_based.rb +19 -0
- data/app/services/embeddable_content/template_manager.rb +100 -0
- data/app/services/embeddable_content/tex/base_renderer.rb +33 -0
- data/app/services/embeddable_content/tex/canvas_renderer.rb +15 -0
- data/app/services/embeddable_content/tex/doc_processor.rb +55 -0
- data/app/services/embeddable_content/tex/mathjax_renderer.rb +11 -0
- data/app/services/embeddable_content/tex/mml_renderer.rb +11 -0
- data/app/services/embeddable_content/tex/schoology_string_renderer.rb +15 -0
- data/app/services/embeddable_content/tex/svg_renderer.rb +11 -0
- data/app/services/embeddable_content/token_replacement_map.rb +32 -0
- data/app/services/embeddable_content/tree_based_node_processor.rb +23 -0
- data/app/services/embeddable_content/video_links/doc_processor.rb +5 -0
- data/app/services/embeddable_content/video_links/node_processor.rb +46 -0
- data/app/services/embeddable_content/video_links/vimeo_player_settings.rb +19 -0
- data/app/services/embeddable_content/visual_element_node_processor.rb +13 -0
- data/app/services/embeddable_content/widget_files/doc_processor.rb +5 -0
- data/app/services/embeddable_content/widget_files/node_processor.rb +7 -0
- data/app/views/.keep +0 -0
- data/app/views/embeddable_content/replacements/desmos_files/_applet.html.slim +8 -0
- data/app/views/embeddable_content/replacements/desmos_files/_description.html.slim +6 -0
- data/app/views/embeddable_content/replacements/desmos_files/cc.html.slim +1 -0
- data/app/views/embeddable_content/replacements/desmos_files/cms.html.slim +1 -0
- data/app/views/embeddable_content/replacements/desmos_files/editable.html.slim +1 -0
- data/app/views/embeddable_content/replacements/desmos_files/exported.html.slim +1 -0
- data/app/views/embeddable_content/replacements/desmos_files/kiddom.html.slim +1 -0
- data/app/views/embeddable_content/replacements/desmos_files/print.html.slim +1 -0
- data/app/views/embeddable_content/replacements/desmos_files/qti.html.slim +1 -0
- data/app/views/embeddable_content/replacements/desmos_files/schoology.html.slim +1 -0
- data/app/views/embeddable_content/replacements/desmos_files/web.html.slim +1 -0
- data/app/views/embeddable_content/replacements/geogebra_files/_applet.html.slim +11 -0
- data/app/views/embeddable_content/replacements/geogebra_files/_description.html.slim +9 -0
- data/app/views/embeddable_content/replacements/geogebra_files/cc.html.slim +1 -0
- data/app/views/embeddable_content/replacements/geogebra_files/cms.html.slim +1 -0
- data/app/views/embeddable_content/replacements/geogebra_files/editable.html.slim +1 -0
- data/app/views/embeddable_content/replacements/geogebra_files/exported.html.slim +1 -0
- data/app/views/embeddable_content/replacements/geogebra_files/kiddom.html.slim +1 -0
- data/app/views/embeddable_content/replacements/geogebra_files/print.html.slim +1 -0
- data/app/views/embeddable_content/replacements/geogebra_files/qti.html.slim +1 -0
- data/app/views/embeddable_content/replacements/geogebra_files/schoology.html.slim +1 -0
- data/app/views/embeddable_content/replacements/geogebra_files/web.html.slim +1 -0
- data/app/views/embeddable_content/replacements/html_tags/editable.html.slim +16 -0
- data/app/views/embeddable_content/replacements/images/_button_close.html.slim +9 -0
- data/app/views/embeddable_content/replacements/images/_button_open.html.slim +9 -0
- data/app/views/embeddable_content/replacements/images/_image_embed.html.slim +14 -0
- data/app/views/embeddable_content/replacements/images/_modal_content.html.slim +30 -0
- data/app/views/embeddable_content/replacements/images/_modal_dialog.html.slim +17 -0
- data/app/views/embeddable_content/replacements/images/cc.html.slim +12 -0
- data/app/views/embeddable_content/replacements/images/cms.html.slim +1 -0
- data/app/views/embeddable_content/replacements/images/editable.html.slim +1 -0
- data/app/views/embeddable_content/replacements/images/exported.html.slim +1 -0
- data/app/views/embeddable_content/replacements/images/kiddom.html.slim +12 -0
- data/app/views/embeddable_content/replacements/images/print.html.slim +1 -0
- data/app/views/embeddable_content/replacements/images/qti.html.slim +6 -0
- data/app/views/embeddable_content/replacements/images/schoology.html.slim +12 -0
- data/app/views/embeddable_content/replacements/images/web.html.slim +1 -0
- data/app/views/embeddable_content/replacements/presentation_tags/_default.html.slim +6 -0
- data/app/views/embeddable_content/replacements/presentation_tags/print.html.slim +1 -0
- data/app/views/embeddable_content/replacements/status/warning.html.slim +12 -0
- data/app/views/embeddable_content/replacements/video_links/_caption.html.slim +13 -0
- data/app/views/embeddable_content/replacements/video_links/_description.html.slim +12 -0
- data/app/views/embeddable_content/replacements/video_links/_video_embed.html.slim +13 -0
- data/app/views/embeddable_content/replacements/video_links/_video_player.html.slim +6 -0
- data/app/views/embeddable_content/replacements/video_links/_vimeo_player.html.slim +6 -0
- data/app/views/embeddable_content/replacements/video_links/cc.html.slim +1 -0
- data/app/views/embeddable_content/replacements/video_links/cms.html.slim +1 -0
- data/app/views/embeddable_content/replacements/video_links/editable.html.slim +1 -0
- data/app/views/embeddable_content/replacements/video_links/exported.html.slim +1 -0
- data/app/views/embeddable_content/replacements/video_links/kiddom.html.slim +1 -0
- data/app/views/embeddable_content/replacements/video_links/print.html.slim +1 -0
- data/app/views/embeddable_content/replacements/video_links/qti.html.slim +1 -0
- data/app/views/embeddable_content/replacements/video_links/schoology.html.slim +1 -0
- data/app/views/embeddable_content/replacements/video_links/web.html.slim +1 -0
- data/app/views/embeddable_content/replacements/widget_files/_widget_omitted.html.slim +8 -0
- data/app/views/embeddable_content/replacements/widget_files/_widget_script.html.slim +13 -0
- data/app/views/embeddable_content/replacements/widget_files/cc.html.slim +1 -0
- data/app/views/embeddable_content/replacements/widget_files/cms.html.slim +1 -0
- data/app/views/embeddable_content/replacements/widget_files/editable.html.slim +1 -0
- data/app/views/embeddable_content/replacements/widget_files/exported.html.slim +1 -0
- data/app/views/embeddable_content/replacements/widget_files/kiddom.html.slim +1 -0
- data/app/views/embeddable_content/replacements/widget_files/print.html.slim +1 -0
- data/app/views/embeddable_content/replacements/widget_files/qti.html.slim +1 -0
- data/app/views/embeddable_content/replacements/widget_files/schoology.html.slim +1 -0
- data/app/views/embeddable_content/replacements/widget_files/web.html.slim +1 -0
- data/bin/rails +25 -0
- data/config/initializers/embeddable_content.rb +3 -0
- data/config/routes.rb +2 -0
- data/embeddable_content.gemspec +48 -0
- data/lib/embeddable_content/engine.rb +8 -0
- data/lib/embeddable_content/version.rb +3 -0
- data/lib/embeddable_content.rb +3 -0
- data/lib/tasks/embeddable_content_tasks.rake +4 -0
- metadata +309 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
module EmbeddableContent
|
2
|
+
class EmbeddedTags
|
3
|
+
attr_reader :record, :attr, :embedded_models
|
4
|
+
|
5
|
+
def initialize(record, attr, embedded_models)
|
6
|
+
@record = record
|
7
|
+
@attr = attr
|
8
|
+
@embedded_models = embedded_models
|
9
|
+
end
|
10
|
+
|
11
|
+
def each
|
12
|
+
embedded_models.each do |model|
|
13
|
+
document.css(model.embeddable_tag_name).each do |dom_node|
|
14
|
+
info = EmbeddedTagInfo.new record, model, attr, dom_node
|
15
|
+
yield info if info.available?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def document
|
23
|
+
@document ||= Nokogiri::HTML.fragment searchable_text
|
24
|
+
end
|
25
|
+
|
26
|
+
def searchable_text
|
27
|
+
record[attr].is_a?(Array) ? record[attr].join(' ') : record[attr]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module EmbeddableContent
|
2
|
+
class Embedder < EmbedderBase
|
3
|
+
NO_REPLACEMENTS_BY_DEFAULT = {}.freeze
|
4
|
+
|
5
|
+
attr_reader :html, :document
|
6
|
+
|
7
|
+
def initialize(target, options = {})
|
8
|
+
super EmbedderConfig.for_target(target), options
|
9
|
+
end
|
10
|
+
|
11
|
+
def image_catalog
|
12
|
+
@image_catalog ||= Set.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def embed_content!(content)
|
16
|
+
@html = content.to_str.dup
|
17
|
+
@document = scrub parse_html
|
18
|
+
perform_replacements
|
19
|
+
run_doc_processors
|
20
|
+
write_embedded_html_to_tmp_file if debug_embedder?
|
21
|
+
@html
|
22
|
+
end
|
23
|
+
|
24
|
+
def rebuild_document
|
25
|
+
@document = parse_html
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def parse_html
|
31
|
+
case scope&.to_sym
|
32
|
+
when :document then html_parser.parse html
|
33
|
+
when :fragment then html_parser.fragment html
|
34
|
+
else raise "Unrecognized parsing scope: #{scope}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def run_doc_processors
|
39
|
+
doc_processors.each(&:process!)
|
40
|
+
end
|
41
|
+
|
42
|
+
def html_parser
|
43
|
+
@html_parser ||=
|
44
|
+
case output_format&.to_sym
|
45
|
+
when :html then Nokogiri::HTML
|
46
|
+
when :xml then Nokogiri::XML
|
47
|
+
else raise "Unrecognized output format: #{output_format}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def scrub(document)
|
52
|
+
Scrubber.new(config).scrub document
|
53
|
+
end
|
54
|
+
|
55
|
+
def perform_replacements
|
56
|
+
replacements.each { |old, new| html.gsub! old, new }
|
57
|
+
end
|
58
|
+
|
59
|
+
def replacements
|
60
|
+
token_replacement_map[target] || NO_REPLACEMENTS_BY_DEFAULT
|
61
|
+
end
|
62
|
+
|
63
|
+
def token_replacement_map
|
64
|
+
@token_replacement_map ||=
|
65
|
+
TokenReplacementMap.new(replacement_map).target_regexp_map
|
66
|
+
end
|
67
|
+
|
68
|
+
def doc_processors
|
69
|
+
@doc_processors ||=
|
70
|
+
embeddings.map { |embedding| embedding.doc_processor_for self }
|
71
|
+
end
|
72
|
+
|
73
|
+
def embeddings
|
74
|
+
@embeddings ||= Embedding.all.in_run_order
|
75
|
+
end
|
76
|
+
|
77
|
+
def debug_embedder?
|
78
|
+
ENV['DEBUG_EMBEDDER'] == 'true'
|
79
|
+
end
|
80
|
+
|
81
|
+
def write_embedded_html_to_tmp_file
|
82
|
+
Rails.root.join('tmp').join(temp_filename).write html
|
83
|
+
end
|
84
|
+
|
85
|
+
def temp_filename
|
86
|
+
Time.current.strftime "embedded-#{target}-%Y-%m-%d-%H%M.html"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require Rails.root.join 'lib/tasks/support/verbosity.rb'
|
2
|
+
|
3
|
+
module EmbeddableContent
|
4
|
+
class EmbedderBase
|
5
|
+
include HtmlService::Helpers
|
6
|
+
include Verbosity
|
7
|
+
|
8
|
+
attr_reader :config, :options
|
9
|
+
|
10
|
+
delegate :tex_output_format, :render_images?, :scope, :xml?,
|
11
|
+
:output_format, :fragment?, :remove_repaired_math_spans?,
|
12
|
+
:replacement_map, :all_other_targets, :aria_attrs?,
|
13
|
+
to: :config
|
14
|
+
|
15
|
+
def self.default_s3_bucket
|
16
|
+
ENV['AWS_S3_BUCKET']
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(config, options = {})
|
20
|
+
@config = config
|
21
|
+
@options = options
|
22
|
+
show_options if show_options?
|
23
|
+
end
|
24
|
+
|
25
|
+
def target
|
26
|
+
@target ||= config.target.to_sym
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
"#{self.class} for #{config.inspect}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def s3_bucket
|
34
|
+
@s3_bucket ||= options[:s3_bucket] ||
|
35
|
+
self.class.default_s3_bucket
|
36
|
+
end
|
37
|
+
|
38
|
+
def ed_node
|
39
|
+
@ed_node ||= options[:ed_node]
|
40
|
+
end
|
41
|
+
|
42
|
+
def tree_node
|
43
|
+
@tree_node ||= ed_node&.root
|
44
|
+
end
|
45
|
+
|
46
|
+
def tree
|
47
|
+
@tree ||= tree_node&.ref
|
48
|
+
end
|
49
|
+
|
50
|
+
def locale
|
51
|
+
@locale ||= tree&.locale
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def module_name
|
57
|
+
@module_name ||= self.class.name.deconstantize
|
58
|
+
end
|
59
|
+
|
60
|
+
def embedding_module
|
61
|
+
@embedding_module ||= module_name.demodulize
|
62
|
+
end
|
63
|
+
|
64
|
+
def route_keys
|
65
|
+
@route_keys ||= [embedding_module.underscore]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module EmbeddableContent
|
2
|
+
class FragmentEmbedder < EmbedderBase
|
3
|
+
attr_reader :fragment
|
4
|
+
|
5
|
+
def initialize(fragment)
|
6
|
+
super EmbedderConfig.for_target(:exported)
|
7
|
+
@fragment = fragment
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
return '' if fragment.blank?
|
12
|
+
|
13
|
+
embedder.embed_content!(fragment)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def embedder
|
19
|
+
@embedder ||= Embedder.new config
|
20
|
+
end
|
21
|
+
|
22
|
+
def opts
|
23
|
+
@opts ||= { s3_bucket: s3_bucket }
|
24
|
+
end
|
25
|
+
|
26
|
+
def s3_bucket
|
27
|
+
ENV.fetch('OCX_EXPORT_DEST_S3_BUCKET', 'cms-im')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module EmbeddableContent
|
2
|
+
module HtmlTags
|
3
|
+
class NodeProcessor < EmbeddableContent::NodeProcessor
|
4
|
+
include TemplateBased
|
5
|
+
|
6
|
+
def data_rows
|
7
|
+
@data_rows ||= list_data.each_slice(num_columns).to_a
|
8
|
+
end
|
9
|
+
|
10
|
+
def node_selector_class
|
11
|
+
'embedded-unstructured-data'
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def list_data
|
17
|
+
@list_data ||= node.css('li').map(&:text)
|
18
|
+
end
|
19
|
+
|
20
|
+
DEFAULT_NUM_COLUMNS = 8
|
21
|
+
def num_columns
|
22
|
+
DEFAULT_NUM_COLUMNS
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module EmbeddableContent
|
2
|
+
module Images
|
3
|
+
class AttributionsProcessor
|
4
|
+
attr_reader :image_processor
|
5
|
+
|
6
|
+
delegate :embedder, :target, :document, :image_catalog, to: :image_processor
|
7
|
+
delegate :locale, to: :embedder
|
8
|
+
|
9
|
+
TARGETS_NEEDING_ATTRIBUTIONS = %i[print].freeze
|
10
|
+
ATTRIBUTIONS_PARTIAL = 'export/shared/attributions_images'.freeze
|
11
|
+
ATTRIBUTIONS_NODE_ID = 'image-attributions-node'.freeze
|
12
|
+
ATTRIBUTIONS_NODE_SELECTOR = "div##{ATTRIBUTIONS_NODE_ID}".freeze
|
13
|
+
|
14
|
+
def initialize(image_processor)
|
15
|
+
@image_processor = image_processor
|
16
|
+
end
|
17
|
+
|
18
|
+
def process!
|
19
|
+
render_attributions if attributions_required?
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def attributions_required?
|
25
|
+
TARGETS_NEEDING_ATTRIBUTIONS.include?(target) &&
|
26
|
+
attributions_node.present? &&
|
27
|
+
image_catalog.present? &&
|
28
|
+
attributions.present?
|
29
|
+
end
|
30
|
+
|
31
|
+
def attributions
|
32
|
+
@attributions =
|
33
|
+
image_catalog.select(&:attribution).compact
|
34
|
+
end
|
35
|
+
|
36
|
+
def attributions_node
|
37
|
+
@attributions_node ||= document.css(ATTRIBUTIONS_NODE_SELECTOR).first
|
38
|
+
end
|
39
|
+
|
40
|
+
def render_attributions
|
41
|
+
attributions_node.replace(attributions_fragment)
|
42
|
+
end
|
43
|
+
|
44
|
+
def attributions_html
|
45
|
+
I18n.with_locale(locale) { render_html }
|
46
|
+
end
|
47
|
+
|
48
|
+
def render_html
|
49
|
+
ApplicationController.renderer.render(
|
50
|
+
partial: ATTRIBUTIONS_PARTIAL,
|
51
|
+
locals: { image_files: image_catalog }
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
def attributions_fragment
|
56
|
+
@attributions_fragment ||= Nokogiri::HTML.fragment(attributions_html)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EmbeddableContent
|
4
|
+
module Images
|
5
|
+
class DocProcessor < EmbeddableContent::DocProcessor
|
6
|
+
delegate :image_catalog, to: :embedder
|
7
|
+
|
8
|
+
CLASS_IMG_TAG_RELOCATED = 'img-tag-relocated-by-embedder'
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def pre_process
|
13
|
+
move_img_tags_out_from_p_tags
|
14
|
+
end
|
15
|
+
|
16
|
+
def post_process
|
17
|
+
attributions_processor.process!
|
18
|
+
end
|
19
|
+
|
20
|
+
def attributions_processor
|
21
|
+
@attributions_processor ||=
|
22
|
+
EmbeddableContent::Images::AttributionsProcessor.new self
|
23
|
+
end
|
24
|
+
|
25
|
+
def node_selector
|
26
|
+
'img'
|
27
|
+
end
|
28
|
+
|
29
|
+
def move_img_tags_out_from_p_tags
|
30
|
+
img_tags_inside_p_tags.each do |img_tag|
|
31
|
+
move_img_tag_out_from_p_tag img_tag
|
32
|
+
end
|
33
|
+
remove_affected_empty_p_tags
|
34
|
+
end
|
35
|
+
|
36
|
+
def remove_affected_empty_p_tags
|
37
|
+
document.css("p.#{CLASS_IMG_TAG_RELOCATED}").each do |p_tag|
|
38
|
+
remove_if_empty p_tag
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def move_img_tag_out_from_p_tag(img_tag)
|
43
|
+
img_tag.parent.tap do |p_tag|
|
44
|
+
p_tag.add_previous_sibling img_tag
|
45
|
+
p_tag.add_class CLASS_IMG_TAG_RELOCATED
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def img_tags_inside_p_tags
|
50
|
+
@img_tags_inside_p_tags ||= document.css 'p > img'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module EmbeddableContent
|
2
|
+
module Images
|
3
|
+
class ImageDownloader
|
4
|
+
attr_reader :attached_image
|
5
|
+
|
6
|
+
delegate :to_path, to: :image_file
|
7
|
+
|
8
|
+
def initialize(attached_image)
|
9
|
+
@attached_image = attached_image
|
10
|
+
end
|
11
|
+
|
12
|
+
def save
|
13
|
+
image_file.tap { |file| file.write raw_image_data }
|
14
|
+
end
|
15
|
+
|
16
|
+
def delete
|
17
|
+
image_file.delete if image_file.exist?
|
18
|
+
end
|
19
|
+
|
20
|
+
def raw_svg
|
21
|
+
@raw_svg ||= downloaded_image_data if svg_attached?
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
TMP_IMAGE_DIR = Rails.root.join('tmp').freeze
|
27
|
+
|
28
|
+
def image_file
|
29
|
+
@image_file ||= TMP_IMAGE_DIR.join filename
|
30
|
+
end
|
31
|
+
|
32
|
+
def raw_image_data
|
33
|
+
png_rendered_from_svg || downloaded_image_data
|
34
|
+
end
|
35
|
+
|
36
|
+
def png_rendered_from_svg
|
37
|
+
Script::SvgToPng.new(downloaded_image_data).to_png if svg_attached?
|
38
|
+
end
|
39
|
+
|
40
|
+
def downloaded_image_data
|
41
|
+
@downloaded_image_data ||=
|
42
|
+
attached_image.download.force_encoding 'utf-8'
|
43
|
+
end
|
44
|
+
|
45
|
+
def filename
|
46
|
+
@filename ||= "embedder-#{Time.now.to_f}.#{extension}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def extension
|
50
|
+
@extension ||=
|
51
|
+
svg_attached? ? 'png' : attached_image.filename.extension
|
52
|
+
end
|
53
|
+
|
54
|
+
CONTENT_TYPE_SVG = 'image/svg+xml'.freeze
|
55
|
+
def svg_attached?
|
56
|
+
attached_image.content_type.eql? CONTENT_TYPE_SVG
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module EmbeddableContent
|
2
|
+
module Images
|
3
|
+
class ImgTagAttributes
|
4
|
+
include Images::Shared
|
5
|
+
attr_reader :node_processor
|
6
|
+
|
7
|
+
delegate :alt_text, :attached_file, :record, :aria_attrs?,
|
8
|
+
:cms_url, :s3_url, :s3_ttl_service_url, :target,
|
9
|
+
:node, to: :node_processor
|
10
|
+
delegate :image, to: :record
|
11
|
+
delegate :raw_svg, to: :image_downloader
|
12
|
+
|
13
|
+
def initialize(node_processor)
|
14
|
+
@node_processor = node_processor
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_h
|
18
|
+
{ alt: strip_tags(alt_text),
|
19
|
+
src: src,
|
20
|
+
height: height,
|
21
|
+
class: css_classes,
|
22
|
+
width: width }.merge(aria_attrs).compact
|
23
|
+
end
|
24
|
+
|
25
|
+
def src
|
26
|
+
case target
|
27
|
+
when :schoology, :cc, :kiddom then s3_url with_extension: false
|
28
|
+
when :cms then cms_url
|
29
|
+
when :editable then downloaded_file_url
|
30
|
+
when :exported, :qti then s3_url with_extension: false
|
31
|
+
when :print, :web then s3_ttl_service_url
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def width
|
36
|
+
case target
|
37
|
+
when :editable then width_for_editable_target
|
38
|
+
when :cc, :kiddom, :qti, :schoology then image_width
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# TODO: should this be abstracted?
|
43
|
+
def height
|
44
|
+
case target
|
45
|
+
when :editable then height_for_editable_target
|
46
|
+
when :cc, :kiddom, :qti, :schoology then image_height
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def aria_attrs
|
53
|
+
aria_attrs? ? { role: :image } : {}
|
54
|
+
end
|
55
|
+
|
56
|
+
def css_classes
|
57
|
+
node.classes.any? ? node.classes.join(' ') : nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def svg_image?
|
61
|
+
image.attached? && image.content_type == 'image/svg+xml'
|
62
|
+
end
|
63
|
+
|
64
|
+
def use_original_svg_dimensions_for_converted_png_file?
|
65
|
+
svg_image? && svg_document.present?
|
66
|
+
end
|
67
|
+
|
68
|
+
def downloaded_file_url
|
69
|
+
downloaded_image.to_path
|
70
|
+
end
|
71
|
+
|
72
|
+
def downloaded_image
|
73
|
+
@downloaded_image ||= image_downloader.save
|
74
|
+
end
|
75
|
+
|
76
|
+
def image_downloader
|
77
|
+
@image_downloader ||= ImageDownloader.new attached_file
|
78
|
+
end
|
79
|
+
|
80
|
+
def height_for_editable_target
|
81
|
+
svg_height if use_original_svg_dimensions_for_converted_png_file?
|
82
|
+
end
|
83
|
+
|
84
|
+
def width_for_editable_target
|
85
|
+
svg_width if use_original_svg_dimensions_for_converted_png_file?
|
86
|
+
end
|
87
|
+
|
88
|
+
def svg_width
|
89
|
+
svg_document.root['width'] if svg_available?
|
90
|
+
end
|
91
|
+
|
92
|
+
def svg_height
|
93
|
+
svg_document.root['height'] if svg_available?
|
94
|
+
end
|
95
|
+
|
96
|
+
def svg_available?
|
97
|
+
svg_image? && svg_document.present?
|
98
|
+
end
|
99
|
+
|
100
|
+
def svg_document
|
101
|
+
@svg_document ||= Nokogiri::XML.parse raw_svg if raw_svg.present?
|
102
|
+
end
|
103
|
+
|
104
|
+
def image_width
|
105
|
+
image.metadata[:width] || image_dimensions[1]
|
106
|
+
end
|
107
|
+
|
108
|
+
def image_height
|
109
|
+
image.metadata[:height] || image_dimensions[2]
|
110
|
+
end
|
111
|
+
|
112
|
+
IMAGE_DIMENSION_SCRIPT = %(identify -ping -format "%[w]x%[h]").freeze
|
113
|
+
IMAGE_DIMENSION_REGEX = /(\d+)x(\d+)/.freeze
|
114
|
+
|
115
|
+
def run_image_dimension_script
|
116
|
+
`#{IMAGE_DIMENSION_SCRIPT} #{downloaded_file_url}`
|
117
|
+
end
|
118
|
+
|
119
|
+
def image_dimensions
|
120
|
+
@image_dimensions ||=
|
121
|
+
run_image_dimension_script.match IMAGE_DIMENSION_REGEX
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module EmbeddableContent
|
2
|
+
module Images
|
3
|
+
class ModalDialog
|
4
|
+
include Images::Shared
|
5
|
+
|
6
|
+
attr_reader :node_processor
|
7
|
+
|
8
|
+
delegate :record_css_id_for, :s3_ttl_service_url, :caption, :attribution,
|
9
|
+
:alt_text, to: :node_processor
|
10
|
+
|
11
|
+
def initialize(node_processor)
|
12
|
+
@node_processor = node_processor
|
13
|
+
end
|
14
|
+
|
15
|
+
def img_tag_attrs
|
16
|
+
{ src: s3_ttl_service_url,
|
17
|
+
width: '80%',
|
18
|
+
alt: strip_tags(alt_text),
|
19
|
+
role: :image }
|
20
|
+
end
|
21
|
+
|
22
|
+
def attribution_display_text
|
23
|
+
[attribution.owner_and_title_info,
|
24
|
+
attribution.license.name,
|
25
|
+
attribution.via_reference].join('. ').strip + '.'
|
26
|
+
end
|
27
|
+
|
28
|
+
def container_attrs
|
29
|
+
{ tabindex: '-1',
|
30
|
+
role: 'dialog',
|
31
|
+
'aria-labelledby': labelled_by_id,
|
32
|
+
'aria-describedby': described_by_id,
|
33
|
+
'aria-label': 'Image information' }
|
34
|
+
end
|
35
|
+
|
36
|
+
def target_attrs
|
37
|
+
{ 'id': target_div_id,
|
38
|
+
'hidden': 'true',
|
39
|
+
'aria-hidden': 'true' }
|
40
|
+
end
|
41
|
+
|
42
|
+
def close_button_attrs
|
43
|
+
{ 'aria-hidden': 'true',
|
44
|
+
height: '18',
|
45
|
+
width: '18',
|
46
|
+
viewBox: '0 0 1200 1200' }
|
47
|
+
end
|
48
|
+
|
49
|
+
def description
|
50
|
+
node_processor.long_description
|
51
|
+
end
|
52
|
+
|
53
|
+
def labelled_by_id
|
54
|
+
record_css_id_for 'dialog-alt-text'
|
55
|
+
end
|
56
|
+
|
57
|
+
def described_by_id
|
58
|
+
record_css_id_for 'dialog-long-description'
|
59
|
+
end
|
60
|
+
|
61
|
+
def target_div_id
|
62
|
+
record_css_id_for 'modal-target'
|
63
|
+
end
|
64
|
+
|
65
|
+
def caption_id
|
66
|
+
record_css_id_for 'dialog-caption'
|
67
|
+
end
|
68
|
+
|
69
|
+
def attribution_id
|
70
|
+
record_css_id_for 'dialog-attribution'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|