Listing Liquid Tag (#3250)

* init wip

still needs html, styling, and specs

* add more html

similar to singleListing.jsx

* add generate_new_liquid method to spec

* remove unexpected period

* add error handling for invalid slugs and expired listings

* initial styling

* styling for author and tags

* import variables to sass

* more styling and swithc tags and author info

* add specs

* handle error for slug with no category

* add listing tag to editor guide

* reduce complexity

* render expired listing rather than raise error

* update expired listing spec

* import mixins to classified listing tag styling
This commit is contained in:
Mario See 2019-07-08 16:19:01 -06:00 committed by Ben Halpern
parent 3c1d51b836
commit 42cb72d185
6 changed files with 275 additions and 0 deletions

View file

@ -0,0 +1,81 @@
@import 'variables';
@import 'mixins';
.ltag__listing {
font-family: $helvetica;
border:1px solid $light-medium-gray;
box-shadow: $shadow;
border-radius:3px;
display:block;
margin:0.95em 0 1.20em;
max-width: 100%;
@media screen and (min-width: 760px) {
margin: 0.95em auto;
width:620px;
}
.ltag__listing-content {
padding: calc(0.5vw + 6px);
h3 {
margin:0;
padding:0;
font-weight: 500;
font-size: 1.5em;
a {
color: var(--theme-color, $black);
}
}
.ltag__listing-body {
max-width: 100%;
margin: 5px 0 15px 0;
font-size: 0.9em;
a {
color: var(--theme-color, $black);
}
}
.ltag__listing-tags {
font-size: 0.7em;
line-height: 1.3em;
.ltag__listing-tag {
margin-right: calc(0.4vw + 4px);
margin-left: 1px;
a {
@include themeable(
color,
theme-secondary-color,
$dark-medium-gray
);
@include themeable(
background,
theme-container-accent-background,
$light-medium-gray
);
display: inline-block;
margin: 2px;
padding: 2px 7px;
border-radius: 3px;
font-weight: bold;
}
}
}
.ltag__listing-author-info {
color: #666666;
color: var(--theme-secondary-color, #666666);
line-height: 1.3em;
a {
margin: 0.1vw 0;
padding: 0;
font-size: 0.7em;
margin-bottom: 0;
font-weight: bold;
color: #666666;
color: var(--theme-secondary-color, #666666);
}
}
}
}

View file

@ -11,3 +11,4 @@
@import 'GistTag';
@import 'GithubReadmeTag';
@import 'PollTag';
@import 'ClassifiedListingTag';

View file

@ -0,0 +1,34 @@
class ClassifiedListingTag < LiquidTagBase
PARTIAL = "classified_listings/liquid".freeze
def initialize(_tag_name, slug_path_url, _tokens)
striped_path = ActionController::Base.helpers.strip_tags(slug_path_url).strip
@listing = get_listing(striped_path)
end
def render(_context)
ActionController::Base.new.render_to_string(
partial: PARTIAL,
locals: { listing: @listing },
)
end
def get_hash(url)
path = Addressable::URI.parse(url).path
path.slice!(0, 10) if path.starts_with?("/listings/") # remove leading slash if present
path.slice!(-1) if path.ends_with?("/") # remove trailing slash if present
Addressable::Template.new("{category}/{slug}").extract(path)&.symbolize_keys
end
def get_listing(url)
hash = get_hash(url)
raise StandardError, "Invalid URL or slug. Listing not found." if hash.nil?
listing = ClassifiedListing.find_by(hash)
raise StandardError, "Invalid URL or slug. Listing not found." unless listing
listing
end
end
Liquid::Template.register_tag("listing", ClassifiedListingTag)

View file

@ -0,0 +1,36 @@
<% if listing.bumped_at < (Time.zone.today - 30) %>
<div class="ltag__listing">
<div class="ltag__listing-content">
<h3>
<a href="/listings">
This listing has expired.
</a>
</h3>
</div>
</div>
<% else %>
<div class="ltag__listing">
<div class="ltag__listing-content">
<h3>
<a href="/listings/<%= listing.category %>/<%= listing.slug %>">
<%= listing.title %>
</a>
</h3>
<div class="ltag__listing-body">
<a href="/listings/<%= listing.category %>/<%= listing.slug %>">
<%= listing.processed_html.html_safe %>
</a>
</div>
<div class="ltag__listing-tags">
<% listing.tag_list.each do |tag| %>
<span class='ltag__listing-tag'><a href='/listings?t=<%= tag %>'><%= tag %></a></span>
<% end %>
</div>
<div class="ltag__listing-author-info">
<a href="/listings/<%= listing.category %>"><%= listing.category %></a>
<a href="/<%= listing.author.username %>"><%= listing.author.name %></a>
</div>
</div>
</div>
<% end %>

View file

@ -72,6 +72,12 @@
<h3><strong>dev.to Podcast Episode Embed</strong></h3>
<p>All you need is the full link of the podcast episode:</p>
<code>{% podcast https://dev.to/basecspodcast/s2e2--queues-irl %}</code>
<h3><strong>dev.to Listing Embed</strong></h3>
<p>All you need is the full link of the listing:</p>
<code>{% listing https://dev.to/listings/collabs/dev-is-open-source-823 %}</code>
<p>You can also use the category and slug like this:</p>
<code>{% listing collabs/dev-is-open-source-823 %}</code>
<p>Note: Expired listings will raise an error. Make sure the listing is published or recently bumped.</p>
<h3><strong>Twitter Embed</strong></h3>
<p>Using the Twitter Liquid tag will allow the tweet to pre-render from the server, providing your reader with a better experience. All you need is the tweet
<code>id</code> from the url.</p>

View file

@ -0,0 +1,117 @@
require "rails_helper"
RSpec.describe ClassifiedListingTag, type: :liquid_template do
let(:user) { create(:user, username: "dariamorgendorffer", name: "Daria Morgendorffer") }
let(:user_listing) do
create(
:classified_listing,
user_id: user.id,
title: "save me pls",
body_markdown: "sigh sigh sigh",
processed_html: "<p>sigh sigh sigh</p>",
category: "cfp",
tag_list: %w[a b c],
organization_id: nil,
)
end
let(:expired_listing) do
create(
:classified_listing,
user_id: user.id,
title: "this old af",
body_markdown: "exxpired",
processed_html: "<p>exxpired</p>",
category: "cfp",
tag_list: %w[x y z],
organization_id: nil,
bumped_at: Time.zone.today - 40,
created_at: Time.zone.today - 40,
updated_at: Time.zone.today - 40,
)
end
let(:org) { create(:organization) }
let(:org_user) { create(:user, organization_id: org.id) }
let(:org_listing) do
create(
:classified_listing,
user_id: org_user.id,
title: "this is a job posting",
body_markdown: "wow code lots get not only money but satisfaction from work",
processed_html: "<p>wow code lots get not only money but satisfaction from work</p>",
category: "misc",
tag_list: %w[a b c],
organization_id: org.id,
)
end
def generate_new_liquid(slug)
Liquid::Template.register_tag("listing", ClassifiedListingTag)
Liquid::Template.parse("{% listing #{slug} %}")
end
def correct_link_html(listing)
tags = ""
listing.tag_list.each do |tag|
tags += "<span class='ltag__listing-tag'><a href='/listings?t=#{tag}'>#{tag}</a></span>\n "
end
tags = tags.rstrip
<<~HTML
<div class="ltag__listing">
<div class="ltag__listing-content">
<h3>
<a href="/listings/#{listing.category}/#{listing.slug}">
#{listing.title}
</a>
</h3>
<div class="ltag__listing-body">
<a href="/listings/#{listing.category}/#{listing.slug}">
#{listing.processed_html}
</a>
</div>
<div class="ltag__listing-tags">
#{tags}
</div>
<div class="ltag__listing-author-info">
<a href="/listings/#{listing.category}">#{listing.category}</a>
<a href="/#{listing.author.username}">#{listing.author.name}</a>
</div>
</div>
</div>
HTML
end
def render_expired_listing
<<~HTML
<div class="ltag__listing">
<div class="ltag__listing-content">
<h3>
<a href="/listings">
This listing has expired.
</a>
</h3>
</div>
</div>
HTML
end
it "raises an error when invalid" do
expect { generate_new_liquid("/listings/fakecategory/fakeslug") }.
to raise_error("Invalid URL or slug. Listing not found.")
end
it "displays expired message when listing is expired" do
liquid = generate_new_liquid("#{expired_listing.category}/#{expired_listing.slug}")
expect(liquid.render).to eq(render_expired_listing)
end
it "renders a proper listing tag from user listing" do
liquid = generate_new_liquid("#{user_listing.category}/#{user_listing.slug}")
expect(liquid.render).to eq(correct_link_html(user_listing))
end
it "renders a proper listing tag from org listing" do
liquid = generate_new_liquid("#{org_listing.category}/#{org_listing.slug}")
expect(liquid.render).to eq(correct_link_html(org_listing))
end
end