[deploy] Rename classified listings (#7910)
* Change models and related files * Update controllers and specs * More renaming * Seek and destroy, I mean search and replace * Round up the stragglers * Ground control to Major Travis... * More fixes * PR feedback * Various fixes * Rename view * Fix list query builder * Unify request specs * Fix some API spec errors * Fix remaining API specs * Make spec conform to API * Fix leftover problems * Fix JS tests * Fix column name in select * Fix API specs * Fix search specs * Paging Mr. Travis
This commit is contained in:
parent
ce28c0b5e5
commit
7f75f99560
146 changed files with 1599 additions and 1576 deletions
|
|
@ -41,7 +41,7 @@
|
|||
onRefresh: () => {
|
||||
return window.location.reload();
|
||||
},
|
||||
resistanceFunction: t => {
|
||||
resistanceFunction: (t) => {
|
||||
return Math.min(1, t / 2.5);
|
||||
},
|
||||
shouldPullToRefresh: () => {
|
||||
|
|
@ -50,7 +50,7 @@
|
|||
(document.getElementById('articles-list') ||
|
||||
document.getElementById('user-dashboard') ||
|
||||
document.getElementById('article-body') ||
|
||||
document.getElementById('classifieds-index-container')) &&
|
||||
document.getElementById('listings-index-container')) &&
|
||||
!document.body.classList.contains('modal-open')
|
||||
);
|
||||
},
|
||||
|
|
@ -178,7 +178,7 @@
|
|||
|
||||
function onTouchStart(e) {
|
||||
// here, we must pick a handler first, and then append their html/css on the DOM
|
||||
var target = shared.handlers.filter(h => {
|
||||
var target = shared.handlers.filter((h) => {
|
||||
return h.contains(e.target);
|
||||
})[0];
|
||||
|
||||
|
|
@ -347,7 +347,7 @@
|
|||
var handler = {};
|
||||
|
||||
// merge options with defaults
|
||||
Object.keys(defaults).forEach(key => {
|
||||
Object.keys(defaults).forEach((key) => {
|
||||
handler[key] = options[key] || defaults[key];
|
||||
});
|
||||
|
||||
|
|
@ -358,7 +358,7 @@
|
|||
: defaults.refreshTimeout;
|
||||
|
||||
// normalize elements
|
||||
methods.forEach(method => {
|
||||
methods.forEach((method) => {
|
||||
if (typeof handler[method] === 'string') {
|
||||
handler[method] = document.querySelector(handler[method]);
|
||||
}
|
||||
|
|
@ -369,7 +369,7 @@
|
|||
shared.events = setupEvents();
|
||||
}
|
||||
|
||||
handler.contains = target => {
|
||||
handler.contains = (target) => {
|
||||
return handler.triggerElement.contains(target);
|
||||
};
|
||||
|
||||
|
|
@ -395,7 +395,7 @@
|
|||
shared.events = null;
|
||||
}
|
||||
|
||||
shared.handlers.forEach(h => {
|
||||
shared.handlers.forEach((h) => {
|
||||
h.destroy();
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
@import 'variables';
|
||||
@import 'mixins';
|
||||
|
||||
.classifieds-container {
|
||||
.listings-container {
|
||||
margin: 24px 0;
|
||||
text-align: left;
|
||||
max-width: 100%;
|
||||
min-height: 100vh;
|
||||
.classified-filters {
|
||||
.listing-filters {
|
||||
padding: 0 16px;
|
||||
a {
|
||||
padding: 3px 16px;
|
||||
|
|
@ -23,11 +23,11 @@
|
|||
margin: 0px 4px;
|
||||
}
|
||||
}
|
||||
.classified-filters-categories {
|
||||
.listing-filters-categories {
|
||||
a {
|
||||
display: block;
|
||||
padding: 7px 16px;
|
||||
&.classified-create-link {
|
||||
&.listing-create-link {
|
||||
border: 2px solid darken($green, 14%);
|
||||
color: darken($green, 19%);
|
||||
padding: 6px 16px;
|
||||
|
|
@ -42,13 +42,13 @@
|
|||
min-width: 50px;
|
||||
text-align: center;
|
||||
padding: 3px 16px;
|
||||
&.classified-create-link {
|
||||
&.listing-create-link {
|
||||
padding: 2px 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.classified-filters-tags {
|
||||
.listing-filters-tags {
|
||||
padding: 20px 0px;
|
||||
position: relative;
|
||||
input {
|
||||
|
|
@ -69,7 +69,7 @@
|
|||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
.classified-search-clear {
|
||||
.listing-search-clear {
|
||||
background: $light-gray;
|
||||
border: 1px solid $light-medium-gray;
|
||||
position: absolute;
|
||||
|
|
@ -80,7 +80,7 @@
|
|||
left: 282px;
|
||||
}
|
||||
}
|
||||
.classified-tag {
|
||||
.listing-tag {
|
||||
font-size: 0.7em;
|
||||
color: $black;
|
||||
border-radius: 100px;
|
||||
|
|
@ -107,7 +107,7 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.classifieds-columns {
|
||||
.listings-columns {
|
||||
display: grid;
|
||||
grid-gap: 24px;
|
||||
padding: 0 16px;
|
||||
|
|
@ -133,13 +133,13 @@
|
|||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
}
|
||||
.classified-form-inner {
|
||||
.listing-form-inner {
|
||||
padding: 0px 15px 40px;
|
||||
@media screen and (min-width: 580px) {
|
||||
padding: 0px 15px 18px;
|
||||
}
|
||||
}
|
||||
.listings-back-button {
|
||||
.listing-back-button {
|
||||
font-size: 0.8em;
|
||||
margin: 2px 0px 8px;
|
||||
display: block;
|
||||
|
|
@ -149,7 +149,7 @@
|
|||
color: var(--accent-brand);
|
||||
margin: 0px;
|
||||
}
|
||||
.classified-errors {
|
||||
.listing-errors {
|
||||
background: $red;
|
||||
color: $black;
|
||||
padding: 20px;
|
||||
|
|
@ -261,7 +261,7 @@
|
|||
font-size: 1.1em;
|
||||
}
|
||||
}
|
||||
.classified-listings-unpublish {
|
||||
.listings-unpublish {
|
||||
background: $red;
|
||||
}
|
||||
p {
|
||||
|
|
@ -269,7 +269,7 @@
|
|||
color: var(--card-color-tertiary);
|
||||
}
|
||||
}
|
||||
.classifieds-load-more-button {
|
||||
.listings-load-more-button {
|
||||
text-align: center;
|
||||
|
||||
button {
|
||||
|
|
@ -342,7 +342,7 @@
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.single-classified-listing-container {
|
||||
.single-listing-container {
|
||||
width: 100%;
|
||||
z-index: 20;
|
||||
position: fixed;
|
||||
|
|
@ -352,7 +352,7 @@
|
|||
bottom: 0px;
|
||||
margin: 12px auto 0px;
|
||||
overflow: hidden;
|
||||
.single-classified-listing-container__inner {
|
||||
.single-listing-container__inner {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
|
@ -367,17 +367,17 @@
|
|||
right: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.single-classified-listing-container__spacer {
|
||||
.single-listing-container__spacer {
|
||||
height: 150px;
|
||||
margin-bottom: 150px;
|
||||
}
|
||||
}
|
||||
.single-classified-listing-container__inner::-webkit-scrollbar {
|
||||
.single-listing-container__inner::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.single-classified-listing {
|
||||
.single-listing {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
border: 1px solid $light-medium-gray;
|
||||
|
|
@ -452,10 +452,10 @@
|
|||
background-color: #f1f1f1;
|
||||
}
|
||||
}
|
||||
.single-classified-listing-body {
|
||||
.single-listing-body {
|
||||
padding: 10px 12px;
|
||||
font-size: 15px;
|
||||
.classified-listing-contact-cta {
|
||||
.listing-contact-cta {
|
||||
border: 2px solid $black;
|
||||
color: $black;
|
||||
padding: 8px 10px;
|
||||
|
|
@ -466,7 +466,7 @@
|
|||
width: 80%;
|
||||
}
|
||||
}
|
||||
.single-classified-listing-tags {
|
||||
.single-listing-tags {
|
||||
padding: 10px 15px;
|
||||
a {
|
||||
color: var(--card-color-tertiary);
|
||||
|
|
@ -483,19 +483,19 @@
|
|||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.single-classified-listing-author-info {
|
||||
.single-listing-author-info {
|
||||
font-size: 0.69em;
|
||||
color: var(--card-color-tertiary);
|
||||
padding: 0px 15px 4px;
|
||||
a {
|
||||
color: var(--card-color-tertiary);
|
||||
display: inline-block;
|
||||
&.classified-listing-edit-button {
|
||||
&.listing-edit-button {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.single-classified-listing--opened {
|
||||
&.single-listing--opened {
|
||||
z-index: 20;
|
||||
display: block;
|
||||
position: relative;
|
||||
|
|
@ -508,13 +508,13 @@
|
|||
h3 {
|
||||
font-size: calc(20px + 0.05vw);
|
||||
}
|
||||
.single-classified-listing-body {
|
||||
.single-listing-body {
|
||||
font-size: calc(18px + 0.05vw);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.single-classified-listing-info-link {
|
||||
.single-listing-info-link {
|
||||
display: block;
|
||||
width: 320px;
|
||||
max-width: 92%;
|
||||
|
|
@ -566,7 +566,7 @@ form.listings-contact-via-connect {
|
|||
}
|
||||
}
|
||||
|
||||
.classified-listings-modal-background {
|
||||
.listings-modal-background {
|
||||
z-index: 15;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
|
|
@ -11,6 +11,6 @@
|
|||
@import 'GistTag';
|
||||
@import 'GithubReadmeTag';
|
||||
@import 'PollTag';
|
||||
@import 'ClassifiedListingTag';
|
||||
@import 'ListingTag';
|
||||
@import 'StackexchangeTag';
|
||||
@import 'WikipediaTag';
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
@import 'sticky-nav';
|
||||
@import 'sidebar-data';
|
||||
@import 'video-collection';
|
||||
@import 'classified_listings';
|
||||
@import 'listings';
|
||||
@import 'credits';
|
||||
@import 'item-list';
|
||||
@import 'color-picker';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
module Admin
|
||||
class ClassifiedListingCategoriesController < Admin::ApplicationController
|
||||
class ListingCategoriesController < Admin::ApplicationController
|
||||
# Overwrite any of the RESTful controller actions to implement custom behavior
|
||||
# For example, you may want to send an email after a foo is updated.
|
||||
#
|
||||
|
|
@ -1,48 +1,46 @@
|
|||
module Api
|
||||
module V0
|
||||
class ClassifiedListingsController < ApiController
|
||||
class ListingsController < ApiController
|
||||
include Pundit
|
||||
include ClassifiedListingsToolkit
|
||||
include ListingsToolkit
|
||||
|
||||
before_action :authenticate_with_api_key_or_current_user!, only: %i[create update]
|
||||
before_action :authenticate_with_api_key_or_current_user, only: %i[show]
|
||||
|
||||
before_action :set_classified_listing, only: %i[update]
|
||||
before_action :set_listing, only: %i[update]
|
||||
|
||||
before_action :set_cache_control_headers, only: %i[index show]
|
||||
|
||||
skip_before_action :verify_authenticity_token, only: %i[create update]
|
||||
|
||||
def index
|
||||
@classified_listings = ClassifiedListing.published.
|
||||
@listings = Listing.published.
|
||||
select(ATTRIBUTES_FOR_SERIALIZATION).
|
||||
includes(:user, :organization, :taggings, :classified_listing_category)
|
||||
includes(:user, :organization, :taggings, :listing_category)
|
||||
|
||||
if params[:category].present?
|
||||
category = ClassifiedListingCategory.find_by(slug: params[:category])
|
||||
@classified_listings =
|
||||
@classified_listings.where(classified_listing_category: category)
|
||||
@listings = @listings.in_category(params[:category])
|
||||
end
|
||||
@classified_listings = @classified_listings.order(bumped_at: :desc)
|
||||
@listings = @listings.order(bumped_at: :desc)
|
||||
|
||||
per_page = (params[:per_page] || 30).to_i
|
||||
num = [per_page, 100].min
|
||||
page = params[:page] || 1
|
||||
@classified_listings = @classified_listings.page(page).per(num)
|
||||
@listings = @listings.page(page).per(num)
|
||||
|
||||
set_surrogate_key_header ClassifiedListing.table_key, @classified_listings.map(&:record_key)
|
||||
set_surrogate_key_header Listing.table_key, @listings.map(&:record_key)
|
||||
end
|
||||
|
||||
def show
|
||||
relation = ClassifiedListing.published
|
||||
relation = Listing.published
|
||||
|
||||
# if the user is authenticated we allow them to access
|
||||
# their own unpublished listings as well
|
||||
relation = relation.union(@user.classified_listings) if @user
|
||||
relation = relation.union(@user.listings) if @user
|
||||
|
||||
@classified_listing = relation.select(ATTRIBUTES_FOR_SERIALIZATION).find(params[:id])
|
||||
@listing = relation.select(ATTRIBUTES_FOR_SERIALIZATION).find(params[:id])
|
||||
|
||||
set_surrogate_key_header @classified_listing.record_key
|
||||
set_surrogate_key_header @listing.record_key
|
||||
end
|
||||
|
||||
def create
|
||||
|
|
@ -53,6 +51,8 @@ module Api
|
|||
super
|
||||
end
|
||||
|
||||
# Note: since this is used for selecting from the DB, we need to use the
|
||||
# actual column name for the listing category, prefixed with classified_.
|
||||
ATTRIBUTES_FOR_SERIALIZATION = %i[
|
||||
id user_id organization_id title slug body_markdown cached_tag_list
|
||||
classified_listing_category_id processed_html published
|
||||
|
|
@ -75,7 +75,7 @@ module Api
|
|||
end
|
||||
|
||||
def process_unsuccessful_draft
|
||||
render json: { errors: @classified_listing.errors }, status: :unprocessable_entity
|
||||
render json: { errors: @listing.errors }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def process_successful_creation
|
||||
|
|
@ -83,7 +83,7 @@ module Api
|
|||
end
|
||||
|
||||
def process_unsuccessful_creation
|
||||
render json: { errors: @classified_listing.errors }, status: :unprocessable_entity
|
||||
render json: { errors: @listing.errors }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
alias process_unsuccessful_update process_unsuccessful_creation
|
||||
|
|
@ -95,6 +95,23 @@ module Api
|
|||
def process_after_unpublish
|
||||
render "show", status: :ok
|
||||
end
|
||||
|
||||
# Note: when doing the big listings refactoring we decided not to break
|
||||
# this API. Since other code assumes the params will be under listing,
|
||||
# we're copying them over.
|
||||
def listing_params
|
||||
params["listing"] = params["classified_listing"]
|
||||
if (category_id = find_category_id(params.dig("listing", "category")))
|
||||
params["listing"]["listing_category_id"] = category_id
|
||||
end
|
||||
super
|
||||
end
|
||||
|
||||
def find_category_id(slug)
|
||||
return if slug.blank?
|
||||
|
||||
ListingCategory.select(:id).find_by(slug: slug)&.id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -86,7 +86,7 @@ class ChatChannelsController < ApplicationController
|
|||
|
||||
def create_chat
|
||||
chat_recipient = User.find(params[:user_id])
|
||||
valid_listing = ClassifiedListing.where(user_id: params[:user_id], contact_via_connect: true).limit(1)
|
||||
valid_listing = Listing.where(user_id: params[:user_id], contact_via_connect: true).limit(1)
|
||||
authorize ChatChannel
|
||||
|
||||
if chat_recipient.inbox_type == "open" || valid_listing.length == 1
|
||||
|
|
|
|||
|
|
@ -1,56 +1,56 @@
|
|||
module ClassifiedListingsToolkit
|
||||
module ListingsToolkit
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
MANDATORY_FIELDS_FOR_UPDATE = %i[body_markdown title tag_list].freeze
|
||||
|
||||
def unpublish_listing
|
||||
@classified_listing.update(published: false)
|
||||
@listing.update(published: false)
|
||||
end
|
||||
|
||||
def publish_listing
|
||||
@classified_listing.update(published: true)
|
||||
@listing.update(published: true)
|
||||
end
|
||||
|
||||
def update_listing_details
|
||||
# [thepracticaldev/oss] Not entirely sure what the intention behind the
|
||||
# original code was, but at least this is more compact.
|
||||
filtered_params = listing_params.reject { |_k, v| v.nil? }
|
||||
@classified_listing.update(filtered_params)
|
||||
@listing.update(filtered_params)
|
||||
end
|
||||
|
||||
def bump_listing_success
|
||||
@classified_listing.update(bumped_at: Time.current)
|
||||
@listing.update(bumped_at: Time.current)
|
||||
end
|
||||
|
||||
def clear_listings_cache
|
||||
ClassifiedListings::BustCacheWorker.perform_async(@classified_listing.id)
|
||||
Listings::BustCacheWorker.perform_async(@listing.id)
|
||||
end
|
||||
|
||||
def set_classified_listing
|
||||
@classified_listing = ClassifiedListing.find(params[:id])
|
||||
def set_listing
|
||||
@listing = Listing.find(params[:id])
|
||||
end
|
||||
|
||||
def create
|
||||
@classified_listing = ClassifiedListing.new(listing_params)
|
||||
@listing = Listing.new(listing_params)
|
||||
|
||||
# this will 401 for now if they don't belong in the org
|
||||
authorize @classified_listing, :authorized_organization_poster? if @classified_listing.organization_id.present?
|
||||
authorize @listing, :authorized_organization_poster? if @listing.organization_id.present?
|
||||
|
||||
@classified_listing.user_id = current_user.id
|
||||
org = Organization.find_by(id: @classified_listing.organization_id)
|
||||
@listing.user_id = current_user.id
|
||||
org = Organization.find_by(id: @listing.organization_id)
|
||||
|
||||
if listing_params[:action] == "draft"
|
||||
create_draft
|
||||
return
|
||||
end
|
||||
|
||||
unless @classified_listing.valid?
|
||||
unless @listing.valid?
|
||||
@credits = current_user.credits.unspent
|
||||
process_unsuccessful_creation
|
||||
return
|
||||
end
|
||||
|
||||
cost = @classified_listing.cost
|
||||
cost = @listing.cost
|
||||
# we use the org's credits if available, otherwise we default to the user's
|
||||
if org&.enough_credits?(cost)
|
||||
create_listing(org, cost)
|
||||
|
|
@ -62,26 +62,26 @@ module ClassifiedListingsToolkit
|
|||
end
|
||||
|
||||
ALLOWED_PARAMS = %i[
|
||||
title body_markdown classified_listing_category_id tag_list
|
||||
title body_markdown listing_category_id tag_list
|
||||
expires_at contact_via_connect location organization_id action
|
||||
].freeze
|
||||
|
||||
# Filter for a set of known safe params
|
||||
def listing_params
|
||||
tags = params["classified_listing"].delete("tags")
|
||||
tags = params["listing"].delete("tags")
|
||||
if tags.present?
|
||||
params["classified_listing"]["tag_list"] = tags.join(", ")
|
||||
params["listing"]["tag_list"] = tags.join(", ")
|
||||
end
|
||||
params.require(:classified_listing).permit(ALLOWED_PARAMS)
|
||||
params.require(:listing).permit(ALLOWED_PARAMS)
|
||||
end
|
||||
|
||||
def create_draft
|
||||
@classified_listing.published = false
|
||||
if @classified_listing.save
|
||||
@listing.published = false
|
||||
if @listing.save
|
||||
process_successful_draft
|
||||
else
|
||||
@credits = current_user.credits.unspent
|
||||
@classified_listing.cached_tag_list = listing_params[:tag_list]
|
||||
@listing.cached_tag_list = listing_params[:tag_list]
|
||||
@organizations = current_user.organizations
|
||||
process_unsuccessful_draft
|
||||
end
|
||||
|
|
@ -93,18 +93,18 @@ module ClassifiedListingsToolkit
|
|||
# subtract credits
|
||||
Credits::Buyer.call(
|
||||
purchaser: purchaser,
|
||||
purchase: @classified_listing,
|
||||
purchase: @listing,
|
||||
cost: cost,
|
||||
)
|
||||
|
||||
# save the listing
|
||||
@classified_listing.bumped_at = Time.current
|
||||
@classified_listing.published = true
|
||||
@listing.bumped_at = Time.current
|
||||
@listing.published = true
|
||||
|
||||
# since we can't raise active record errors in this transaction
|
||||
# due to the fact that we need to display them in the :new view,
|
||||
# we manually rollback the transaction if there are validation errors
|
||||
raise ActiveRecord::Rollback unless @classified_listing.save
|
||||
raise ActiveRecord::Rollback unless @listing.save
|
||||
|
||||
successful_transaction = true
|
||||
end
|
||||
|
|
@ -115,16 +115,16 @@ module ClassifiedListingsToolkit
|
|||
process_successful_creation
|
||||
else
|
||||
@credits = current_user.credits.unspent
|
||||
@classified_listing.cached_tag_list = listing_params[:tag_list]
|
||||
@listing.cached_tag_list = listing_params[:tag_list]
|
||||
@organizations = current_user.organizations
|
||||
process_unsuccessful_creation
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @classified_listing
|
||||
authorize @listing
|
||||
|
||||
cost = @classified_listing.cost
|
||||
cost = @listing.cost
|
||||
|
||||
# NOTE: this should probably be split in three different actions: bump, unpublish, publish
|
||||
return bump_listing(cost) if listing_params[:action] == "bump"
|
||||
|
|
@ -134,7 +134,7 @@ module ClassifiedListingsToolkit
|
|||
process_after_unpublish
|
||||
return
|
||||
elsif listing_params[:action] == "publish"
|
||||
unless @classified_listing.bumped_at?
|
||||
unless @listing.bumped_at?
|
||||
first_publish(cost)
|
||||
return
|
||||
end
|
||||
|
|
@ -150,7 +150,7 @@ module ClassifiedListingsToolkit
|
|||
end
|
||||
|
||||
def bump_listing(cost)
|
||||
org = Organization.find_by(id: @classified_listing.organization_id)
|
||||
org = Organization.find_by(id: @listing.organization_id)
|
||||
|
||||
if org&.enough_credits?(cost)
|
||||
charge_credits_before_bump(org, cost)
|
||||
|
|
@ -165,7 +165,7 @@ module ClassifiedListingsToolkit
|
|||
ActiveRecord::Base.transaction do
|
||||
Credits::Buyer.call(
|
||||
purchaser: purchaser,
|
||||
purchase: @classified_listing,
|
||||
purchase: @listing,
|
||||
cost: cost,
|
||||
)
|
||||
|
||||
|
|
@ -174,14 +174,14 @@ module ClassifiedListingsToolkit
|
|||
end
|
||||
|
||||
def first_publish(cost)
|
||||
available_author_credits = @classified_listing.author.credits.unspent
|
||||
available_author_credits = @listing.author.credits.unspent
|
||||
available_user_credits = []
|
||||
if @classified_listing.author.is_a?(Organization)
|
||||
if @listing.author.is_a?(Organization)
|
||||
available_user_credits = current_user.credits.unspent
|
||||
end
|
||||
|
||||
if available_author_credits.size >= cost
|
||||
create_listing(@classified_listing.author, cost)
|
||||
create_listing(@listing.author, cost)
|
||||
elsif available_user_credits.size >= cost
|
||||
create_listing(current_user, cost)
|
||||
else
|
||||
|
|
@ -190,7 +190,7 @@ module ClassifiedListingsToolkit
|
|||
end
|
||||
|
||||
def listing_updatable?
|
||||
at_least_one_param_present? && (bumped_in_last_24_hrs? || !@classified_listing.published)
|
||||
at_least_one_param_present? && (bumped_in_last_24_hrs? || !@listing.published)
|
||||
end
|
||||
|
||||
def at_least_one_param_present?
|
||||
|
|
@ -198,6 +198,6 @@ module ClassifiedListingsToolkit
|
|||
end
|
||||
|
||||
def bumped_in_last_24_hrs?
|
||||
@classified_listing.bumped_at && @classified_listing.bumped_at > 24.hours.ago
|
||||
@listing.bumped_at && @listing.bumped_at > 24.hours.ago
|
||||
end
|
||||
end
|
||||
|
|
@ -5,7 +5,7 @@ class Internal::BufferUpdatesController < Internal::ApplicationController
|
|||
fb_post = params[:fb_post]
|
||||
tweet = params[:tweet]
|
||||
listing_id = params[:listing_id]
|
||||
listing = ClassifiedListing.find(params[:listing_id]) if listing_id.present?
|
||||
listing = Listing.find(params[:listing_id]) if listing_id.present?
|
||||
article&.update(featured: true)
|
||||
case params[:social_channel]
|
||||
when "main_twitter"
|
||||
|
|
|
|||
|
|
@ -1,54 +0,0 @@
|
|||
class Internal::ClassifiedListingsController < Internal::ApplicationController
|
||||
include ClassifiedListingsToolkit
|
||||
layout "internal"
|
||||
|
||||
def index
|
||||
@classified_listings =
|
||||
ClassifiedListing.includes(%i[user classified_listing_category]).
|
||||
page(params[:page]).order("bumped_at DESC").per(50)
|
||||
|
||||
@classified_listings = @classified_listings.published unless include_unpublished?
|
||||
@classified_listings = @classified_listings.in_category(params[:filter]) if params[:filter].present?
|
||||
end
|
||||
|
||||
def edit
|
||||
@classified_listing = ClassifiedListing.find(params[:id])
|
||||
end
|
||||
|
||||
def update
|
||||
@classified_listing = ClassifiedListing.find(params[:id])
|
||||
handle_publish_status if listing_params[:published]
|
||||
bump_listing(@classified_listing.cost) if listing_params[:action] == "bump"
|
||||
update_listing_details
|
||||
clear_listings_cache
|
||||
flash[:success] = "Listing updated successfully"
|
||||
redirect_to edit_internal_classified_listing_path(@classified_listing)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@classified_listing = ClassifiedListing.find(params[:id])
|
||||
@classified_listing.destroy
|
||||
flash[:warning] = "'#{@classified_listing.title}' was destroyed successfully"
|
||||
redirect_to internal_classified_listings_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
ALLOWED_PARAMS = %i[
|
||||
published body_markdown title category classified_listing_category_id tag_list action
|
||||
].freeze
|
||||
private_constant :ALLOWED_PARAMS
|
||||
|
||||
def listing_params
|
||||
params.require(:classified_listing).permit(ALLOWED_PARAMS)
|
||||
end
|
||||
|
||||
def handle_publish_status
|
||||
unpublish_listing if listing_params[:published] == "0"
|
||||
publish_listing if listing_params[:published] == "1"
|
||||
end
|
||||
|
||||
def include_unpublished?
|
||||
params[:include_unpublished] == "1"
|
||||
end
|
||||
end
|
||||
54
app/controllers/internal/listings_controller.rb
Normal file
54
app/controllers/internal/listings_controller.rb
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
class Internal::ListingsController < Internal::ApplicationController
|
||||
include ListingsToolkit
|
||||
layout "internal"
|
||||
|
||||
def index
|
||||
@listings =
|
||||
Listing.includes(%i[user listing_category]).
|
||||
page(params[:page]).order("bumped_at DESC").per(50)
|
||||
|
||||
@listings = @listings.published unless include_unpublished?
|
||||
@listings = @listings.in_category(params[:filter]) if params[:filter].present?
|
||||
end
|
||||
|
||||
def edit
|
||||
@listing = Listing.find(params[:id])
|
||||
end
|
||||
|
||||
def update
|
||||
@listing = Listing.find(params[:id])
|
||||
handle_publish_status if listing_params[:published]
|
||||
bump_listing(@listing.cost) if listing_params[:action] == "bump"
|
||||
update_listing_details
|
||||
clear_listings_cache
|
||||
flash[:success] = "Listing updated successfully"
|
||||
redirect_to edit_internal_listing_path(@listing)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@listing = Listing.find(params[:id])
|
||||
@listing.destroy
|
||||
flash[:warning] = "'#{@listing.title}' was destroyed successfully"
|
||||
redirect_to internal_listings_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
ALLOWED_PARAMS = %i[
|
||||
published body_markdown title category listing_category_id tag_list action
|
||||
].freeze
|
||||
private_constant :ALLOWED_PARAMS
|
||||
|
||||
def listing_params
|
||||
params.require(:listing).permit(ALLOWED_PARAMS)
|
||||
end
|
||||
|
||||
def handle_publish_status
|
||||
unpublish_listing if listing_params[:published] == "0"
|
||||
publish_listing if listing_params[:published] == "1"
|
||||
end
|
||||
|
||||
def include_unpublished?
|
||||
params[:include_unpublished] == "1"
|
||||
end
|
||||
end
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
class ClassifiedListingsController < ApplicationController
|
||||
include ClassifiedListingsToolkit
|
||||
class ListingsController < ApplicationController
|
||||
include ListingsToolkit
|
||||
before_action :check_limit, only: [:create]
|
||||
|
||||
JSON_OPTIONS = {
|
||||
|
|
@ -11,39 +11,40 @@ class ClassifiedListingsController < ApplicationController
|
|||
}
|
||||
}.freeze
|
||||
|
||||
before_action :set_classified_listing, only: %i[edit update destroy]
|
||||
before_action :set_listing, only: %i[edit update destroy]
|
||||
before_action :set_cache_control_headers, only: %i[index]
|
||||
before_action :raise_suspended, only: %i[new create update]
|
||||
after_action :verify_authorized, only: %i[edit update]
|
||||
before_action :authenticate_user!, only: %i[edit update new dashboard]
|
||||
after_action :verify_authorized, only: %i[edit update]
|
||||
|
||||
def index
|
||||
published_listings = ClassifiedListing.where(published: true)
|
||||
@displayed_classified_listing = published_listings.find_by(slug: params[:slug]) if params[:slug]
|
||||
published_listings = Listing.where(published: true)
|
||||
@displayed_listing = published_listings.find_by(slug: params[:slug]) if params[:slug]
|
||||
|
||||
if params[:view] == "moderate"
|
||||
not_found unless @displayed_classified_listing
|
||||
return redirect_to edit_internal_listing_path(id: @displayed_classified_listing.id)
|
||||
not_found unless @displayed_listing
|
||||
return redirect_to edit_internal_listing_path(id: @displayed_listing.id)
|
||||
end
|
||||
|
||||
@classified_listings =
|
||||
@listings =
|
||||
if params[:category].blank?
|
||||
published_listings.
|
||||
order("bumped_at DESC").
|
||||
includes(:user, :organization, :taggings).
|
||||
limit(12)
|
||||
else
|
||||
ClassifiedListing.none
|
||||
Listing.none
|
||||
end
|
||||
|
||||
@listings_json = @classified_listings.to_json(JSON_OPTIONS)
|
||||
@displayed_listing_json = @displayed_classified_listing.to_json(JSON_OPTIONS)
|
||||
@listings_json = @listings.to_json(JSON_OPTIONS)
|
||||
@displayed_listing_json = @displayed_listing.to_json(JSON_OPTIONS)
|
||||
|
||||
# TODO: [mkohl] Can we change this to listings-#{params[:category]}?
|
||||
set_surrogate_key_header "classified-listings-#{params[:category]}"
|
||||
end
|
||||
|
||||
def new
|
||||
@classified_listing = ClassifiedListing.new
|
||||
@listing = Listing.new
|
||||
@organizations = current_user.organizations
|
||||
@credits = current_user.credits.unspent
|
||||
end
|
||||
|
|
@ -57,32 +58,32 @@ class ClassifiedListingsController < ApplicationController
|
|||
end
|
||||
|
||||
def edit
|
||||
authorize @classified_listing
|
||||
authorize @listing
|
||||
@organizations = current_user.organizations
|
||||
@credits = current_user.credits.unspent
|
||||
end
|
||||
|
||||
def dashboard
|
||||
@classified_listings = current_user.classified_listings.
|
||||
@listings = current_user.listings.
|
||||
includes(:organization, :taggings)
|
||||
|
||||
organizations_ids = current_user.organization_memberships.
|
||||
where(type_of_user: "admin").
|
||||
pluck(:organization_id)
|
||||
@orgs = Organization.where(id: organizations_ids)
|
||||
@org_listings = ClassifiedListing.where(organization_id: organizations_ids)
|
||||
@org_listings = Listing.where(organization_id: organizations_ids)
|
||||
@user_credits = current_user.unspent_credits_count
|
||||
end
|
||||
|
||||
def delete_confirm
|
||||
@classified_listing = ClassifiedListing.find_by(slug: params[:slug])
|
||||
not_found unless @classified_listing
|
||||
authorize @classified_listing
|
||||
@listing = Listing.find_by(slug: params[:slug])
|
||||
not_found unless @listing
|
||||
authorize @listing
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @classified_listing
|
||||
@classified_listing.destroy!
|
||||
authorize @listing
|
||||
@listing.destroy!
|
||||
redirect_to "/listings/dashboard", notice: "Listing was successfully deleted."
|
||||
end
|
||||
|
||||
|
|
@ -101,7 +102,7 @@ class ClassifiedListingsController < ApplicationController
|
|||
end
|
||||
|
||||
def process_successful_creation
|
||||
redirect_to classified_listings_path
|
||||
redirect_to listings_path
|
||||
end
|
||||
|
||||
def process_unsuccessful_creation
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
class SearchController < ApplicationController
|
||||
before_action :authenticate_user!, only: %i[tags chat_channels reactions]
|
||||
before_action :format_integer_params
|
||||
before_action :sanitize_params, only: %i[classified_listings reactions feed_content]
|
||||
before_action :sanitize_params, only: %i[listings reactions feed_content]
|
||||
|
||||
CLASSIFIED_LISTINGS_PARAMS = [
|
||||
LISTINGS_PARAMS = [
|
||||
:category,
|
||||
:classified_listing_search,
|
||||
:listing_search,
|
||||
:page,
|
||||
:per_page,
|
||||
:tag_boolean_mode,
|
||||
|
|
@ -65,9 +65,9 @@ class SearchController < ApplicationController
|
|||
render json: { result: ccm_docs }
|
||||
end
|
||||
|
||||
def classified_listings
|
||||
cl_docs = Search::ClassifiedListing.search_documents(
|
||||
params: classified_listing_params.to_h,
|
||||
def listings
|
||||
cl_docs = Search::Listing.search_documents(
|
||||
params: listing_params.to_h,
|
||||
)
|
||||
|
||||
render json: { result: cl_docs }
|
||||
|
|
@ -125,8 +125,8 @@ class SearchController < ApplicationController
|
|||
params.permit(accessible)
|
||||
end
|
||||
|
||||
def classified_listing_params
|
||||
params.permit(CLASSIFIED_LISTINGS_PARAMS)
|
||||
def listing_params
|
||||
params.permit(LISTINGS_PARAMS)
|
||||
end
|
||||
|
||||
def user_params
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class SocialPreviewsController < ApplicationController
|
|||
end
|
||||
|
||||
def listing
|
||||
@listing = ClassifiedListing.find(params[:id]).decorate
|
||||
@listing = Listing.find(params[:id]).decorate
|
||||
set_respond
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ class StoriesController < ApplicationController
|
|||
title path id user_id comments_count public_reactions_count organization_id
|
||||
reading_time video_thumbnail_url video video_duration_in_minutes language
|
||||
experience_level_rating experience_level_rating_distribution cached_user cached_organization
|
||||
classified_listing_category_id
|
||||
listing_category_id
|
||||
],
|
||||
methods: %i[
|
||||
readable_publish_date cached_tag_list_array flare_tag class_name
|
||||
|
|
@ -157,7 +157,7 @@ class StoriesController < ApplicationController
|
|||
assign_feed_stories
|
||||
assign_hero_html
|
||||
assign_podcasts
|
||||
assign_classified_listings
|
||||
assign_listings
|
||||
get_latest_campaign_articles if SiteConfig.campaign_sidebar_enabled?
|
||||
@article_index = true
|
||||
@featured_story = (featured_story || Article.new)&.decorate
|
||||
|
|
@ -338,8 +338,8 @@ class StoriesController < ApplicationController
|
|||
select(:slug, :title, :podcast_id, :image)
|
||||
end
|
||||
|
||||
def assign_classified_listings
|
||||
@classified_listings = ClassifiedListing.where(published: true).select(:title, :classified_listing_category_id, :slug, :bumped_at)
|
||||
def assign_listings
|
||||
@listings = Listing.where(published: true).select(:title, :classified_listing_category_id, :slug, :bumped_at)
|
||||
end
|
||||
|
||||
def redirect_to_lowercase_username
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class DashboardManifest
|
|||
html_variant_trials
|
||||
html_variant_successes
|
||||
sponsorships
|
||||
classified_listing_categories
|
||||
listing_categories
|
||||
].freeze
|
||||
# DASHBOARDS = [
|
||||
# :users,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
require "administrate/base_dashboard"
|
||||
|
||||
class ClassifiedListingCategoryDashboard < Administrate::BaseDashboard
|
||||
class ListingCategoryDashboard < Administrate::BaseDashboard
|
||||
# ATTRIBUTE_TYPES
|
||||
# a hash that describes the type of each of the model's fields.
|
||||
#
|
||||
|
|
@ -8,7 +8,7 @@ class ClassifiedListingCategoryDashboard < Administrate::BaseDashboard
|
|||
# which determines how the attribute is displayed
|
||||
# on pages throughout the dashboard.
|
||||
ATTRIBUTE_TYPES = {
|
||||
classified_listings: Field::HasMany,
|
||||
listings: Field::HasMany,
|
||||
id: Field::Number,
|
||||
cost: Field::Number,
|
||||
created_at: Field::DateTime,
|
||||
|
|
@ -72,10 +72,10 @@ class ClassifiedListingCategoryDashboard < Administrate::BaseDashboard
|
|||
# }.freeze
|
||||
COLLECTION_FILTERS = {}.freeze
|
||||
|
||||
# Overwrite this method to customize how classified listing categories are displayed
|
||||
# Overwrite this method to customize how listing categories are displayed
|
||||
# across all pages of the admin dashboard.
|
||||
#
|
||||
# def display_resource(classified_listing_category)
|
||||
# "ClassifiedListingCategory ##{classified_listing_category.id}"
|
||||
# def display_resource(listing_category)
|
||||
# "ListingCategory ##{listing_category.id}"
|
||||
# end
|
||||
end
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
class ClassifiedListingDecorator < ApplicationDecorator
|
||||
class ListingDecorator < ApplicationDecorator
|
||||
DEFAULT_COLOR = "#000000".freeze
|
||||
|
||||
def social_preview_category
|
||||
category = object.classified_listing_category
|
||||
category = object.listing_category
|
||||
category.social_preview_description.presence || category.name
|
||||
end
|
||||
|
||||
def social_preview_color(brightness: 1.0)
|
||||
category = object.classified_listing_category
|
||||
category = object.listing_category
|
||||
color = category.social_preview_color.presence || DEFAULT_COLOR
|
||||
HexComparer.new([color]).brightness(brightness)
|
||||
end
|
||||
|
|
@ -1,18 +1,18 @@
|
|||
module ClassifiedListingHelper
|
||||
module ListingHelper
|
||||
def select_options_for_categories
|
||||
ClassifiedListingCategory.select(:id, :name, :cost).map do |cl|
|
||||
ListingCategory.select(:id, :name, :cost).map do |cl|
|
||||
["#{cl.name} (#{cl.cost} #{'Credit'.pluralize(cl.cost)})", cl.id]
|
||||
end
|
||||
end
|
||||
|
||||
def categories_for_display
|
||||
ClassifiedListingCategory.pluck(:slug, :name).map do |slug, name|
|
||||
ListingCategory.pluck(:slug, :name).map do |slug, name|
|
||||
{ slug: slug, name: name }
|
||||
end
|
||||
end
|
||||
|
||||
def categories_available
|
||||
ClassifiedListingCategory.all.each_with_object({}) do |cat, h|
|
||||
ListingCategory.all.each_with_object({}) do |cat, h|
|
||||
h[cat.slug] = cat.attributes.slice("cost", "name", "rules")
|
||||
end.deep_symbolize_keys
|
||||
end
|
||||
|
|
@ -34,7 +34,7 @@ exports[`<ArticleForm /> renders properly 1`] = `
|
|||
autoComplete="off"
|
||||
class="articleform__tags"
|
||||
id="tag-input"
|
||||
name="classified_listing[tag_list]"
|
||||
name="listing[tag_list]"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onInput={[Function]}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ exports[`<Tags /> renders properly 1`] = `
|
|||
autoComplete="off"
|
||||
class="articleform__tags"
|
||||
id="tag-input"
|
||||
name="classified_listing[tag_list]"
|
||||
name="listing[tag_list]"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onInput={[Function]}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { h } from 'preact';
|
||||
import { deep } from 'preact-render-spy';
|
||||
import ClassifiedFiltersTags from '../components/ClassifiedFiltersTags';
|
||||
import ListingFiltersTags from '../components/ListingFiltersTags';
|
||||
|
||||
describe('<ClassifiedFilterTags />', () => {
|
||||
describe('<ListingFilterTags />', () => {
|
||||
const firstTag = {
|
||||
id: 1,
|
||||
tag: 'clojure',
|
||||
|
|
@ -36,11 +36,11 @@ describe('<ClassifiedFilterTags />', () => {
|
|||
tags: getTags(),
|
||||
});
|
||||
|
||||
const renderClassifiedFilterTags = (props = getProps()) =>
|
||||
deep(<ClassifiedFiltersTags {...props} />);
|
||||
const renderListingFilterTags = (props = getProps()) =>
|
||||
deep(<ListingFiltersTags {...props} />);
|
||||
|
||||
describe('Should render a search field', () => {
|
||||
const context = renderClassifiedFilterTags();
|
||||
const context = renderListingFilterTags();
|
||||
const searchField = context.find('#listings-search');
|
||||
|
||||
it('Should have "search" as placeholder', () => {
|
||||
|
|
@ -57,7 +57,7 @@ describe('<ClassifiedFilterTags />', () => {
|
|||
});
|
||||
|
||||
describe('<ClearQueryButton />', () => {
|
||||
const context = renderClassifiedFilterTags();
|
||||
const context = renderListingFilterTags();
|
||||
|
||||
it('Should render the clear query button', () => {
|
||||
expect(context.find('#clear-query-button').exists()).toBe(true);
|
||||
|
|
@ -65,7 +65,7 @@ describe('<ClassifiedFilterTags />', () => {
|
|||
|
||||
it('Should not render the clear query button', () => {
|
||||
const propsWithoutQuery = { ...getProps(), query: '' };
|
||||
const contextWithAnotherProps = renderClassifiedFilterTags(
|
||||
const contextWithAnotherProps = renderListingFilterTags(
|
||||
propsWithoutQuery,
|
||||
);
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ describe('<ClassifiedFilterTags />', () => {
|
|||
});
|
||||
|
||||
it('Should render the selected Tags', () => {
|
||||
const context = renderClassifiedFilterTags();
|
||||
const context = renderListingFilterTags();
|
||||
getTags().forEach((tag) => {
|
||||
const selectedTag = context.find(`#selected-tag-${tag.id}`);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { h } from 'preact';
|
||||
import render from 'preact-render-to-json';
|
||||
import ClassifiedFilters from '../components/ClassifiedFilters';
|
||||
import ListingFilters from '../components/ListingFilters';
|
||||
|
||||
describe('<ClassifiedFilters />', () => {
|
||||
describe('<ListingFilters />', () => {
|
||||
const firstTag = {
|
||||
id: 1,
|
||||
tag: 'clojure',
|
||||
|
|
@ -61,11 +61,11 @@ describe('<ClassifiedFilters />', () => {
|
|||
tags: getTags(),
|
||||
});
|
||||
|
||||
const renderClassifiedFilters = (props = getProps()) =>
|
||||
render(<ClassifiedFilters {...props} />);
|
||||
const renderListingFilters = (props = getProps()) =>
|
||||
render(<ListingFilters {...props} />);
|
||||
|
||||
it('Should match the snapshot', () => {
|
||||
const context = renderClassifiedFilters();
|
||||
const context = renderListingFilters();
|
||||
expect(context).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { h } from 'preact';
|
||||
import { deep } from 'preact-render-spy';
|
||||
import ClassifiedFiltersCategories from '../components/ClassifiedFiltersCategories';
|
||||
import ListingFiltersCategories from '../components/ListingFiltersCategories';
|
||||
|
||||
describe('<ClassifiedFiltersCategories />', () => {
|
||||
describe('<ListingFiltersCategories />', () => {
|
||||
const firstCategory = {
|
||||
id: 20,
|
||||
slug: 'clojure',
|
||||
|
|
@ -31,11 +31,11 @@ describe('<ClassifiedFiltersCategories />', () => {
|
|||
},
|
||||
});
|
||||
|
||||
const renderClassifiedFilterCategories = (props = getProps()) =>
|
||||
deep(<ClassifiedFiltersCategories {...props} />);
|
||||
const renderListingFilterCategories = (props = getProps()) =>
|
||||
deep(<ListingFiltersCategories {...props} />);
|
||||
|
||||
describe('Should render the links to allow navigation', () => {
|
||||
const context = renderClassifiedFilterCategories();
|
||||
const context = renderListingFilterCategories();
|
||||
|
||||
it('Should render a link and a message relative to listings', () => {
|
||||
const listingsLink = context.find('#listings-link');
|
||||
|
|
@ -47,7 +47,7 @@ describe('<ClassifiedFiltersCategories />', () => {
|
|||
|
||||
it('When there\'s no category, the className of the listings link should be "selected"', () => {
|
||||
const propsWithoutCategory = { ...getProps(), category: '' };
|
||||
const contextWithoutCategory = renderClassifiedFilterCategories(
|
||||
const contextWithoutCategory = renderListingFilterCategories(
|
||||
propsWithoutCategory,
|
||||
);
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ describe('<ClassifiedFiltersCategories />', () => {
|
|||
const newListingLink = context.find('#listings-new-link');
|
||||
|
||||
expect(newListingLink.attr('href')).toBe('/listings/new');
|
||||
expect(newListingLink.attr('className')).toBe('classified-create-link');
|
||||
expect(newListingLink.attr('className')).toBe('listing-create-link');
|
||||
expect(newListingLink.text()).toBe('Create a Listing');
|
||||
});
|
||||
|
||||
|
|
@ -67,13 +67,13 @@ describe('<ClassifiedFiltersCategories />', () => {
|
|||
const dashboardLink = context.find('#listings-dashboard-link');
|
||||
|
||||
expect(dashboardLink.attr('href')).toBe('/listings/dashboard');
|
||||
expect(dashboardLink.attr('className')).toBe('classified-create-link');
|
||||
expect(dashboardLink.attr('className')).toBe('listing-create-link');
|
||||
expect(dashboardLink.text()).toBe('Manage Listings');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Should render categories links', () => {
|
||||
const context = renderClassifiedFilterCategories();
|
||||
const context = renderListingFilterCategories();
|
||||
it('Should render the categories name and their respective links', () => {
|
||||
categories.forEach((category) => {
|
||||
const categoryLink = context.find(`#category-link-${category.id}`);
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ const l = [
|
|||
},
|
||||
},
|
||||
];
|
||||
global.document.body.innerHTML = `<div id="classifieds-listings-dashboard" data-listings=${JSON.stringify(
|
||||
global.document.body.innerHTML = `<div id="listings-dashboard" data-listings=${JSON.stringify(
|
||||
l,
|
||||
)} data-usercredits="3" data-orglistings=${JSON.stringify([
|
||||
{
|
||||
|
|
|
|||
|
|
@ -59,12 +59,12 @@ describe('<SingleListing />', () => {
|
|||
isOpen={false}
|
||||
/>,
|
||||
);
|
||||
expect(context.find('.single-classified-listing').exists()).toBeTruthy();
|
||||
expect(context.find('.single-listing').exists()).toBeTruthy();
|
||||
|
||||
it('for listing title', () => {
|
||||
expect(
|
||||
context
|
||||
.find('.single-classified-listing-header')
|
||||
.find('.single-listing-header')
|
||||
.at(0)
|
||||
.childAt(0)
|
||||
.childAt(0)
|
||||
|
|
@ -73,35 +73,26 @@ describe('<SingleListing />', () => {
|
|||
});
|
||||
|
||||
it('for listing tags', () => {
|
||||
expect(
|
||||
context.find('.single-classified-listing-tags').childAt(0).text(),
|
||||
).toEqual(listing.tags[0]);
|
||||
expect(context.find('.single-listing-tags').childAt(0).text()).toEqual(
|
||||
listing.tags[0],
|
||||
);
|
||||
});
|
||||
|
||||
it('for listing category', () => {
|
||||
expect(
|
||||
context
|
||||
.find('.single-classified-listing-author-info')
|
||||
.childAt(0)
|
||||
.text(),
|
||||
context.find('.single-listing-author-info').childAt(0).text(),
|
||||
).toEqual(listing.category);
|
||||
});
|
||||
|
||||
it('for listing location', () => {
|
||||
expect(
|
||||
context
|
||||
.find('.single-classified-listing-author-info')
|
||||
.childAt(1)
|
||||
.text(),
|
||||
context.find('.single-listing-author-info').childAt(1).text(),
|
||||
).toEqual(`・${listing.location}`);
|
||||
});
|
||||
|
||||
it('for listing author', () => {
|
||||
expect(
|
||||
context
|
||||
.find('.single-classified-listing-author-info')
|
||||
.childAt(3)
|
||||
.text(),
|
||||
context.find('.single-listing-author-info').childAt(3).text(),
|
||||
).toEqual(listing.author.name);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,18 +2,18 @@
|
|||
|
||||
exports[`<AllListings /> Should render all listings 1`] = `
|
||||
<div
|
||||
class="classifieds-columns"
|
||||
id="classified-listings-results"
|
||||
class="listings-columns"
|
||||
id="listings-results"
|
||||
>
|
||||
<div
|
||||
class="single-classified-listing"
|
||||
id="single-classified-listing-20"
|
||||
class="single-listing"
|
||||
id="single-listing-20"
|
||||
>
|
||||
<div
|
||||
class="listing-content"
|
||||
>
|
||||
<h3
|
||||
class="single-classified-listing-header"
|
||||
class="single-listing-header"
|
||||
>
|
||||
<a
|
||||
data-listing-id={20}
|
||||
|
|
@ -46,7 +46,7 @@ exports[`<AllListings /> Should render all listings 1`] = `
|
|||
class="dropdown-content "
|
||||
>
|
||||
<a
|
||||
class="classified-listing-edit-button"
|
||||
class="listing-edit-button"
|
||||
href="/listings/20/edit"
|
||||
>
|
||||
Edit
|
||||
|
|
@ -56,7 +56,7 @@ exports[`<AllListings /> Should render all listings 1`] = `
|
|||
</div>
|
||||
</h3>
|
||||
<div
|
||||
class="single-classified-listing-body"
|
||||
class="single-listing-body"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "<p>Eius et ullam. Dolores et qui. Quis <strong>qui</strong> omnis.</p>
|
||||
|
|
@ -65,7 +65,7 @@ exports[`<AllListings /> Should render all listings 1`] = `
|
|||
}
|
||||
/>
|
||||
<div
|
||||
class="single-classified-listing-tags"
|
||||
class="single-listing-tags"
|
||||
>
|
||||
<a
|
||||
data-no-instant={true}
|
||||
|
|
@ -83,7 +83,7 @@ exports[`<AllListings /> Should render all listings 1`] = `
|
|||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="single-classified-listing-author-info"
|
||||
class="single-listing-author-info"
|
||||
>
|
||||
<a
|
||||
data-no-instant={true}
|
||||
|
|
@ -107,14 +107,14 @@ exports[`<AllListings /> Should render all listings 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="single-classified-listing"
|
||||
id="single-classified-listing-21"
|
||||
class="single-listing"
|
||||
id="single-listing-21"
|
||||
>
|
||||
<div
|
||||
class="listing-content"
|
||||
>
|
||||
<h3
|
||||
class="single-classified-listing-header"
|
||||
class="single-listing-header"
|
||||
>
|
||||
<a
|
||||
data-listing-id={21}
|
||||
|
|
@ -147,7 +147,7 @@ exports[`<AllListings /> Should render all listings 1`] = `
|
|||
class="dropdown-content "
|
||||
>
|
||||
<a
|
||||
class="classified-listing-edit-button"
|
||||
class="listing-edit-button"
|
||||
href="/listings/21/edit"
|
||||
>
|
||||
Edit
|
||||
|
|
@ -157,7 +157,7 @@ exports[`<AllListings /> Should render all listings 1`] = `
|
|||
</div>
|
||||
</h3>
|
||||
<div
|
||||
class="single-classified-listing-body"
|
||||
class="single-listing-body"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "<p>Eius et ullam. Dolores et qui. Quis <strong>qui</strong> omnis.</p>
|
||||
|
|
@ -166,7 +166,7 @@ exports[`<AllListings /> Should render all listings 1`] = `
|
|||
}
|
||||
/>
|
||||
<div
|
||||
class="single-classified-listing-tags"
|
||||
class="single-listing-tags"
|
||||
>
|
||||
<a
|
||||
data-no-instant={true}
|
||||
|
|
@ -184,7 +184,7 @@ exports[`<AllListings /> Should render all listings 1`] = `
|
|||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="single-classified-listing-author-info"
|
||||
class="single-listing-author-info"
|
||||
>
|
||||
<a
|
||||
data-no-instant={true}
|
||||
|
|
@ -208,14 +208,14 @@ exports[`<AllListings /> Should render all listings 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="single-classified-listing"
|
||||
id="single-classified-listing-22"
|
||||
class="single-listing"
|
||||
id="single-listing-22"
|
||||
>
|
||||
<div
|
||||
class="listing-content"
|
||||
>
|
||||
<h3
|
||||
class="single-classified-listing-header"
|
||||
class="single-listing-header"
|
||||
>
|
||||
<a
|
||||
data-listing-id={22}
|
||||
|
|
@ -248,7 +248,7 @@ exports[`<AllListings /> Should render all listings 1`] = `
|
|||
class="dropdown-content "
|
||||
>
|
||||
<a
|
||||
class="classified-listing-edit-button"
|
||||
class="listing-edit-button"
|
||||
href="/listings/22/edit"
|
||||
>
|
||||
Edit
|
||||
|
|
@ -258,7 +258,7 @@ exports[`<AllListings /> Should render all listings 1`] = `
|
|||
</div>
|
||||
</h3>
|
||||
<div
|
||||
class="single-classified-listing-body"
|
||||
class="single-listing-body"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "<p>Eius et ullam. Dolores et qui. Quis <strong>qui</strong> omnis.</p>
|
||||
|
|
@ -267,7 +267,7 @@ exports[`<AllListings /> Should render all listings 1`] = `
|
|||
}
|
||||
/>
|
||||
<div
|
||||
class="single-classified-listing-tags"
|
||||
class="single-listing-tags"
|
||||
>
|
||||
<a
|
||||
data-no-instant={true}
|
||||
|
|
@ -285,7 +285,7 @@ exports[`<AllListings /> Should render all listings 1`] = `
|
|||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="single-classified-listing-author-info"
|
||||
class="single-listing-author-info"
|
||||
>
|
||||
<a
|
||||
data-no-instant={true}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ exports[`<BodyMarkdown /> Should match the snapshot 1`] = `
|
|||
class="listingform__input listingform__bodymarkdown"
|
||||
id="body_markdown"
|
||||
maxLength="400"
|
||||
name="classified_listing[body_markdown]"
|
||||
name="listing[body_markdown]"
|
||||
onInput={[Function]}
|
||||
placeholder="400 characters max, 12 line break max, no images allowed, *markdown is encouraged*"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<ClassifiedFilters /> Should match the snapshot 1`] = `
|
||||
exports[`<ListingFilters /> Should match the snapshot 1`] = `
|
||||
<div
|
||||
class="classified-filters"
|
||||
id="classified-filters"
|
||||
class="listing-filters"
|
||||
id="listing-filters"
|
||||
>
|
||||
<div
|
||||
class="classified-filters-categories"
|
||||
class="listing-filters-categories"
|
||||
>
|
||||
<a
|
||||
data-no-instant={true}
|
||||
|
|
@ -47,14 +47,14 @@ exports[`<ClassifiedFilters /> Should match the snapshot 1`] = `
|
|||
</a>
|
||||
</section>
|
||||
<a
|
||||
class="classified-create-link"
|
||||
class="listing-create-link"
|
||||
href="/listings/new"
|
||||
id="listings-new-link"
|
||||
>
|
||||
Create a Listing
|
||||
</a>
|
||||
<a
|
||||
class="classified-create-link"
|
||||
class="listing-create-link"
|
||||
href="/listings/dashboard"
|
||||
id="listings-dashboard-link"
|
||||
>
|
||||
|
|
@ -62,8 +62,8 @@ exports[`<ClassifiedFilters /> Should match the snapshot 1`] = `
|
|||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="classified-filters-tags"
|
||||
id="classified-filters-tags"
|
||||
class="listing-filters-tags"
|
||||
id="listing-filters-tags"
|
||||
>
|
||||
<input
|
||||
autoComplete="off"
|
||||
|
|
@ -74,7 +74,7 @@ exports[`<ClassifiedFilters /> Should match the snapshot 1`] = `
|
|||
type="text"
|
||||
/>
|
||||
<button
|
||||
class="classified-search-clear"
|
||||
class="listing-search-clear"
|
||||
id="clear-query-button"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
|
|
@ -83,7 +83,7 @@ exports[`<ClassifiedFilters /> Should match the snapshot 1`] = `
|
|||
</button>
|
||||
<section>
|
||||
<span
|
||||
class="classified-tag"
|
||||
class="listing-tag"
|
||||
id="selected-tag-1"
|
||||
>
|
||||
<a
|
||||
|
|
@ -110,7 +110,7 @@ exports[`<ClassifiedFilters /> Should match the snapshot 1`] = `
|
|||
</a>
|
||||
</span>
|
||||
<span
|
||||
class="classified-tag"
|
||||
class="listing-tag"
|
||||
id="selected-tag-2"
|
||||
>
|
||||
<a
|
||||
|
|
@ -137,7 +137,7 @@ exports[`<ClassifiedFilters /> Should match the snapshot 1`] = `
|
|||
</a>
|
||||
</span>
|
||||
<span
|
||||
class="classified-tag"
|
||||
class="listing-tag"
|
||||
id="selected-tag-3"
|
||||
>
|
||||
<a
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
exports[`<ClearQueryButton /> Should match the snapshot 1`] = `
|
||||
<button
|
||||
class="classified-search-clear"
|
||||
class="listing-search-clear"
|
||||
id="clear-query-button"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ exports[`<ModalBackground /> Should match the snapshot 1`] = `
|
|||
preact-render-spy (1 nodes)
|
||||
-------
|
||||
<div
|
||||
class="classified-listings-modal-background"
|
||||
class="listings-modal-background"
|
||||
onClick={[Function onClick]}
|
||||
role="presentation"
|
||||
id="classified-listings-modal-background"
|
||||
id="listings-modal-background"
|
||||
>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
exports[`<NextPageButton /> Should match the snapshot 1`] = `
|
||||
preact-render-spy (1 nodes)
|
||||
-------
|
||||
<div class="classifieds-load-more-button">
|
||||
<div class="listings-load-more-button">
|
||||
<button
|
||||
onClick={[Function onClick]}
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
exports[`<SelectedTags /> Should render all the tags 1`] = `
|
||||
<section>
|
||||
<span
|
||||
class="classified-tag"
|
||||
class="listing-tag"
|
||||
id="selected-tag-1"
|
||||
>
|
||||
<a
|
||||
|
|
@ -30,7 +30,7 @@ exports[`<SelectedTags /> Should render all the tags 1`] = `
|
|||
</a>
|
||||
</span>
|
||||
<span
|
||||
class="classified-tag"
|
||||
class="listing-tag"
|
||||
id="selected-tag-2"
|
||||
>
|
||||
<a
|
||||
|
|
@ -57,7 +57,7 @@ exports[`<SelectedTags /> Should render all the tags 1`] = `
|
|||
</a>
|
||||
</span>
|
||||
<span
|
||||
class="classified-tag"
|
||||
class="listing-tag"
|
||||
id="selected-tag-3"
|
||||
>
|
||||
<a
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ exports[`<SingleListing /> should load a single user listing 1`] = `
|
|||
preact-render-spy (1 nodes)
|
||||
-------
|
||||
<div
|
||||
class="single-classified-listing"
|
||||
id="single-classified-listing-22"
|
||||
class="single-listing"
|
||||
id="single-listing-22"
|
||||
>
|
||||
<div class="listing-content">
|
||||
<h3 class="single-classified-listing-header">
|
||||
<h3 class="single-listing-header">
|
||||
<a
|
||||
href="/listings/misc/illo-iure-quos-perspiciatis-5hk7"
|
||||
data-no-instant={true}
|
||||
|
|
@ -37,11 +37,11 @@ preact-render-spy (1 nodes)
|
|||
</div>
|
||||
</div>
|
||||
</h3>
|
||||
<div class="single-classified-listing-body">
|
||||
<div class="single-listing-body">
|
||||
<p>Eius et ullam. Dolores et qui. Quis <strong>qui</strong> omnis.</p>
|
||||
|
||||
</div>
|
||||
<div class="single-classified-listing-tags">
|
||||
<div class="single-listing-tags">
|
||||
<a
|
||||
href="/listings?t=go"
|
||||
onClick={[Function onClick]}
|
||||
|
|
@ -57,7 +57,7 @@ preact-render-spy (1 nodes)
|
|||
git
|
||||
</a>
|
||||
</div>
|
||||
<div class="single-classified-listing-author-info">
|
||||
<div class="single-listing-author-info">
|
||||
<a
|
||||
href="/listings/misc"
|
||||
onClick={[Function onClick]}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const AllListings = ({
|
|||
onOpenModal,
|
||||
}) => {
|
||||
return (
|
||||
<div className="classifieds-columns" id="classified-listings-results">
|
||||
<div className="listings-columns" id="listings-results">
|
||||
{listings.map((listing) => (
|
||||
<SingleListing
|
||||
onAddTag={onAddTag}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const BodyMarkdown = ({ onChange, defaultValue }) => (
|
|||
<textarea
|
||||
className="listingform__input listingform__bodymarkdown"
|
||||
id="body_markdown"
|
||||
name="classified_listing[body_markdown]"
|
||||
name="listing[body_markdown]"
|
||||
maxLength="400"
|
||||
placeholder="400 characters max, 12 line break max, no images allowed, *markdown is encouraged*"
|
||||
value={defaultValue}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class Categories extends Component {
|
|||
<select
|
||||
id="category"
|
||||
className="listingform__input"
|
||||
name="classified_listing[classified_listing_category_id]"
|
||||
name="listing[listing_category_id]"
|
||||
onChange={onChange}
|
||||
onBlur={onChange}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
|||
const ClearQueryButton = ({ onClick }) => (
|
||||
<button
|
||||
type="button"
|
||||
className="classified-search-clear"
|
||||
className="listing-search-clear"
|
||||
onClick={onClick}
|
||||
id="clear-query-button"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const ContactViaConnect = ({ onChange, checked }) => (
|
|||
type="checkbox"
|
||||
className="listingform__input listingform__contact_via_connect"
|
||||
id="contact_via_connect"
|
||||
name="classified_listing[contact_via_connect]"
|
||||
name="listing[contact_via_connect]"
|
||||
onInput={onChange}
|
||||
checked={checked}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ const ExpireDate = ({ onChange, defaultValue }) => {
|
|||
type="date"
|
||||
className="listingform__input"
|
||||
id="expires_at"
|
||||
name="classified_listing[expires_at]"
|
||||
name="listing[expires_at]"
|
||||
value={defaultValue}
|
||||
onInput={onChange}
|
||||
min={tomorrow}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { h } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
import ClassifiedFiltersCategories from './ClassifiedFiltersCategories';
|
||||
import ClassifiedFiltersTags from './ClassifiedFiltersTags';
|
||||
import ListingFiltersCategories from './ListingFiltersCategories';
|
||||
import ListingFiltersTags from './ListingFiltersTags';
|
||||
import { tagPropTypes } from '../../common-prop-types';
|
||||
|
||||
const ClassifiedFilters = ({
|
||||
const ListingFilters = ({
|
||||
categories,
|
||||
category,
|
||||
onSelectCategory,
|
||||
|
|
@ -17,13 +17,13 @@ const ClassifiedFilters = ({
|
|||
query,
|
||||
}) => {
|
||||
return (
|
||||
<div className="classified-filters" id="classified-filters">
|
||||
<ClassifiedFiltersCategories
|
||||
<div className="listing-filters" id="listing-filters">
|
||||
<ListingFiltersCategories
|
||||
categories={categories}
|
||||
category={category}
|
||||
onClick={onSelectCategory}
|
||||
/>
|
||||
<ClassifiedFiltersTags
|
||||
<ListingFiltersTags
|
||||
message={message}
|
||||
onKeyUp={onKeyUp}
|
||||
onClearQuery={onClearQuery}
|
||||
|
|
@ -36,7 +36,7 @@ const ClassifiedFilters = ({
|
|||
);
|
||||
};
|
||||
|
||||
ClassifiedFilters.propTypes = {
|
||||
ListingFilters.propTypes = {
|
||||
categories: PropTypes.isRequired,
|
||||
category: PropTypes.isRequired,
|
||||
onSelectCategory: PropTypes.func.isRequired,
|
||||
|
|
@ -49,4 +49,4 @@ ClassifiedFilters.propTypes = {
|
|||
query: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default ClassifiedFilters;
|
||||
export default ListingFilters;
|
||||
|
|
@ -2,8 +2,8 @@ import { h } from 'preact';
|
|||
import PropTypes from 'prop-types';
|
||||
import CategoryLinks from './CategoryLinks';
|
||||
|
||||
const ClassifiedFiltersCategories = ({ categories, category, onClick }) => (
|
||||
<div className="classified-filters-categories">
|
||||
const ListingFiltersCategories = ({ categories, category, onClick }) => (
|
||||
<div className="listing-filters-categories">
|
||||
<a
|
||||
id="listings-link"
|
||||
href="/listings"
|
||||
|
|
@ -21,24 +21,24 @@ const ClassifiedFiltersCategories = ({ categories, category, onClick }) => (
|
|||
<a
|
||||
id="listings-new-link"
|
||||
href="/listings/new"
|
||||
className="classified-create-link"
|
||||
className="listing-create-link"
|
||||
>
|
||||
Create a Listing
|
||||
</a>
|
||||
<a
|
||||
id="listings-dashboard-link"
|
||||
href="/listings/dashboard"
|
||||
className="classified-create-link"
|
||||
className="listing-create-link"
|
||||
>
|
||||
Manage Listings
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
|
||||
ClassifiedFiltersCategories.propTypes = {
|
||||
ListingFiltersCategories.propTypes = {
|
||||
categories: PropTypes.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
category: PropTypes.isRequired,
|
||||
};
|
||||
|
||||
export default ClassifiedFiltersCategories;
|
||||
export default ListingFiltersCategories;
|
||||
|
|
@ -4,7 +4,7 @@ import { tagPropTypes } from '../../common-prop-types';
|
|||
import ClearQueryButton from './ClearQueryButton';
|
||||
import SelectedTags from './SelectedTags';
|
||||
|
||||
const ClassifiedFiltersTags = ({
|
||||
const ListingFiltersTags = ({
|
||||
message,
|
||||
onKeyUp,
|
||||
onClearQuery,
|
||||
|
|
@ -16,7 +16,7 @@ const ClassifiedFiltersTags = ({
|
|||
const shouldRenderClearQueryButton = query.length > 0;
|
||||
|
||||
return (
|
||||
<div className="classified-filters-tags" id="classified-filters-tags">
|
||||
<div className="listing-filters-tags" id="listing-filters-tags">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="search"
|
||||
|
|
@ -33,7 +33,7 @@ const ClassifiedFiltersTags = ({
|
|||
);
|
||||
};
|
||||
|
||||
ClassifiedFiltersTags.propTypes = {
|
||||
ListingFiltersTags.propTypes = {
|
||||
message: PropTypes.string.isRequired,
|
||||
onKeyUp: PropTypes.func.isRequired,
|
||||
onClearQuery: PropTypes.func.isRequired,
|
||||
|
|
@ -43,4 +43,4 @@ ClassifiedFiltersTags.propTypes = {
|
|||
query: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default ClassifiedFiltersTags;
|
||||
export default ListingFiltersTags;
|
||||
|
|
@ -17,10 +17,10 @@ const Modal = ({
|
|||
const shouldRenderMessageModal = listing && listing.contact_via_connect;
|
||||
|
||||
return (
|
||||
<div className="single-classified-listing-container">
|
||||
<div className="single-listing-container">
|
||||
<div
|
||||
id="single-classified-listing-container__inner"
|
||||
className="single-classified-listing-container__inner"
|
||||
id="single-listing-container__inner"
|
||||
className="single-listing-container__inner"
|
||||
onClick={onClick}
|
||||
role="button"
|
||||
onKeyPress={onClick}
|
||||
|
|
@ -42,13 +42,10 @@ const Modal = ({
|
|||
listing={listing}
|
||||
/>
|
||||
)}
|
||||
<a
|
||||
href="/about-listings"
|
||||
className="single-classified-listing-info-link"
|
||||
>
|
||||
<a href="/about-listings" className="single-listing-info-link">
|
||||
About DEV Listings
|
||||
</a>
|
||||
<div className="single-classified-listing-container__spacer" />
|
||||
<div className="single-listing-container__spacer" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ import PropTypes from 'prop-types';
|
|||
|
||||
const ModalBackground = ({ onClick }) => (
|
||||
<div
|
||||
className="classified-listings-modal-background"
|
||||
className="listings-modal-background"
|
||||
onClick={onClick}
|
||||
role="presentation"
|
||||
id="classified-listings-modal-background"
|
||||
id="listings-modal-background"
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { h } from 'preact';
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
const NextPageButton = ({ onClick }) => (
|
||||
<div className="classifieds-load-more-button">
|
||||
<div className="listings-load-more-button">
|
||||
<button onClick={onClick} type="button">
|
||||
Load More Listings
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ const SelectedTags = ({ tags, onClick, onKeyPress }) => {
|
|||
<section>
|
||||
{tags.map((tag) => (
|
||||
<span
|
||||
className="classified-tag"
|
||||
className="listing-tag"
|
||||
key={tag.id}
|
||||
id={`selected-tag-${tag.id}`}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ const Title = ({ onChange, defaultValue }) => (
|
|||
type="text"
|
||||
className="listingform__input"
|
||||
id={domId}
|
||||
name="classified_listing[title]"
|
||||
name="listing[title]"
|
||||
maxLength="128"
|
||||
size="128"
|
||||
placeholder="128 characters max, plain text"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export class ListingDashboard extends Component {
|
|||
|
||||
componentDidMount() {
|
||||
const t = this;
|
||||
const container = document.getElementById('classifieds-listings-dashboard');
|
||||
const container = document.getElementById('listings-dashboard');
|
||||
let listings = [];
|
||||
let orgs = [];
|
||||
let orgListings = [];
|
||||
|
|
@ -38,24 +38,24 @@ export class ListingDashboard extends Component {
|
|||
sort,
|
||||
} = this.state;
|
||||
|
||||
const isExpired = listing =>
|
||||
const isExpired = (listing) =>
|
||||
listing.bumped_at && !listing.published
|
||||
? (Date.now() - new Date(listing.bumped_at.toString()).getTime()) /
|
||||
(1000 * 60 * 60 * 24) >
|
||||
30
|
||||
: false;
|
||||
const isDraft = listing =>
|
||||
const isDraft = (listing) =>
|
||||
listing.bumped_at ? !isExpired(listing) && !listing.published : true;
|
||||
|
||||
const filterListings = (listingsToFilter, selectedFilter) => {
|
||||
if (selectedFilter === 'Draft') {
|
||||
return listingsToFilter.filter(listing => isDraft(listing));
|
||||
return listingsToFilter.filter((listing) => isDraft(listing));
|
||||
}
|
||||
if (selectedFilter === 'Expired') {
|
||||
return listingsToFilter.filter(listing => isExpired(listing));
|
||||
return listingsToFilter.filter((listing) => isExpired(listing));
|
||||
}
|
||||
if (selectedFilter === 'Active') {
|
||||
return listingsToFilter.filter(listing => listing.published === true);
|
||||
return listingsToFilter.filter((listing) => listing.published === true);
|
||||
}
|
||||
return listingsToFilter;
|
||||
};
|
||||
|
|
@ -84,7 +84,7 @@ export class ListingDashboard extends Component {
|
|||
displayedListings = filterListings(userListings, selectedFilter).sort(
|
||||
customSort,
|
||||
);
|
||||
return displayedListings.map(listing => (
|
||||
return displayedListings.map((listing) => (
|
||||
<ListingRow listing={listing} />
|
||||
));
|
||||
}
|
||||
|
|
@ -92,7 +92,7 @@ export class ListingDashboard extends Component {
|
|||
organizationListings,
|
||||
selectedFilter,
|
||||
).sort(customSort);
|
||||
return displayedListings.map(listing =>
|
||||
return displayedListings.map((listing) =>
|
||||
listing.organization_id === selected ? (
|
||||
<ListingRow listing={listing} />
|
||||
) : (
|
||||
|
|
@ -101,17 +101,20 @@ export class ListingDashboard extends Component {
|
|||
);
|
||||
};
|
||||
|
||||
const setStateOnKeyPress = (event, state) => (event.key === 'Enter' || event.key === ' ') && this.setState(state)
|
||||
const setStateOnKeyPress = (event, state) =>
|
||||
(event.key === 'Enter' || event.key === ' ') && this.setState(state);
|
||||
|
||||
const filters = ['All', 'Active', 'Draft', 'Expired'];
|
||||
const filterButtons = filters.map(f => (
|
||||
const filterButtons = filters.map((f) => (
|
||||
<span
|
||||
onClick={event => {
|
||||
onClick={(event) => {
|
||||
this.setState({ filter: event.target.textContent });
|
||||
}}
|
||||
className={`rounded-btn ${filter === f ? 'active' : ''}`}
|
||||
role="button"
|
||||
onKeyPress={event => setStateOnKeyPress(event, { filter: event.target.textContent })}
|
||||
onKeyPress={(event) =>
|
||||
setStateOnKeyPress(event, { filter: event.target.textContent })
|
||||
}
|
||||
tabIndex="0"
|
||||
>
|
||||
{f}
|
||||
|
|
@ -122,10 +125,10 @@ export class ListingDashboard extends Component {
|
|||
<div className="dashboard-listings-actions">
|
||||
<div className="listings-dashboard-filter-buttons">{filterButtons}</div>
|
||||
<select
|
||||
onChange={event => {
|
||||
onChange={(event) => {
|
||||
this.setState({ sort: event.target.value });
|
||||
}}
|
||||
onBlur={event => {
|
||||
onBlur={(event) => {
|
||||
this.setState({ sort: event.target.value });
|
||||
}}
|
||||
>
|
||||
|
|
@ -137,13 +140,15 @@ export class ListingDashboard extends Component {
|
|||
</div>
|
||||
);
|
||||
|
||||
const orgButtons = orgs.map(org => (
|
||||
const orgButtons = orgs.map((org) => (
|
||||
<span
|
||||
onClick={() => this.setState({ selectedListings: org.id })}
|
||||
className={`rounded-btn ${selectedListings === org.id ? 'active' : ''}`}
|
||||
role="button"
|
||||
tabIndex="0"
|
||||
onKeyPress={event => setStateOnKeyPress(event, { selectedListings: org.id })}
|
||||
onKeyPress={(event) =>
|
||||
setStateOnKeyPress(event, { selectedListings: org.id })
|
||||
}
|
||||
>
|
||||
{org.name}
|
||||
</span>
|
||||
|
|
@ -151,18 +156,13 @@ export class ListingDashboard extends Component {
|
|||
|
||||
const listingLength = (selected, userListings, organizationListings) => {
|
||||
return selected === 'user' ? (
|
||||
<h4>
|
||||
Listings Made:
|
||||
{' '}
|
||||
{userListings.length}
|
||||
</h4>
|
||||
<h4>Listings Made: {userListings.length}</h4>
|
||||
) : (
|
||||
<h4>
|
||||
Listings Made:
|
||||
{' '}
|
||||
Listings Made:{' '}
|
||||
{
|
||||
organizationListings.filter(
|
||||
listing => listing.organization_id === selected,
|
||||
(listing) => listing.organization_id === selected,
|
||||
).length
|
||||
}
|
||||
</h4>
|
||||
|
|
@ -171,16 +171,14 @@ export class ListingDashboard extends Component {
|
|||
|
||||
const creditCount = (selected, userCreds, organizations) => {
|
||||
return selected === 'user' ? (
|
||||
<h4>
|
||||
Credits Available:
|
||||
{' '}
|
||||
{userCreds}
|
||||
</h4>
|
||||
<h4>Credits Available: {userCreds}</h4>
|
||||
) : (
|
||||
<h4>
|
||||
Credits Available:
|
||||
{' '}
|
||||
{organizations.find(org => org.id === selected).unspent_credits_count}
|
||||
Credits Available:{' '}
|
||||
{
|
||||
organizations.find((org) => org.id === selected)
|
||||
.unspent_credits_count
|
||||
}
|
||||
</h4>
|
||||
);
|
||||
};
|
||||
|
|
@ -194,7 +192,9 @@ export class ListingDashboard extends Component {
|
|||
}`}
|
||||
role="button"
|
||||
tabIndex="0"
|
||||
onKeyPress={event =>setStateOnKeyPress(event, { selectedListings: 'user' })}
|
||||
onKeyPress={(event) =>
|
||||
setStateOnKeyPress(event, { selectedListings: 'user' })
|
||||
}
|
||||
>
|
||||
Personal
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ export default class ListingForm extends Component {
|
|||
<div className="field">
|
||||
<label htmlFor="organizationId">Post under an organization:</label>
|
||||
<OrganizationPicker
|
||||
name="classified_listing[organization_id]"
|
||||
name="listing[organization_id]"
|
||||
id="listing_organization_id"
|
||||
organizations={organizations}
|
||||
organizationId={organizationId}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import ModalBackground from './components/ModalBackground';
|
|||
import Modal from './components/Modal';
|
||||
import AllListings from './components/AllListings';
|
||||
import NextPageButton from './components/NextPageButton';
|
||||
import ClassifiedFilters from './components/ClassifiedFilters';
|
||||
import ListingFilters from './components/ListingFilters';
|
||||
import {
|
||||
LISTING_PAGE_SIZE,
|
||||
MATCH_LISTING,
|
||||
|
|
@ -33,7 +33,7 @@ export class Listings extends Component {
|
|||
|
||||
componentWillMount() {
|
||||
const params = getQueryParams();
|
||||
const container = document.getElementById('classifieds-index-container');
|
||||
const container = document.getElementById('listings-index-container');
|
||||
const category = container.dataset.category || '';
|
||||
const allCategories = JSON.parse(container.dataset.allcategories || []);
|
||||
let tags = [];
|
||||
|
|
@ -57,10 +57,10 @@ export class Listings extends Component {
|
|||
document.body.classList.add('modal-open');
|
||||
}
|
||||
|
||||
this.debouncedClassifiedListingSearch = debounceAction(
|
||||
this.handleQuery.bind(this),
|
||||
{ time: 150, config: { leading: true } },
|
||||
);
|
||||
this.debouncedListingSearch = debounceAction(this.handleQuery.bind(this), {
|
||||
time: 150,
|
||||
config: { leading: true },
|
||||
});
|
||||
|
||||
this.setState({
|
||||
query,
|
||||
|
|
@ -235,7 +235,7 @@ export class Listings extends Component {
|
|||
};
|
||||
|
||||
/**
|
||||
* Call search API for ClassifiedListings
|
||||
* Call search API for Listings
|
||||
*
|
||||
* @param {string} query - The search term
|
||||
* @param {string} tags - The tags selected by the user
|
||||
|
|
@ -248,21 +248,21 @@ export class Listings extends Component {
|
|||
const { page } = this.state;
|
||||
const dataHash = {
|
||||
category,
|
||||
classified_listing_search: query,
|
||||
listing_search: query,
|
||||
page,
|
||||
per_page: LISTING_PAGE_SIZE,
|
||||
tags,
|
||||
tag_boolean_mode: 'all',
|
||||
};
|
||||
|
||||
const responsePromise = fetchSearch('classified_listings', dataHash);
|
||||
const responsePromise = fetchSearch('listings', dataHash);
|
||||
return responsePromise.then((response) => {
|
||||
const classifiedListings = response.result;
|
||||
const fullListings = updateListings(classifiedListings);
|
||||
const listings = response.result;
|
||||
const fullListings = updateListings(listings);
|
||||
this.setState({
|
||||
listings: fullListings,
|
||||
initialFetch: false,
|
||||
showNextPageButton: classifiedListings.length === LISTING_PAGE_SIZE,
|
||||
showNextPageButton: listings.length === LISTING_PAGE_SIZE,
|
||||
});
|
||||
this.setLocation(query, tags, category, slug);
|
||||
});
|
||||
|
|
@ -293,12 +293,12 @@ export class Listings extends Component {
|
|||
{shouldRenderModal && (
|
||||
<ModalBackground onClick={this.handleCloseModal} />
|
||||
)}
|
||||
<ClassifiedFilters
|
||||
<ListingFilters
|
||||
categories={allCategories}
|
||||
category={category}
|
||||
onSelectCategory={this.selectCategory}
|
||||
message={message}
|
||||
onKeyUp={this.debouncedClassifiedListingSearch}
|
||||
onKeyUp={this.debouncedListingSearch}
|
||||
onClearQuery={this.clearQuery}
|
||||
onRemoveTag={this.removeTag}
|
||||
tags={tags}
|
||||
|
|
|
|||
|
|
@ -25,17 +25,15 @@ const AuthorInfo = ({ listing, onCategoryClick }) => {
|
|||
const { category, location, author = {} } = listing;
|
||||
const { username, name } = author;
|
||||
return (
|
||||
<div className="single-classified-listing-author-info">
|
||||
<div className="single-listing-author-info">
|
||||
<a
|
||||
href={`/listings/${category}`}
|
||||
onClick={e => onCategoryClick(e, category)}
|
||||
onClick={(e) => onCategoryClick(e, category)}
|
||||
data-no-instant
|
||||
>
|
||||
{category}
|
||||
</a>
|
||||
<LocationText location={location} />
|
||||
・
|
||||
<a href={`/${username}`}>{name}</a>
|
||||
<LocationText location={location} />・<a href={`/${username}`}>{name}</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class DropdownMenu extends Component {
|
|||
: document.removeEventListener('mousedown', this.handleClickOutside);
|
||||
};
|
||||
|
||||
handleClickOutside = e => {
|
||||
handleClickOutside = (e) => {
|
||||
if (
|
||||
this.componentRef.current &&
|
||||
!this.componentRef.current.contains(e.target)
|
||||
|
|
@ -75,7 +75,7 @@ class DropdownMenu extends Component {
|
|||
className={['dropdown-content', isOpen ? 'showing' : ''].join(' ')}
|
||||
>
|
||||
{isOwner ? (
|
||||
<a href={editUrl} className="classified-listing-edit-button">
|
||||
<a href={editUrl} className="listing-edit-button">
|
||||
Edit
|
||||
</a>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ import DropdownMenu from './DropdownMenu';
|
|||
const Header = ({ listing, currentUserId, onTitleClick }) => {
|
||||
const { id, user_id: userId, category, slug, title } = listing;
|
||||
return (
|
||||
<h3 className="single-classified-listing-header">
|
||||
<h3 className="single-listing-header">
|
||||
<a
|
||||
href={`/listings/${category}/${slug}`}
|
||||
data-no-instant
|
||||
onClick={e => onTitleClick(e, listing)}
|
||||
onClick={(e) => onTitleClick(e, listing)}
|
||||
data-listing-id={id}
|
||||
>
|
||||
{title}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ import PropTypes from 'prop-types';
|
|||
import { h } from 'preact';
|
||||
|
||||
const TagLinks = ({ tags, onClick }) => (
|
||||
<div className="single-classified-listing-tags">
|
||||
<div className="single-listing-tags">
|
||||
{tags.length
|
||||
? tags.map(tag => {
|
||||
? tags.map((tag) => {
|
||||
return (
|
||||
<a
|
||||
href={`/listings?t=${tag}`}
|
||||
onClick={e => onClick(e, tag)}
|
||||
onClick={(e) => onClick(e, tag)}
|
||||
data-no-instant
|
||||
>
|
||||
{tag}
|
||||
|
|
|
|||
|
|
@ -15,14 +15,11 @@ const SingleListing = ({
|
|||
isOpen,
|
||||
}) => {
|
||||
const definedClass = isOpen
|
||||
? 'single-classified-listing single-classified-listing--opened'
|
||||
: 'single-classified-listing';
|
||||
? 'single-listing single-listing--opened'
|
||||
: 'single-listing';
|
||||
|
||||
return (
|
||||
<div
|
||||
className={definedClass}
|
||||
id={`single-classified-listing-${listing.id}`}
|
||||
>
|
||||
<div className={definedClass} id={`single-listing-${listing.id}`}>
|
||||
<div className="listing-content">
|
||||
<Header
|
||||
listing={listing}
|
||||
|
|
@ -30,7 +27,7 @@ const SingleListing = ({
|
|||
onTitleClick={onOpenModal}
|
||||
/>
|
||||
<div
|
||||
className="single-classified-listing-body"
|
||||
className="single-listing-body"
|
||||
dangerouslySetInnerHTML={{ __html: listing.processed_html }} // eslint-disable-line react/no-danger
|
||||
/>
|
||||
<TagLinks tags={listing.tags} onClick={onAddTag} />
|
||||
|
|
|
|||
|
|
@ -5,15 +5,15 @@
|
|||
export const LISTING_PAGE_SIZE = 75;
|
||||
|
||||
export const MATCH_LISTING = [
|
||||
'single-classified-listing-container__inner',
|
||||
'classified-filters',
|
||||
'classified-listings-modal-background',
|
||||
'single-listing-container__inner',
|
||||
'listing-filters',
|
||||
'listings-modal-background',
|
||||
];
|
||||
|
||||
export function updateListings(classifiedListings) {
|
||||
export function updateListings(listings) {
|
||||
const fullListings = [];
|
||||
|
||||
classifiedListings.forEach((listing) => {
|
||||
listings.forEach((listing) => {
|
||||
if (listing.bumped_at) {
|
||||
fullListings.push(listing);
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ export function getQueryParams() {
|
|||
|
||||
function resizeMasonryItem(item) {
|
||||
/* Get the grid object, its row-gap, and the size of its implicit rows */
|
||||
const grid = document.getElementsByClassName('classifieds-columns')[0];
|
||||
const grid = document.getElementsByClassName('listings-columns')[0];
|
||||
const rowGap = parseInt(
|
||||
window.getComputedStyle(grid).getPropertyValue('grid-row-gap'),
|
||||
10,
|
||||
|
|
@ -63,7 +63,7 @@ function resizeMasonryItem(item) {
|
|||
|
||||
export function resizeAllMasonryItems() {
|
||||
// Get all item class objects in one list
|
||||
const allItems = document.getElementsByClassName('single-classified-listing');
|
||||
const allItems = document.getElementsByClassName('single-listing');
|
||||
|
||||
/*
|
||||
* Loop through the above list and execute the spanning function to
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { h, render } from 'preact';
|
|||
import { ListingDashboard } from '../listings/listingDashboard';
|
||||
|
||||
function loadElement() {
|
||||
const root = document.getElementById('classifieds-listings-dashboard');
|
||||
const root = document.getElementById('listings-dashboard');
|
||||
if (root) {
|
||||
render(<ListingDashboard />, root, root.firstElementChild);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { h, render } from 'preact';
|
|||
import { Listings } from '../listings/listings';
|
||||
|
||||
function loadElement() {
|
||||
const root = document.getElementById('classifieds-index-container');
|
||||
const root = document.getElementById('listings-index-container');
|
||||
if (root) {
|
||||
render(<Listings />, root, root.firstElementChild);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -433,7 +433,7 @@ class Tags extends Component {
|
|||
return this.textArea;
|
||||
}}
|
||||
className={`${classPrefix}__tags`}
|
||||
name="classified_listing[tag_list]"
|
||||
name="listing[tag_list]"
|
||||
placeholder={`${maxTags} tags max, comma separated, no spaces or special characters`}
|
||||
autoComplete="off"
|
||||
value={defaultValue}
|
||||
|
|
|
|||
|
|
@ -170,15 +170,15 @@ module CacheBuster
|
|||
podcast_episode.purge_all
|
||||
end
|
||||
|
||||
def self.bust_classified_listings(classified_listing)
|
||||
def self.bust_listings(listing)
|
||||
# we purge all listings as it's the wanted behavior with the following URL purging
|
||||
classified_listing.purge_all
|
||||
listing.purge_all
|
||||
|
||||
bust("/listings")
|
||||
bust("/listings?i=i")
|
||||
bust("/listings/#{classified_listing.category}/#{classified_listing.slug}")
|
||||
bust("/listings/#{classified_listing.category}/#{classified_listing.slug}?i=i")
|
||||
bust("/listings/#{classified_listing.category}")
|
||||
bust("/listings/#{listing.category}/#{listing.slug}")
|
||||
bust("/listings/#{listing.category}/#{listing.slug}?i=i")
|
||||
bust("/listings/#{listing.category}")
|
||||
end
|
||||
|
||||
def self.bust_user(user)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
class ClassifiedListingTag < LiquidTagBase
|
||||
PARTIAL = "classified_listings/liquid".freeze
|
||||
class ListingTag < LiquidTagBase
|
||||
PARTIAL = "listings/liquid".freeze
|
||||
|
||||
def initialize(_tag_name, slug_path_url, _tokens)
|
||||
stripped_path = ActionController::Base.helpers.strip_tags(slug_path_url).strip
|
||||
|
|
@ -24,11 +24,11 @@ class ClassifiedListingTag < LiquidTagBase
|
|||
hash = get_hash(url)
|
||||
raise StandardError, "Invalid URL or slug. Listing not found." if hash.nil?
|
||||
|
||||
listing = ClassifiedListing.in_category(hash[:category]).find_by(slug: hash[:slug])
|
||||
listing = Listing.in_category(hash[:category]).find_by(slug: hash[:slug])
|
||||
raise StandardError, "Invalid URL or slug. Listing not found." unless listing
|
||||
|
||||
listing
|
||||
end
|
||||
end
|
||||
|
||||
Liquid::Template.register_tag("listing", ClassifiedListingTag)
|
||||
Liquid::Template.register_tag("listing", ListingTag)
|
||||
|
|
@ -1,15 +1,18 @@
|
|||
class ClassifiedListing < ApplicationRecord
|
||||
class Listing < ApplicationRecord
|
||||
# We used to use both "classified listing" and "listing" throughout the app.
|
||||
# We standardized on the latter, but keeping the table name was easier.
|
||||
self.table_name = "classified_listings"
|
||||
|
||||
include Searchable
|
||||
|
||||
SEARCH_SERIALIZER = Search::ClassifiedListingSerializer
|
||||
SEARCH_CLASS = Search::ClassifiedListing
|
||||
SEARCH_SERIALIZER = Search::ListingSerializer
|
||||
SEARCH_CLASS = Search::Listing
|
||||
|
||||
attr_accessor :action
|
||||
|
||||
# Note: categories were hardcoded at first and this model was only added later,
|
||||
# so the association name is a bit verbose since the original "category" attribute
|
||||
# was kept to minimize code changes.
|
||||
belongs_to :classified_listing_category
|
||||
# Note: categories were hardcoded at first and the model was only added later.
|
||||
# The foreign_key and inverse_of options are used because of legacy table names.
|
||||
belongs_to :listing_category, inverse_of: :listings, foreign_key: :classified_listing_category_id
|
||||
belongs_to :user
|
||||
belongs_to :organization, optional: true
|
||||
before_save :evaluate_markdown
|
||||
|
|
@ -30,15 +33,25 @@ class ClassifiedListing < ApplicationRecord
|
|||
validate :validate_tags
|
||||
|
||||
scope :published, -> { where(published: true) }
|
||||
|
||||
# Note: we still need to use the old column name for the join query
|
||||
scope :in_category, lambda { |slug|
|
||||
joins(:classified_listing_category).
|
||||
where("classified_listing_categories.slug" => slug)
|
||||
joins(:listing_category).where("classified_listing_categories.slug" => slug)
|
||||
}
|
||||
|
||||
delegate :cost, to: :classified_listing_category
|
||||
delegate :cost, to: :listing_category
|
||||
|
||||
# Wrapping the column accessor names for consistency. Aliasing did not work.
|
||||
def listing_category_id
|
||||
classified_listing_category_id
|
||||
end
|
||||
|
||||
def listing_category_id=(id)
|
||||
self.classified_listing_category_id = id
|
||||
end
|
||||
|
||||
def category
|
||||
classified_listing_category&.slug
|
||||
listing_category&.slug
|
||||
end
|
||||
|
||||
def author
|
||||
|
|
@ -60,7 +73,7 @@ class ClassifiedListing < ApplicationRecord
|
|||
end
|
||||
|
||||
def modify_inputs
|
||||
ActsAsTaggableOn::Taggable::Cache.included(ClassifiedListing)
|
||||
ActsAsTaggableOn::Taggable::Cache.included(Listing)
|
||||
ActsAsTaggableOn.default_parser = ActsAsTaggableOn::TagParser
|
||||
self.body_markdown = body_markdown.to_s.gsub(/\r\n/, "\n")
|
||||
end
|
||||
|
|
@ -1,5 +1,9 @@
|
|||
class ClassifiedListingCategory < ApplicationRecord
|
||||
has_many :classified_listings
|
||||
class ListingCategory < ApplicationRecord
|
||||
# We used to use both "classified listing" and "listing" throughout the app.
|
||||
# We standardized on the latter, but keeping the table name was easier.
|
||||
self.table_name = "classified_listing_categories"
|
||||
|
||||
has_many :listings, inverse_of: :listing_category
|
||||
|
||||
before_validation :normalize_social_preview_color
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ class Organization < ApplicationRecord
|
|||
|
||||
has_many :api_secrets, through: :users
|
||||
has_many :articles
|
||||
has_many :classified_listings
|
||||
has_many :listings
|
||||
has_many :collections
|
||||
has_many :credits
|
||||
has_many :display_ads
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ class User < ApplicationRecord
|
|||
has_many :blocker_blocks, class_name: "UserBlock", foreign_key: :blocker_id, inverse_of: :blocker, dependent: :delete_all
|
||||
has_many :chat_channel_memberships, dependent: :destroy
|
||||
has_many :chat_channels, through: :chat_channel_memberships
|
||||
has_many :classified_listings, dependent: :destroy
|
||||
has_many :listings, dependent: :destroy
|
||||
has_many :collections, dependent: :destroy
|
||||
has_many :comments, dependent: :destroy
|
||||
has_many :created_podcasts, class_name: "Podcast", foreign_key: :creator_id, inverse_of: :creator, dependent: :nullify
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
class ClassifiedListingPolicy < ApplicationPolicy
|
||||
class ListingPolicy < ApplicationPolicy
|
||||
def edit?
|
||||
user_is_author? || authorized_organization_admin_editor?
|
||||
end
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
module Search
|
||||
class ClassifiedListingAuthorSerializer
|
||||
class ListingAuthorSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
|
||||
attributes :username, :name, :profile_image_90
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
module Search
|
||||
class ClassifiedListingSerializer
|
||||
class ListingSerializer
|
||||
include FastJsonapi::ObjectSerializer
|
||||
|
||||
attributes :id,
|
||||
|
|
@ -18,7 +18,7 @@ module Search
|
|||
attribute :tags, &:tag_list
|
||||
|
||||
attribute :author do |cl|
|
||||
ClassifiedListingAuthorSerializer.new(cl.author).
|
||||
ListingAuthorSerializer.new(cl.author).
|
||||
serializable_hash.
|
||||
dig(:data, :attributes)
|
||||
end
|
||||
|
|
@ -29,7 +29,7 @@ module Credits
|
|||
attr_reader :user
|
||||
|
||||
def purchaseable
|
||||
%w[ClassifiedListing]
|
||||
%w[Listing]
|
||||
end
|
||||
|
||||
def load_credits_purchases(credits)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ module Search
|
|||
class Cluster
|
||||
SEARCH_CLASSES = [
|
||||
Search::ChatChannelMembership,
|
||||
Search::ClassifiedListing,
|
||||
Search::Listing,
|
||||
Search::FeedContent,
|
||||
Search::Reaction,
|
||||
Search::Tag,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
module Search
|
||||
class ClassifiedListing < Base
|
||||
class Listing < Base
|
||||
# We used to use both "classified listing" and "listing" throughout the app.
|
||||
# We standardized on the latter in most places, but kept the index name here.
|
||||
INDEX_NAME = "classified_listings_#{Rails.env}".freeze
|
||||
INDEX_ALIAS = "classified_listings_#{Rails.env}_alias".freeze
|
||||
MAPPINGS = JSON.parse(File.read("config/elasticsearch/mappings/classified_listings.json"), symbolize_names: true).freeze
|
||||
MAPPINGS = JSON.parse(File.read("config/elasticsearch/mappings/listings.json"), symbolize_names: true).freeze
|
||||
DEFAULT_PAGE = 0
|
||||
DEFAULT_PER_PAGE = 75
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
module Search
|
||||
module QueryBuilders
|
||||
class ClassifiedListing < QueryBase
|
||||
class Listing < QueryBase
|
||||
TERM_KEYS = %i[
|
||||
category
|
||||
contact_via_connect
|
||||
|
|
@ -31,7 +31,7 @@ module Search
|
|||
def initialize(params:)
|
||||
@params = params.deep_symbolize_keys
|
||||
|
||||
# For now, we're not allowing searches for ClassifiedListings that are
|
||||
# For now, we're not allowing searches for Listings that are
|
||||
# not published. If we want to change this in the future we can do:
|
||||
# @params[:published] = DEFAULT_PARAMS[:published] unless @params.key?(:published)
|
||||
@params[:published] = DEFAULT_PARAMS[:published]
|
||||
|
|
@ -23,7 +23,7 @@ module Users
|
|||
user.poll_votes.delete_all
|
||||
user.rating_votes.delete_all
|
||||
user.response_templates.delete_all
|
||||
user.classified_listings.destroy_all
|
||||
user.listings.destroy_all
|
||||
delete_feedback_messages(user)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
json.type_of "classified_listing"
|
||||
|
||||
json.extract!(
|
||||
listing,
|
||||
:id,
|
||||
:title,
|
||||
:slug,
|
||||
:body_markdown,
|
||||
:category,
|
||||
:classified_listing_category_id,
|
||||
:processed_html,
|
||||
:published,
|
||||
)
|
||||
|
||||
json.tag_list listing.cached_tag_list
|
||||
json.tags listing.tag_list
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
json.partial! "listing", listing: @classified_listing
|
||||
|
||||
json.partial! "api/v0/shared/user", user: @classified_listing.user
|
||||
|
||||
if @classified_listing.organization
|
||||
json.partial! "api/v0/shared/organization", organization: @classified_listing.organization
|
||||
end
|
||||
16
app/views/api/v0/listings/_listing.json.jbuilder
Normal file
16
app/views/api/v0/listings/_listing.json.jbuilder
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
json.type_of "listing"
|
||||
|
||||
json.extract!(
|
||||
listing,
|
||||
:id,
|
||||
:title,
|
||||
:slug,
|
||||
:body_markdown,
|
||||
:category,
|
||||
:processed_html,
|
||||
:published,
|
||||
)
|
||||
|
||||
json.listing_category_id listing.classified_listing_category_id
|
||||
json.tag_list listing.cached_tag_list
|
||||
json.tags listing.tag_list
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
json.array! @classified_listings do |listing|
|
||||
json.array! @listings do |listing|
|
||||
json.partial! "listing", listing: listing
|
||||
json.partial! "api/v0/shared/user", user: listing.user
|
||||
|
||||
7
app/views/api/v0/listings/show.json.jbuilder
Normal file
7
app/views/api/v0/listings/show.json.jbuilder
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
json.partial! "listing", listing: @listing
|
||||
|
||||
json.partial! "api/v0/shared/user", user: @listing.user
|
||||
|
||||
if @listing.organization
|
||||
json.partial! "api/v0/shared/organization", organization: @listing.organization
|
||||
end
|
||||
|
|
@ -21,14 +21,14 @@
|
|||
</div>
|
||||
<% end %>
|
||||
<%= render "articles/sidebar_campaign" if SiteConfig.campaign_sidebar_enabled? %>
|
||||
<% if params[:timeframe].blank? && @classified_listings.any? %>
|
||||
<% if params[:timeframe].blank? && @listings.any? %>
|
||||
<div class="widget" id="sidebar-listings-widget">
|
||||
<header class="widget-header-listings">
|
||||
<a href="/listings"><h4>Listings <span class="widget-listing-live-count"><%= @classified_listings.size %></span></h4></a>
|
||||
<a href="/listings"><h4>Listings <span class="widget-listing-live-count"><%= @listings.size %></span></h4></a>
|
||||
<button type="button" class="widget-minimize-button" id="sidebar-listings-widget-minimize-button">-</button>
|
||||
</header>
|
||||
<div class="widget-body widget-body-listings">
|
||||
<% @classified_listings.order("bumped_at DESC").limit(5).each do |listing| %>
|
||||
<% @listings.order("bumped_at DESC").limit(5).each do |listing| %>
|
||||
<a class="widget-listing-link" href="<%= listing.path %>">
|
||||
<span class="widget-listing-link-title"><%= listing.title %></span>
|
||||
<span class="widget-listing-link-category"><%= listing.category %></span>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
Reading list <span id="reading-list-count" class="crayons-indicator"></span>
|
||||
</a>
|
||||
<% end %>
|
||||
<a href="<%= classified_listings_path %>" class="crayons-link crayons-link--block">
|
||||
<a href="<%= listings_path %>" class="crayons-link crayons-link--block">
|
||||
<%= inline_svg_tag("twemoji/listing.svg", aria: true, class: "crayons-icon crayons-icon--default", title: "Listings") %>
|
||||
Listings
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
<% if classified_listing.errors.any? %>
|
||||
<div class="classified-errors">
|
||||
<h2><%= pluralize(classified_listing.errors.count, "error") %> prohibited this listing from being saved:</h2>
|
||||
<ul>
|
||||
<% classified_listing.errors.full_messages.each do |message| %>
|
||||
<li><%= message %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<% title "New Listing" %>
|
||||
|
||||
<div class="classifieds-container classifieds-form-container" id="classifieds-container">
|
||||
<%= render "form", classified_listing: @classified_listing %>
|
||||
</div>
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
<p id="notice"><%= notice %></p>
|
||||
|
||||
<%= link_to 'Edit', edit_classified_listing_path(@classified_listing) %> |
|
||||
<%= link_to 'Back', classified_listings_path %>
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
<% end %>
|
||||
<td>
|
||||
<% if item.purchase %>
|
||||
<%= item.purchase.class.name == "ClassifiedListing" ? "Listing" : item.purchase.class.name.titleize %>
|
||||
<%= item.purchase.class.name == "Listing" ? "Listing" : item.purchase.class.name.titleize %>
|
||||
<% end %>
|
||||
</td>
|
||||
<td>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<a
|
||||
href="<%= classified_listing_slug_path(purchase.category, purchase.slug) %>"
|
||||
href="<%= listing_slug_path(purchase.category, purchase.slug) %>"
|
||||
title="<%= purchase.title %>">
|
||||
<%= truncate(purchase.title, length: 20) %>
|
||||
</a>
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
<% if @user_unspent_credits_count.positive? %>
|
||||
<center>
|
||||
<p>
|
||||
<a href="<%= new_classified_listing_path %>">Create a Listing</a>
|
||||
<a href="<%= new_listing_path %>">Create a Listing</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="/listings/dashboard">Go to Listings Dashboard</a>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<%= link_to "Back to All Listings", internal_listings_path, class: "btn btn-primary float-right" %>
|
||||
|
||||
<h2>Edit <%= link_to @classified_listing.title, "/listings/#{@classified_listing.category}/#{@classified_listing.slug}", target: "_blank", rel: "noopener" %></h2>
|
||||
<h2>Edit <%= link_to @listing.title, "/listings/#{@listing.category}/#{@listing.slug}", target: "_blank", rel: "noopener" %></h2>
|
||||
|
||||
<%= form_with model: [:internal, @classified_listing], method: :patch do |form| %>
|
||||
<%= form_with model: [:internal, @listing], method: :patch do |form| %>
|
||||
<div class="form-group">
|
||||
<%= form.label :title %>
|
||||
<%= form.text_field :title, size: 100, maxlength: 128, class: "form-control" %>
|
||||
|
|
@ -14,23 +14,23 @@
|
|||
<div class="form-group">
|
||||
<%= form.label :tag_list %>
|
||||
<i style="font-size: 15px;">Comma separated, one space, 8 tags max</i>
|
||||
<%= form.text_field :tag_list, value: @classified_listing.cached_tag_list, size: 100, class: "form-control" %>
|
||||
<%= form.text_field :tag_list, value: @listing.cached_tag_list, size: 100, class: "form-control" %>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<%= form.label :classified_listing_category_id %>
|
||||
<%= form.select :classified_listing_category_id, select_options_for_categories %>
|
||||
<%= form.label :listing_category_id %>
|
||||
<%= form.select :listing_category_id, select_options_for_categories %>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<%= form.check_box :published, checked: @classified_listing.published? %>
|
||||
<%= form.check_box :published, checked: @listing.published? %>
|
||||
<%= form.label :published %>
|
||||
</div>
|
||||
<%= form.submit "Update Listing", class: "btn btn-primary float-right" %>
|
||||
<% end %>
|
||||
<hr class="mt-5">
|
||||
<div class="d-flex justify-content-start">
|
||||
<%= form_with model: [:internal, @classified_listing], method: :patch do |f| %>
|
||||
<input type="hidden" name="classified_listing[action]" value="bump" />
|
||||
<%= form_with model: [:internal, @listing], method: :patch do |f| %>
|
||||
<input type="hidden" name="listing[action]" value="bump" />
|
||||
<%= f.submit "Bump Listing ⏫", class: "btn btn-secondary" %>
|
||||
<% end %>
|
||||
<%= link_to "Destroy Listing", url_for(action: :destroy, id: @classified_listing.id), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-danger ml-2" %>
|
||||
<%= link_to "Destroy Listing", url_for(action: :destroy, id: @listing.id), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-danger ml-2" %>
|
||||
</div>
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<%= paginate @classified_listings %>
|
||||
<%= paginate @listings %>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @classified_listings.each do |listing| %>
|
||||
<% @listings.each do |listing| %>
|
||||
<tr>
|
||||
<td><%= link_to listing.title, edit_internal_listing_path(listing.id), target: "_blank", rel: "noopener" %></td>
|
||||
<td><%= link_to listing.user.username, edit_internal_user_path(listing.user.id) %></td>
|
||||
|
|
@ -78,4 +78,4 @@
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
<%= paginate @classified_listings %>
|
||||
<%= paginate @listings %>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<% menu_items = %w[comments articles users tags welcome broadcasts reports response_templates pages tools chat_channels permissions growth mods config events badges organizations sponsorships podcasts negative_reactions moderator_actions].sort.each_with_object({}) { |v, h| h[v] = v } %>
|
||||
<% menu_items[:listings] = "classified_listings" %>
|
||||
<% menu_items[:listings] = "listings" %>
|
||||
<% menu_items[:webhooks] = "webhook_endpoints" %>
|
||||
|
||||
<div class="mt-4">
|
||||
|
|
|
|||
|
|
@ -83,12 +83,12 @@
|
|||
<%= Rails.application.assets["article-form-needed-legacy.css"].to_s.html_safe %>
|
||||
<%= Rails.application.assets["footer.css"].to_s.html_safe %>
|
||||
</style>
|
||||
<% elsif view_class.include? "classified_listings-" %>
|
||||
<% elsif view_class.include? "listings-" %>
|
||||
<style>
|
||||
<%= Rails.application.assets["crayons.css"].to_s.html_safe %>
|
||||
<%= Rails.application.assets["scaffolds.css"].to_s.html_safe %>
|
||||
<%= Rails.application.assets["top-bar.css"].to_s.html_safe %>
|
||||
<%= Rails.application.assets["classified_listings.css"].to_s.html_safe %>
|
||||
<%= Rails.application.assets["listings.css"].to_s.html_safe %>
|
||||
<%= Rails.application.assets["footer.css"].to_s.html_safe %>
|
||||
</style>
|
||||
<% elsif view_class.include? "credits-" %>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
<%= form_with(model: classified_listing, local: true) do |form| %>
|
||||
<%= form_with(model: listing, local: true) do |form| %>
|
||||
<header>
|
||||
<a href="/listings/dashboard" class="listings-back-button">< return to listings dashboard</a>
|
||||
<h2>Create a Community Listing</h2>
|
||||
</header>
|
||||
<div class="classified-form-inner">
|
||||
<%= render partial: "form_errors", locals: { classified_listing: classified_listing } %>
|
||||
<div class="listing-form-inner">
|
||||
<%= render partial: "form_errors", locals: { listing: listing } %>
|
||||
|
||||
<div id="listingform-data"
|
||||
data-listing="<%= classified_listing.to_json(only: %i[id title body_markdown category cached_tag_list]) %>"
|
||||
data-listing="<%= listing.to_json(only: %i[id title body_markdown category cached_tag_list]) %>"
|
||||
data-organizations="<%= @organizations.to_json(only: %i[id name]) %>"
|
||||
data-categories-for-select="<%= select_options_for_categories.to_json %>"
|
||||
data-categories-for-details="<%= categories_available.transform_values { |value_hash| value_hash.except(:cost) }.values.to_json %>">
|
||||
|
|
@ -61,7 +61,7 @@
|
|||
<div class="actions">
|
||||
<br />
|
||||
<p>You will not be charged credits to save a draft.</p>
|
||||
<%= form.button "SAVE DRAFT", type: "submit", name: "classified_listing[action]", class: "cta cta-main-listing-form cta-draft", value: "draft" %>
|
||||
<%= form.button "SAVE DRAFT", type: "submit", name: "listing[action]", class: "cta cta-main-listing-form cta-draft", value: "draft" %>
|
||||
<% if @credits.size > 0 || (@organizations.present? && @organizations.sum(:unspent_credits_count) > 0) %>
|
||||
<%= form.submit "PUBLISH LISTING", class: "cta cta-main-listing-form" %>
|
||||
<% else %>
|
||||
10
app/views/listings/_form_errors.html.erb
Normal file
10
app/views/listings/_form_errors.html.erb
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<% if listing.errors.any? %>
|
||||
<div class="listing-errors">
|
||||
<h2><%= pluralize(listing.errors.count, "error") %> prohibited this listing from being saved:</h2>
|
||||
<ul>
|
||||
<% listing.errors.full_messages.each do |message| %>
|
||||
<li><%= message %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<% title "Listings Dashboard" %>
|
||||
|
||||
<div id="classifieds-listings-dashboard" data-listings="<%= @classified_listings.to_json(
|
||||
<div id="listings-dashboard" data-listings="<%= @listings.to_json(
|
||||
only: %i[title tag_list created_at expires_at bumped_at updated_at category id user_id slug organization_id location published],
|
||||
include: {
|
||||
author: { only: %i[username name], methods: %i[username profile_image_90] },
|
||||
|
|
@ -1,18 +1,18 @@
|
|||
<%= javascript_include_tag "application" %>
|
||||
|
||||
<div class="container delete-confirm">
|
||||
<h4><%= @classified_listing.title %></h4>
|
||||
<h4><%= @listing.title %></h4>
|
||||
<h1>Are you sure you want to delete this listing?</h1>
|
||||
|
||||
<h2>
|
||||
You cannot undo this action, perhaps you just want to
|
||||
<a data-no-instant href="/listings/<%= @classified_listing.id %>/edit">
|
||||
<%= @classified_listing.published ? "unpublish" : "edit" %>
|
||||
<a data-no-instant href="/listings/<%= @listing.id %>/edit">
|
||||
<%= @listing.published ? "unpublish" : "edit" %>
|
||||
</a>
|
||||
instead?
|
||||
</h2>
|
||||
|
||||
<%= form_tag "/listings/#{@classified_listing.id}", method: :delete do %>
|
||||
<%= form_tag "/listings/#{@listing.id}", method: :delete do %>
|
||||
<button class="cta">DELETE</button>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
<% title "Edit Listing" %>
|
||||
|
||||
<div class="classifieds-container classifieds-form-container">
|
||||
<%= form_for(@classified_listing) do |f| %>
|
||||
<div class="listings-container listings-form-container">
|
||||
<%= form_for(@listing) do |f| %>
|
||||
<header>
|
||||
<a href="/listings/dashboard" class="listings-back-button">< return to listings dashboard</a>
|
||||
<h2>You can only edit title/body/tags of drafts or within the first 24 hours of listing or bumping</h2>
|
||||
</header>
|
||||
<div class="classified-form-inner">
|
||||
<%= render partial: "form_errors", locals: { classified_listing: @classified_listing } %>
|
||||
<div class="listings-form-inner">
|
||||
<%= render partial: "form_errors", locals: { listing: @listing } %>
|
||||
|
||||
<% if (@classified_listing.bumped_at && @classified_listing.bumped_at > 24.hours.ago) || @classified_listing.updated_at && !@classified_listing.published %>
|
||||
<% if (@listing.bumped_at && @listing.bumped_at > 24.hours.ago) || @listing.updated_at && !@listing.published %>
|
||||
<div class="field">
|
||||
<%= f.label "title", "Title" %>
|
||||
<%= f.text_field "title", maxlength: 128, placeholder: "128 characters max, plain text" %>
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
</div>
|
||||
<div class="field">
|
||||
<%= f.label "tag_list", "Tags" %>
|
||||
<%= f.text_field "tag_list", value: @classified_listing.cached_tag_list, placeholder: "8 tags max, comma separated, no spaces or special characters" %>
|
||||
<%= f.text_field "tag_list", value: @listing.cached_tag_list, placeholder: "8 tags max, comma separated, no spaces or special characters" %>
|
||||
</div>
|
||||
<div class="field">
|
||||
<%= f.label "location", "Location" %>
|
||||
|
|
@ -28,13 +28,13 @@
|
|||
</div>
|
||||
<div class="field">
|
||||
<%= f.label "expires_at", "Custom Expire Date" %>
|
||||
<%= f.date_field "expires_at", min: Date.tomorrow, max: @classified_listing.natural_expiration_date %>
|
||||
<%= f.date_field "expires_at", min: Date.tomorrow, max: @listing.natural_expiration_date %>
|
||||
</div>
|
||||
<div class="field">
|
||||
<%= f.label "contact_via_connect", "Allow Users to Message Me Via In-App Chat (#{community_name} Connect)" %>
|
||||
<%= f.check_box "contact_via_connect" %>
|
||||
</div>
|
||||
<% if @classified_listing.published %>
|
||||
<% if @listing.published %>
|
||||
<%= f.submit "Update Listings Info", class: "cta cta-main-listing-form" %>
|
||||
<% else %>
|
||||
<%= f.submit "Update Draft", class: "cta cta-main-listing-form cta-draft" %>
|
||||
|
|
@ -42,40 +42,40 @@
|
|||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if @classified_listing.bumped_at %>
|
||||
<%= form_for(@classified_listing) do |f| %>
|
||||
<% if @listing.bumped_at %>
|
||||
<%= form_for(@listing) do |f| %>
|
||||
<header>
|
||||
<a href="<%= @classified_listing.path %>" class="listings-back-button" style="font-size: 1.1em;">Last Published/Bumped: <%= time_ago_in_words @classified_listing.bumped_at %> ago</a>
|
||||
<a href="<%= @listing.path %>" class="listings-back-button" style="font-size: 1.1em;">Last Published/Bumped: <%= time_ago_in_words @listing.bumped_at %> ago</a>
|
||||
<h2>You can bump your listing for the same price as the original listing</h2>
|
||||
</header>
|
||||
<div class="classified-form-inner">
|
||||
<% if (@classified_listing.natural_expiration_date) < Date.today %>
|
||||
<div class="listing-form-inner">
|
||||
<% if (@listing.natural_expiration_date) < Date.today %>
|
||||
<h4>
|
||||
(Expired <%= time_ago_in_words @classified_listing.natural_expiration_date %> ago)
|
||||
(Expired <%= time_ago_in_words @listing.natural_expiration_date %> ago)
|
||||
</h4>
|
||||
<% else %>
|
||||
<h4>
|
||||
(Expires in <%= time_ago_in_words @classified_listing.natural_expiration_date %>)
|
||||
(Expires in <%= time_ago_in_words @listing.natural_expiration_date %>)
|
||||
</h4>
|
||||
<% end %>
|
||||
<input type="hidden" name="classified_listing[action]" value="bump" />
|
||||
<input type="hidden" name="listing[action]" value="bump" />
|
||||
<%= f.submit "Bump Listing", class: "cta cta-main-listing-form" %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= form_for(@classified_listing) do |f| %>
|
||||
<div class="classified-form-inner">
|
||||
<% unless @classified_listing.published %>
|
||||
<%= form_for(@listing) do |f| %>
|
||||
<div class="listing-form-inner">
|
||||
<% unless @listing.published %>
|
||||
<h2 style="text-align: center; margin-top: 15px;">
|
||||
This listing is not published
|
||||
</h2>
|
||||
<% end %>
|
||||
<% if @classified_listing.published == false %>
|
||||
<input type="hidden" name="classified_listing[action]" value="publish" />
|
||||
<%= f.submit "Publish Listing", class: "cta classified-listings-publish cta-main-listing-form" %>
|
||||
<% if @listing.published == false %>
|
||||
<input type="hidden" name="listing[action]" value="publish" />
|
||||
<%= f.submit "Publish Listing", class: "cta listings-publish cta-main-listing-form" %>
|
||||
<% else %>
|
||||
<input type="hidden" name="classified_listing[action]" value="unpublish" />
|
||||
<%= f.submit "Unpublish Listing", class: "cta classified-listings-unpublish cta-main-listing-form" %>
|
||||
<input type="hidden" name="listing[action]" value="unpublish" />
|
||||
<%= f.submit "Unpublish Listing", class: "cta listings-unpublish cta-main-listing-form" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
@ -7,13 +7,13 @@
|
|||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="<%= app_url(request.path) %>" />
|
||||
<meta property="og:site_name" content="<%= community_qualified_name %>" />
|
||||
<% if @displayed_classified_listing %>
|
||||
<meta property="og:title" content="<%= truncate @displayed_classified_listing.title, length: 54 %>">
|
||||
<% if @displayed_listing %>
|
||||
<meta property="og:title" content="<%= truncate @displayed_listing.title, length: 54 %>">
|
||||
<meta property="og:description" content="<%= community_name %> Listing" />
|
||||
<meta property="og:image" content="<%= listing_social_image_url @displayed_classified_listing %>">
|
||||
<meta name="twitter:title" content="<%= truncate @displayed_classified_listing.title, length: 54 %>">
|
||||
<meta property="og:image" content="<%= listing_social_image_url @displayed_listing %>">
|
||||
<meta name="twitter:title" content="<%= truncate @displayed_listing.title, length: 54 %>">
|
||||
<meta name="twitter:description" content="<%= community_name %> Listing">
|
||||
<meta name="twitter:image:src" content="<%= listing_social_image_url @displayed_classified_listing %>">
|
||||
<meta name="twitter:image:src" content="<%= listing_social_image_url @displayed_listing %>">
|
||||
<% else %>
|
||||
<meta property="og:title" content="Listings" />
|
||||
<meta property="og:description" content="<%= SiteConfig.community_description %>" />
|
||||
|
|
@ -27,22 +27,22 @@
|
|||
<% end %>
|
||||
|
||||
<div class="home">
|
||||
<div class="classifieds-container" id="classifieds-index-container"
|
||||
<div class="listings-container" id="listings-index-container"
|
||||
data-category="<%= params[:category] %>" data-listings="<%= @listings_json %>"
|
||||
data-allcategories="<%= categories_for_display.to_json %>"
|
||||
<% if @displayed_classified_listing %>
|
||||
<% if @displayed_listing %>
|
||||
data-displayedlisting="<%= @displayed_listing_json %> "
|
||||
<% end %>>
|
||||
|
||||
<div class="classified-filters">
|
||||
<div class="classified-filters-categories">
|
||||
<a href="<%= classified_listings_path %>" class="<%= "selected" if params[:category].blank? %> data-no-instant=" true ">all</a>
|
||||
<div class="listing-filters">
|
||||
<div class="listing-filters-categories">
|
||||
<a href="<%= listings_path %>" class="<%= "selected" if params[:category].blank? %> data-no-instant=" true ">all</a>
|
||||
<% categories_for_display.each do |cat| %>
|
||||
<a href="<%= classified_listing_category_path(category: cat[:slug]) %>" class="<%= "selected" if params[:category] == cat[:slug] %> data-no-instant="true"><%= cat[:name] %></a>
|
||||
<a href="<%= listing_category_path(category: cat[:slug]) %>" class="<%= "selected" if params[:category] == cat[:slug] %> data-no-instant="true"><%= cat[:name] %></a>
|
||||
<% end %>
|
||||
<a href="<%= new_classified_listing_path %>" class='classified-create-link'>Create a Listing</a>
|
||||
<% if @displayed_classified_listing %>
|
||||
<div class="classified-listings-modal-background" role='presentation'></div>
|
||||
<a href="<%= new_listing_path %>" class='listing-create-link'>Create a Listing</a>
|
||||
<% if @displayed_listing %>
|
||||
<div class="listing-listings-modal-background" role='presentation'></div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
5
app/views/listings/new.html.erb
Normal file
5
app/views/listings/new.html.erb
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<% title "New Listing" %>
|
||||
|
||||
<div class="listings-container listings-form-container" id="listings-container">
|
||||
<%= render "form", listing: @listing %>
|
||||
</div>
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue