Skip to content

Commit

Permalink
feat(NewCollectivePage): About section
Browse files Browse the repository at this point in the history
  • Loading branch information
Betree committed Jun 3, 2019
1 parent 1df68ff commit 10a0e5f
Show file tree
Hide file tree
Showing 8 changed files with 224 additions and 21 deletions.
84 changes: 84 additions & 0 deletions src/components/HTMLContent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';

/**
* React-Quill usually saves something like `<p><br/></p` when saving with an empty
* editor. This function tries to detect this and returns true if there's no real
* text, image or iframe contents.
*/
export const isEmptyValue = value => {
if (!value) {
return true;
} else if (value.length > 50) {
// Running the regex on long strings can be costly, and there's very few chances
// to have a blank content with tons of empty markup.
return false;
} else if (/(<img)|(<iframe)|(<video)/.test(value)) {
// If the content has no text but has an image or an iframe (video) then it's not blank
return false;
} else {
// Strip all tags and check if there's something left
const cleanStr = value.replace(/(<([^>]+)>)/gi, '');
return cleanStr.length === 0;
}
};

/**
* `HTMLEditor`'s associate, this component will display raw HTML with some CSS
* resets to ensure we don't mess with the styles.
*
* ⚠️ Be careful! This component will pass content to `dangerouslySetInnerHTML` so
* always ensure `content` is properly sanitized!
*/
const HTMLContent = styled(({ content, ...props }) => {
return <div dangerouslySetInnerHTML={{ __html: content }} {...props} />;
})`
/** Override global styles to match what we have in the editor */
h1,
h2,
h3 {
margin: 0;
}
img {
max-width: 100%;
}
.ql-align-center {
text-align: center;
}
.ql-align-right {
text-align: right;
}
.ql-align-left {
text-align: left;
}
ul li {
list-style: none;
position: relative;
padding: 0 0 0 2em;
margin-bottom: 0.5em;
&::before {
content: '';
position: absolute;
left: 0;
top: 0.4em;
width: 0.625em;
height: 0.625em;
border-radius: 50%;
border: 0.1em solid #1f87ff;
}
}
`;

HTMLContent.propTypes = {
/** The HTML string. Makes sure this is sanitized properly! */
content: PropTypes.string,
};

