Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chrome: Adding the visibility selector #832

Merged
merged 4 commits into from
May 19, 2017
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions editor/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,26 @@ export function getPostEdits( state ) {
return state.editor.edits;
}

export function getEditedPostAttribute( state, attributeName ) {
return state.editor.edits[ attributeName ] === undefined
? state.currentPost[ attributeName ]
: state.editor.edits[ attributeName ];
}

export function getEditedPostStatus( state ) {
return state.editor.edits.status === undefined
? state.currentPost.status
: state.editor.edits.status;
return getEditedPostAttribute( state, 'status' );
}

export function getEditedPostVisibility( state ) {
const status = getEditedPostStatus( state );
const password = getEditedPostAttribute( state, 'password' );

if ( status === 'private' ) {
return 'private';
} else if ( password !== undefined && password !== null ) {
return 'password';
}
return 'public';
}

export function getEditedPostTitle( state ) {
Expand Down
5 changes: 5 additions & 0 deletions editor/sidebar/post-status/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import FormToggle from 'components/form-toggle';
* Internal Dependencies
*/
import './style.scss';
import PostVisibility from '../post-visibility';
import { getEditedPostStatus } from '../../selectors';
import { editPost } from '../../actions';

Expand All @@ -34,6 +35,10 @@ function PostStatus( { status, onUpdateStatus } ) {
onChange={ onToggle }
/>
</label>

<div className="editor-post-status__row">
<PostVisibility />
</div>
</PanelBody>
);
/* eslint-enable jsx-a11y/label-has-for */
Expand Down
2 changes: 1 addition & 1 deletion editor/sidebar/post-status/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 10px;
padding-top: 20px;
}
126 changes: 126 additions & 0 deletions editor/sidebar/post-visibility/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/**
* External dependencies
*/
import { connect } from 'react-redux';
import clickOutside from 'react-click-outside';
import { find } from 'lodash';

/**
* WordPress dependencies
*/
import { __ } from 'i18n';
import { Component } from 'element';

/**
* Internal Dependencies
*/
import './style.scss';
import {
getEditedPostAttribute,
getEditedPostStatus,
getEditedPostVisibility,
} from '../../selectors';
import { editPost } from '../../actions';

class PostVisibility extends Component {
constructor() {
super( ...arguments );
this.state = {
opened: false,
};
this.toggleDialog = this.toggleDialog.bind( this );
}

toggleDialog( event ) {
event.preventDefault();
this.setState( { opened: ! this.state.opened } );
}

handleClickOutside() {
this.setState( { opened: false } );
}

render() {
const { status, visibility, password, onUpdateVisibility } = this.props;

const setPublic = () => onUpdateVisibility( visibility === 'private' ? 'draft' : status );
const setPrivate = () => onUpdateVisibility( 'private' );
const setPasswordProtected = () => onUpdateVisibility( visibility === 'private' ? 'draft' : status, password || '' );
const updatePassword = ( event ) => onUpdateVisibility( status, event.target.value );

const visibilityOptions = [
{
value: 'public',
label: __( 'Public' ),
info: __( 'Visible to everyone.' ),
changeHandler: setPublic,
},
{
value: 'private',
label: __( 'Private' ),
info: __( 'Only visible to site admins and editors.' ),
changeHandler: setPrivate,
},
{
value: 'password',
label: __( 'Password Protected' ),
info: __( 'Protected with a password you choose. Only those with the password can view this post.' ),
changeHandler: setPasswordProtected,
},
];
const getVisibilityLabel = () => find( visibilityOptions, { value: visibility } ).label;

// Disable Reason: The input is inside the label, we shouldn't need the htmlFor
/* eslint-disable jsx-a11y/label-has-for */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get rid of this rule?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you mean completely disable it from the .eslintrc?

return (
<div className="editor-post-visibility">
<span>{ __( 'Visibility' ) }</span>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the <span> necessary? Both here and in the <label> below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's is not required here, but since the parent is "flex", I like the explicitness of having flex children. It's useless though for the label.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's is not required here, but since the parent is "flex"

Hmm, now I'm curious what is the default flex behavior for handling text nodes. 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to work properly though

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to handle them independently only when they're broken up by other elements (notably not by react-text comments):

Explicitness seems good in these cases 👍

<a className="editor-post-visibility__toggle" href="" onClick={ this.toggleDialog }>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's not navigational, it shouldn't be an anchor. Core has a .button-link class which gives an appearance close to but not exactly the same as a link which we could use as a base.

{ getVisibilityLabel( visibility ) }
</a>

{ this.state.opened &&
<div className="editor-post-visibility__dialog">
<div className="editor-post-visibility__dialog-arrow" />
<div className="editor-post-visibility__dialog-legend">
{ __( 'Post Visibility' ) }
</div>
{ visibilityOptions.map( ( { value, label, info, changeHandler } ) => (
<label key={ value } className="editor-post-visibility__dialog-label">
<input type="radio" value={ value } onChange={ changeHandler } checked={ value === visibility } />
<span>{ label }</span>
{ <div className="editor-post-visibility__dialog-info">{ info }</div> }
</label>
) ) }
{ visibility === 'password' &&
<input
className="editor-post-visibility__dialog-password-input"
type="text"
onChange={ updatePassword }
value={ password }
placeholder={ __( 'Create password' ) }
/>
}
</div>
}
</div>
);
/* eslint-enable jsx-a11y/label-has-for */
}
}

