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

Make Permalinks editable #3418

Merged
merged 22 commits into from
Jan 24, 2018
Merged
Show file tree
Hide file tree
Changes from all 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
181 changes: 163 additions & 18 deletions editor/components/post-permalink/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,60 +9,197 @@ import { connect } from 'react-redux';
import { Component } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { Dashicon, ClipboardButton, Button } from '@wordpress/components';
import { addQueryArgs } from '@wordpress/url';

/**
* Internal Dependencies
*/
import './style.scss';
import { isEditedPostNew, getEditedPostAttribute } from '../../store/selectors';
import {
isEditedPostNew,
isCurrentPostPublished,
getEditedPostAttribute,
} from '../../store/selectors';
import { editPost } from '../../store/actions';

/**
* Constants
*/
const REGEXP_NEWLINES = /[\r\n]+/g;

class PostPermalink extends Component {
constructor() {
super( ...arguments );
this.state = {
showCopyConfirmation: false,
editingSlug: false,
permalink: '',
};
this.getSlug = this.getSlug.bind( this );
this.onChangePermalink = this.onChangePermalink.bind( this );
this.onEditPermalink = this.onEditPermalink.bind( this );
this.onSavePermalink = this.onSavePermalink.bind( this );
this.onCopy = this.onCopy.bind( this );
this.onFinishCopy = this.onFinishCopy.bind( this );
}

/**
* Returns a permalink for a given post slug.
*
* @param {string} slug The post slug to insert in into the permalink.
*
* @returns {string} The full permalink.
*/
getPermalink( slug ) {
let permalink = this.props.link;
const samplePermalink = this.props.samplePermalink;
if ( samplePermalink ) {
permalink = samplePermalink[ 0 ].replace( '%postname%', slug || samplePermalink[ 1 ] );
}

return permalink;
}

/**
* Returns the slug for the current post.
*
* @returns {string} The slug.
*/
getSlug() {
const { actualSlug, isPublished, samplePermalink } = this.props;

if ( isPublished ) {
return actualSlug;
}

if ( samplePermalink ) {
return samplePermalink[ 1 ];
}

return '';
}

componentDidMount() {
this.setState( {
permalink: this.getPermalink(),
slug: this.getSlug(),
} );
}

componentWillUnmount() {
clearTimeout( this.dismissCopyConfirmation );
}

onCopy() {
componentWillReceiveProps() {
const slug = this.getSlug();
this.setState( {
showCopyConfirmation: true,
permalink: this.getPermalink( slug ),
slug: slug,
} );
}

/**
* Event handler for the slug input field being changed.
*
* @param {Object} event Change event
*/
onChangePermalink( event ) {
this.setState( { slug: event.target.value } );
}

/**
* Event handler for the Edit button being clicked.
*/
onEditPermalink() {
this.setState( { editingSlug: true } );
}

/**
* Event handler for the slug being saved.
*/
onSavePermalink() {
this.setState( {
editingSlug: false,
permalink: this.getPermalink( this.state.slug ),
} );
const newSlug = this.state.slug.replace( REGEXP_NEWLINES, ' ' );
this.props.onUpdate( newSlug );
}

onFinishCopy() {
/**
* Event handler for the copy button to show feedback.
*/
onCopy() {
this.setState( {
showCopyConfirmation: false,
showCopyConfirmation: true,
} );

clearTimeout( this.dismissCopyConfirmation );
this.dismissCopyConfirmation = setTimeout( () => {
this.setState( {
showCopyConfirmation: false,
} );
}, 4000 );
}

render() {
const { isNew, link } = this.props;
const { editingSlug, permalink, slug } = this.state;
if ( isNew || ! link ) {
return null;
}
const prefix = permalink.replace( /[^/]+\/?$/, '' );

return (
<div className="editor-post-permalink">
<Dashicon icon="admin-links" />
<span className="editor-post-permalink__label">{ __( 'Permalink:' ) }</span>
<Button className="editor-post-permalink__link" href={ link } target="_blank">
{ link }
</Button>
<ClipboardButton
className="button"
text={ link }
onCopy={ this.onCopy }
onFinishCopy={ this.onFinishCopy }
>
{ this.state.showCopyConfirmation ? __( 'Copied!' ) : __( 'Copy' ) }
</ClipboardButton>
{ ! editingSlug &&
<Button
className="editor-post-permalink__link"
href={ addQueryArgs( this.props.link, { preview: true } ) }
target="_blank"
>
{ permalink }
</Button>
}
{ editingSlug &&
<form className="editor-post-permalink__slug-form" onSubmit={ this.onSavePermalink }>
<span className="editor-post-permalink__prefix">
{ prefix }
</span>
<input
type="text"
className="editor-post-permalink__slug-input"
defaultValue={ slug }
onChange={ this.onChangePermalink }
required
/>
/
<Button
className="editor-post-permalink__save"
onClick={ this.onSavePermalink }
isLarge
>
{ __( 'Ok' ) }
</Button>
</form>
}
{ ! editingSlug &&
<ClipboardButton
className="button"
text={ link }
onCopy={ this.onCopy }
>
{ this.state.showCopyConfirmation ? __( 'Copied!' ) : __( 'Copy' ) }
</ClipboardButton>
}
{ ! editingSlug &&
<Button
className="editor-post-permalink__edit button"
onClick={ this.onEditPermalink }
>
{ __( 'Edit' ) }
</Button>
}
</div>
);
}
Expand All @@ -72,8 +209,16 @@ export default connect(
( state ) => {
return {
isNew: isEditedPostNew( state ),
isPublished: isCurrentPostPublished( state ),
link: getEditedPostAttribute( state, 'link' ),
samplePermalink: getEditedPostAttribute( state, 'sample_permalink' ),
actualSlug: getEditedPostAttribute( state, 'slug' ),
};
},
{
onUpdate( slug ) {
return editPost( { slug } );
},
}
)( PostPermalink );

15 changes: 15 additions & 0 deletions editor/components/post-permalink/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,18 @@
@include long-content-fade( $size: 20% );
}
}

.editor-post-permalink__slug {
color: $dark-gray-300;
font-weight: bold;
padding: 0;
}

.editor-post-permalink__slug-form {
display: inline;
width: 100%;
}

.editor-post-permalink__save {
float: right;
}
10 changes: 9 additions & 1 deletion editor/components/post-title/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { __ } from '@wordpress/i18n';
import { Component, compose } from '@wordpress/element';
import { keycodes } from '@wordpress/utils';
import { createBlock, getDefaultBlockName } from '@wordpress/blocks';
import { withContext } from '@wordpress/components';
import { Button, Dashicon, withContext } from '@wordpress/components';

/**
* Internal dependencies
Expand Down Expand Up @@ -110,6 +110,14 @@ class PostTitle extends Component {
className={ className }
tabIndex={ -1 /* Necessary for Firefox to include relatedTarget in blur event */ }
>
{ ! isSelected &&
<Button
className="editor-post-title__permalink-button"
onClick={ this.onSelect }
>
<Dashicon icon="admin-links" />
</Button>
}
{ isSelected && <PostPermalink /> }
<div>
<Textarea
Expand Down
13 changes: 13 additions & 0 deletions editor/components/post-title/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@
position: relative;
padding: 5px 0;