export default HTMLContent;
4 changes: 4 additions & 0 deletions src/components/HTMLEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ class HTMLEditor extends React.Component {
container: [
[{ header: props.allowedHeaders }],
['bold', 'italic', 'underline', 'blockquote'],
[{ color: [] }],
[{ align: '' }, { align: 'center' }, { align: 'right' }],
[{ list: 'ordered' }, { list: 'bullet' }],
['link', 'image'],
],
Expand All @@ -66,6 +68,8 @@ class HTMLEditor extends React.Component {
* See https://quilljs.com/docs/formats/
*/
this.formats = [
'align',
'color',
'header',
'font',
'size',
Expand Down
18 changes: 15 additions & 3 deletions src/components/InlineEditField.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ class InlineEditField extends Component {
mutation: PropTypes.object.isRequired,
/** Can user edit the description */
canEdit: PropTypes.bool,
/** Set to false to disable edit icon even if user is allowed to edit */
showEditIcon: PropTypes.bool,
/** If given, this function will be used to render the field */
children: PropTypes.func,
/**
Expand All @@ -47,6 +49,10 @@ class InlineEditField extends Component {
placeholder: PropTypes.node,
};

static defaultProps = {
showEditIcon: true,
};

state = { isEditing: false, draft: '' };

enableEditor = () => {
Expand All @@ -69,22 +75,28 @@ class InlineEditField extends Component {
</StyledButton>
);
} else if (children) {
return children({ isEditing: false, value });
return children({
value,
isEditing: false,
enableEditor: this.enableEditor,
closeEditor: this.closeEditor,
setValue: this.setDraft,
});
} else {
return <span>{value}</span>;
}
}

render() {
const { field, values, mutation, canEdit, placeholder, children } = this.props;
const { field, values, mutation, canEdit, showEditIcon, placeholder, children } = this.props;
const { isEditing, draft } = this.state;
const value = get(values, field);
const touched = draft !== value;

if (!isEditing) {
return (
<Container position="relative">
{canEdit && (
{canEdit && showEditIcon && (
<Container position="absolute" top={0} right={0}>
<EditIcon size={24} onClick={this.enableEditor} data-cy={`InlineEditField-Trigger-${field}`} />
</Container>
Expand Down
1 change: 1 addition & 0 deletions src/components/collective-page/Hero.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const MainContainer = styled.div`
flex-direction: column;
justify-content: space-between;
transition: flex;
z-index: 999;
${props =>
props.isFixed &&
Expand Down
81 changes: 81 additions & 0 deletions src/components/collective-page/SectionAbout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { Flex, Box } from '@rebass/grid';

import { H3, Span } from '../Text';
import HTMLEditor from '../HTMLEditor';
import HTMLContent, { isEmptyValue } from '../HTMLContent';
import InlineEditField from '../InlineEditField';
import Container from '../Container';
import StyledButton from '../StyledButton';

/**
* Display the inline editable description section for the collective
*/
const SectionAbout = ({ collective, canEdit, editMutation }) => {
const isEmptyDescription = isEmptyValue(collective.longDescription);

return (
<Flex flexDirection="column" alignItems="center" px={2} py={5}>
<H3 fontSize="H2" fontWeight="normal" mb={5}>
<FormattedMessage id="SectionAbout.Title" defaultMessage="Why we do what we do" />
</H3>

<Container width="100%" maxWidth={700} margin="0 auto">
<InlineEditField
mutation={editMutation}
values={collective}
field="longDescription"
canEdit={canEdit}
showEditIcon={!isEmptyDescription}
>
{({ isEditing, value, setValue, enableEditor }) => {
if (isEditing) {
return (
<HTMLEditor defaultValue={value} onChange={setValue} allowedHeaders={[false, 2, 3]} /** Disable H1 */ />
);
} else if (isEmptyDescription) {
return (
<Flex justifyContent="center">
{canEdit ? (
<Box margin="0 auto">
<StyledButton buttonSize="large" onClick={enableEditor}>
<FormattedMessage id="CollectivePage.AddLongDescription" defaultMessage="Add your mission" />
</StyledButton>
</Box>
) : (
<Span color="black.500" fontStyle="italic">
<FormattedMessage
id="SectionAbout.MissingDescription"
defaultMessage="{collectiveName} didn't write a presentation yet"
values={{ collectiveName: collective.name }}
/>
</Span>
)}
</Flex>
);
} else {
return <HTMLContent content={collective.longDescription} data-cy="longDescription" />;
}
}}
</InlineEditField>
</Container>
</Flex>
);
};

SectionAbout.propTypes = {
/** The collective to display description for */
collective: PropTypes.shape({
id: PropTypes.number.isRequired,
longDescription: PropTypes.string,
name: PropTypes.string,
}).isRequired,
/** A mutation used to update the description */
editMutation: PropTypes.object,
/** Can user edit the description? */
canEdit: PropTypes.bool,
};

export default SectionAbout;
50 changes: 35 additions & 15 deletions src/components/collective-page/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import gql from 'graphql-tag';

import theme from '../../constants/theme';
import { debounceScroll } from '../../lib/ui-utils';
import Container from '../Container';

import { AllSectionsNames, Dimensions } from './_constants';
import { AllSectionsNames, Sections, Dimensions } from './_constants';
import Hero from './Hero';
import SectionAbout from './SectionAbout';

/** A mutation used by child components to update the collective */
const EditCollectiveMutation = gql`
mutation EditCollective($id: Int!, $longDescription: String) {
editCollective(collective: { id: $id, longDescription: $longDescription }) {
id
longDescription
}
}
`;

/**
* This is the collective page main layout, holding different blocks together
Expand Down Expand Up @@ -103,6 +115,25 @@ export default class CollectivePage extends Component {
window.scrollTo(0, 0);
};

renderSection(section, canEditCollective) {
if (section === Sections.ABOUT) {
return (
<SectionAbout
collective={this.props.collective}
canEdit={canEditCollective}
editMutation={EditCollectiveMutation}
/>
);
}

// Placeholder for sections not implemented yet
return (
<Container display="flex" borderBottom="1px solid lightgrey" py={8} justifyContent="center" fontSize={36}>
[Section] {section}
</Container>
);
}

render() {
const { collective, host, LoggedInUser } = this.props;
const { isFixed, selectedSection } = this.state;
Expand All @@ -122,21 +153,10 @@ export default class CollectivePage extends Component {
onCollectiveClick={this.onCollectiveClick}
/>
</Container>

{/* Placeholders for sections not implemented yet */}
{AllSectionsNames.map(section => (
<Container
ref={sectionRef => (this.sectionsRefs[section] = sectionRef)}
key={section}
id={`section-${section}`}
display="flex"
borderBottom="1px solid lightgrey"
py={8}
justifyContent="center"
fontSize={36}
>
[Section] {section}
</Container>
<div key={section} ref={sectionRef => (this.sectionsRefs[section] = sectionRef)} id={`section-${section}`}>
{this.renderSection(section, canEditCollective)}
</div>
))}
</Container>
);
Expand Down
6 changes: 3 additions & 3 deletions src/components/tier-page/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ class TierPage extends Component {
canEdit={canEditTier}
values={tier}
field="name"
placeholder={<FormattedMessage id="TierPage.AddTitle" defaultMessage="Add a title" />}
adminPlaceholder={<FormattedMessage id="TierPage.AddTitle" defaultMessage="Add a title" />}
/>
</H1>
<H3 color="black.500" fontSize="H5" mb={4} whiteSpace="pre-line" data-cy="shortDescription">
Expand All @@ -218,7 +218,7 @@ class TierPage extends Component {
canEdit={canEditTier}
values={tier}
field="description"
placeholder={
adminPlaceholder={
<FormattedMessage id="TierPage.AddDescription" defaultMessage="Add a short description" />
}
/>
Expand All @@ -228,7 +228,7 @@ class TierPage extends Component {
values={tier}
field="longDescription"
canEdit={canEditTier}
placeholder={
adminPlaceholder={
<FormattedMessage id="TierPage.AddLongDescription" defaultMessage="Add a rich description" />
}
>
Expand Down
1 change: 1 addition & 0 deletions src/pages/new-collective-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ const getCollective = graphql(gql`
slug
name
description
longDescription
image
backgroundImage
twitterHandle
Expand Down

0 comments on commit 10a0e5f

Please sign in to comment.