[deploy] Add Reddit Liquid Tag (#8111)

* Implement Reddit liquid tag

* Set reddit content in locals

* Change Reddit liquid tag content layout

I decided a UI similar to the Stack Exchange tag would be better.

Now the Reddit tag looks a bit similar.

* Add tests for RedditTag

* Add reddit liquid tag example to docs

Also emphasize notice about compilation of article when using any
liquid tag

* Parse markdown text reddit content for RedditTag

* Display date RedditTag post was published

* Remove file location comments

* Change order of conditional for Reddit liquid tag

Sometimes the attribute `post_hint` might be null and neither condition
would apply.

* Add example to use Reddit liquid tag

* Change reddit svg icon for Reddit liquid tag

* handle and verify status of Reddit links

Here we make sure the passed link is a valid one in terms of URI composition (e.g. protocol)
but also that the URL is one that points to existing content in Reddit.

If the URL isn't returning anything but a successful response code (200) then
we treat it as an invalid one.

* Do not use VCR for RedditTag specs

The generated cassette for this tag was really huge (6000+ lines).

Using mocks (as in the MediumTag spec) still does the job and without
that huge VCR file.

* Remove index line from schema.db

* Improve RedditJsonFromUrlService

- Change User-Agent in requests to one read from app configuration
- Remove Reddit URL check by making a GET request
- Add regexp to validate URL looks like a Reddit URL

* Move RedditJsonFromUrlService methods to RedditTag

Also changed the way the request was being mocked. Previously, it was by
mocking the service class now I'm mocking HTTParty directly and returning
the JSON structure Reddit uses.
This commit is contained in:
Francisco Quintero 2020-06-01 17:52:07 -05:00 committed by GitHub
parent fb6685ec1b
commit 3b939f6b9f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 302 additions and 2 deletions

View file

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="reddit" class="svg-inline--fa fa-reddit fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#FE5022" d="M201.5 305.5c-13.8 0-24.9-11.1-24.9-24.6 0-13.8 11.1-24.9 24.9-24.9 13.6 0 24.6 11.1 24.6 24.9 0 13.6-11.1 24.6-24.6 24.6zM504 256c0 137-111 248-248 248S8 393 8 256 119 8 256 8s248 111 248 248zm-132.3-41.2c-9.4 0-17.7 3.9-23.8 10-22.4-15.5-52.6-25.5-86.1-26.6l17.4-78.3 55.4 12.5c0 13.6 11.1 24.6 24.6 24.6 13.8 0 24.9-11.3 24.9-24.9s-11.1-24.9-24.9-24.9c-9.7 0-18 5.8-22.1 13.8l-61.2-13.6c-3-.8-6.1 1.4-6.9 4.4l-19.1 86.4c-33.2 1.4-63.1 11.3-85.5 26.8-6.1-6.4-14.7-10.2-24.1-10.2-34.9 0-46.3 46.9-14.4 62.8-1.1 5-1.7 10.2-1.7 15.5 0 52.6 59.2 95.2 132 95.2 73.1 0 132.3-42.6 132.3-95.2 0-5.3-.6-10.8-1.9-15.8 31.3-16 19.8-62.5-14.9-62.5zM302.8 331c-18.2 18.2-76.1 17.9-93.6 0-2.2-2.2-6.1-2.2-8.3 0-2.5 2.5-2.5 6.4 0 8.6 22.8 22.8 87.3 22.8 110.2 0 2.5-2.2 2.5-6.1 0-8.6-2.2-2.2-6.1-2.2-8.3 0zm7.7-75c-13.6 0-24.6 11.1-24.6 24.9 0 13.6 11.1 24.6 24.6 24.6 13.8 0 24.9-11.1 24.9-24.6 0-13.8-11-24.9-24.9-24.9z"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -14,3 +14,4 @@
@import 'ListingTag';
@import 'StackexchangeTag';
@import 'WikipediaTag';
@import 'RedditTag';

View file

@ -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;
}
}
}

View file

@ -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)

View file

@ -0,0 +1,32 @@
<div class="ltag__reddit--container">
<div class="ltag__reddit--title-container">
<header>
<div class="ltag__reddit--title">
<h1>
<img src="<%= asset_path("reddit-icon.svg") %>" alt="Reddit Logo" />
<a href="<%= post_url %>" target="_blank" rel="noopener noreferrer">
<%= title %>
</a>
</h1>
<div class="ltag__reddit--post-metadata">
<span><%= created_at %></span>
<span>Author: <%= author %></span>
</div>
</div>
</header>
</div>
<div class="ltag__reddit--body">
<p>
<% if post_hint == 'image' %>
<img src="<%= thumbnail %>" alt="<%= title %>">
<% else %>
<%= markdown_text %>
<% end %>
</p>
</div>
<div class="ltag__reddit--btn--container">
<button class="ltag__reddit--btn" type="button">
<a href="<%= post_url %>" target="_blank" rel="noopener noreferrer">See Full Post</a>
</button>
</div>
</div>

View file

@ -342,6 +342,13 @@
<h3><strong>Asciinema Embed</strong></h3>
<p>All you need is the Asciinema id:</p>
<code>{% asciinema 239367 %}</code>
<h3><strong>Reddit Tag</strong></h3>
<p>Enter the full URL of the post you want to embed</p>
<pre>
{% reddit https://www.reddit.com/r/aww/comments/ag3s4b/ive_waited_28_years_to_finally_havr_my_first_pet %}
</pre>
<h3><strong>Parsing Liquid Tags as a Code Example</strong></h3>
<p>To parse Liquid tags as code, simply wrap it with a single backtick or triple backticks.</p>
<p><code>`{% mytag %}{{ site.SOMETHING }}{% endmytag %}`</code></p>

View file

@ -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

View file

@ -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 worlds 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