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

Add Editable Permalinks #5756

Merged
merged 63 commits into from
Apr 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
b9c5934
Remove the copy button, add the Change Permalinks button
pento Mar 23, 2018
4f1c501
Display the sample permalink
pento Mar 23, 2018
8a451e2
Remove the last bits of the Copy button
pento Mar 23, 2018
8353e7b
Add the permalink editing form
pento Mar 23, 2018
6253a45
Fix a strange PHP notice, because WordPress is strange.
pento Mar 23, 2018
6d99339
Turn the Dashicon into a ClipboardButton
pento Mar 23, 2018
e1e0830
Grey the icon after it's been clicked.
pento Mar 23, 2018
8307f97
Use Tooltip for the permalink copy button tip
pento Mar 24, 2018
e7a3547
Rename the copied CSS class to is-copied
pento Mar 24, 2018
034a077
Tweak the form a bit
pento Mar 24, 2018
19aa275
Move the permalink edit form to its own component.
pento Mar 25, 2018
a7da16a
Allow for when the permalink hasn't been changed.
pento Mar 25, 2018
a44c379
Switch to using withDispatch() instead of using editPost() directly.
pento Mar 25, 2018
7a23f77
💪🏻📦
pento Mar 25, 2018
ab21ea5
Check that we have a sample permalink before we try to use it.
pento Mar 25, 2018
a0b9205
The sample_permalink is also the actual permalink for published posts.
pento Mar 26, 2018
8523412
Don't add a sample permlink for post types that aren't public.
pento Mar 26, 2018
4b0bce4
Move the PostPermalink component after the title editor.
pento Mar 26, 2018
edbbe9e
Make the permalink editor input autofocus when the form is opened.
pento Mar 26, 2018
d9a8440
Move the permalink copy button later in the DOM, for better keyboard …
pento Mar 26, 2018
6761c51
Allow autofox to be used on the permalink editor form.
pento Mar 27, 2018
680b4b6
Remove the full stop from the tooltip text.
pento Mar 27, 2018
85c4bda
Revert "Move the permalink copy button later in the DOM, for better k…
pento Mar 27, 2018
6660458
After the permalink is saved, focus on the new permalink.
pento Mar 27, 2018
3778a32
Use ellipsis for overflow of the URL prefix.
pento Mar 27, 2018
4e83662
Add a comment to explain autofocus being allowed
pento Apr 9, 2018
95b5ceb
Prevent the form from doing the default submit action.
pento Apr 9, 2018
d8f1ef2
Add a focus() method to Button, so the element can be focussed.
pento Apr 9, 2018
7a2a12c
Use Button's new focus() method to focus it.
pento Apr 9, 2018
3ea1481
Use the edited post name as the form text input value.
pento Apr 9, 2018
71a29c7
Replace connect() with withSelect()
pento Apr 9, 2018
c135851
Use the Button isLarge property, instead of the button class.
pento Apr 9, 2018
8bbcf6e
Add an edge to the permalink button fade.
pento Apr 9, 2018
b697cae
Add an is_viewable flag to post_type API responses.
pento Apr 9, 2018
2b0156c
Always add the sample permalink to post responses.
pento Apr 9, 2018
b281331
Don't show the PostPermalink on post types that aren't viewable.
pento Apr 9, 2018
6f796fb
Remove some old permalink data validity checks
pento Apr 9, 2018
02dea6c
Remove a redundant CSS block.
pento Apr 9, 2018
a02c4a0
Remove some more redundant CSS.
pento Apr 9, 2018
5e6baab
Fix some failing unit tests.
pento Apr 9, 2018
c401948
lov2work w/ old phpunit
pento Apr 9, 2018
65cf88e
Make the permalink display in the correct order in LTR languages.
pento Apr 11, 2018
b0be34a
Rename `sample_permalink` to `permalink_template`, remove the duplica…
pento Apr 11, 2018
19f4f3a
Fix Travis
pento Apr 11, 2018
aeb3a84
Reset the local copy of the postname, if the server has returned a sa…
pento Apr 11, 2018
14d23ee
Actually fix Travis.
pento Apr 11, 2018
38e5444
💀
pento Apr 11, 2018
380f40e
Allow the postname field to be cleared.
pento Apr 12, 2018
98c36f7
Button: remove an unnecessary bind().
pento Apr 12, 2018
f63debc
Rename editingPermalink to isEditingPermalink.
pento Apr 12, 2018
daa4b8f
Nudge the copy button a little.
pento Apr 12, 2018
1f703b0
Refresh the post when the user returns to the page after having click…
pento Apr 12, 2018
40eff87
Add tests for the permalink selectors.
pento Apr 12, 2018
0bf43de
Use getPostType() for getting the post type.
pento Apr 12, 2018
5db2b58
Fix some linting errors.
pento Apr 12, 2018
6ded524
Move the addEventListener to a callback, and remove it when the compo…
pento Apr 13, 2018
fb49a35
Rename UPDATE_POST_FROM_SERVER to REFRESH_POST
pento Apr 13, 2018
91e26fb
Performance improvement of a get() call.
pento Apr 13, 2018
17392bd
Remove POSTNAME_SANITIZED, handle it in UPDATE_POST
pento Apr 13, 2018
48fbdec
Remove the GET method from the post refresh request, as that's the de…
pento Apr 13, 2018
8766dab
Rename the posttype is_viewable flag to viewable.
pento Apr 13, 2018
7e56148
Add a draft_slug to the REST API response for posts that are drafts, …
pento Apr 13, 2018
6d3960f
Linting.
pento Apr 13, 2018
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
4 changes: 4 additions & 0 deletions components/button/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ class Button extends Component {
this.ref = ref;
}

