From dcbb188cf1e3ddbbf87711a43837766eb609676b Mon Sep 17 00:00:00 2001 From: Ben Halpern Date: Mon, 24 Jun 2019 15:18:29 -0400 Subject: [PATCH] Add pinned articles box to profiles (#3269) * Initial pins work * Add pin box to profiles * Fix test snapshot spacing and optimize svg * Fix listings spacing --- app/assets/images/pushpin.svg | 1 + app/assets/stylesheets/articles.scss | 38 ++ app/assets/stylesheets/dashboard.scss | 19 +- app/controllers/profile_pins_controller.rb | 30 ++ app/controllers/stories_controller.rb | 4 + app/javascript/history/history.jsx | 5 +- .../__tests__/listingDashboard.test.jsx | 247 +++++++++-- .../listings/dashboard/listingRow.jsx | 44 +- app/javascript/listings/listingDashboard.jsx | 86 +++- app/javascript/listings/listings.jsx | 389 ++++++++++++------ app/models/article.rb | 1 + app/models/organization.rb | 1 + app/models/profile_pin.rb | 21 + app/models/user.rb | 1 + app/views/articles/_single_story.html.erb | 3 +- app/views/articles/manage.html.erb | 6 + .../dashboards/_dashboard_article.html.erb | 10 + app/views/users/_main_feed.html.erb | 8 + config/routes.rb | 1 + .../20190616053854_create_profile_pins.rb | 13 + db/schema.rb | 13 + spec/factories/profile_pins.rb | 6 + spec/models/profile_pin_spec.rb | 44 ++ spec/requests/profile_pins_spec.rb | 25 ++ spec/requests/user_profile_spec.rb | 14 + 25 files changed, 827 insertions(+), 203 deletions(-) create mode 100644 app/assets/images/pushpin.svg create mode 100644 app/controllers/profile_pins_controller.rb create mode 100644 app/models/profile_pin.rb create mode 100644 db/migrate/20190616053854_create_profile_pins.rb create mode 100644 spec/factories/profile_pins.rb create mode 100644 spec/models/profile_pin_spec.rb create mode 100644 spec/requests/profile_pins_spec.rb diff --git a/app/assets/images/pushpin.svg b/app/assets/images/pushpin.svg new file mode 100644 index 000000000..3b84f8926 --- /dev/null +++ b/app/assets/images/pushpin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/stylesheets/articles.scss b/app/assets/stylesheets/articles.scss index 1430b8ff4..c05c2384d 100644 --- a/app/assets/stylesheets/articles.scss +++ b/app/assets/stylesheets/articles.scss @@ -216,6 +216,44 @@ } } } + .pinned-articles { + @include themeable(background, theme-container-background, $white); + @include themeable( + border, + theme-container-border, + 1px solid $outline-color + ); + padding: 8px 0px; + margin: auto; + margin-top: 8px; + @media screen and (min-width: 750px) { + border-radius: 3px; + box-sizing: border-box; + padding: 8px 8px; + padding-right: 9px; + width: calc(100% + 2px); + max-width: 722px; + } + .pinned-articles-indicator { + text-align: left; + font-size: 15px; + font-weight: bold; + padding-top: 8px; + padding-left: 2.8%; + @media screen and (min-width: 750px) { + padding-left: 22px; + } + @media screen and (min-width: 950px) { + padding-left: 2px; + } + img { + width: 20px; + transform: rotate(-8deg); + vertical-align: -5px; + @include themeable(filter, theme-social-icon-invert, invert(0)); + } + } + } a { @include themeable(color, theme-container-color, $black); } diff --git a/app/assets/stylesheets/dashboard.scss b/app/assets/stylesheets/dashboard.scss index ed283dfc0..2b2c16be3 100644 --- a/app/assets/stylesheets/dashboard.scss +++ b/app/assets/stylesheets/dashboard.scss @@ -513,13 +513,13 @@ .dashboard-actions { padding: 7px 0px 10px; - - .mute-form { + + form { margin: 0; padding: 0; display: inline; - input[type='submit'] { + input[type='submit'], button { height: 30.4px; border: 2px solid transparent; } @@ -747,6 +747,19 @@ padding: 50px 0px 120px; margin-bottom: -160px; line-height: 1.3em; + .manage-page-error { + background:$red; + color:white; + padding:20px 10px; + line-height: 1.4em; + border-radius: 3px; + } + .manage-page-success { + background:$green; + padding:20px 10px; + line-height: 1.4em; + border-radius: 3px; + } li { margin: 20px auto; } diff --git a/app/controllers/profile_pins_controller.rb b/app/controllers/profile_pins_controller.rb new file mode 100644 index 000000000..29d89dbc2 --- /dev/null +++ b/app/controllers/profile_pins_controller.rb @@ -0,0 +1,30 @@ +class ProfilePinsController < ApplicationController + before_action :authenticate_user!, only: %i[create] + + def create + @profile_pin = ProfilePin.new + @profile_pin.profile_id = current_user.id + @profile_pin.profile_type = "User" + @profile_pin.pinnable_id = profile_pin_params[:pinnable_id].to_i + @profile_pin.pinnable_type = "Article" + if @profile_pin.save! + flash[:pins_success] = "📌 Pinned! (pinned posts display chronologically, 5 max)" + else + flash[:pins_error] = "You can only have five pins" + end + redirect_back(fallback_location: "/dashboard") + end + + def update + # for removing pinnable + ProfilePin.find(params[:id]).destroy + flash[:pins_success] = "🗑 Pin removed" + redirect_back(fallback_location: "/dashboard") + end + + private + + def profile_pin_params + params.require(:profile_pin).permit(:pinnable_id) + end +end diff --git a/app/controllers/stories_controller.rb b/app/controllers/stories_controller.rb index 615e31701..317180e94 100644 --- a/app/controllers/stories_controller.rb +++ b/app/controllers/stories_controller.rb @@ -164,8 +164,12 @@ class StoriesController < ApplicationController return end assign_user_comments + @pinned_stories = Article.published.where(id: @user.profile_pins.pluck(:pinnable_id)). + limited_column_select. + order("published_at DESC").decorate @stories = ArticleDecorator.decorate_collection(@user.articles.published. limited_column_select. + where.not(id: @pinned_stories.pluck(:id)). order("published_at DESC").page(@page).per(user_signed_in? ? 2 : 5)) @article_index = true @list_of = "articles" diff --git a/app/javascript/history/history.jsx b/app/javascript/history/history.jsx index ae76a84b1..4096e2837 100644 --- a/app/javascript/history/history.jsx +++ b/app/javascript/history/history.jsx @@ -184,9 +184,8 @@ min read・ ); - } - return ''; - + } + return ''; } render() { diff --git a/app/javascript/listings/__tests__/listingDashboard.test.jsx b/app/javascript/listings/__tests__/listingDashboard.test.jsx index db9b15f79..3809e6c9a 100644 --- a/app/javascript/listings/__tests__/listingDashboard.test.jsx +++ b/app/javascript/listings/__tests__/listingDashboard.test.jsx @@ -5,12 +5,106 @@ import { ListingDashboard } from '../listingDashboard'; const doc = new JSDOM(''); global.document = doc; -const l = [{id:23,bumped_at:"2019-06-11T16:45:37.229Z",category:"cfp",organization_id:null,slug:"asdfasdf-2ea8",title:"asdfasdf",updated_at:"2019-06-11T16:45:37.237Z",user_id:11,tag_list:["computerscience","career"],author:{name:"MarioSee",username:"mariocsee",profile_image_90:"/uploads/user/profile_image/11/e594d777-b57b-41d6-a793-5d8127bd11b3.jpeg"}},{id:24,bumped_at:"2019-06-11T16:59:16.312Z",category:"events",organization_id:2,slug:"yoyoyoyoyoooooooo-4jcb",title:"YOYOYOYOYOOOOOOOO",updated_at:"2019-06-11T16:59:16.316Z",user_id:11,tag_list:["computerscience","conference","career"],author:{name:"Yoyodyne",username:"org3737",profile_image_90:"/uploads/organization/profile_image/2/5edb1e49-bea9-4e99-bc32-acc10c52a935.png"}},{id:25,bumped_at:"2019-06-11T17:01:25.143Z",category:"cfp",organization_id:3,slug:"hehhehe-5hld",title:"hehhehe",updated_at:"2019-06-11T17:01:25.169Z",user_id:11,tag_list:[],author:{name:"Infotrode",username:"org5254",profile_image_90:"/uploads/organization/profile_image/3/04d4e1f1-c2e0-4147-81e2-bc8a2657296b.png"}}]; -global.document.body.innerHTML = `
`; +const l = [ + { + id: 23, + bumped_at: '2019-06-11T16:45:37.229Z', + category: 'cfp', + organization_id: null, + slug: 'asdfasdf-2ea8', + title: 'asdfasdf', + updated_at: '2019-06-11T16:45:37.237Z', + user_id: 11, + tag_list: ['computerscience', 'career'], + author: { + name: 'MarioSee', + username: 'mariocsee', + profile_image_90: + '/uploads/user/profile_image/11/e594d777-b57b-41d6-a793-5d8127bd11b3.jpeg', + }, + }, + { + id: 24, + bumped_at: '2019-06-11T16:59:16.312Z', + category: 'events', + organization_id: 2, + slug: 'yoyoyoyoyoooooooo-4jcb', + title: 'YOYOYOYOYOOOOOOOO', + updated_at: '2019-06-11T16:59:16.316Z', + user_id: 11, + tag_list: ['computerscience', 'conference', 'career'], + author: { + name: 'Yoyodyne', + username: 'org3737', + profile_image_90: + '/uploads/organization/profile_image/2/5edb1e49-bea9-4e99-bc32-acc10c52a935.png', + }, + }, + { + id: 25, + bumped_at: '2019-06-11T17:01:25.143Z', + category: 'cfp', + organization_id: 3, + slug: 'hehhehe-5hld', + title: 'hehhehe', + updated_at: '2019-06-11T17:01:25.169Z', + user_id: 11, + tag_list: [], + author: { + name: 'Infotrode', + username: 'org5254', + profile_image_90: + '/uploads/organization/profile_image/3/04d4e1f1-c2e0-4147-81e2-bc8a2657296b.png', + }, + }, +]; +global.document.body.innerHTML = `
`; global.window = doc.defaultView; -const listingState = { listings: - [ { id: 23, +const listingState = { + listings: [ + { + id: 23, bumped_at: '2019-06-11T16:45:37.229Z', category: 'cfp', organization_id: null, @@ -19,8 +113,10 @@ const listingState = { listings: updated_at: '2019-06-11T16:45:37.237Z', user_id: 11, tag_list: [Array], - author: [Object] }, - { id: 24, + author: [Object], + }, + { + id: 24, bumped_at: '2019-06-11T16:59:16.312Z', category: 'events', organization_id: 2, @@ -29,8 +125,10 @@ const listingState = { listings: updated_at: '2019-06-11T16:59:16.316Z', user_id: 11, tag_list: [Array], - author: [Object] }, - { id: 25, + author: [Object], + }, + { + id: 25, bumped_at: '2019-06-11T17:01:25.143Z', category: 'cfp', organization_id: 3, @@ -39,9 +137,12 @@ const listingState = { listings: updated_at: '2019-06-11T17:01:25.169Z', user_id: 11, tag_list: [], - author: [Object] } ], - orgListings: - [ { id: 24, + author: [Object], + }, + ], + orgListings: [ + { + id: 24, bumped_at: '2019-06-11T16:59:16.312Z', category: 'events', organization_id: 2, @@ -50,8 +151,10 @@ const listingState = { listings: updated_at: '2019-06-11T16:59:16.316Z', user_id: 11, tag_list: [Array], - author: [Object] }, - { id: 25, + author: [Object], + }, + { + id: 25, bumped_at: '2019-06-11T17:01:25.143Z', category: 'cfp', organization_id: 3, @@ -60,24 +163,22 @@ const listingState = { listings: updated_at: '2019-06-11T17:01:25.169Z', user_id: 11, tag_list: [], - author: [Object] } ], - orgs: - [ { id: 2, - name: 'Yoyodyne', - slug: 'org3737', - unspent_credits_count: 1 }, - { id: 3, - name: 'Infotrode', - slug: 'org5254', - unspent_credits_count: 1 } ], - userCredits: '3', - selectedListings: 'user' }; + author: [Object], + }, + ], + orgs: [ + { id: 2, name: 'Yoyodyne', slug: 'org3737', unspent_credits_count: 1 }, + { id: 3, name: 'Infotrode', slug: 'org5254', unspent_credits_count: 1 }, + ], + userCredits: '3', + selectedListings: 'user', +}; describe('', () => { it('should load listing dashboard', () => { const tree = deep(); expect(tree).toMatchSnapshot(); - }) + }); describe('should load the proper elements', () => { const context = deep(); @@ -85,29 +186,85 @@ describe('', () => { context.setState(listingState); it('for user and org buttons', () => { - expect(context.find('.rounded-btn').at(0).text()).toEqual('Personal') - expect(context.find('.rounded-btn').at(1).text()).toEqual(listingState.orgs[0].name) - expect(context.find('.rounded-btn').at(2).text()).toEqual(listingState.orgs[1].name) + expect( + context + .find('.rounded-btn') + .at(0) + .text(), + ).toEqual('Personal'); + expect( + context + .find('.rounded-btn') + .at(1) + .text(), + ).toEqual(listingState.orgs[0].name); + expect( + context + .find('.rounded-btn') + .at(2) + .text(), + ).toEqual(listingState.orgs[1].name); + + context + .find('.rounded-btn') + .at(1) + .simulate('click'); + expect( + context + .find('.rounded-btn') + .at(1) + .attr('className'), + ).toEqual('rounded-btn active'); + expect(context.state('selectedListings')).toEqual( + listingState.orgs[0].id, + ); + }); - context.find('.rounded-btn').at(1).simulate('click'); - expect(context.find('.rounded-btn').at(1).attr('className')).toEqual('rounded-btn active') - expect(context.state('selectedListings')).toEqual(listingState.orgs[0].id) - }) - it('for listing and credits header', () => { - expect(context.find('.dashboard-listings-header-wrapper').exists()).toEqual(true); + expect( + context.find('.dashboard-listings-header-wrapper').exists(), + ).toEqual(true); expect(context.find('.dashboard-listings-header').exists()).toEqual(true); - - expect(context.find('.dashboard-listings-header').at(0).childAt(0).text()).toEqual('Listings'); - expect(context.find('.dashboard-listings-header').at(0).childAt(2).text()).toEqual('Create a Listing'); - expect(context.find('.dashboard-listings-header').at(1).childAt(0).text()).toEqual('Credits'); - expect(context.find('.dashboard-listings-header').at(1).childAt(2).text()).toEqual('Buy Credits'); - }) + expect( + context + .find('.dashboard-listings-header') + .at(0) + .childAt(0) + .text(), + ).toEqual('Listings'); + expect( + context + .find('.dashboard-listings-header') + .at(0) + .childAt(2) + .text(), + ).toEqual('Create a Listing'); + + expect( + context + .find('.dashboard-listings-header') + .at(1) + .childAt(0) + .text(), + ).toEqual('Credits'); + expect( + context + .find('.dashboard-listings-header') + .at(1) + .childAt(2) + .text(), + ).toEqual('Buy Credits'); + }); it('for listingRow view', () => { expect(context.find('.dashboard-listings-view').exists()).toEqual(true); - expect(context.find('.dashboard-listings-view').children().text()).toEqual(context.find('ListingRow').text()); - }) - }) -}) \ No newline at end of file + expect( + context + .find('.dashboard-listings-view') + .children() + .text(), + ).toEqual(context.find('ListingRow').text()); + }); + }); +}); diff --git a/app/javascript/listings/dashboard/listingRow.jsx b/app/javascript/listings/dashboard/listingRow.jsx index 7142462a5..5bdc7fd75 100644 --- a/app/javascript/listings/dashboard/listingRow.jsx +++ b/app/javascript/listings/dashboard/listingRow.jsx @@ -1,42 +1,58 @@ import PropTypes from 'prop-types'; import { h } from 'preact'; -export const ListingRow = ({listing}) => { - +export const ListingRow = ({ listing }) => { const tagLinks = listing.tag_list.map(tag => ( -# + # {tag} {' '} - )); - const listingDate = listing.bumped_at ? (new Date(listing.bumped_at.toString())).toLocaleDateString("default", {day: "2-digit", month: "short"}) : (new Date(listing.updated_at.toString())).toLocaleDateString("default", {day: "2-digit", month: "short"}) ; - const orgName = (l) => (l.organization_id) ? ({l.author.name}) : ''; + const listingDate = listing.bumped_at + ? new Date(listing.bumped_at.toString()).toLocaleDateString('default', { + day: '2-digit', + month: 'short', + }) + : new Date(listing.updated_at.toString()).toLocaleDateString('default', { + day: '2-digit', + month: 'short', + }); + const orgName = l => + l.organization_id ? ( + {l.author.name} + ) : ( + '' + ); - return( + return (
{orgName(listing)} - -

- {listing.title} -

+
+

{listing.title}

{listingDate} {' '} - {listing.category} + + {listing.category} + {tagLinks}
{/* BUMP */} - EDIT + + EDIT + {/* DELETE */}
); -} +}; ListingRow.propTypes = { listing: PropTypes.object.isRequired, diff --git a/app/javascript/listings/listingDashboard.jsx b/app/javascript/listings/listingDashboard.jsx index 84976b490..f4951456f 100644 --- a/app/javascript/listings/listingDashboard.jsx +++ b/app/javascript/listings/listingDashboard.jsx @@ -7,12 +7,12 @@ export class ListingDashboard extends Component { orgListings: [], orgs: [], userCredits: 0, - selectedListings: "user", - } + selectedListings: 'user', + }; componentDidMount() { const t = this; - const container = document.getElementById('classifieds-listings-dashboard') + const container = document.getElementById('classifieds-listings-dashboard'); let listings = []; let orgs = []; let orgListings = []; @@ -24,46 +24,100 @@ export class ListingDashboard extends Component { } render() { - const { listings, orgListings, userCredits, orgs, selectedListings } = this.state + const { + listings, + orgListings, + userCredits, + orgs, + selectedListings, + } = this.state; const showListings = (selected, userListings, organizationListings) => { - return (selected === "user") ? userListings.map(listing => ) : organizationListings.map(listing => (listing.organization_id === selected) ? : ''); - } + return selected === 'user' + ? userListings.map(listing => ) + : organizationListings.map(listing => + listing.organization_id === selected ? ( + + ) : ( + '' + ), + ); + }; const orgButtons = orgs.map(org => ( - this.setState({selectedListings: org.id})} className={"rounded-btn " + (selectedListings === org.id ? 'active': '')}> + this.setState({ selectedListings: org.id })} + className={`rounded-btn ${selectedListings === org.id ? 'active' : ''}`} + > {org.name} - )) + )); const listingLength = (selected, userListings, organizationListings) => { - return (selected === "user") ? (

Listings Made: {userListings.length}

) : (

Listings Made: {organizationListings.filter((listing) => (listing.organization_id == selected)).length}

); - } + return selected === 'user' ? ( +

+ Listings Made: + {' '} + {userListings.length} +

+ ) : ( +

+ Listings Made: + {' '} + { + organizationListings.filter( + listing => listing.organization_id === selected, + ).length + } +

+ ); + }; const creditCount = (selected, userCreds, organizations) => { - return (selected === "user") ? (

Credits Available: {userCredits}

) : (

Credits Available: {organizations.find((org) => (org.id === selected)).unspent_credits_count}

); - } + return selected === 'user' ? ( +

+ Credits Available: + {' '} + {userCredits} +

+ ) : ( +

+ Credits Available: + {' '} + {organizations.find(org => org.id === selected).unspent_credits_count} +

+ ); + }; return (
- this.setState({selectedListings: "user"})} className={"rounded-btn " + (selectedListings === "user" ? 'active': '')}>Personal + this.setState({ selectedListings: 'user' })} + className={`rounded-btn ${ + selectedListings === 'user' ? 'active' : '' + }`} + > + Personal + {orgButtons}

Listings

{listingLength(selectedListings, listings, orgListings)} - Create a Listing + Create a Listing

Credits

{creditCount(selectedListings, userCredits, orgs)} - Buy Credits + + Buy Credits +
{showListings(selectedListings, listings, orgListings)}
- ) + ); } } diff --git a/app/javascript/listings/listings.jsx b/app/javascript/listings/listings.jsx index cc59a7861..050ad1288 100644 --- a/app/javascript/listings/listings.jsx +++ b/app/javascript/listings/listings.jsx @@ -28,17 +28,17 @@ export class Listings extends Component { const env = document.querySelector("meta[name='environment']").content; const client = algoliasearch(algoliaId, algoliaKey); const index = client.initIndex(`ClassifiedListing_${env}`); - const container = document.getElementById('classifieds-index-container') - const category = container.dataset.category || '' + const container = document.getElementById('classifieds-index-container'); + const category = container.dataset.category || ''; const allCategories = JSON.parse(container.dataset.allcategories || []); let tags = []; if (params.t) { - tags = params.t.split(',') + tags = params.t.split(','); } - const query = params.q || '' + const query = params.q || ''; let listings = []; if (tags.length === 0 && query === '') { - listings = JSON.parse(container.dataset.listings) + listings = JSON.parse(container.dataset.listings); } let openedListing = null; let slug = null; @@ -47,27 +47,35 @@ export class Listings extends Component { slug = openedListing.slug; document.body.classList.add('modal-open'); } - t.setState({query, tags, index, category, allCategories, listings, openedListing, slug }); + t.setState({ + query, + tags, + index, + category, + allCategories, + listings, + openedListing, + slug, + }); t.listingSearch(query, tags, category, slug); - t.setUser() + t.setUser(); - document.body.addEventListener('keydown', t.handleKeyDown) + document.body.addEventListener('keydown', t.handleKeyDown); /* The width of the columns also changes when the browser is resized so we will also call this function on window resize to recalculate each grid item's height to avoid content overflow */ - window.addEventListener("resize", resizeAllMasonryItems); - + window.addEventListener('resize', resizeAllMasonryItems); } componentDidUpdate() { - this.triggerMasonry() + this.triggerMasonry(); } componentWillUnmount() { - document.body.removeEventListener('keydown', this.handleKeyDown) + document.body.removeEventListener('keydown', this.handleKeyDown); } addTag = (e, tag) => { @@ -75,12 +83,12 @@ export class Listings extends Component { const { query, tags, category } = this.state; const newTags = tags; if (newTags.indexOf(tag) === -1) { - newTags.push(tag) + newTags.push(tag); } - this.setState({tags: newTags, page: 0, listings: []}) - this.listingSearch(query, newTags, category, null) - window.scroll(0,0) - } + this.setState({ tags: newTags, page: 0, listings: [] }); + this.listingSearch(query, newTags, category, null); + window.scroll(0, 0); + }; removeTag = (e, tag) => { e.preventDefault(); @@ -90,85 +98,93 @@ export class Listings extends Component { if (newTags.indexOf(tag) > -1) { newTags.splice(index, 1); } - this.setState({tags: newTags, page: 0, listings: []}) - this.listingSearch(query, newTags, category, null) - } + this.setState({ tags: newTags, page: 0, listings: [] }); + this.listingSearch(query, newTags, category, null); + }; selectCategory = (e, cat) => { e.preventDefault(); const { query, tags } = this.state; - this.setState({category: cat, page: 0, listings: []}) - this.listingSearch(query, tags, cat, null) - } + this.setState({ category: cat, page: 0, listings: [] }); + this.listingSearch(query, tags, cat, null); + }; - handleKeyDown = (e) => { + handleKeyDown = e => { // Enable Escape key to close an open listing. if (this.openedListing !== null && e.key === 'Escape') { - this.handleCloseModal() + this.handleCloseModal(); } - } + }; - handleCloseModal = (e) => { - if (e.target.id === 'single-classified-listing-container__inner' || + handleCloseModal = e => { + if ( + e.target.id === 'single-classified-listing-container__inner' || e.target.id === 'classified-filters' || - e.target.id === 'classified-listings-modal-background' ) { + e.target.id === 'classified-listings-modal-background' + ) { const { query, tags, category } = this.state; - this.setState({openedListing: null, page: 0}) + this.setState({ openedListing: null, page: 0 }); this.setLocation(query, tags, category, null); - document.body.classList.remove('modal-open'); + document.body.classList.remove('modal-open'); } - } + }; handleOpenModal = (e, listing) => { e.preventDefault(); - this.setState({openedListing: listing}); - window.history.replaceState(null, null, `/listings/${listing.category}/${listing.slug}`); + this.setState({ openedListing: listing }); + window.history.replaceState( + null, + null, + `/listings/${listing.category}/${listing.slug}`, + ); this.setLocation(null, null, listing.category, listing.slug); document.body.classList.add('modal-open'); - } + }; - handleDraftingMessage = (e) => { + handleDraftingMessage = e => { e.preventDefault(); - this.setState({ message: e.target.value }) - } + this.setState({ message: e.target.value }); + }; - handleSubmitMessage = (e) => { + handleSubmitMessage = e => { e.preventDefault(); - const { message, openedListing} = this.state + const { message, openedListing } = this.state; if (this.state.message.replace(/\s/g, '').length === 0) { return; } const formData = new FormData(); formData.append('user_id', openedListing.user_id); - formData.append('message', `**re: ${openedListing.title}** ${message}`) + formData.append('message', `**re: ${openedListing.title}** ${message}`); formData.append('controller', 'chat_channels'); const destination = `/connect/@${openedListing.author.username}`; const metaTag = document.querySelector("meta[name='csrf-token']"); - window.fetch('/chat_channels/create_chat', { - method: 'POST', - headers: { - 'X-CSRF-Token': metaTag.getAttribute("content"), - }, - body: formData, - credentials: 'same-origin', - }).then(() => { - window.location.href = destination; - }); - } + window + .fetch('/chat_channels/create_chat', { + method: 'POST', + headers: { + 'X-CSRF-Token': metaTag.getAttribute('content'), + }, + body: formData, + credentials: 'same-origin', + }) + .then(() => { + window.location.href = destination; + }); + }; handleQuery = e => { const { tags, category } = this.state; - this.setState({query: e.target.value, page: 0, listings: []}) - this.listingSearch(e.target.value, tags, category, null) - } + this.setState({ query: e.target.value, page: 0, listings: [] }); + this.listingSearch(e.target.value, tags, category, null); + }; clearQuery = () => { const { tags, category } = this.state; document.getElementById('listings-search').value = ''; - this.setState({query: '', page: 0, listings: []}); + this.setState({ query: '', page: 0, listings: [] }); this.listingSearch('', tags, category, null); - } + }; getQueryParams = () => { let qs = document.location.search; @@ -179,83 +195,96 @@ export class Listings extends Component { const re = /[?&]?([^=]+)=([^&]*)/g; // eslint-disable-next-line no-cond-assign - while (tokens = re.exec(qs)) { + while ((tokens = re.exec(qs))) { params[decodeURIComponent(tokens[1])] = decodeURIComponent(tokens[2]); } return params; - } + }; loadNextPage = () => { const { query, tags, category, slug, page } = this.state; - this.setState({page: page + 1}); + this.setState({ page: page + 1 }); this.listingSearch(query, tags, category, slug); - } + }; setUser = () => { const t = this; setTimeout(function() { if (window.currentUser && t.state.currentUserId === null) { - t.setState({currentUserId: window.currentUser.id }); + t.setState({ currentUserId: window.currentUser.id }); } - }, 300) + }, 300); setTimeout(function() { if (window.currentUser && t.state.currentUserId === null) { - t.setState({currentUserId: window.currentUser.id }); + t.setState({ currentUserId: window.currentUser.id }); } - }, 1000) - } + }, 1000); + }; triggerMasonry = () => { resizeAllMasonryItems(); setTimeout(function() { resizeAllMasonryItems(); - }, 1) + }, 1); setTimeout(function() { resizeAllMasonryItems(); - }, 3) - } + }, 3); + }; setLocation = (query, tags, category, slug) => { - let newLocation = '' + let newLocation = ''; if (slug) { newLocation = `/listings/${category}/${slug}`; } else if (query.length > 0 && tags.length > 0) { newLocation = `/listings/${category}?q=${query}&t=${tags}`; - } else if (query.length > 0){ + } else if (query.length > 0) { newLocation = `/listings/${category}?q=${query}`; } else if (tags.length > 0) { newLocation = `/listings/${category}?t=${tags}`; } else if (category.length > 0) { newLocation = `/listings/${category}`; } else { - newLocation = '/listings' + newLocation = '/listings'; } window.history.replaceState(null, null, newLocation); - } + }; listingSearch(query, tags, category, slug) { const t = this; const { index, page, listings } = t.state; - const filterObject = {tagFilters: tags, hitsPerPage: 75, page} + const filterObject = { tagFilters: tags, hitsPerPage: 75, page }; if (category.length > 0) { - filterObject.filters = `category:${category}` + filterObject.filters = `category:${category}`; } - index.search(query, filterObject) - .then(function searchDone(content) { + index.search(query, filterObject).then(function searchDone(content) { const fullListings = listings; content.hits.forEach(listing => { - if (!listings.map(l => (l.id)).includes(listing.id)) { - fullListings.push(listing) + if (!listings.map(l => l.id).includes(listing.id)) { + fullListings.push(listing); } }); - t.setState({listings: fullListings, initialFetch: false, showNextPageButt: content.hits.length === 75}); + t.setState({ + listings: fullListings, + initialFetch: false, + showNextPageButt: content.hits.length === 75, + }); }); this.setLocation(query, tags, category, slug); } render() { - const { listings, query, tags, category, allCategories, currentUserId, openedListing, showNextPageButt, initialFetch } = this.state; + const { + listings, + query, + tags, + category, + allCategories, + currentUserId, + openedListing, + showNextPageButt, + initialFetch, + } = this.state; const allListings = listings.map(listing => ( ( - this.removeTag(e, tag)} data-no-instant> + this.removeTag(e, tag)} + data-no-instant + > {tag} - this.removeTag(e, tag)} data-no-instant>× + this.removeTag(e, tag)} + data-no-instant + > + × + - )) + )); const categoryLinks = allCategories.map(cat => ( - this.selectCategory(e, cat.slug)} data-no-instant>{cat.name} - )) + this.selectCategory(e, cat.slug)} + data-no-instant + > + {cat.name} + + )); let nextPageButt = ''; if (showNextPageButt) { nextPageButt = ( -
- +
+
- ) + ); } - const clearQueryButton = query.length > 0 ? : ''; + const clearQueryButton = + query.length > 0 ? ( + + ) : ( + '' + ); let modal = ''; let modalBg = ''; let messageModal = ''; if (openedListing) { - modalBg =