diff --git a/fronts-client/src/bundles/recipesBundle.ts b/fronts-client/src/bundles/recipesBundle.ts index 8ea6aeea143..9ad72570d6f 100644 --- a/fronts-client/src/bundles/recipesBundle.ts +++ b/fronts-client/src/bundles/recipesBundle.ts @@ -1,7 +1,7 @@ import createAsyncResourceBundle from 'lib/createAsyncResourceBundle'; import { Recipe } from 'types/Recipe'; -import recipe1 from "./fixtures/recipe1.json" -import recipe2 from "./fixtures/recipe2.json" +import recipe1 from './fixtures/recipe1.json'; +import recipe2 from './fixtures/recipe2.json'; type RecipesState = Recipe[]; @@ -9,7 +9,5 @@ export const { actions, reducer, selectors } = createAsyncResourceBundle('recipes', { indexById: true, // Add stub data in the absence of proper search data. - initialData: [ - recipe1, recipe2 - ], + initialData: [recipe1, recipe2], }); diff --git a/fronts-client/src/components/FrontsEdit/CollectionComponents/Card.tsx b/fronts-client/src/components/FrontsEdit/CollectionComponents/Card.tsx index c4ea4f6f0ab..96689e0c0ba 100644 --- a/fronts-client/src/components/FrontsEdit/CollectionComponents/Card.tsx +++ b/fronts-client/src/components/FrontsEdit/CollectionComponents/Card.tsx @@ -8,8 +8,7 @@ import { selectExternalArticleFromCard, selectSupportingArticleCount, } from 'selectors/shared'; -import cardTypes from 'constants/cardTypes'; -import { CardTypes, CardSizes, CardMeta } from 'types/Collection'; +import { CardSizes, CardMeta } from 'types/Collection'; import SnapLink from 'components/snapLink/SnapLink'; import { copyCardImageMetaWithPersist, @@ -43,6 +42,7 @@ import { getPillarColor } from 'util/getPillarColor'; import { isLive as isArticleLive } from 'util/CAPIUtils'; import { DefaultDropIndicator } from 'components/DropZone'; import DragIntentContainer from 'components/DragIntentContainer'; +import { CardTypes, CardTypesMap } from 'constants/cardTypes'; export const createCardId = (id: string) => `collection-item-${id}`; @@ -153,7 +153,7 @@ class Card extends React.Component { const getCard = () => { switch (type) { - case cardTypes.ARTICLE: + case CardTypesMap.ARTICLE: return (
{
); - case cardTypes.SNAP_LINK: + case CardTypesMap.SNAP_LINK: return ( <> void; } -const ArticleFeedItemComponent = ({ article, id, onAddToClipboard }: ComponentProps) => { +const ArticleFeedItemComponent = ({ + article, + id, + onAddToClipboard, +}: ComponentProps) => { if (!article) { return

Article with id {id} not found.

; } - const handleDragStart = (event: React.DragEvent, dragNode: HTMLDivElement) => { + const handleDragStart = ( + event: React.DragEvent, + dragNode: HTMLDivElement + ) => { event.dataTransfer.setData('capi', JSON.stringify(article)); if (dragNode) { - event.dataTransfer.setDragImage( - dragNode, - dragOffsetX, - dragOffsetY - ); + event.dataTransfer.setDragImage(dragNode, dragOffsetX, dragOffsetY); } }; @@ -58,11 +64,8 @@ const ArticleFeedItemComponent = ({ article, id, onAddToClipboard }: ComponentPr ); -} - -const getState = (state: any) => state; +}; const mapStateToProps = (state: State, { id }: ContainerProps) => ({ - shouldObscureFeed: selectFeatureValue(getState(state), 'obscure-feed'), + shouldObscureFeed: selectFeatureValue(state, 'obscure-feed'), article: selectArticleAcrossResources(state, id), }); @@ -113,4 +114,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => { }; }; -export const ArticleFeedItem = connect(mapStateToProps, mapDispatchToProps)(ArticleFeedItemComponent); +export const ArticleFeedItem = connect( + mapStateToProps, + mapDispatchToProps +)(ArticleFeedItemComponent); diff --git a/fronts-client/src/components/feed/CapiSearchContainer.tsx b/fronts-client/src/components/feed/CapiSearchContainer.tsx index df11d9bf994..048658d89b5 100644 --- a/fronts-client/src/components/feed/CapiSearchContainer.tsx +++ b/fronts-client/src/components/feed/CapiSearchContainer.tsx @@ -23,7 +23,6 @@ import ShortVerticalPinline from 'components/layout/ShortVerticalPinline'; import { DEFAULT_PARAMS } from 'services/faciaApi'; import ScrollContainer from '../ScrollContainer'; import ClipboardHeader from 'components/ClipboardHeader'; -import { media } from 'util/mediaQueries'; import ContainerHeading from 'components/typography/ContainerHeading'; import { ClearIcon } from 'components/icons/Icons'; import Button from 'components/inputs/ButtonDefault'; diff --git a/fronts-client/src/components/feed/Feed.tsx b/fronts-client/src/components/feed/Feed.tsx index 14c76c2c9eb..7928cde0261 100644 --- a/fronts-client/src/components/feed/Feed.tsx +++ b/fronts-client/src/components/feed/Feed.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { styled } from 'constants/theme'; -import FeedItem from './FeedItem'; import { liveSelectors, previewSelectors, @@ -9,7 +8,7 @@ import { import { selectIsPrefillMode } from 'selectors/feedStateSelectors'; import type { State } from 'types/State'; import { connect } from 'react-redux'; -import {ArticleFeedItem} from "./ArticleFeedItem"; +import { ArticleFeedItem } from './ArticleFeedItem'; interface ErrorDisplayProps { error?: string; diff --git a/fronts-client/src/components/feed/FeedItem.tsx b/fronts-client/src/components/feed/FeedItem.tsx index a3b9447d6e0..9e179b98f86 100644 --- a/fronts-client/src/components/feed/FeedItem.tsx +++ b/fronts-client/src/components/feed/FeedItem.tsx @@ -13,9 +13,7 @@ import { } from 'components/inputs/HoverActionButtons'; import noop from 'lodash/noop'; import { ThumbnailSmall } from 'components/image/Thumbnail'; -import { - DraggingArticleComponent, -} from 'components/FrontsEdit/CollectionComponents/ArticleDrag'; +import { DraggingArticleComponent } from 'components/FrontsEdit/CollectionComponents/ArticleDrag'; import { media } from 'util/mediaQueries'; import { VideoIcon } from 'components/icons/Icons'; import CircularIconContainer from 'components/icons/CircularIconContainer'; @@ -53,7 +51,7 @@ const Title = styled.h2` font-weight: normal; `; -const FeedItemContainer = styled.a<{ blur: boolean }>` +const FeedItemContainer = styled.a<{ blur?: boolean }>` text-decoration: none; display: flex; color: inherit; @@ -88,8 +86,6 @@ const ScheduledPublication = styled(FirstPublished)` color: ${notLiveColour}; `; - - const Body = styled.div` padding-left: 8px; `; @@ -103,15 +99,19 @@ const VideoIconContainer = styled(CircularIconContainer)` interface FeedItemProps { id: string; title: string; - liveUrl: string; - metaContent: JSX.Element; + liveUrl?: string; + metaContent?: JSX.Element; scheduledPublicationDate?: string; firstPublishedDate?: string; thumbnail?: string; hasVideo: boolean; isLive: boolean; onAddToClipboard: () => void; - handleDragStart: (event: React.DragEvent, dragNode: HTMLDivElement) => void; + handleDragStart: ( + event: React.DragEvent, + dragNode: HTMLDivElement + ) => void; + shouldObscureFeed?: boolean; } export class FeedItem extends React.Component { @@ -133,14 +133,16 @@ export class FeedItem extends React.Component { firstPublishedDate, thumbnail, hasVideo, - handleDragStart + handleDragStart, } = this.props; return ( this.dragNode.current && handleDragStart(event, this.dragNode.current)} + onDragStart={(event) => + this.dragNode.current && handleDragStart(event, this.dragNode.current) + } > diff --git a/fronts-client/src/components/feed/RecipeFeedItem.tsx b/fronts-client/src/components/feed/RecipeFeedItem.tsx index 5a2af6d27cd..4985ab1077e 100644 --- a/fronts-client/src/components/feed/RecipeFeedItem.tsx +++ b/fronts-client/src/components/feed/RecipeFeedItem.tsx @@ -1,13 +1,18 @@ import {Recipe} from "../../types/Recipe"; import {FeedItem} from "./FeedItem"; import React from "react"; +import {State} from "../../types/State"; import {dragOffsetX, dragOffsetY} from "../FrontsEdit/CollectionComponents/ArticleDrag"; +import noop from "lodash/noop"; +import {selectFeatureValue} from "../../selectors/featureSwitchesSelectors"; +import {connect} from "react-redux"; interface ComponentProps { - recipe: Recipe + recipe: Recipe, + shouldObscureFeed: boolean } -export const RecipeFeedItem = ({ recipe }: ComponentProps) => { +export const RecipeFeedItemComponent = ({ recipe, shouldObscureFeed }: ComponentProps) => { const handleDragStart = (event: React.DragEvent, dragNode: HTMLDivElement) => { event.dataTransfer.setData('recipe', JSON.stringify(recipe)); if (dragNode) { @@ -21,11 +26,21 @@ export const RecipeFeedItem = ({ recipe }: ComponentProps) => { return ( + handleDragStart={handleDragStart} + onAddToClipboard={noop} + shouldObscureFeed={shouldObscureFeed} + /> ) } + +const mapStateToProps = (state: State) => ({ + shouldObscureFeed: selectFeatureValue(state, 'obscure-feed'), +}) + +export const RecipeFeedItem = connect(mapStateToProps)(RecipeFeedItemComponent) diff --git a/fronts-client/src/components/feed/RecipeSearchContainer.tsx b/fronts-client/src/components/feed/RecipeSearchContainer.tsx index 2cee1653b5c..72ba2c29236 100644 --- a/fronts-client/src/components/feed/RecipeSearchContainer.tsx +++ b/fronts-client/src/components/feed/RecipeSearchContainer.tsx @@ -1,7 +1,7 @@ import ClipboardHeader from 'components/ClipboardHeader'; import TextInput from 'components/inputs/TextInput'; import ShortVerticalPinline from 'components/layout/ShortVerticalPinline'; -import { styled, theme } from 'constants/theme'; +import { styled } from 'constants/theme'; import React from 'react'; import { connect } from 'react-redux'; import { selectors as recipeSelectors } from 'bundles/recipesBundle'; @@ -9,7 +9,7 @@ import { State } from 'types/State'; import { Recipe } from 'types/Recipe'; import { SearchResultsHeadingContainer } from './SearchResultsHeadingContainer'; import { SearchTitle } from './SearchTitle'; -import {RecipeFeedItem} from "./RecipeFeedItem"; +import { RecipeFeedItem } from './RecipeFeedItem'; const InputContainer = styled.div` margin-bottom: 10px; @@ -48,7 +48,9 @@ const FeastSearchContainerComponent = ({ - {recipes.map((recipe) => )} + {recipes.map((recipe) => ( + + ))} ); diff --git a/fronts-client/src/components/inputs/TextInput.tsx b/fronts-client/src/components/inputs/TextInput.tsx index 62bf4e534a3..4d684ec8ab4 100644 --- a/fronts-client/src/components/inputs/TextInput.tsx +++ b/fronts-client/src/components/inputs/TextInput.tsx @@ -59,13 +59,13 @@ interface TextInputProps extends React.InputHTMLAttributes { onClear?: () => void; onSearch?: () => void; displaySearchIcon: boolean; - searchTermsExist: boolean; + searchTermsExist?: boolean; } const TextInput = ({ onClear, onSearch, - searchTermsExist, + searchTermsExist = true, displaySearchIcon, ...props }: TextInputProps) => ( diff --git a/fronts-client/src/constants/cardTypes.ts b/fronts-client/src/constants/cardTypes.ts index 9dc51e0adac..d9130439f3c 100644 --- a/fronts-client/src/constants/cardTypes.ts +++ b/fronts-client/src/constants/cardTypes.ts @@ -1,6 +1,7 @@ -import { CardTypes } from 'types/Collection'; +export const CardTypesMap = { + SNAP_LINK: 'snap-link', + ARTICLE: 'article', + RECIPE: 'recipe', +} as const; -export default { - SNAP_LINK: 'SNAP_LINK', - ARTICLE: 'ARTICLE', -} as { [type: string]: CardTypes }; +export type CardTypes = (typeof CardTypesMap)[keyof typeof CardTypesMap]; diff --git a/fronts-client/src/selectors/__tests__/cardSelectors.spec.ts b/fronts-client/src/selectors/__tests__/cardSelectors.spec.ts index 875433e0c88..9341b1d304d 100644 --- a/fronts-client/src/selectors/__tests__/cardSelectors.spec.ts +++ b/fronts-client/src/selectors/__tests__/cardSelectors.spec.ts @@ -1,6 +1,6 @@ import { createSelectCardType } from '../cardSelectors'; import { stateWithSnaplinksAndArticles } from 'fixtures/shared'; -import cardTypes from 'constants/cardTypes'; +import { CardTypesMap } from 'constants/cardTypes'; describe('Card selectors', () => { describe('createCardTypeSelector', () => { @@ -11,7 +11,7 @@ describe('Card selectors', () => { stateWithSnaplinksAndArticles, '4c21ff2c-e2c5-4bac-ae14-24beb3f8d8b5' ) - ).toEqual(cardTypes.SNAP_LINK); + ).toEqual(CardTypesMap.SNAP_LINK); }); it('should identify articles', () => { const selectCardType = createSelectCardType(); @@ -20,7 +20,7 @@ describe('Card selectors', () => { stateWithSnaplinksAndArticles, '134c9d4f-b05c-43f4-be41-a605b6dccab9' ) - ).toEqual(cardTypes.ARTICLE); + ).toEqual(CardTypesMap.ARTICLE); }); }); }); diff --git a/fronts-client/src/selectors/cardSelectors.ts b/fronts-client/src/selectors/cardSelectors.ts index 4326042ab28..b9b7bd87f5c 100644 --- a/fronts-client/src/selectors/cardSelectors.ts +++ b/fronts-client/src/selectors/cardSelectors.ts @@ -1,14 +1,14 @@ import { createSelector } from 'reselect'; import { selectCard, selectExternalArticleFromCard } from './shared'; import { validateId } from 'util/snap'; -import CardTypes from 'constants/cardTypes'; +import { CardTypesMap } from 'constants/cardTypes'; import { getContributorImage } from 'util/CAPIUtils'; const createSelectCardType = () => createSelector(selectCard, (card) => { return card && validateId(card.id) - ? CardTypes.SNAP_LINK - : CardTypes.ARTICLE; + ? CardTypesMap.SNAP_LINK + : CardTypesMap.ARTICLE; }); const createSelectCutoutUrl = () => diff --git a/fronts-client/src/types/Collection.ts b/fronts-client/src/types/Collection.ts index b32f009e942..b5df7b0fa13 100644 --- a/fronts-client/src/types/Collection.ts +++ b/fronts-client/src/types/Collection.ts @@ -24,8 +24,6 @@ interface Group { type CardSets = 'draft' | 'live' | 'previously'; // Stages represent only those lists which are curated by the user. type Stages = 'draft' | 'live'; - -type CardTypes = 'SNAP_LINK' | 'ARTICLE'; type CardSizes = 'wide' | 'default' | 'medium' | 'small'; interface NestedCardRootFields { @@ -166,7 +164,6 @@ export { CollectionWithNestedArticles, CollectionFromResponse, Collection, - CardTypes, CardSizes, Group, Stages, diff --git a/fronts-client/src/util/card.ts b/fronts-client/src/util/card.ts index 30e844c33a4..d6d8499330f 100644 --- a/fronts-client/src/util/card.ts +++ b/fronts-client/src/util/card.ts @@ -30,25 +30,42 @@ import { isGuardianUrl, isValidURL, } from 'util/url'; -import {Recipe} from "../types/Recipe"; +import { Recipe } from '../types/Recipe'; +import type { CardTypes } from '../constants/cardTypes'; + +interface CreateCardOptions { + cardType?: CardTypes; + imageHide?: boolean; + imageReplace?: boolean; + imageCutoutReplace?: boolean; + imageCutoutSrc?: string; + showByline?: boolean; + showQuotedHeadline?: boolean; + showKickerCustom?: boolean; + customKicker?: string; +} // Ideally we will convert this to a type. See // https://trello.com/c/wIMDut8V/138-add-a-type-to-the-createcard-function-in-src-shared-util-cardts const createCard = ( id: string, isEditionsApp: boolean, - imageHide: boolean = false, - imageReplace: boolean = false, - imageCutoutReplace: boolean = false, - imageCutoutSrc?: string, - showByline: boolean = false, - showQuotedHeadline: boolean = false, - showKickerCustom: boolean = false, - customKicker: string = '' + { + cardType = 'article', + imageHide = false, + imageReplace = false, + imageCutoutReplace = false, + showByline = false, + showQuotedHeadline = false, + showKickerCustom = false, + customKicker = '', + imageCutoutSrc, + }: CreateCardOptions = {} ) => ({ uuid: v4(), id, frontPublicationDate: Date.now(), + cardType, meta: { ...(imageHide ? { imageHide } : {}), ...(imageReplace ? { imageReplace } : {}), @@ -150,7 +167,7 @@ const getCardEntitiesFromDrop = async ( } if (drop.type === 'RECIPE') { - return getRecipeEntityFromFeedDrop(drop.data) + return getRecipeEntityFromFeedDrop(drop.data); } const droppedDataURL = drop.data.trim(); @@ -257,28 +274,27 @@ const getCardEntitiesFromDrop = async ( }; const getRecipeEntityFromFeedDrop = (recipe: Recipe): [Card] => { - const card = createCard(recipe.id, false); + const card = createCard(recipe.id, false, { cardType: 'recipe' }); return [card]; -} +}; const getArticleEntitiesFromFeedDrop = ( capiArticle: CapiArticle, isEdition: boolean ): TArticleEntities => { const article = transformExternalArticle(capiArticle); - const card = createCard( - article.id, - isEdition, - article.frontsMeta.defaults.imageHide, - article.frontsMeta.defaults.imageReplace, - article.frontsMeta.defaults.imageCutoutReplace, - article.frontsMeta.cutout, - article.frontsMeta.defaults.showByline, - article.frontsMeta.defaults.showQuotedHeadline, - article.frontsMeta.defaults.showKickerCustom, - article.frontsMeta.pickedKicker - ); + const card = createCard(article.id, isEdition, { + cardType: 'article', + imageHide: article.frontsMeta.defaults.imageHide, + imageReplace: article.frontsMeta.defaults.imageReplace, + imageCutoutReplace: article.frontsMeta.defaults.imageCutoutReplace, + imageCutoutSrc: article.frontsMeta.cutout, + showByline: article.frontsMeta.defaults.showByline, + showQuotedHeadline: article.frontsMeta.defaults.showQuotedHeadline, + showKickerCustom: article.frontsMeta.defaults.showKickerCustom, + customKicker: article.frontsMeta.pickedKicker, + }); return [card, article]; }; diff --git a/fronts-client/tsconfig.json b/fronts-client/tsconfig.json index 1b29cde1c2f..0f42208acf6 100644 --- a/fronts-client/tsconfig.json +++ b/fronts-client/tsconfig.json @@ -17,6 +17,7 @@ "strict": true, "outDir": "../../public/fronts-client-v2/dist", "skipLibCheck": true, + "resolveJsonModule": true }, "exclude": [ "config",