diff --git a/app/assets/stylesheets/ltags/ClassifiedListingTag.scss b/app/assets/stylesheets/ltags/ClassifiedListingTag.scss new file mode 100644 index 000000000..f1cfac465 --- /dev/null +++ b/app/assets/stylesheets/ltags/ClassifiedListingTag.scss @@ -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); + } + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/ltags/LiquidTags.scss b/app/assets/stylesheets/ltags/LiquidTags.scss index 89f6eda72..c15c4162b 100644 --- a/app/assets/stylesheets/ltags/LiquidTags.scss +++ b/app/assets/stylesheets/ltags/LiquidTags.scss @@ -11,3 +11,4 @@ @import 'GistTag'; @import 'GithubReadmeTag'; @import 'PollTag'; +@import 'ClassifiedListingTag'; \ No newline at end of file diff --git a/app/liquid_tags/classified_listing_tag.rb b/app/liquid_tags/classified_listing_tag.rb new file mode 100644 index 000000000..f745dc209 --- /dev/null +++ b/app/liquid_tags/classified_listing_tag.rb @@ -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) diff --git a/app/views/classified_listings/_liquid.html.erb b/app/views/classified_listings/_liquid.html.erb new file mode 100644 index 000000000..a5e803cf4 --- /dev/null +++ b/app/views/classified_listings/_liquid.html.erb @@ -0,0 +1,36 @@ +<% if listing.bumped_at < (Time.zone.today - 30) %> +
+
+

+ + This listing has expired. + +

+
+
+<% else %> +
+
+

+ + <%= listing.title %> + +

+ +
+ <% listing.tag_list.each do |tag| %> + <%= tag %> + <% end %> +
+ +
+
+<% end %> diff --git a/app/views/pages/_editor_guide_text.html.erb b/app/views/pages/_editor_guide_text.html.erb index bc2a1df86..c2b4aeed0 100644 --- a/app/views/pages/_editor_guide_text.html.erb +++ b/app/views/pages/_editor_guide_text.html.erb @@ -72,6 +72,12 @@

dev.to Podcast Episode Embed

All you need is the full link of the podcast episode:

{% podcast https://dev.to/basecspodcast/s2e2--queues-irl %} +

dev.to Listing Embed

+

All you need is the full link of the listing:

+ {% listing https://dev.to/listings/collabs/dev-is-open-source-823 %} +

You can also use the category and slug like this:

+ {% listing collabs/dev-is-open-source-823 %} +

Note: Expired listings will raise an error. Make sure the listing is published or recently bumped.

Twitter Embed

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 id from the url.

diff --git a/spec/liquid_tags/classified_listing_tag_spec.rb b/spec/liquid_tags/classified_listing_tag_spec.rb new file mode 100644 index 000000000..18902130f --- /dev/null +++ b/spec/liquid_tags/classified_listing_tag_spec.rb @@ -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: "

sigh sigh sigh

", + 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: "

exxpired

", + 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: "

wow code lots get not only money but satisfaction from work

", + 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 += "#{tag}\n " + end + tags = tags.rstrip + <<~HTML +
+
+

+ + #{listing.title} + +

+ +
+ #{tags} +
+ +
+
+ HTML + end + + def render_expired_listing + <<~HTML +
+
+

+ + This listing has expired. + +

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