diff --git a/Dockerfile b/Dockerfile index 2f07b2390b0..4b8881b587e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,8 +2,6 @@ FROM node:12.14-alpine WORKDIR /app -ENV CDN_PRODUCTION_URL=https://d1s2w0upia4e9w.cloudfront.net CDN_STAGING_URL=https://d1rmpw1xlv9rxa.cloudfront.net - # Install system dependencies # Add deploy user RUN apk --no-cache --quiet add \ diff --git a/package.json b/package.json index 559f5806737..764948ff759 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "@artsy/gemup": "0.0.3", "@artsy/palette": "7.1.0", "@artsy/passport": "1.1.11", - "@artsy/reaction": "25.17.1", + "@artsy/reaction": "25.18.0", "@artsy/stitch": "6.1.6", "@babel/core": "7.6.0", "@babel/node": "7.6.1", diff --git a/src/desktop/apps/article/__tests__/routes.jest.ts b/src/desktop/apps/article/__tests__/routes.jest.ts index 337d4470d16..76499c9ad00 100644 --- a/src/desktop/apps/article/__tests__/routes.jest.ts +++ b/src/desktop/apps/article/__tests__/routes.jest.ts @@ -5,18 +5,21 @@ import * as routes from "../routes" import { extend } from "lodash" import { GalleryInsightsRedirects } from "../gallery_insights_redirects" const Article = require("desktop/models/article.coffee") -const Channel = require("desktop/models/channel.coffee") jest.mock("desktop/lib/positronql", () => ({ positronql: jest.fn(), })) +jest.mock("lib/metaphysics.coffee", () => jest.fn()) + jest.mock("sharify", () => ({ data: { ARTSY_EDITORIAL_CHANNEL: "123", GALLERY_INSIGHTS_CHANNEL: "987", APP_URL: "https://artsy.net", EOY_2018_ARTISTS: "5bf30690d8b9430baaf6c6de", + PC_ARTSY_CHANNEL: "5759e508b5989e6f98f77999", + PC_AUCTION_CHANNEL: "5759e4d7b5989e6f98f77997", }, })) @@ -26,6 +29,7 @@ jest.mock("@artsy/stitch", () => ({ const positronql = require("desktop/lib/positronql").positronql as jest.Mock const stitch = require("@artsy/stitch").stitch as jest.Mock +const metaphysics = require("lib/metaphysics.coffee") as jest.Mock describe("Article Routes", () => { let req @@ -68,6 +72,7 @@ describe("Article Routes", () => { afterEach(() => { positronql.mockClear() stitch.mockClear() + metaphysics.mockClear() }) describe("#index", () => { @@ -159,7 +164,9 @@ describe("Article Routes", () => { positronql.mockReturnValue(Promise.resolve({ article })) routes.index(req, res, next).then(() => { - expect(res.render.mock.calls[0][0]).toBe("article") + const { data, locals } = stitch.mock.calls[0][0] + expect(locals.assetPackage).toBe("article") + expect(data.article.title).toBe("New York's Next Art District") done() }) }) @@ -333,39 +340,71 @@ describe("Article Routes", () => { }) describe("#classic", () => { - let channel + it("renders a classic article", done => { + routes.classic(req, res, next, fixtures.ClassicArticle).then(() => { + const { data } = stitch.mock.calls[0][0] + expect(data.article.title).toBe( + "New Study of Yale Grads Shows the Gender Pay Gap for Artists Is Not So Simple" + ) + done() + }) + }) - beforeEach(() => { - channel = new Channel({ name: "Foo" }) - article = extend(fixtures.ClassicArticle, { - slug: "foobar", + it("fetches partner for gallery promoted content", done => { + article = Object.assign({}, fixtures.ClassicArticlePromotedContent, { + channel_id: sd.PC_ARTSY_CHANNEL, + partner_ids: ["123"], + sale: null, }) + metaphysics.mockReturnValue( + Promise.resolve({ partner: fixtures.ClassicArticlePartner }) + ) - Article.prototype.fetchWithRelated.mockImplementation(options => { - options.success({ article: new Article(article), channel }) + routes.classic(req, res, next, article).then(() => { + const { data } = stitch.mock.calls[0][0] + expect(data.article.partner.name).toBe("Contessa Gallery") + done() }) }) - it("renders a classic article", () => { - routes.classic(req, res, next) - expect(res.render.mock.calls[0][1].article.get("slug")).toBe("foobar") - expect(res.render.mock.calls[0][1].channel.get("name")).toBe("Foo") + it("fetches sale for auction promoted content", done => { + article = Object.assign({}, fixtures.ClassicArticlePromotedContent) + delete article.sale + metaphysics.mockReturnValue( + Promise.resolve({ sale: fixtures.ClassicArticleSale }) + ) + + routes.classic(req, res, next, article).then(() => { + const { data } = stitch.mock.calls[0][0] + expect(data.article.sale.name).toBe("ICI: Benefit Auction 2019") + done() + }) }) - it("renders a ghosted article (no channel)", () => { - Article.prototype.fetchWithRelated.mockImplementationOnce(options => { - options.success({ article: new Article(article) }) + it("renders a ghosted article (no channel)", done => { + article = Object.assign({}, fixtures.ClassicArticle) + delete article.channel_id + delete article.author + delete article.partner_channel_id + + routes.classic(req, res, next, article).then(() => { + const { data } = stitch.mock.calls[0][0] + expect(data.article.title).toBe( + "New Study of Yale Grads Shows the Gender Pay Gap for Artists Is Not So Simple" + ) + done() }) - routes.classic(req, res, next) - expect(res.render.mock.calls[0][1].article.get("slug")).toBe("foobar") }) - it("sets the correct jsonld", () => { - routes.classic(req, res, next) - expect(res.locals.jsonLD).toMatch( - "Gender Pay Gap for Artists Is Not So Simple" - ) - expect(res.locals.jsonLD).toMatch("Partner") + it("sets the correct jsonld", done => { + routes.classic(req, res, next, fixtures.ClassicArticle).then(() => { + const { + data: { jsonLD }, + } = stitch.mock.calls[0][0] + expect(jsonLD).toMatch("Gender Pay Gap for Artists Is Not So Simple") + expect(jsonLD).toMatch("Partner") + done() + }) }) }) diff --git a/src/desktop/apps/article/client/__tests__/classic.test.js b/src/desktop/apps/article/client/__tests__/classic.test.js deleted file mode 100644 index 56b163929c4..00000000000 --- a/src/desktop/apps/article/client/__tests__/classic.test.js +++ /dev/null @@ -1,137 +0,0 @@ -import _ from "underscore" -import sinon from "sinon" -import Backbone from "backbone" -import benv from "benv" - -describe("Classic Article", () => { - let init - let RewireApi - let rewire - let rewires = [] - - beforeEach(done => { - benv.setup(() => { - benv.expose({ - $: benv.require("jquery"), - jQuery: benv.require("jquery"), - }) - Backbone.$ = window.$ - sinon.stub(Backbone, "sync") - - rewire = require("rewire")("../classic") - init = rewire.init - rewires.push(rewire.__set__("$", window.$)) - done() - }) - }) - - afterEach(() => { - Backbone.sync.restore() - benv.teardown() - rewires.forEach(reset => reset()) - }) - - it("initializes ArticleView", () => { - rewire.__set__("ArticleView", sinon.stub()) - rewire.__set__("sd", { - ARTICLE: { - title: "Foo", - }, - }) - const ArticleView = sinon.stub() - rewire.__set__("ArticleView", ArticleView) - init() - ArticleView.args[0][0].article.get("title").should.equal("Foo") - }) - - it("initializes TeamChannelNavView", () => { - rewire.__set__("ArticleView", sinon.stub()) - rewire.__set__("sd", { - ARTICLE_CHANNEL: { - type: "team", - }, - }) - const TeamChannelNavView = sinon.stub() - rewire.__set__("TeamChannelNavView", TeamChannelNavView) - init() - TeamChannelNavView.callCount.should.equal(1) - }) - - it("initializes ArticlesGridView", () => { - rewire.__set__("ArticleView", sinon.stub()) - rewire.__set__("sd", { - ARTICLE_CHANNEL: { - type: "team", - id: "123", - name: "Team Channel", - }, - }) - const ArticlesGridView = sinon.stub() - rewire.__set__("ArticlesGridView", ArticlesGridView) - init() - ArticlesGridView.callCount.should.equal(1) - ArticlesGridView.args[0][0].header.should.equal("More from Team Channel") - Backbone.sync.callCount.should.equal(1) - }) - - xit("sets up promoted content gallery", done => { - rewire.__set__("sd", { - ARTICLE: { - title: "Foo", - partner_ids: ["789"], - channel_id: "123", - }, - PC_ARTSY_CHANNEL: "123", - PC_AUCTION_CHANNEL: null, - }) - const data = { - partner: { - name: "Lisson Gallery", - type: "Gallery", - profile: { - href: "lisson-gallery", - image: "", - }, - }, - } - rewire.__set__("metaphysics", sinon.stub().resolves(data)) - $("body").html('
') - init() - _.defer(() => { - const html = $("#articles-show").html() - html.should.containEql("Promoted Content") - html.should.containEql("Lisson Gallery") - html.should.containEql("Explore Gallery") - done() - }) - }) - - xit("sets up promoted content auctions", done => { - rewire.__set__("sd", { - ARTICLE: { - title: "Foo", - auction_ids: ["789"], - channel_id: "123", - }, - PC_ARTSY_CHANNEL: null, - PC_AUCTION_CHANNEL: "123", - }) - const data = { - sale: { - name: "Summer School", - href: "/auction/summer-school", - cover_image: "", - }, - } - rewire.__set__("metaphysics", sinon.stub().resolves(data)) - $("body").html('
') - init() - _.defer(() => { - const html = $("#articles-show").html() - html.should.containEql("Promoted Content") - html.should.containEql("Summer School") - html.should.containEql("Explore Auction") - done() - }) - }) -}) diff --git a/src/desktop/apps/article/client/article.js b/src/desktop/apps/article/client/article.tsx similarity index 100% rename from src/desktop/apps/article/client/article.js rename to src/desktop/apps/article/client/article.tsx diff --git a/src/desktop/apps/article/client/classic.js b/src/desktop/apps/article/client/classic.js deleted file mode 100644 index 47df07def97..00000000000 --- a/src/desktop/apps/article/client/classic.js +++ /dev/null @@ -1,107 +0,0 @@ -// Initializes all client-side Backbone views for "classic" layouts - -import _$ from "jquery" -import { data as _sd } from "sharify" -import { - auctionQuery, - partnerQuery, -} from "desktop/apps/article/queries/promotedContent" -import metaphysics from "lib/metaphysics.coffee" -import Article from "desktop/models/article.coffee" -import Articles from "desktop/collections/articles.coffee" -import _ArticlesGridView from "desktop/components/articles_grid/view.coffee" -import _ArticleView from "desktop/components/article/client/view.coffee" -import Channel from "desktop/models/channel.coffee" -import _TeamChannelNavView from "desktop/components/channel_nav/view.coffee" - -// FIXME: Rewire -let $ = _$ -let sd = _sd -let ArticlesGridView = _ArticlesGridView -let TeamChannelNavView = _TeamChannelNavView -let ArticleView = _ArticleView - -const promotedTemplate = args => { - return require("desktop/apps/article/templates/promoted_content.jade")(args) -} - -export const init = () => { - const article = new Article(sd.ARTICLE) - const channel = new Channel(sd.ARTICLE_CHANNEL) - - new ArticleView({ - el: $("body"), - article, - }) - - if (channel.isTeam()) { - new TeamChannelNavView({ - el: $("body"), - $content: $(".article-content"), - offset: 0, - }) - } - - setupFooterArticles(channel) - setupPromotedContent(article) -} - -const setupFooterArticles = channel => { - const data = { - published: true, - sort: "-published_at", - limit: 12, - channel_id: channel.get("id"), - } - const collection = new Articles() - - new ArticlesGridView({ - el: $("#articles-footer").addClass("articles-grid"), - hideMore: true, - header: `More from ${channel.get("name") || "Artsy"}`, - collection, - }) - - collection.fetch({ - data: data, - }) -} - -const setupPromotedContent = article => { - const channel = article.get("channel_id") - if (channel === sd.PC_ARTSY_CHANNEL && article.get("partner_ids")) { - const send = { - method: "post", - query: partnerQuery(article.get("partner_ids")[0]), - } - metaphysics(send).then(data => { - const cropped = data.partner.profile.image.cropped - const src = cropped ? cropped.url : "" - $("#articles-show").prepend( - promotedTemplate({ - src, - name: data.partner.name, - href: data.partner.profile.href, - type: data.partner.type === "Gallery" ? "Gallery" : "Institution", - }) - ) - }) - } else if (channel === sd.PC_AUCTION_CHANNEL && article.get("auction_ids")) { - const send = { - method: "post", - query: auctionQuery(article.get("auction_ids")[0]), - } - metaphysics(send).then(data => { - const cropped = data.sale.cover_image.cropped - const src = cropped ? cropped.url : "" - $("#articles-show").prepend( - promotedTemplate({ - src, - name: data.sale.name, - href: data.sale.href, - type: "Auction", - }) - ) - }) - } -} diff --git a/src/desktop/apps/article/components/App.tsx b/src/desktop/apps/article/components/App.tsx index 67c75cb57ae..c2b5bc338e6 100644 --- a/src/desktop/apps/article/components/App.tsx +++ b/src/desktop/apps/article/components/App.tsx @@ -7,11 +7,13 @@ import { EditButton } from "desktop/apps/article/components/EditButton" import { ArticleLayout } from "./layouts/Article" import { data as sd } from "sharify" import { ArticleProps } from "@artsy/reaction/dist/Components/Publishing/Article" +import { ClassicArticleLayout } from "desktop/apps/article/components/layouts/Classic" export interface AppProps extends ArticleProps { templates?: { SuperArticleFooter: string SuperArticleHeader: string + ArticlesGridView: string } } @@ -39,6 +41,9 @@ export class App extends React.Component { ) } + case "classic": { + return + } default: { return } diff --git a/src/desktop/apps/article/components/layouts/Article.tsx b/src/desktop/apps/article/components/layouts/Article.tsx index b150b9c6169..f23c89791c3 100644 --- a/src/desktop/apps/article/components/layouts/Article.tsx +++ b/src/desktop/apps/article/components/layouts/Article.tsx @@ -1,23 +1,19 @@ import React from "react" import { once } from "lodash" import { Article } from "@artsy/reaction/dist/Components/Publishing/Article" -import { - ModalOptions, - ModalType, -} from "@artsy/reaction/dist/Components/Authentication/Types" +import { ModalType } from "@artsy/reaction/dist/Components/Authentication/Types" import { AppProps } from "../App" import { InfiniteScrollArticle } from "../InfiniteScrollArticle" -import { shouldAdRender } from "desktop/apps/article/helpers" +import { + shouldAdRender, + handleOpenAuthModal, +} from "desktop/apps/article/helpers" const SuperArticleView = require("desktop/components/article/client/super_article.coffee") const ArticleModel = require("desktop/models/article.coffee") const Cookies = require("desktop/components/cookies/index.coffee") const mediator = require("desktop/lib/mediator.coffee") -interface ArticleModalOptions extends ModalOptions { - signupIntent: string -} - export class ArticleLayout extends React.Component { componentDidMount() { const { article, isSuper } = this.props @@ -51,7 +47,7 @@ export class ArticleLayout extends React.Component { "scroll", once(() => { setTimeout(() => { - this.handleOpenAuthModal("register", { + handleOpenAuthModal("register", { mode: ModalType.signup, intent: "Viewed editorial", signupIntent: "signup", @@ -70,13 +66,6 @@ export class ArticleLayout extends React.Component { } } - handleOpenAuthModal = (mode, options: ArticleModalOptions) => { - mediator.trigger("open:auth", { - mode, - ...options, - }) - } - render() { const { article, @@ -110,7 +99,7 @@ export class ArticleLayout extends React.Component { isMobile={isMobile} isLoggedIn={isLoggedIn} isSuper={isSuper} - onOpenAuthModal={this.handleOpenAuthModal} + onOpenAuthModal={handleOpenAuthModal} relatedArticlesForPanel={article.relatedArticlesPanel} relatedArticlesForCanvas={article.relatedArticlesCanvas} showTooltips={showTooltips} @@ -121,7 +110,7 @@ export class ArticleLayout extends React.Component { { + componentDidMount() { + const { article } = this.props + + if (article.channel_id) { + this.setupFooterArticles() + } + } + + setupFooterArticles = () => { + const { article } = this.props + const collection = new Articles() + // @ts-ignore + const _ArticlesGridView = new ArticlesGridView({ + el: document.querySelector("#articles-footer"), + hideMore: true, + header: `More from ${article.channel.name || "Artsy"}`, + collection, + }) + + collection.fetch({ + data: { + published: true, + sort: "-published_at", + limit: 12, + channel_id: article.channel_id, + }, + }) + } + + render() { + const { + article, + isLoggedIn, + isMobile, + templates: { ArticlesGridView } = {} as any, + } = this.props + + return ( +
+
+ + {article.channel_id && ( + + ) + } +} diff --git a/src/desktop/apps/article/components/layouts/__tests__/Classic.jest.tsx b/src/desktop/apps/article/components/layouts/__tests__/Classic.jest.tsx new file mode 100644 index 00000000000..00821b30b5e --- /dev/null +++ b/src/desktop/apps/article/components/layouts/__tests__/Classic.jest.tsx @@ -0,0 +1,65 @@ +import React from "react" +import { ClassicArticleLayout } from "../Classic" +import { mount } from "enzyme" +import { + ClassicArticle, + ClassicArticleInternalChannel, + ClassicArticlePromotedContent, +} from "@artsy/reaction/dist/Components/Publishing/Fixtures/Articles" +jest.mock("desktop/collections/articles.coffee") +jest.mock("desktop/components/articles_grid/view.coffee") +const ArticlesGridView = require("desktop/components/articles_grid/view.coffee") as jest.Mock + +describe("ClassicArticleLayout", () => { + let props + const getWrapper = (passedProps = props) => { + return mount() + } + + beforeEach(() => { + props = { + article: ClassicArticle, + templates: { + ArticlesGridView: "articles-grid-view", + }, + } + }) + + it("renders an article", () => { + const component = getWrapper().html() + expect(component).toMatch("ClassicHeader") + expect(component).toMatch( + "New Study of Yale Grads Shows the Gender Pay Gap for Artists Is Not So Simple" + ) + expect(component).toMatch("Joanne Artman Gallery") + }) + + it("renders a team-channel article", () => { + props.article = ClassicArticleInternalChannel + const component = getWrapper().html() + expect(component).toMatch("ClassicHeader") + expect(component).toMatch("Consignments Intern") + expect(component).toMatch("Artsy Jobs") + }) + + it("renders a sponsored article", () => { + props.article = ClassicArticlePromotedContent + const component = getWrapper().html() + expect(component).toMatch("ClassicHeader") + expect(component).toMatch("Promoted Content") + expect(component).toMatch( + "ICI: Benefit Auction 2019 Curatorial Committee Picks" + ) + expect(component).toMatch("Independent Curators International") + }) + + it("renders ArticlesGrid", () => { + props.article = ClassicArticleInternalChannel + const component = getWrapper().html() + expect(component).toMatch("articles-grid-view") + expect(ArticlesGridView).toBeCalled() + expect(ArticlesGridView.mock.calls[0][0].header).toMatch( + "More from Artsy Jobs" + ) + }) +}) diff --git a/src/desktop/apps/article/gallery_insights_redirects.ts b/src/desktop/apps/article/gallery_insights_redirects.ts index 055dd0d97f1..ee1b6fe4f70 100644 --- a/src/desktop/apps/article/gallery_insights_redirects.ts +++ b/src/desktop/apps/article/gallery_insights_redirects.ts @@ -8,7 +8,7 @@ export const GalleryInsightsRedirects = { "gallery-insights-brett-gorvy-new-storefont": "resource/brett-gorvy-online-storefront", - "gallery-insights-the-pop-up-gallery-checklist?": "resource/pop-up-galleries", + "gallery-insights-the-pop-up-gallery-checklist": "resource/pop-up-galleries", "gallery-insights-artful-pitch": "resource/press-for-your-gallery", diff --git a/src/desktop/apps/article/helpers.tsx b/src/desktop/apps/article/helpers.tsx index de0659c395a..58584bca8d6 100644 --- a/src/desktop/apps/article/helpers.tsx +++ b/src/desktop/apps/article/helpers.tsx @@ -1,6 +1,8 @@ import { isCustomEditorial } from "./editorial_features" +import { ModalOptions } from "@artsy/reaction/dist/Components/Authentication/Types" const { stringifyJSONForWeb } = require("desktop/components/util/json.coffee") const Article = require("desktop/models/article.coffee") +const mediator = require("desktop/lib/mediator.coffee") // Helper method to determine how frequently ads should be rendered in Article components export const shouldAdRender = ( @@ -70,3 +72,14 @@ export const getJsonLd = article => { const articleModel = new Article(article) return stringifyJSONForWeb(articleModel.toJSONLD()) } + +interface ArticleModalOptions extends ModalOptions { + signupIntent: string +} + +export const handleOpenAuthModal = (mode, options: ArticleModalOptions) => { + mediator.trigger("open:auth", { + mode, + ...options, + }) +} diff --git a/src/desktop/apps/article/queries/articleBody.js b/src/desktop/apps/article/queries/articleBody.js index 21df63bd546..4b93251b057 100644 --- a/src/desktop/apps/article/queries/articleBody.js +++ b/src/desktop/apps/article/queries/articleBody.js @@ -17,6 +17,9 @@ export const articleBody = ` featured channel_id partner_channel_id + auction_ids + partner_ids + lead_paragraph indexable keywords published @@ -64,6 +67,9 @@ export const articleBody = ` author image_url } + author { + name + } authors { image_url twitter_handle @@ -71,6 +77,9 @@ export const articleBody = ` id bio } + channel { + name + } contributing_authors { name } diff --git a/src/desktop/apps/article/routes.tsx b/src/desktop/apps/article/routes.tsx index c9353fd2295..f16fae259ba 100644 --- a/src/desktop/apps/article/routes.tsx +++ b/src/desktop/apps/article/routes.tsx @@ -27,6 +27,10 @@ import { } from "./helpers" import cheerio from "cheerio" import React from "react" +import { + partnerQuery, + auctionQuery, +} from "desktop/apps/article/queries/promotedContent" import { ArticleMeta } from "@artsy/reaction/dist/Components/Publishing/ArticleMeta" import { GalleryInsightsRedirects } from "./gallery_insights_redirects" const Articles = require("desktop/collections/articles.coffee") @@ -34,6 +38,7 @@ const markdown = require("desktop/components/util/markdown.coffee") const { crop, resize } = require("desktop/components/resizer/index.coffee") const Article = require("desktop/models/article.coffee") const { stringifyJSONForWeb } = require("desktop/components/util/json.coffee") +const metaphysics = require("lib/metaphysics.coffee") export const index = async (req, res, next) => { let articleId = req.params.slug @@ -72,7 +77,23 @@ export const index = async (req, res, next) => { return res.redirect("https://partners.artsy.net") } } - return classic(req, res, next) + if (req.params.slug !== article.slug) { + // Redirect to most recent slug + return res.redirect(`/article/${article.slug}`) + } + + if (article.partner_channel_id) { + // redirect partner channels to partner page + const { partner } = await metaphysics({ + method: "post", + query: partnerQuery(article.partner_channel_id), + }).then(data => data) + + return res.redirect( + `/${partner.default_profile_id}/article/${article.slug}` + ) + } + return classic(req, res, next, article) } if (isVanguardSubArticle(article)) { @@ -95,7 +116,7 @@ export const index = async (req, res, next) => { } if ( - !["standard", "feature"].includes(article.layout) && + !["classic", "standard", "feature"].includes(article.layout) && req.path.includes("/article") ) { return res.redirect(`/${article.layout}/${article.slug}${search}`) @@ -177,42 +198,65 @@ export const index = async (req, res, next) => { } } -export const classic = (req, res, _next) => { - const article = new Article({ - id: req.params.slug, - }) - const accessToken = req.user ? req.user.get("accessToken") : null +export const classic = async (_req, res, next, article) => { + const { CURRENT_USER, IS_MOBILE } = res.locals.sd + const isMobile = IS_MOBILE + const isLoggedIn = typeof CURRENT_USER !== "undefined" - article.fetchWithRelated({ - accessToken, - error: res.backboneError, - success: data => { - if (req.params.slug !== data.article.get("slug")) { - return res.redirect(`/article/${data.article.get("slug")}`) - } + if (article.channel_id === sd.PC_ARTSY_CHANNEL && article.partner_ids) { + const send = { + method: "post", + query: partnerQuery(article.partner_ids[0]), + } - if (data.partner) { - return res.redirect( - `/${data.partner.get( - "default_profile_id" - )}/article/${data.article.get("slug")}` - ) - } + await metaphysics(send).then(data => { + article.partner = data.partner + }) + } - res.locals.sd.ARTICLE = data.article.toJSON() - res.locals.sd.INCLUDE_SAILTHRU = - res.locals.sd.ARTICLE && res.locals.sd.ARTICLE.published - res.locals.sd.ARTICLE_CHANNEL = data.channel && data.channel.toJSON() - res.locals.jsonLD = stringifyJSONForWeb(data.article.toJSONLD()) + if (article.channel_id === sd.PC_AUCTION_CHANNEL && article.auction_ids) { + const send = { + method: "post", + query: auctionQuery(article.auction_ids[0]), + } - res.render("article", { - embed, + await metaphysics(send).then(data => { + article.sale = data.sale + }) + } + + try { + const layout = await stitch({ + basePath: res.app.get("views"), + layout: getLayoutTemplate(article), + config: { + styledComponents: true, + }, + blocks: { + head: () => , + body: App, + }, + locals: { + ...res.locals, + assetPackage: "article", + bodyClass: getBodyClass(article), crop, - resize, - ...data, - }) - }, - }) + markdown, + }, + data: { + article, + isLoggedIn, + isMobile, + jsonLD: getJsonLd(article), + }, + templates: { + ArticlesGridView: "../../../components/articles_grid/view.coffee", + }, + }) + res.send(layout) + } catch (error) { + next(error) + } } export const amp = (req, res, next) => { diff --git a/src/desktop/apps/experimental-app-shell/apps/search/searchMiddleware.tsx b/src/desktop/apps/experimental-app-shell/apps/search/searchMiddleware.tsx index 0e599366cab..3b86352f8d8 100644 --- a/src/desktop/apps/experimental-app-shell/apps/search/searchMiddleware.tsx +++ b/src/desktop/apps/experimental-app-shell/apps/search/searchMiddleware.tsx @@ -66,6 +66,7 @@ export const searchMiddleware = async (req, res, next) => { html: layout, } res.status(status).send(layout) + return } catch (error) { console.log(error) next(error) diff --git a/src/desktop/assets/article.coffee b/src/desktop/assets/article.coffee index 0870aaa2b58..6a22a1ac143 100644 --- a/src/desktop/assets/article.coffee +++ b/src/desktop/assets/article.coffee @@ -5,9 +5,5 @@ layout = sd.ARTICLE?.layout $ -> if location.pathname.match('/article/|/video/|/series/|/news/') - if layout is 'classic' - { init } = require('../apps/article/client/classic.js') - init() - else - { init } = require('../apps/article/client/article.js') - init() + { init } = require('../apps/article/client/article.tsx') + init() diff --git a/src/desktop/lib/global_client_setup.tsx b/src/desktop/lib/global_client_setup.tsx index d7baedd3a17..967d0d2b932 100644 --- a/src/desktop/lib/global_client_setup.tsx +++ b/src/desktop/lib/global_client_setup.tsx @@ -2,12 +2,27 @@ * Set webpack public-path asset lookup to CDN in production, but only on * the client, as we use the assetMiddleware helper to map URLs on the server. * @see https://github.com/artsy/force/blob/master/src/lib/middleware/assetMiddleware.ts + * + * FIXME: Move this into Circle config and or Docker */ if (process.env.NODE_ENV === "production") { - __webpack_public_path__ = - (window.location.hostname === "www.artsy.net" - ? process.env.CDN_PRODUCTION_URL - : process.env.CDN_STAGING_URL) + "/assets/" + const { hostname } = window.location + let cdnUrl + + // Production + if (hostname === "www.artsy.net") { + cdnUrl = "https://d1s2w0upia4e9w.cloudfront.net" + + // Localhost + } else if (hostname === "localhost") { + cdnUrl = "" + + // Everything else + } else { + cdnUrl = "https://d1rmpw1xlv9rxa.cloudfront.net" + } + + __webpack_public_path__ = cdnUrl + "/assets/" } import $ from "jquery" diff --git a/src/desktop/models/article.coffee b/src/desktop/models/article.coffee index 2eb424d500f..e1a5701424f 100644 --- a/src/desktop/models/article.coffee +++ b/src/desktop/models/article.coffee @@ -208,14 +208,14 @@ module.exports = class Article extends Backbone.Model new Article(id: id).fetch cache: true headers: 'X-Access-Token': accessToken - success: (article) => + success: (article) -> superSubArticles.add article getParselySection: -> if @get('channel_id') is ARTSY_EDITORIAL_CHANNEL 'Editorial' else if @get('channel_id') - @get('channel')?.get('name') + @get('channel')?.name || @get('channel')?.get('name') else if @get('partner_channel_id') 'Partner' else diff --git a/webpack/envs/baseConfig.js b/webpack/envs/baseConfig.js index 71b81941913..8d56878db8e 100644 --- a/webpack/envs/baseConfig.js +++ b/webpack/envs/baseConfig.js @@ -65,8 +65,6 @@ exports.baseConfig = { new webpack.DefinePlugin({ "process.env": { NODE_ENV: JSON.stringify(NODE_ENV), - CND_PRODUCTION_URL: JSON.stringify(process.env.CND_PRODUCTION_URL), - CDN_STAGING_URL: JSON.stringify(process.env.CDN_STAGING_URL), }, }), // Remove moment.js localization files diff --git a/yarn.lock b/yarn.lock index 91c397b726f..d8708fff19e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -76,10 +76,10 @@ resolved "https://registry.yarnpkg.com/@artsy/react-html-parser/-/react-html-parser-3.0.2.tgz#6213d662441acf0bd8c9ee953aa77ac8d9cf1cdd" integrity sha512-FXiRSqfSvwpz/QgwaqFvjJsbDmo9qMGpvUp/0p1V6muaJbGnfxkTSxAYWmYlFst6bmjhVxCYKxvgQhEVE03Wfg== -"@artsy/reaction@25.17.1": - version "25.17.1" - resolved "https://registry.yarnpkg.com/@artsy/reaction/-/reaction-25.17.1.tgz#98797fdcb76bb24a492c7369fa3a81671b88967c" - integrity sha512-eqqQPTxcsdfpHXlCWSwxuc2i1l+qcrTW40CB++PFxkdRcyqblWHhR4wq4A8D8lp7I7QLLKixyuKcN6vod3JzXQ== +"@artsy/reaction@25.18.0": + version "25.18.0" + resolved "https://registry.yarnpkg.com/@artsy/reaction/-/reaction-25.18.0.tgz#d5b0b6f432bd526e037c5906cc55abb7816c79b7" + integrity sha512-65cXbo3vDBHMkuGZjsyZJmRtWx7jk/BtKDkUJ82eTIShx7tUQVsXQULtwmXmiVsXa5nVb70E1Myv+dEJm2d8Nw== dependencies: "@artsy/detect-responsive-traits" "^0.0.5" "@artsy/fresnel" "^1.0.13"