Skip to content

Commit

Permalink
Make Permalinks editable (#3418)
Browse files Browse the repository at this point in the history
Note: This is a first pass at getting this feature working, it will be iterated on in subsequent PRs.
  • Loading branch information
pento authored and mcsf committed Jan 25, 2018
1 parent 1d57d2d commit 8963d49
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 19 deletions.
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' );

0 comments on commit 8963d49

Please sign in to comment.