diff --git a/app/assets/images/reddit-icon.svg b/app/assets/images/reddit-icon.svg new file mode 100644 index 000000000..524c15d52 --- /dev/null +++ b/app/assets/images/reddit-icon.svg @@ -0,0 +1 @@ + diff --git a/app/assets/stylesheets/ltags/LiquidTags.scss b/app/assets/stylesheets/ltags/LiquidTags.scss index 1cb633022..f8ef04748 100644 --- a/app/assets/stylesheets/ltags/LiquidTags.scss +++ b/app/assets/stylesheets/ltags/LiquidTags.scss @@ -14,3 +14,4 @@ @import 'ListingTag'; @import 'StackexchangeTag'; @import 'WikipediaTag'; +@import 'RedditTag'; diff --git a/app/assets/stylesheets/ltags/RedditTag.scss b/app/assets/stylesheets/ltags/RedditTag.scss new file mode 100644 index 000000000..91684fd57 --- /dev/null +++ b/app/assets/stylesheets/ltags/RedditTag.scss @@ -0,0 +1,98 @@ +@import 'variables'; + +.ltag__reddit--container { + max-width: 620px; + border: 1px solid $light-medium-gray; + border-radius: 3px; + box-shadow: $shadow; + margin:1.1em auto 1.30em; + padding: 10px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !important; + overflow: hidden; + + .ltag__reddit--title-container { + border-bottom: 1px solid #eaecef; + display: flex; + flex-direction: column; + + header { + display: flex; + justify-content: space-between; + + .ltag__reddit--title { + h1 { + margin-left: 5px; + margin-bottom: 0; + margin-top: 8px; + font-size: 1.4em; + padding-bottom: 0; + + img { + height: 40px; + width: 40px; + display: inline-block; + vertical-align: -9px; + } + } + } + + .ltag__reddit--post-metadata { + font-size: 0.6em; + + span { + display: inline-block; + padding: 7px 6px; + margin-left: 6px; + } + } + } + } + + .ltag__reddit--body { + padding: 1em 0.5em; + max-height: calc(21vw + 165px); + overflow: hidden; + + @media screen and (min-width: 430px) { + max-height: calc(20vw + 153px); + } + @media screen and (min-width: 800px) { + max-height: 310px; + } + + h1,h2,h3,h4,h5,h6 { + font-weight: 500 !important; + } + + p { + margin-top: 0px; + padding: 0px; + margin-bottom: 15px; + font-size: 1.1rem; + } + } + + .ltag__reddit--btn--container { + padding: 0.1em 0 1.15em; + z-index: 100; + text-align: center; + position: relative; + box-shadow: 0px 0px 60px 42px #fff; + } + + .ltag__reddit--btn { + color: #0366d6; + background-color: #f1f8ff; + border-radius: 3px; + line-height: 20px; + padding: 0.25em 1.2em; + opacity: 0.9; + font-weight: bold; + border: 1px solid #0366d6; + font-size: 0.75em; + + &:hover { + opacity: 1; + } + } +} diff --git a/app/liquid_tags/reddit_tag.rb b/app/liquid_tags/reddit_tag.rb new file mode 100644 index 000000000..b385b686c --- /dev/null +++ b/app/liquid_tags/reddit_tag.rb @@ -0,0 +1,73 @@ +class RedditTag < LiquidTagBase + include ActionView::Helpers::SanitizeHelper + + PARTIAL = "liquids/reddit".freeze + URL_REGEXP = /\Ahttps\:\/\/(www.)?reddit.com/.freeze + + def initialize(_tag_name, url, _tokens) + @url = ActionController::Base.helpers.strip_tags(url).strip + @reddit_content = parse_url + end + + def render(_context) + ActionController::Base.new.render_to_string( + partial: PARTIAL, + locals: { + author: @reddit_content[:author], + title: @reddit_content[:title], + post_url: @reddit_content[:post_url], + created_at: @reddit_content[:created_at], + post_hint: @reddit_content[:post_hint], + image_url: @reddit_content[:image_url], + thumbnail: @reddit_content[:thumbnail], + html_text: @reddit_content[:selftext_html], + markdown_text: @reddit_content[:selftext] + }, + ) + end + + private + + def parse_url + validate_url + + # Requests to Reddit require a custom `User-Agent` header to prevent 429 errors + json = HTTParty.get("#{@url}.json", headers: { "User-Agent" => "#{ApplicationConfig['COMMUNITY_NAME']} (#{URL.url})" }) + + # The JSON response is an array with two items. + # The first one is the post itself, the second one are the comments + data = json.first["data"]["children"][0]["data"] + + { + author: data["author"], + title: data["title"], + post_url: @url, + created_at: Time.zone.at(data["created_utc"]).strftime("%b %e '%y"), + post_hint: data["post_hint"], + image_url: data["url"], + thumbnail: data["thumbnail"], + selftext: parse_markdown_content(data["selftext"]), + selftext_html: data["selftext_html"] + } + end + + def parse_markdown_content(content) + markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTMLRouge, REDCARPET_CONFIG) + text = markdown.render(content) + + sanitize(HTML_Truncator.truncate(text, 60)) + end + + def validate_url + return true if valid_url?(@url.delete(" ")) && (@url =~ URL_REGEXP)&.zero? + + raise StandardError, "Invalid Reddit link: #{@url}" + end + + def valid_url?(url) + url = URI.parse(url) + url.is_a?(URI::HTTP) + end +end + +Liquid::Template.register_tag("reddit", RedditTag) diff --git a/app/views/liquids/_reddit.html.erb b/app/views/liquids/_reddit.html.erb new file mode 100644 index 000000000..7c78f5b20 --- /dev/null +++ b/app/views/liquids/_reddit.html.erb @@ -0,0 +1,32 @@ +
+
+
+
+

+ " alt="Reddit Logo" /> + + <%= title %> + +

+ +
+
+
+
+

+ <% if post_hint == 'image' %> + <%= title %> + <% else %> + <%= markdown_text %> + <% end %> +

+
+
+ +
+
diff --git a/app/views/pages/_editor_guide_text.html.erb b/app/views/pages/_editor_guide_text.html.erb index 887b2e548..67fa3ae9e 100644 --- a/app/views/pages/_editor_guide_text.html.erb +++ b/app/views/pages/_editor_guide_text.html.erb @@ -342,6 +342,13 @@

Asciinema Embed

All you need is the Asciinema id:

{% asciinema 239367 %} + +

Reddit Tag

+

Enter the full URL of the post you want to embed

+
+{% reddit https://www.reddit.com/r/aww/comments/ag3s4b/ive_waited_28_years_to_finally_havr_my_first_pet %}
+        
+

Parsing Liquid Tags as a Code Example

To parse Liquid tags as code, simply wrap it with a single backtick or triple backticks.

`{% mytag %}{{ site.SOMETHING }}{% endmytag %}`

diff --git a/docs/frontend/liquid-tags.md b/docs/frontend/liquid-tags.md index fd0d46ed5..d4cc26efc 100644 --- a/docs/frontend/liquid-tags.md +++ b/docs/frontend/liquid-tags.md @@ -24,8 +24,7 @@ Develop them with that mindset in terms of naming things. They should be documented but also intuitive. They should also be fairly flexible in the arguments they take. Currently, this could use improvements. -Liquid tags are "compiled" when an article is saved. So you will need to re-save -articles to see HTML changes. +_Note: Liquid tags are "compiled" when an article is saved. So you will need to re-save articles to see HTML changes._ Here is a bunch of liquid tags supported on DEV: @@ -54,6 +53,7 @@ Here is a bunch of liquid tags supported on DEV: {% blogcast 1234 %} {% kotlin https://pl.kotl.in/owreUFFUG %} {% wikipedia https://en.wikipedia.org/wiki/Wikipedia %} +{% reddit https://www.reddit.com/r/aww/comments/ag3s4b/ive_waited_28_years_to_finally_havr_my_first_pet %} ``` ## How liquid tags are developed diff --git a/spec/liquid_tags/reddit_tag_spec.rb b/spec/liquid_tags/reddit_tag_spec.rb new file mode 100644 index 000000000..d96dfd45e --- /dev/null +++ b/spec/liquid_tags/reddit_tag_spec.rb @@ -0,0 +1,88 @@ +require "rails_helper" + +RSpec.describe RedditTag, type: :liquid_tag do + context "when it is an image post" do + describe "#render" do + let(:image_post) { "https://www.reddit.com/r/aww/comments/ag3s4b/ive_waited_28_years_to_finally_havr_my_first_pet" } + let(:response) do + { + "author" => "Miaogua007", + "title" => "I've waited 28 years to …Everyone, meet Mycroft.", + "post_url" => "https://www.reddit.com/r/aww/comments/ag3s4b/ive_waited_28_years_to_finally_havr_my_first_pet", + "created_utc" => 1_547_520_425, + "post_hint" => "image", + "image_url" => "https://i.redd.it/jpqodmc83ia21.jpg", + "thumbnail" => "https://b.thumbs.redditm…ge_mn1MHd6uirdou8H3o.jpg", + "selftext" => "", + "selftext_html" => nil + } + end + + def generate_reddit_liquid(url) + Liquid::Template.register_tag("reddit", RedditTag) + Liquid::Template.parse("{% reddit #{url} %}") + end + + before do + allow(HTTParty).to receive(:get).and_return([ + { + "data" => { + "children" => [ + { "data" => response }, + ] + } + }, + ]) + end + + it "renders reddit content" do + reddit_liquid = generate_reddit_liquid(image_post) + expect(reddit_liquid.render).to include("ltag__reddit") + expect(reddit_liquid.render).to include(response["author"]) + expect(reddit_liquid.render).to include(response["thumbnail"]) + end + end + end + + context "when it is a text post" do + describe "#render" do + let(:text_post) { "https://www.reddit.com/r/IAmA/comments/afvl2w/im_scott_from_scotts_cheap_flights_my_profession" } + let(:response) do + { + "author" => "scottkeyes", + "title" => "I'm Scott from Scott's C…r the next 8 hours. AMA", + "post_url" => "https://www.reddit.com/r/IAmA/comments/afvl2w/im_scott_from_scotts_cheap_flights_my_profession", + "created_utc" => 1_547_470_871, + "post_hint" => "self", + "image_url" => "", + "thumbnail" => "self", + "selftext" => "I may have the world’s b…year for cheap flights!!", + "selftext_html" => "" + } + end + + def generate_reddit_liquid(url) + Liquid::Template.register_tag("reddit", RedditTag) + Liquid::Template.parse("{% reddit #{url} %}") + end + + before do + allow(HTTParty).to receive(:get).and_return([ + { + "data" => { + "children" => [ + { "data" => response }, + ] + } + }, + ]) + end + + it "renders reddit content" do + reddit_liquid = generate_reddit_liquid(text_post) + expect(reddit_liquid.render).to include("ltag__reddit") + expect(reddit_liquid.render).to include(response["author"]) + end + end + end +end