embeddable_content 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|