article_json 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -1
- data/CODE_OF_CONDUCT.md +46 -0
- data/README.md +145 -1
- data/lib/article_json/article.rb +31 -2
- data/lib/article_json/configuration.rb +56 -0
- data/lib/article_json/elements/paragraph.rb +14 -0
- data/lib/article_json/elements/text.rb +13 -0
- data/lib/article_json/export/amp/custom_element_library_resolver.rb +55 -0
- data/lib/article_json/export/amp/elements/base.rb +36 -0
- data/lib/article_json/export/amp/elements/embed.rb +97 -0
- data/lib/article_json/export/amp/elements/heading.rb +11 -0
- data/lib/article_json/export/amp/elements/image.rb +30 -0
- data/lib/article_json/export/amp/elements/list.rb +11 -0
- data/lib/article_json/export/amp/elements/paragraph.rb +11 -0
- data/lib/article_json/export/amp/elements/quote.rb +11 -0
- data/lib/article_json/export/amp/elements/text.rb +11 -0
- data/lib/article_json/export/amp/elements/text_box.rb +11 -0
- data/lib/article_json/export/amp/exporter.rb +36 -0
- data/lib/article_json/export/common/html/elements/base.rb +111 -0
- data/lib/article_json/export/common/html/elements/embed.rb +34 -0
- data/lib/article_json/export/common/html/elements/heading.rb +23 -0
- data/lib/article_json/export/common/html/elements/image.rb +36 -0
- data/lib/article_json/export/common/html/elements/list.rb +36 -0
- data/lib/article_json/export/common/html/elements/paragraph.rb +29 -0
- data/lib/article_json/export/common/html/elements/quote.rb +32 -0
- data/lib/article_json/export/common/html/elements/shared/caption.rb +32 -0
- data/lib/article_json/export/common/html/elements/shared/float.rb +19 -0
- data/lib/article_json/export/common/html/elements/text.rb +57 -0
- data/lib/article_json/export/common/html/elements/text_box.rb +29 -0
- data/lib/article_json/export/common/html/exporter.rb +31 -0
- data/lib/article_json/export/html/elements/base.rb +14 -43
- data/lib/article_json/export/html/elements/embed.rb +1 -18
- data/lib/article_json/export/html/elements/heading.rb +1 -9
- data/lib/article_json/export/html/elements/image.rb +1 -23
- data/lib/article_json/export/html/elements/list.rb +1 -15
- data/lib/article_json/export/html/elements/paragraph.rb +1 -7
- data/lib/article_json/export/html/elements/quote.rb +1 -19
- data/lib/article_json/export/html/elements/text.rb +1 -34
- data/lib/article_json/export/html/elements/text_box.rb +1 -15
- data/lib/article_json/export/html/exporter.rb +6 -11
- data/lib/article_json/import/google_doc/html/embedded_vimeo_video_parser.rb +4 -4
- data/lib/article_json/import/google_doc/html/node_analyzer.rb +24 -2
- data/lib/article_json/import/google_doc/html/parser.rb +16 -7
- data/lib/article_json/import/google_doc/html/shared/caption.rb +7 -0
- data/lib/article_json/import/google_doc/html/text_parser.rb +4 -1
- data/lib/article_json/utils/additional_element_placer.rb +66 -0
- data/lib/article_json/utils/o_embed_resolver/facebook_video.rb +1 -1
- data/lib/article_json/utils/o_embed_resolver/slideshare.rb +1 -1
- data/lib/article_json/utils/o_embed_resolver/youtube_video.rb +1 -1
- data/lib/article_json/utils.rb +1 -0
- data/lib/article_json/version.rb +1 -1
- data/lib/article_json.rb +25 -2
- metadata +31 -6
- data/lib/article_json/export/html/elements/shared/caption.rb +0 -22
- data/lib/article_json/export/html/elements/shared/float.rb +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 416c6c5505374ac9303aa393dcfbfb361276fa45
|
4
|
+
data.tar.gz: 5e22d9856b75f0d16203678d2b00e9faa05831b5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 015714d59724701d0fc6edda9a7257315d903055c6d5c3a7c233e4f0780e2f6cb227e2c02c0f30df5999d6d8bc75da643c59bda757858386c55f604f8166b601
|
7
|
+
data.tar.gz: dd5b53abece174c23c2639ae80ef53f28bd79004249c46dc4aec26e1c0de4fa9bb91512be40625394b3e7c740bd3229e2596fba0655d2837d0cca5e0636db651
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,19 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
##
|
3
|
+
## 0.2.0 - 2017/11/03
|
4
|
+
In this second release we **added support** to:
|
5
|
+
- Export AMP along with required libraries for AMP rendering
|
6
|
+
- Configure custom HTML and AMP element exporters
|
7
|
+
- Resolve oembed elements in HTML export
|
8
|
+
|
9
|
+
One potentially **breaking change** was added:
|
10
|
+
- Export quotes as `<div>` instead of `<aside>`
|
11
|
+
|
12
|
+
**Fixes**:
|
13
|
+
- Support Vimeo videos with old flash player URLs
|
14
|
+
- Make Google Parser more fault tolerant
|
15
|
+
- Respect linebreaks when importing Google Docs
|
16
|
+
- Export linebreaks in JSON to `<br>` tags in HTML / AMP
|
4
17
|
|
5
18
|
## 0.1.0 - 2017/09/20
|
6
19
|
This is the very first release, with the following functionality:
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
6
|
+
|
7
|
+
## Our Standards
|
8
|
+
|
9
|
+
Examples of behavior that contributes to creating a positive environment include:
|
10
|
+
|
11
|
+
* Using welcoming and inclusive language
|
12
|
+
* Being respectful of differing viewpoints and experiences
|
13
|
+
* Gracefully accepting constructive criticism
|
14
|
+
* Focusing on what is best for the community
|
15
|
+
* Showing empathy towards other community members
|
16
|
+
|
17
|
+
Examples of unacceptable behavior by participants include:
|
18
|
+
|
19
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
20
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
21
|
+
* Public or private harassment
|
22
|
+
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
23
|
+
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
24
|
+
|
25
|
+
## Our Responsibilities
|
26
|
+
|
27
|
+
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
28
|
+
|
29
|
+
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
30
|
+
|
31
|
+
## Scope
|
32
|
+
|
33
|
+
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
34
|
+
|
35
|
+
## Enforcement
|
36
|
+
|
37
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at info@devex.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
38
|
+
|
39
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
40
|
+
|
41
|
+
## Attribution
|
42
|
+
|
43
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
44
|
+
|
45
|
+
[homepage]: http://contributor-covenant.org
|
46
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/README.md
CHANGED
@@ -27,6 +27,11 @@ article = ArticleJSON::Article.from_hash(parsed_json)
|
|
27
27
|
# export article as HTML
|
28
28
|
puts article.to_html
|
29
29
|
|
30
|
+
# export article as AMP
|
31
|
+
puts article.to_amp
|
32
|
+
# get javascript libraries needed for the AMP article
|
33
|
+
puts article.amp_exporter.amp_libraries
|
34
|
+
|
30
35
|
# export article as JSON
|
31
36
|
puts article.to_json
|
32
37
|
```
|
@@ -42,6 +47,44 @@ $ ./bin/article_json_export_google_doc.rb $DOC_ID \
|
|
42
47
|
| ./bin/article_json_export_html.rb
|
43
48
|
```
|
44
49
|
|
50
|
+
### Configuration
|
51
|
+
There are some configuration options that allow a more tailored usage of the
|
52
|
+
`article_json` gem. The following code snippet gives an example for every
|
53
|
+
available setting:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
ArticleJSON.configure do |config|
|
57
|
+
# set a custom user agent used for o-embed API calls
|
58
|
+
config.oembed_user_agent = 'devex oembed (+https://www.devex.com/)'
|
59
|
+
|
60
|
+
# Register additional html exporters, just make sure that it complies with the
|
61
|
+
# interface of other element exporter classes (extend Base, implement #export)
|
62
|
+
config.register_element_exporters(
|
63
|
+
:html,
|
64
|
+
advertisement: ArticleJSON::Export::HTML::Elements::Advertisement
|
65
|
+
)
|
66
|
+
|
67
|
+
# You can also overwrite existing exporters:
|
68
|
+
config.register_element_exporters(
|
69
|
+
:html,
|
70
|
+
image: ArticleJSON::Export::HTML::Elements::ScaledImage
|
71
|
+
)
|
72
|
+
|
73
|
+
# And you can define multiple custom exporters:
|
74
|
+
config.register_element_exporters(
|
75
|
+
:html,
|
76
|
+
advertisement: ArticleJSON::Export::HTML::Elements::Advertisement,
|
77
|
+
image: ArticleJSON::Export::HTML::Elements::ScaledImage
|
78
|
+
)
|
79
|
+
|
80
|
+
# It works the same way for custom AMP exporters:
|
81
|
+
config.register_element_exporters(
|
82
|
+
:amp,
|
83
|
+
image: ArticleJSON::Export::AMP::Elements::ScaledImage
|
84
|
+
)
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
45
88
|
## Format
|
46
89
|
A full example of the format can be found in the test fixtures:
|
47
90
|
[Parsed Reference Document](https://github.com/Devex/article_json/blob/master/spec/fixtures/reference_document_parsed.json)
|
@@ -51,12 +94,113 @@ A full example of the format can be found in the test fixtures:
|
|
51
94
|
This [Reference Document](https://docs.google.com/document/d/1E4lncZE2jDkbE34eDyYQmXKA9O26BHUiwguz4S9qyE8/edit?usp=sharing)
|
52
95
|
lists contains all supported formatting along with some descriptions.
|
53
96
|
|
97
|
+
## Add custom elements
|
98
|
+
Sometimes you might want to place additional elements into the article, like e.g. advertisements.
|
99
|
+
`article_json` supports this via `article.place_additional_elements` which accepts an array of elements that you can define in your own code.
|
100
|
+
Each element that is added this way will directly get placed in between paragraphs of the article.
|
101
|
+
The method ensures that an additional element is never added before or after any node other than paragraphs (e.g. an image).
|
102
|
+
The elements are added in the order you pass them into the method.
|
103
|
+
If the article should not have enough spaces to place all the provided elements, they will be placed after the last element in the article.
|
104
|
+
|
105
|
+
You can pass any type of element into this method.
|
106
|
+
If the objects you pass in are instances of elements defined within this gem (e.g. `ArticleJSON::Elements::Image`), you won't have to do anything else to get them rendered.
|
107
|
+
If you pass in an instance of a custom class (e.g. `MyAdvertisement`), make sure to register an exporter for this type (check the _Configuration_ section for more details).
|
108
|
+
|
109
|
+
Example using only existing elements:
|
110
|
+
```ruby
|
111
|
+
# Create your article instance as you normally do
|
112
|
+
article = ArticleJSON::Article.from_hash(parsed_json)
|
113
|
+
|
114
|
+
# Within your code, create additional elements you would like to add
|
115
|
+
image_advertisement =
|
116
|
+
ArticleJSON::Elements::Image.new(source_url: 'https://robohash.org/great-ad',
|
117
|
+
caption: ArticleJSON::Elements::Text.new(
|
118
|
+
content: 'Buy more robots!',
|
119
|
+
href: '/robot-sale'
|
120
|
+
))
|
121
|
+
text_box_similar_articles =
|
122
|
+
ArticleJSON::Elements::TextBox.new(content: [
|
123
|
+
ArticleJSON::Elements::Heading.new(level: 3, content: 'Read more...'),
|
124
|
+
ArticleJSON::Elements::List.new(content: [
|
125
|
+
ArticleJSON::Elements::Paragraph(content: [
|
126
|
+
ArticleJSON::Elements::Text.new(content: 'Very similar article',
|
127
|
+
href: '/news/123'),
|
128
|
+
]),
|
129
|
+
ArticleJSON::Elements::Paragraph(content: [
|
130
|
+
ArticleJSON::Elements::Text.new(content: 'Great article!',
|
131
|
+
href: '/news/42'),
|
132
|
+
]),
|
133
|
+
]),
|
134
|
+
])
|
135
|
+
|
136
|
+
# Add these elements to the article
|
137
|
+
article.place_additional_elements([image_advertisement,
|
138
|
+
text_box_similar_articles])
|
139
|
+
|
140
|
+
# Export the article to the different formats as you would normally do
|
141
|
+
article.to_html # this will now include the custom elements
|
142
|
+
```
|
143
|
+
|
144
|
+
Example with custom advertisement elements:
|
145
|
+
```ruby
|
146
|
+
# Define your custom element class
|
147
|
+
class MyAdvertisement
|
148
|
+
attr_reader :url
|
149
|
+
|
150
|
+
def initialize(url:)
|
151
|
+
@url = url
|
152
|
+
end
|
153
|
+
|
154
|
+
def type
|
155
|
+
:my_advertisement
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Define an exporter for your class, we only use HTML in this example but this
|
160
|
+
# would work similarly for AMP or other formats
|
161
|
+
class MyAdvertisementExporter <
|
162
|
+
ArticleJSON::Export::HTML::Elements::Base
|
163
|
+
|
164
|
+
# Needs to implement the `#export` method
|
165
|
+
def export
|
166
|
+
create_element(:iframe, src: @element.url)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Register your custom exporter for your element type
|
171
|
+
config.register_element_exporters(
|
172
|
+
:html,
|
173
|
+
my_advertisement: MyAdvertisementExporter
|
174
|
+
)
|
175
|
+
|
176
|
+
# Create the elements you want to add
|
177
|
+
ad_1 = MyAdvertisement.new(url: '/my_first_ad')
|
178
|
+
ad_2 = MyAdvertisement.new(url: '/my_second_ad')
|
179
|
+
ad_3 = MyAdvertisement.new(url: '/my_last_ad')
|
180
|
+
|
181
|
+
# Add them to the article
|
182
|
+
article.place_additional_elements([ad_1, ad_2, ad_3])
|
183
|
+
|
184
|
+
# And again, export the article as you would normally do it
|
185
|
+
article.to_html
|
186
|
+
```
|
187
|
+
|
54
188
|
## Export
|
55
189
|
### HTML
|
56
190
|
The HTML exporter generates a HTML string for a list of elements. An example of
|
57
191
|
the HTML export for the parsed reference document can be found
|
58
192
|
[here](https://github.com/Devex/article_json/blob/master/spec/fixtures/reference_document_exported.html).
|
59
193
|
|
194
|
+
### AMP
|
195
|
+
The AMP exporter generates an AMP HTML representation of the elements.
|
196
|
+
|
197
|
+
AMP uses [custom HTML tags](https://www.ampproject.org/docs/reference/components), some of which require additional Javascript libraries.
|
198
|
+
If you have an `article` (see code example in _Usage_ section), you can get a list of the custom tags required by this article by calling `article.amp_exporter.custom_element_tags` and by calling `article.amp_exporter.amp_libraries` you get a list of `<script>` tags that can directly be included on your page to render the AMP article.
|
199
|
+
|
200
|
+
An example of
|
201
|
+
the AMP HTML export for the parsed reference document can be found
|
202
|
+
[here](https://github.com/Devex/article_json/blob/master/spec/fixtures/reference_document_exported.amp.html).
|
203
|
+
|
60
204
|
## Contributing
|
61
205
|
- Fork this repository
|
62
206
|
- Implement your feature or fix including Tests
|
@@ -72,7 +216,7 @@ See the
|
|
72
216
|
### Tests
|
73
217
|
For the whole test suite, run `bundle exec rspec`.
|
74
218
|
|
75
|
-
For individual tests, run `bundle exec rspec spec/article_json/version_spec.rb`.
|
219
|
+
For individual tests, run `bundle exec rspec spec/article_json/version_spec.rb`.
|
76
220
|
|
77
221
|
## License
|
78
222
|
MIT License, see the [license file](LICENSE).
|
data/lib/article_json/article.rb
CHANGED
@@ -2,7 +2,7 @@ module ArticleJSON
|
|
2
2
|
class Article
|
3
3
|
attr_reader :elements
|
4
4
|
|
5
|
-
# @param [
|
5
|
+
# @param [Array[ArticleJSON::Elements::Base]] elements
|
6
6
|
def initialize(elements)
|
7
7
|
@elements = elements
|
8
8
|
end
|
@@ -22,10 +22,39 @@ module ArticleJSON
|
|
22
22
|
to_h.to_json
|
23
23
|
end
|
24
24
|
|
25
|
+
# Exporter instance for HTML
|
26
|
+
# @return [ArticleJSON::Export::HTML::Exporter]
|
27
|
+
def html_exporter
|
28
|
+
@html_exporter = ArticleJSON::Export::HTML::Exporter.new(@elements)
|
29
|
+
end
|
30
|
+
|
25
31
|
# HTML export of the article
|
26
32
|
# @return [String]
|
27
33
|
def to_html
|
28
|
-
|
34
|
+
html_exporter.html
|
35
|
+
end
|
36
|
+
|
37
|
+
# Exporter instance for AMP
|
38
|
+
# @return [ArticleJSON::Export::AMP::Exporter]
|
39
|
+
def amp_exporter
|
40
|
+
ArticleJSON::Export::AMP::Exporter.new(@elements)
|
41
|
+
end
|
42
|
+
|
43
|
+
# AMP export of the article
|
44
|
+
# @return [String]
|
45
|
+
def to_amp
|
46
|
+
amp_exporter.html
|
47
|
+
end
|
48
|
+
|
49
|
+
# Distribute passed elements evenly throughout the article. All passed
|
50
|
+
# elements need to have an exporter to be represented in the rendered
|
51
|
+
# article.
|
52
|
+
# @param [Object] additional_elements
|
53
|
+
def place_additional_elements(additional_elements)
|
54
|
+
@elements =
|
55
|
+
ArticleJSON::Utils::AdditionalElementPlacer
|
56
|
+
.new(self, additional_elements)
|
57
|
+
.merge_elements
|
29
58
|
end
|
30
59
|
|
31
60
|
class << self
|
@@ -18,6 +18,62 @@ module ArticleJSON
|
|
18
18
|
|
19
19
|
def initialize
|
20
20
|
@oembed_user_agent = nil
|
21
|
+
@custom_element_exporters = {}
|
22
|
+
end
|
23
|
+
|
24
|
+
# Register a new HTML element exporter or overwrite existing ones.
|
25
|
+
# @param [Symbol] type
|
26
|
+
# @param [Class] klass
|
27
|
+
# @deprecated Use `#register_element_exporters_for(:html, ...)` instead
|
28
|
+
def register_html_element_exporter(type, klass)
|
29
|
+
register_element_exporters(:html, type => klass)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Return custom HTML exporters
|
33
|
+
# @return [Hash[Symbol => Class]]
|
34
|
+
# @deprecated use `#exporter_for` instead
|
35
|
+
def html_element_exporters
|
36
|
+
@custom_element_exporters[:html] || {}
|
37
|
+
end
|
38
|
+
|
39
|
+
# Set custom HTML exporters
|
40
|
+
# @param [Hash[Symbol => Class]] value
|
41
|
+
# @deprecated use `#register_element_exporters(:html, ...)` instead
|
42
|
+
def html_element_exporters=(value)
|
43
|
+
@custom_element_exporters[:html] = value
|
44
|
+
end
|
45
|
+
|
46
|
+
# Register new element exporters or overwrite existing ones for a given
|
47
|
+
# exporter type.
|
48
|
+
# Usage example:
|
49
|
+
# register_element_exporters(:html,
|
50
|
+
# image: MyImageExporter,
|
51
|
+
# advertisement: MyAdExporter)
|
52
|
+
# @param [Symbol] exporter
|
53
|
+
# @param [Hash[Symbol => Class]] type_class_mapping
|
54
|
+
def register_element_exporters(exporter, type_class_mapping)
|
55
|
+
unless %i(html amp).include?(exporter)
|
56
|
+
raise ArgumentError, '`exporter` needs to be either `:html` or `:amp` '\
|
57
|
+
"but is `#{exporter.inspect}`"
|
58
|
+
end
|
59
|
+
if !type_class_mapping.is_a?(Hash) ||
|
60
|
+
type_class_mapping.keys.any? { |key| !key.is_a? Symbol } ||
|
61
|
+
type_class_mapping.values.any? { |value| !value.is_a? Class }
|
62
|
+
raise ArgumentError, '`type_class_mapping` has to be a Hash with '\
|
63
|
+
'symbolized keys and classes as values but is '\
|
64
|
+
"`#{type_class_mapping.inspect}`"
|
65
|
+
end
|
66
|
+
|
67
|
+
@custom_element_exporters[exporter.to_sym] ||= {}
|
68
|
+
@custom_element_exporters[exporter.to_sym].merge!(type_class_mapping)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Get custom exporter class for a given exporter and element type
|
72
|
+
# @param [Symbol] exporter_type
|
73
|
+
# @param [Symbol] element_type
|
74
|
+
# @return [Class|nil]
|
75
|
+
def element_exporter_for(exporter_type, element_type)
|
76
|
+
@custom_element_exporters.dig(exporter_type, element_type)
|
21
77
|
end
|
22
78
|
end
|
23
79
|
end
|
@@ -18,6 +18,20 @@ module ArticleJSON
|
|
18
18
|
}
|
19
19
|
end
|
20
20
|
|
21
|
+
# Return `true` if the paragraph has no elements
|
22
|
+
# @return [Boolean]
|
23
|
+
def empty?
|
24
|
+
!content || content.empty?
|
25
|
+
end
|
26
|
+
|
27
|
+
# Return `true` if the paragraph is empty or if all elements are blank
|
28
|
+
# @return [Boolean]
|
29
|
+
def blank?
|
30
|
+
empty? || content.all? do |element|
|
31
|
+
element.respond_to?(:blank?) && element.blank?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
21
35
|
class << self
|
22
36
|
# Create a paragraph element from Hash
|
23
37
|
# @return [ArticleJSON::Elements::Paragraph]
|
@@ -27,6 +27,19 @@ module ArticleJSON
|
|
27
27
|
}
|
28
28
|
end
|
29
29
|
|
30
|
+
# Returns `true` if `content` has a length of zero or is `nil`
|
31
|
+
# @return [Boolean]
|
32
|
+
def empty?
|
33
|
+
!content || content.empty?
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns `true` if `content` is empty (see `#empty?`) or only contains
|
37
|
+
# whitespace (including non-breaking whitespace) characters
|
38
|
+
# @return [Boolean]
|
39
|
+
def blank?
|
40
|
+
empty? || content.gsub(/[\s\u00A0]/, '').empty?
|
41
|
+
end
|
42
|
+
|
30
43
|
class << self
|
31
44
|
# Create a text element from Hash
|
32
45
|
# @return [ArticleJSON::Elements::Text]
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module ArticleJSON
|
2
|
+
module Export
|
3
|
+
module AMP
|
4
|
+
# AMP uses custom HTML tags for elements like iframes or embedded youtube
|
5
|
+
# videos. These elements each require a javascript to be loaded to be
|
6
|
+
# properly rendered by the browser. This class resolves the custom tags to
|
7
|
+
# a list of javascript libraries which then can be included on the page.
|
8
|
+
class CustomElementLibraryResolver
|
9
|
+
# @param [Array[Symbol]] custom_element_tags
|
10
|
+
def initialize(custom_element_tags)
|
11
|
+
@custom_element_tags = custom_element_tags
|
12
|
+
end
|
13
|
+
|
14
|
+
# List of all custom tags with their library source URI
|
15
|
+
# @return [Hash[Symbol => String]]
|
16
|
+
def sources
|
17
|
+
@custom_element_tags.each_with_object({}) do |custom_element, mapping|
|
18
|
+
src = custom_element_script_mapping(custom_element)
|
19
|
+
mapping[custom_element] = src if src
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Return all custom library script tags required for the given custom
|
24
|
+
# element tags
|
25
|
+
# @return [Array[String]]
|
26
|
+
def script_tags
|
27
|
+
sources.map do |custom_element_tag, src|
|
28
|
+
<<-HTML.gsub(/\s+/, ' ').strip
|
29
|
+
<script async
|
30
|
+
custom-element="#{custom_element_tag}"
|
31
|
+
src="#{src}"></script>
|
32
|
+
HTML
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# Given a custom_element identifier, get the script script source
|
39
|
+
# @param [Symbol] custom_element_tag
|
40
|
+
# @return [String]
|
41
|
+
def custom_element_script_mapping(custom_element_tag)
|
42
|
+
{
|
43
|
+
'amp-iframe': 'https://cdn.ampproject.org/v0/amp-iframe-0.1.js',
|
44
|
+
'amp-twitter': 'https://cdn.ampproject.org/v0/amp-twitter-0.1.js',
|
45
|
+
'amp-youtube': 'https://cdn.ampproject.org/v0/amp-youtube-0.1.js',
|
46
|
+
'amp-vimeo': 'https://cdn.ampproject.org/v0/amp-vimeo-0.1.js',
|
47
|
+
'amp-facebook':
|
48
|
+
'https://cdn.ampproject.org/v0/amp-facebook-0.1.js',
|
49
|
+
}[custom_element_tag]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module ArticleJSON
|
2
|
+
module Export
|
3
|
+
module AMP
|
4
|
+
module Elements
|
5
|
+
class Base
|
6
|
+
include ArticleJSON::Export::Common::HTML::Elements::Base
|
7
|
+
|
8
|
+
# List of custom element tags used by this element
|
9
|
+
# @return [Array[Symbol]]
|
10
|
+
def custom_element_tags
|
11
|
+
[]
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# Return the module namespace this class and its subclasses are
|
16
|
+
# nested in
|
17
|
+
# @return [Module]
|
18
|
+
def namespace
|
19
|
+
ArticleJSON::Export::AMP::Elements
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# The format this exporter is returning. This is used to determine
|
25
|
+
# which custom element exporters should be applied from the
|
26
|
+
# configuration.
|
27
|
+
# @return [Symbol]
|
28
|
+
def export_format
|
29
|
+
:amp
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module ArticleJSON
|
2
|
+
module Export
|
3
|
+
module AMP
|
4
|
+
module Elements
|
5
|
+
class Embed < Base
|
6
|
+
include ArticleJSON::Export::Common::HTML::Elements::Embed
|
7
|
+
|
8
|
+
# Custom element tags required for this embedded element
|
9
|
+
# @return [Array[Symbol]]
|
10
|
+
def custom_element_tags
|
11
|
+
case @element.embed_type.to_sym
|
12
|
+
when :youtube_video then %i(amp-youtube)
|
13
|
+
when :vimeo_video then %i(amp-vimeo)
|
14
|
+
when :facebook_video then %i(amp-facebook)
|
15
|
+
when :tweet then %i(amp-twitter)
|
16
|
+
when :slideshare then %i(amp-iframe)
|
17
|
+
else []
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# Type specific object that should be embedded
|
24
|
+
# @return [Nokogiri::XML::Element|nil]
|
25
|
+
def embedded_object
|
26
|
+
case @element.embed_type.to_sym
|
27
|
+
when :youtube_video
|
28
|
+
youtube_node
|
29
|
+
when :vimeo_video
|
30
|
+
vimeo_node
|
31
|
+
when :facebook_video
|
32
|
+
facebook_node
|
33
|
+
when :tweet
|
34
|
+
tweet_node
|
35
|
+
when :slideshare
|
36
|
+
slideshare_node
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Nokogiri::XML::Element]
|
41
|
+
def youtube_node
|
42
|
+
create_element('amp-youtube',
|
43
|
+
'data-videoid' => @element.embed_id,
|
44
|
+
width: default_width,
|
45
|
+
height: default_height)
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [Nokogiri::XML::Element]
|
49
|
+
def vimeo_node
|
50
|
+
create_element('amp-vimeo',
|
51
|
+
'data-videoid' => @element.embed_id,
|
52
|
+
width: default_width,
|
53
|
+
height: default_height)
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [Nokogiri::XML::Element]
|
57
|
+
def tweet_node
|
58
|
+
create_element('amp-twitter',
|
59
|
+
'data-tweetid' => @element.embed_id,
|
60
|
+
width: default_width,
|
61
|
+
height: default_height)
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [Nokogiri::XML::Element]
|
65
|
+
def facebook_node
|
66
|
+
url = "#{@element.oembed_data[:author_url]}videos/#{@element.embed_id}"
|
67
|
+
create_element('amp-facebook',
|
68
|
+
'data-embedded-as' => 'video',
|
69
|
+
'data-href' => url,
|
70
|
+
width: default_width,
|
71
|
+
height: default_height)
|
72
|
+
end
|
73
|
+
|
74
|
+
# @return [Nokogiri::XML::Element]
|
75
|
+
def slideshare_node
|
76
|
+
node = Nokogiri::HTML(@element.oembed_data[:html]).xpath('//iframe')
|
77
|
+
create_element('amp-iframe',
|
78
|
+
src: node.attribute('src').value,
|
79
|
+
width: node.attribute('width').value,
|
80
|
+
height: node.attribute('height').value,
|
81
|
+
frameborder: '0',)
|
82
|
+
end
|
83
|
+
|
84
|
+
# @return [String]
|
85
|
+
def default_width
|
86
|
+
'560'
|
87
|
+
end
|
88
|
+
|
89
|
+
# @return [String]
|
90
|
+
def default_height
|
91
|
+
'315'
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module ArticleJSON
|
2
|
+
module Export
|
3
|
+
module AMP
|
4
|
+
module Elements
|
5
|
+
class Image < Base
|
6
|
+
include ArticleJSON::Export::Common::HTML::Elements::Image
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
# @return [Nokogiri::HTML::NodeSet]
|
11
|
+
def image_node
|
12
|
+
create_element('amp-img',
|
13
|
+
src: @element.source_url,
|
14
|
+
width: default_width,
|
15
|
+
height: default_height,
|
16
|
+
layout: :responsive)
|
17
|
+
end
|
18
|
+
|
19
|
+
def default_width
|
20
|
+
'640'
|
21
|
+
end
|
22
|
+
|
23
|
+
def default_height
|
24
|
+
'480'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|