.editor-post-title__permalink-button {
position: absolute;
top: 35px;
left: 0px;
}

.editor-post-permalink {
position: absolute;
top: -34px;
left: 0;
right: 0;
}

div {
border: 1px solid transparent;
font-size: $editor-font-size;
Expand Down
38 changes: 38 additions & 0 deletions lib/compat.php
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,41 @@ function gutenberg_register_rest_api_post_type_capabilities() {
);
}
add_action( 'rest_api_init', 'gutenberg_register_rest_api_post_type_capabilities' );

/**
* Add a sample permalink to draft posts in the post REST API response.
*
* @param WP_REST_Response $response WP REST API response of a post.
* @param WP_Post $post The post being returned.
* @param WP_REST_Request $request WP REST API request.
* @return WP_REST_Response Response containing the sample_permalink, where appropriate.
*/
function gutenberg_add_sample_permalink_to_draft_posts( $response, $post, $request ) {
if ( empty( $response->data['status'] ) || 'draft' !== $response->data['status'] ) {
return $response;
}

if ( 'edit' !== $request['context'] ) {
return $response;
}

if ( ! function_exists( 'get_sample_permalink' ) ) {
require_once ABSPATH . '/wp-admin/includes/post.php';
}

$response->data['sample_permalink'] = get_sample_permalink( $post );

return $response;
}

/**
* Whenever a post type is registered, ensure we're hooked into it's WP REST API response.
*
* @param string $post_type The newly registered post type.
* @return string That same post type.
*/
function gutenberg_register_sample_permalink_function( $post_type ) {
add_filter( "rest_prepare_{$post_type}", 'gutenberg_add_sample_permalink_to_draft_posts', 10, 3 );
return $post_type;
}
add_filter( 'registered_post_type', 'gutenberg_register_sample_permalink_function' );