focus() {
this.ref.focus();
}

render() {
const {
href,
Expand Down
98 changes: 98 additions & 0 deletions editor/components/post-permalink/editor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* WordPress dependencies
*/
import { withDispatch, withSelect } from '@wordpress/data';
import { Component, compose } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { Button } from '@wordpress/components';

/**
* Internal Dependencies
*/
import './style.scss';

class PostPermalinkEditor extends Component {
constructor( { permalinkParts } ) {
super( ...arguments );

this.state = {
editedPostName: permalinkParts.postName,
};

this.onSavePermalink = this.onSavePermalink.bind( this );
}

onSavePermalink( event ) {
const postName = this.state.editedPostName.replace( /\s+/g, '-' );

event.preventDefault();

this.props.onSave();

if ( ! postName || postName === this.props.postName ) {
return;
}

this.props.editPost( {
slug: postName,
} );

this.setState( {
editedPostName: postName,
} );
}

render() {
const { prefix, suffix } = this.props.permalinkParts;
const { editedPostName } = this.state;

/* eslint-disable jsx-a11y/no-autofocus */
// Autofocus is allowed here, as this mini-UI is only loaded when the user clicks to open it.
return (
<form
className="editor-post-permalink-editor"
onSubmit={ this.onSavePermalink }
>
<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 this <span> necessary?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep. To use the &lrm; character (for forcing the correct layout in RTL languages), we should be able to place it next to each chunk of text, but I couldn't find a combination that worked. Placing it all inside a <span> with the &lrm; at the end works.

<span className="editor-post-permalink-editor__prefix">
{ prefix }
</span>
<input
className="editor-post-permalink-editor__edit"
aria-label={ __( 'Edit post permalink' ) }
value={ editedPostName }
onChange={ ( event ) => this.setState( { editedPostName: event.target.value } ) }
required
autoFocus
/>
<span className="editor-post-permalink-editor__suffix">
{ suffix }
</span>
&lrm;
</span>
<Button
className="editor-post-permalink-editor__save"
isLarge
onClick={ this.onSavePermalink }
>
{ __( 'OK' ) }
</Button>
</form>
);
/* eslint-enable jsx-a11y/no-autofocus */
}
}

export default compose( [
withSelect( ( select ) => {
const { getPermalinkParts } = select( 'core/editor' );
return {
permalinkParts: getPermalinkParts(),
};
} ),
withDispatch( ( dispatch ) => {
const { editPost } = dispatch( 'core/editor' );
return { editPost };
} ),
] )( PostPermalinkEditor );

137 changes: 96 additions & 41 deletions editor/components/post-permalink/index.js
Original file line number Diff line number Diff line change
@@ -1,79 +1,134 @@
/**
* External dependencies
*/
import { connect } from 'react-redux';

/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
import { withDispatch, withSelect } from '@wordpress/data';
import { Component, compose } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { Dashicon, ClipboardButton, Button } from '@wordpress/components';
import { Dashicon, Button, ClipboardButton, Tooltip } from '@wordpress/components';

/**
* Internal Dependencies
*/
import './style.scss';
import { isEditedPostNew, getEditedPostAttribute } from '../../store/selectors';
import PostPermalinkEditor from './editor.js';
import { getWPAdminURL } from '../../utils/url';

class PostPermalink extends Component {
constructor() {
super( ...arguments );

this.addVisibilityCheck = this.addVisibilityCheck.bind( this );
this.onVisibilityChange = this.onVisibilityChange.bind( this );

this.state = {
showCopyConfirmation: false,
iconClass: '',
isEditingPermalink: false,
};
this.onCopy = this.onCopy.bind( this );
this.onFinishCopy = this.onFinishCopy.bind( this );
}

componentWillUnmount() {
clearTimeout( this.dismissCopyConfirmation );
addVisibilityCheck() {
window.addEventListener( 'visibilitychange', this.onVisibilityChange );
}

onVisibilityChange() {
const { isEditable, refreshPost } = this.props;
// If the user just returned after having clicked the "Change Permalinks" button,
// fetch a new copy of the post from the server, just in case they enabled permalinks.
if ( ! isEditable && 'visible' === document.visibilityState ) {
refreshPost();
}
}

onCopy() {
this.setState( {
showCopyConfirmation: true,
} );
componentDidUpdate( prevProps, prevState ) {
// If we've just stopped editing the permalink, focus on the new permalink.
if ( prevState.isEditingPermalink && ! this.state.isEditingPermalink ) {
this.permalinkButton.focus();
}
}

onFinishCopy() {
this.setState( {
showCopyConfirmation: false,
} );
componentWillUnmount() {
window.removeEventListener( 'visibilitychange', this.addVisibilityCheck );
}

render() {
const { isNew, link } = this.props;
if ( isNew || ! link ) {
const { isNew, previewLink, isEditable, samplePermalink } = this.props;
const { iconClass, isEditingPermalink } = this.state;

if ( isNew || ! previewLink ) {
return null;
}

return (
<div className="editor-post-permalink">
<Dashicon icon="admin-links" />
<Tooltip text={ __( 'Copy the permalink to your clipboard' ) }>
Copy link
Member

Choose a reason for hiding this comment

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

This <Tooltip> wraps its children with a silly <span> which is causing the icon to not be vertically aligned:

screen shot 2018-04-12 at 11 34 43

Copy link
Member Author

Choose a reason for hiding this comment

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

👍🏻

<ClipboardButton
className="editor-post-permalink__copy"
text={ samplePermalink }
onCopy={ () => this.setState( { iconClass: 'is-copied' } ) }
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we don't need iconClass as state. Couldn't this just be a :focus style for the copy button?

Copy link
Member Author

Choose a reason for hiding this comment

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

This was the behaviour that @karmatosed chose, we can revisit it later.

Copy link
Member

@noisysocks noisysocks Apr 12, 2018

Choose a reason for hiding this comment

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

I think what @aduth means is that we maybe can replace this CSS:

.editor-post-permalink__copy .is-copied {
	opacity: 0.3;
}

With:

.editor-post-permalink__copy::focus .dashicon {
	opacity: 0.3;
}

And then remove iconClass from component state. Less state is more good.

Copy link
Member Author

Choose a reason for hiding this comment

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

:focus only applies the style while the button has the focus: @karmatosed chose to have it applied from when the copy is triggered, until page reload. Sadly, there isn't a pseudo class that matches this behaviour.

>
<Dashicon icon="admin-links" className={ iconClass } />
</ClipboardButton>
</Tooltip>

<span className="editor-post-permalink__label">{ __( 'Permalink:' ) }</span>
<Button className="editor-post-permalink__link" href={ link } target="_blank">
{ decodeURI( link ) }
</Button>
<ClipboardButton
className="button"
text={ link }
onCopy={ this.onCopy }
onFinishCopy={ this.onFinishCopy }
>
{ this.state.showCopyConfirmation ? __( 'Copied!' ) : __( 'Copy' ) }
</ClipboardButton>

{ ! isEditingPermalink &&
<Button
className="editor-post-permalink__link"
href={ previewLink }
target="_blank"
ref={ ( permalinkButton ) => this.permalinkButton = permalinkButton }
>
{ decodeURI( samplePermalink ) }
&lrm;
</Button>
}

{ isEditingPermalink &&
<PostPermalinkEditor
onSave={ () => this.setState( { isEditingPermalink: false } ) }
/>
}

{ isEditable && ! isEditingPermalink &&
<Button
className="editor-post-permalink__edit"
isLarge
onClick={ () => this.setState( { isEditingPermalink: true } ) }
>
{ __( 'Edit' ) }
</Button>
}

{ ! isEditable &&
<Button
className="editor-post-permalink__change"
isLarge
href={ getWPAdminURL( 'options-permalink.php' ) }
onClick={ this.addVisibilityCheck }
target="_blank"
>
{ __( 'Change Permalinks' ) }
</Button>
}
</div>
);
}
}

export default connect(
( state ) => {
export default compose( [
withSelect( ( select ) => {
const { isEditedPostNew, isPermalinkEditable, getEditedPostPreviewLink, getPermalink } = select( 'core/editor' );
return {
isNew: isEditedPostNew( state ),
link: getEditedPostAttribute( state, 'link' ),
isNew: isEditedPostNew(),
previewLink: getEditedPostPreviewLink(),
isEditable: isPermalinkEditable(),
samplePermalink: getPermalink(),
};
}
)( PostPermalink );
} ),
withDispatch( ( dispatch ) => {
const { refreshPost } = dispatch( 'core/editor' );
return { refreshPost };
} ),
] )( PostPermalink );

41 changes: 40 additions & 1 deletion editor/components/post-permalink/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@
}
}

.editor-post-permalink__copy {
margin-top: 4px;
}

.editor-post-permalink__copy .is-copied {
opacity: 0.3;
}

.editor-post-permalink__label {
margin: 0 10px;
}
Expand All @@ -31,6 +39,37 @@
white-space: nowrap;

&:after {
@include long-content-fade( $size: 20% );
@include long-content-fade( $size: 20%, $edge: 1px );
}
}

.editor-post-permalink-editor {
width: 100%;
min-width: 20%;
display: inline-flex;
align-items: center;

// Higher specificity required to override core margin styles
.editor-post-permalink-editor__save {
margin-left: auto;
}
}

.editor-post-permalink-editor__prefix {
color: $dark-gray-300;
min-width: 20%;
overflow: hidden;
position: relative;
white-space: nowrap;
text-overflow: ellipsis;
}

.editor-post-permalink-editor__edit {
min-width: 20%;
margin: 0 5px;
}

.editor-post-permalink-editor__suffix {
color: $dark-gray-300;
margin-right: 10px;
}
8 changes: 6 additions & 2 deletions editor/components/post-title/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
import Textarea from 'react-autosize-textarea';
import classnames from 'classnames';
import { get } from 'lodash';

/**
* WordPress dependencies
Expand Down Expand Up @@ -87,15 +88,14 @@ class PostTitle extends Component {
}

render() {
const { title, placeholder, instanceId } = this.props;
const { title, placeholder, instanceId, isPostTypeViewable } = this.props;
const { isSelected } = this.state;
const className = classnames( 'editor-post-title', { 'is-selected': isSelected } );
const decodedPlaceholder = decodeEntities( placeholder );

return (
<PostTypeSupportCheck supportKeys="title">
<div className={ className }>
{ isSelected && <PostPermalink /> }
<KeyboardShortcuts
shortcuts={ {
'mod+z': this.redirectHistory,
Expand All @@ -116,6 +116,7 @@ class PostTitle extends Component {
onKeyPress={ this.onUnselect }
/>
</KeyboardShortcuts>
{ isSelected && isPostTypeViewable && <PostPermalink /> }
</div>
</PostTypeSupportCheck>
);
Expand All @@ -124,9 +125,12 @@ class PostTitle extends Component {

const applyWithSelect = withSelect( ( select ) => {
const { getEditedPostAttribute } = select( 'core/editor' );
const { getPostType } = select( 'core' );
const postType = getPostType( getEditedPostAttribute( 'type' ) );

return {
title: getEditedPostAttribute( 'title' ),
isPostTypeViewable: get( postType, [ 'viewable' ], false ),
};
} );

Expand Down
6 changes: 6 additions & 0 deletions editor/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,12 @@ export function savePost() {
};
}

export function refreshPost() {
return {
type: 'REFRESH_POST',
};
}

export function trashPost( postId, postType ) {
return {
type: 'TRASH_POST',
Expand Down
Loading