in fronts-client/src/components/form/ArticleMetaForm.tsx [449:978]
public render() {
const {
cardId,
change,
kickerOptions,
pickedKicker,
imageHide,
articleCapiFieldValues,
pristine,
showByline,
editableFields = [],
showKickerTag,
showKickerSection,
frontId,
articleExists,
imageReplace,
imageCutoutReplace,
cutoutImage,
imageSlideshowReplace,
slideshow,
isBreaking,
editMode,
primaryImage,
hasMainVideo,
coverCardImageReplace,
coverCardMobileImage,
coverCardTabletImage,
valid,
groupSizeId,
collectionType,
} = this.props;
const isEditionsMode = editMode === 'editions';
const imageDefined = (img: ImageData | undefined) => img && img.src;
const slideshowHasAtLeastTwoImages =
(slideshow ?? []).filter((field) => !!field).length >= 2;
const invalidCardReplacement = coverCardImageReplace
? !imageDefined(coverCardMobileImage) ||
!imageDefined(coverCardTabletImage)
: false;
const setCustomKicker = (customKickerValue: string) => {
change('customKicker', customKickerValue);
change('showKickerCustom', true);
// kicker suggestions now set the value of `customKicker` rather than set a flag
// set the old flags to false
['showKickerTag', 'showKickerSection'].forEach((field) =>
change(field, false),
);
};
const renderKickerSuggestion = (
value: string,
index: number,
array: string[],
) => (
<Field
name={'kickerSuggestion' + value}
key={'kickerSuggestion' + value}
component={KickerSuggestionButton}
buttonText={value}
size="s"
onClick={() => setCustomKicker(value)}
/>
);
const getKickerContents = () => {
const uniqueKickerSuggestions = [
...new Set([
pickedKicker || '',
kickerOptions.webTitle || '',
kickerOptions.sectionName || '',
]),
];
return (
<>
<span>Suggested: </span>
{uniqueKickerSuggestions
.filter((value) => !!value)
.map(renderKickerSuggestion)}
<span> </span>
<Field
name={'clearKickerSuggestion'}
key={'clearKickerSuggestion'}
component={KickerSuggestionButton}
buttonText={'Clear'}
style={{ fontStyle: 'italic' }}
size="s"
onClick={() => setCustomKicker('')}
/>
</>
);
};
const cardCriteria = this.determineCardCriteria();
return (
<FormContainer
data-testid="edit-form"
topBorder={false}
onClick={
(e: React.MouseEvent) =>
e.stopPropagation() /* Prevent clicks passing through the form */
}
>
{!articleExists && (
<CollectionEditedError>
{this.state.lastKnownCollectionId &&
`This collection has been edited by ${this.props.getLastUpdatedBy(
this.state.lastKnownCollectionId,
)} since you started editing this article. Your changes have not been saved.`}
</CollectionEditedError>
)}
<FormContent size={this.props.size}>
<TextOptionsInputGroup>
<ConditionalField
name="customKicker"
label="Kicker"
permittedFields={editableFields}
component={InputText}
disabled={isBreaking}
title={
isBreaking
? "You cannot edit the kicker if the 'Breaking News' toggle is set."
: ''
}
labelContent={
<KickerSuggestionsContainer>
{getKickerContents()}
</KickerSuggestionsContainer>
}
placeholder="Add custom kicker"
format={(value: string) => {
if (showKickerTag) {
return kickerOptions.webTitle;
}
if (showKickerSection) {
return kickerOptions.sectionName;
}
return value;
}}
onChange={(e: React.ChangeEvent<any>) => {
if (e) {
setCustomKicker(e.target.value);
}
}}
/>
{shouldRenderField('headline', editableFields) && (
<Field
name="headline"
label={this.getHeadlineLabel()}
rows="2"
placeholder={articleCapiFieldValues.headline}
component={
this.props.snapType === 'html' ? RichTextInput : InputTextArea
}
originalValue={articleCapiFieldValues.headline}
data-testid="edit-form-headline-field"
/>
)}
<CheckboxFieldsContainer
editableFields={editableFields}
size={this.props.size}
extraBottomMargin="8px"
>
{[
...this.getBoostToggles(groupSizeId, cardId, collectionType),
<Field
name="isImmersive"
component={InputCheckboxToggleInline}
label="Immersive"
id={getInputId(cardId, 'immersive')}
type="checkbox"
onChange={(event: any) =>
this.toggleCardStyleField(
'isImmersive',
event as boolean,
groupSizeId,
)
}
/>,
]}
</CheckboxFieldsContainer>
<CheckboxFieldsContainer
editableFields={editableFields}
size={this.props.size}
>
<Field
name="isBoosted"
component={InputCheckboxToggleInline}
label="Boost"
id={getInputId(cardId, 'boost')}
type="checkbox"
/>
<Field
name="showLargeHeadline"
component={InputCheckboxToggleInline}
label="Large headline"
id={getInputId(cardId, 'large-headline')}
type="checkbox"
/>
<Field
name="showQuotedHeadline"
component={InputCheckboxToggleInline}
label="Quote headline"
id={getInputId(cardId, 'quote-headline')}
type="checkbox"
/>
<Field
name="isBreaking"
component={InputCheckboxToggleInline}
label="Breaking News"
id={getInputId(cardId, 'breaking-news')}
type="checkbox"
dataTestId="edit-form-breaking-news-toggle"
/>
<Field
name="showByline"
component={InputCheckboxToggleInline}
label="Show Byline"
id={getInputId(cardId, 'show-byline')}
type="checkbox"
/>
<Field
name="showLivePlayable"
component={InputCheckboxToggleInline}
label="Show updates"
id={getInputId(cardId, 'show-updates')}
type="checkbox"
/>
</CheckboxFieldsContainer>
{showByline && (
<ConditionalField
permittedFields={editableFields}
name="byline"
label="Byline"
component={InputText}
placeholder={articleCapiFieldValues.byline}
useHeadlineFont
originalValue={articleCapiFieldValues.byline}
/>
)}
<ConditionalField
permittedFields={editableFields}
name="trailText"
component={InputTextArea}
placeholder={articleCapiFieldValues.trailText}
originalValue={articleCapiFieldValues.trailText}
label="Trail text"
/>
<ConditionalField
permittedFields={editableFields}
name="sportScore"
label="Sport Score"
component={InputText}
placeholder=""
originalValue={''}
/>
</TextOptionsInputGroup>
<ImageOptionsInputGroup size={this.props.size}>
<ImageRowContainer size={this.props.size}>
<Row rowGap={4}>
<ImageCol faded={imageHide || !!coverCardImageReplace}>
{shouldRenderField(
this.getImageFieldName(),
editableFields,
) && (
<InputLabel htmlFor={this.getImageFieldName()}>
Trail image
</InputLabel>
)}
<ConditionalField
permittedFields={editableFields}
name={this.getImageFieldName()}
component={InputImage}
disabled={imageHide || coverCardImageReplace}
criteria={
isEditionsMode ? editionsCardImageCriteria : cardCriteria
}
frontId={frontId}
defaultImageUrl={
imageCutoutReplace
? cutoutImage
: articleCapiFieldValues.thumbnail
}
useDefault={!imageCutoutReplace && !imageReplace}
message={
imageCutoutReplace ? 'Add cutout' : 'Replace image'
}
hasVideo={hasMainVideo}
onChange={this.handleImageChange}
collectionType={this.props.collectionType}
/>
</ImageCol>
<ToggleCol flex={1}>
<InputGroup>
<ConditionalField
permittedFields={editableFields}
name="imageHide"
component={InputCheckboxToggleInline}
label="Hide media"
id={getInputId(cardId, 'hide-media')}
type="checkbox"
default={false}
onChange={() => this.changeImageField('imageHide')}
/>
</InputGroup>
<InputGroup>
<ConditionalField
permittedFields={editableFields}
name="imageCutoutReplace"
component={InputCheckboxToggleInline}
label="Use cutout"
id={getInputId(cardId, 'use-cutout')}
type="checkbox"
default={false}
onChange={() =>
this.changeImageField('imageCutoutReplace')
}
/>
</InputGroup>
<InputGroup>
<ConditionalField
permittedFields={editableFields}
name="coverCardImageReplace"
id={getInputId(cardId, 'coverCardImageReplace')}
component={InputCheckboxToggleInline}
label="Replace Cover Card Image"
type="checkbox"
default={false}
onChange={() =>
this.changeImageField('coverCardImageReplace')
}
/>
</InputGroup>
{primaryImage &&
!!primaryImage.src &&
!this.props.showMainVideo &&
!this.props.imageSlideshowReplace && (
<InputGroup>
<ConditionalField
permittedFields={editableFields}
name="imageReplace"
component={InputCheckboxToggleInline}
label="Use replacement image"
id={getInputId(cardId, 'image-replace')}
type="checkbox"
default={false}
onChange={() => this.changeImageField('imageReplace')}
/>
</InputGroup>
)}
{articleCapiFieldValues.urlPath && (
// the below tag is empty and meaningless to the fronts tool itself, but serves as a handle for
// Pinboard to attach itself via, identified/distinguished by the urlPath data attribute
// @ts-ignore
<pinboard-article-button
data-url-path={articleCapiFieldValues.urlPath}
data-with-draggable-thumbs-of-ratio={`${cardCriteria.widthAspectRatio}:${cardCriteria.heightAspectRatio}`}
/>
)}
</ToggleCol>
<Col flex={2}>
<InputLabel htmlFor="media-select">Select Media</InputLabel>
<InputGroup>
<Field
component={InputRadio}
disabled={
editableFields.indexOf(this.getImageFieldName()) === -1
}
usesBlockStyling={true}
name="media-select"
type="radio"
label="Trail Image"
id={getInputId(cardId, 'select-trail-image')}
value="select-trail-image"
initialValues="select-trail-image"
onClick={() =>
this.changeImageField(this.getImageFieldName())
}
checked={
!this.props.showMainVideo &&
!this.props.imageSlideshowReplace
}
/>
</InputGroup>
<InputGroup>
<Field
component={InputRadio}
disabled={editableFields.indexOf('showMainVideo') === -1}
icon={<SelectVideoIcon />}
usesBlockStyling={true}
name="media-select"
type="radio"
label="Video"
id={getInputId(cardId, 'select-video')}
value="select-video"
onClick={() => this.changeImageField('showMainVideo')}
checked={
this.props.showMainVideo !== undefined
? this.props.showMainVideo
: false
}
/>
</InputGroup>
{!hasMainVideo && (
<Explainer>Main media video required</Explainer>
)}
<InputGroup>
<Field
component={InputRadio}
disabled={
editableFields.indexOf('imageSlideshowReplace') === -1
}
icon={<SlideshowIcon />}
usesBlockStyling={true}
name="media-select"
type="radio"
label="Slideshow"
id={getInputId(cardId, 'select-slideshow')}
value="select-slideshow"
onClick={() =>
this.changeImageField('imageSlideshowReplace')
}
checked={
this.props.imageSlideshowReplace !== undefined
? this.props.imageSlideshowReplace
: false
}
/>
</InputGroup>
</Col>
</Row>
<ConditionalComponent
permittedNames={editableFields}
name={['primaryImage', 'imageHide']}
/>
</ImageRowContainer>
{imageSlideshowReplace && (
<SlideshowRowContainer size={this.props.size}>
<FieldArray
name="slideshow"
frontId={frontId}
component={RenderSlideshow}
change={change}
criteria={cardCriteria}
slideshowHasAtLeastTwoImages={slideshowHasAtLeastTwoImages}
/>
</SlideshowRowContainer>
)}
</ImageOptionsInputGroup>
{isEditionsMode && coverCardImageReplace && (
<RowContainer>
<Row>
<ImageCol faded={!coverCardImageReplace}>
<InputLabel htmlFor={this.getImageFieldName()}>
Mobile Cover Card
</InputLabel>
<Field
name="coverCardMobileImage"
component={InputImage}
message="Add Mobile Card Image"
criteria={editionsMobileCardImageCriteria}
disabled={!coverCardImageReplace}
/>
</ImageCol>
<ImageCol faded={!coverCardImageReplace}>
<InputLabel htmlFor={this.getImageFieldName()}>
Tablet Cover Card
</InputLabel>
<Field
name="coverCardTabletImage"
component={InputImage}
message="Add Tablet Card Image"
criteria={editionsTabletCardImageCriteria}
disabled={!coverCardImageReplace}
/>
</ImageCol>
</Row>
<Row>
<Col>
<a
href={url.editionsCardBuilder}
target="_blank"
rel="noreferrer noopener"
>
Open Cover Card Builder
</a>
</Col>
</Row>
<Row>
<Col flex={2}>
{invalidCardReplacement && (
<CardReplacementWarning>
You must set both the mobile and tablet card overrides!
</CardReplacementWarning>
)}
</Col>
</Row>
</RowContainer>
)}
</FormContent>
<FormButtonContainer>
<Button onClick={this.handleCancel} type="button" size="l">
Cancel
</Button>
<Button
priority="primary"
onClick={this.handleSubmit}
disabled={
pristine ||
!articleExists ||
invalidCardReplacement ||
!valid ||
(imageSlideshowReplace && !slideshowHasAtLeastTwoImages)
}
size="l"
data-testid="edit-form-save-button"
>
Save
</Button>
</FormButtonContainer>
</FormContainer>
);
}