export default connect(
( state ) => ( {
status: getEditedPostStatus( state ),
visibility: getEditedPostVisibility( state ),
password: getEditedPostAttribute( state, 'password' ),
} ),
( dispatch ) => {
return {
onUpdateVisibility( status, password = null ) {
dispatch( editPost( { status, password } ) );
},
};
}
)( clickOutside( PostVisibility ) );

73 changes: 73 additions & 0 deletions editor/sidebar/post-visibility/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
.editor-post-visibility {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
position: relative;
}

.editor-post-visibility__toggle:focus {
box-shadow: none;
}

.editor-post-visibility__dialog {
position: absolute;
top: 30px;
right: 0;
box-shadow: $shadow-popover;
border: 1px solid $light-gray-500;
background: $white;
padding: 10px;
min-width: 240px;
}

.editor-post-visibility__dialog-arrow {
position: absolute;
border: 10px dashed $light-gray-500;
height: 0;
width: 0;
line-height: 0;
top: -10px;
right: 5px;
margin-left: -10px;
border-bottom-style: solid;
border-top: none;
border-left-color: transparent;
border-right-color: transparent;

&:before {
top: 2px;
border: 10px solid $white;
content: " ";
position: absolute;
left: 50%;
margin-left: -10px;
border-bottom-style: solid;
border-top: none;
border-left-color: transparent;
border-right-color: transparent;
}
}

.editor-post-visibility__dialog-legend {
font-weight: 600;
font-size: 0.9em;

}

.editor-post-visibility__dialog-label {
display: block;
margin: 10px 0;
}

.editor-post-visibility__dialog-password-input {
width: calc( 100% - 20px );
margin-left: 20px;
}

.editor-post-visibility__dialog-info {
color: $dark-gray-200;
padding-left: 20px;
font-style: italic;
margin-top: 4px;
}
60 changes: 60 additions & 0 deletions editor/test/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
getPostEdits,
getEditedPostStatus,
getEditedPostTitle,
getEditedPostVisibility,
getEditedPostPreviewLink,
getBlock,
getBlocks,
Expand Down Expand Up @@ -220,6 +221,65 @@ describe( 'selectors', () => {
} );
} );

describe( 'getEditedPostVisibility', () => {
it( 'should return public by default', () => {
const state = {
currentPost: {
status: 'draft',
},
editor: {
edits: {},
},
};

expect( getEditedPostVisibility( state ) ).to.equal( 'public' );
} );

it( 'should return private for private posts', () => {
const state = {
currentPost: {
status: 'private',
},
editor: {
edits: {},
},
};

expect( getEditedPostVisibility( state ) ).to.equal( 'private' );
} );

it( 'should return private for password for password protected posts', () => {
const state = {
currentPost: {
status: 'draft',
password: 'chicken',
},
editor: {
edits: {},
},
};

expect( getEditedPostVisibility( state ) ).to.equal( 'password' );
} );

it( 'should use the edited status and password if edits present', () => {
const state = {
currentPost: {
status: 'draft',
password: 'chicken',
},
editor: {
edits: {
status: 'private',
password: null,
},
},
};

expect( getEditedPostVisibility( state ) ).to.equal( 'private' );
} );
} );

describe( 'getEditedPostPreviewLink', () => {
it( 'should return null if the post has not link yet', () => {
const state = {
Expand Down