From 0efaf5ddc5b7e6b11aae280abac3d81746b378ae Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 17 Feb 2017 20:53:57 +0000 Subject: [PATCH] REST API: JavaScript client - improve route discovery for custom namespaces. Fix parsing of custom namespace routes. Transform class names, removing dashes and capitalizing each word/route part so a route path of `widgets/recent-posts` becomes a collection with the name `WidgetsRecentPosts`. Correct parent route part when routes are longer than expected, reversing parse direction. Props westonruter, jazbek. Fixes #39561. git-svn-id: https://develop.svn.wordpress.org/trunk@40074 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/js/wp-api.js | 90 +++++++++++++++++++++++++++--------- 1 file changed, 67 insertions(+), 23 deletions(-) diff --git a/src/wp-includes/js/wp-api.js b/src/wp-includes/js/wp-api.js index da16e3c491..0ce6bbce09 100644 --- a/src/wp-includes/js/wp-api.js +++ b/src/wp-includes/js/wp-api.js @@ -119,21 +119,52 @@ return str.charAt( 0 ).toUpperCase() + str.slice( 1 ); }; + /** + * Helper function that capitalizes the first word and camel cases any words starting + * after dashes, removing the dashes. + */ + wp.api.utils.capitalizeAndCamelCaseDashes = function( str ) { + if ( _.isUndefined( str ) ) { + return str; + } + str = wp.api.utils.capitalize( str ); + + return wp.api.utils.camelCaseDashes( str ); + }; + + /** + * Helper function to camel case the letter after dashes, removing the dashes. + */ + wp.api.utils.camelCaseDashes = function( str ) { + return str.replace( /-([a-z])/g, function( g ) { + return g[ 1 ].toUpperCase(); + } ); + }; + /** * Extract a route part based on negative index. * - * @param {string} route The endpoint route. - * @param {int} part The number of parts from the end of the route to retrieve. Default 1. - * Example route `/a/b/c`: part 1 is `c`, part 2 is `b`, part 3 is `a`. + * @param {string} route The endpoint route. + * @param {int} part The number of parts from the end of the route to retrieve. Default 1. + * Example route `/a/b/c`: part 1 is `c`, part 2 is `b`, part 3 is `a`. + * @param {string} [versionString] Version string, defaults to `wp.api.versionString`. + * @param {boolean} [reverse] Whether to reverse the order when extracting the route part. Optional, default false. */ - wp.api.utils.extractRoutePart = function( route, part ) { + wp.api.utils.extractRoutePart = function( route, part, versionString, reverse ) { var routeParts; - part = part || 1; + part = part || 1; + versionString = versionString || wp.api.versionString; // Remove versions string from route to avoid returning it. - route = route.replace( wp.api.versionString, '' ); - routeParts = route.split( '/' ).reverse(); + if ( 0 === route.indexOf( '/' + versionString ) ) { + route = route.substr( versionString.length + 1 ); + } + + routeParts = route.split( '/' ); + if ( reverse ) { + routeParts = routeParts.reverse(); + } if ( _.isUndefined( routeParts[ --part ] ) ) { return ''; } @@ -1126,9 +1157,14 @@ // Extract the name and any parent from the route. var modelClassName, - routeName = wp.api.utils.extractRoutePart( modelRoute.index, 2 ), - parentName = wp.api.utils.extractRoutePart( modelRoute.index, 4 ), - routeEnd = wp.api.utils.extractRoutePart( modelRoute.index, 1 ); + routeName = wp.api.utils.extractRoutePart( modelRoute.index, 2, routeModel.get( 'versionString' ), true ), + parentName = wp.api.utils.extractRoutePart( modelRoute.index, 1, routeModel.get( 'versionString' ), false ), + routeEnd = wp.api.utils.extractRoutePart( modelRoute.index, 1, routeModel.get( 'versionString' ), true ); + + // Clear the parent part of the rouite if its actually the version string. + if ( parentName === routeModel.get( 'versionString' ) ) { + parentName = ''; + } // Handle the special case of the 'me' route. if ( 'me' === routeEnd ) { @@ -1137,18 +1173,21 @@ // If the model has a parent in its route, add that to its class name. if ( '' !== parentName && parentName !== routeName ) { - modelClassName = wp.api.utils.capitalize( parentName ) + wp.api.utils.capitalize( routeName ); + modelClassName = wp.api.utils.capitalizeAndCamelCaseDashes( parentName ) + wp.api.utils.capitalizeAndCamelCaseDashes( routeName ); modelClassName = mapping.models[ modelClassName ] || modelClassName; loadingObjects.models[ modelClassName ] = wp.api.WPApiBaseModel.extend( { // Return a constructed url based on the parent and id. url: function() { - var url = routeModel.get( 'apiRoot' ) + routeModel.get( 'versionString' ) + - parentName + '/' + + var url = + routeModel.get( 'apiRoot' ) + + routeModel.get( 'versionString' ) + + parentName + '/' + ( ( _.isUndefined( this.get( 'parent' ) ) || 0 === this.get( 'parent' ) ) ? - this.get( 'parent_post' ) : - this.get( 'parent' ) ) + '/' + - routeName; + ( _.isUndefined( this.get( 'parent_post' ) ) ? '' : this.get( 'parent_post' ) + '/' ) : + this.get( 'parent' ) + '/' ) + + routeName; + if ( ! _.isUndefined( this.get( 'id' ) ) ) { url += '/' + this.get( 'id' ); } @@ -1164,7 +1203,8 @@ // Include the array of route methods for easy reference. methods: modelRoute.route.methods, - initialize: function() { + initialize: function( attributes, options ) { + wp.api.WPApiBaseModel.prototype.initialize.call( this, attributes, options ); /** * Posts and pages support trashing, other types don't support a trash @@ -1184,7 +1224,7 @@ } else { // This is a model without a parent in its route - modelClassName = wp.api.utils.capitalize( routeName ); + modelClassName = wp.api.utils.capitalizeAndCamelCaseDashes( routeName ); modelClassName = mapping.models[ modelClassName ] || modelClassName; loadingObjects.models[ modelClassName ] = wp.api.WPApiBaseModel.extend( { @@ -1212,7 +1252,11 @@ } // Add defaults to the new model, pulled form the endpoint. - wp.api.utils.decorateFromRoute( modelRoute.route.endpoints, loadingObjects.models[ modelClassName ] ); + wp.api.utils.decorateFromRoute( + modelRoute.route.endpoints, + loadingObjects.models[ modelClassName ], + routeModel.get( 'versionString' ) + ); } ); @@ -1226,12 +1270,12 @@ // Extract the name and any parent from the route. var collectionClassName, modelClassName, routeName = collectionRoute.index.slice( collectionRoute.index.lastIndexOf( '/' ) + 1 ), - parentName = wp.api.utils.extractRoutePart( collectionRoute.index, 3 ); + parentName = wp.api.utils.extractRoutePart( collectionRoute.index, 1, routeModel.get( 'versionString' ), false ); // If the collection has a parent in its route, add that to its class name. - if ( '' !== parentName && parentName !== routeName ) { + if ( '' !== parentName && parentName !== routeName && routeModel.get( 'versionString' ) !== parentName ) { - collectionClassName = wp.api.utils.capitalize( parentName ) + wp.api.utils.capitalize( routeName ); + collectionClassName = wp.api.utils.capitalizeAndCamelCaseDashes( parentName ) + wp.api.utils.capitalizeAndCamelCaseDashes( routeName ); modelClassName = mapping.models[ collectionClassName ] || collectionClassName; collectionClassName = mapping.collections[ collectionClassName ] || collectionClassName; loadingObjects.collections[ collectionClassName ] = wp.api.WPApiBaseCollection.extend( { @@ -1260,7 +1304,7 @@ } else { // This is a collection without a parent in its route. - collectionClassName = wp.api.utils.capitalize( routeName ); + collectionClassName = wp.api.utils.capitalizeAndCamelCaseDashes( routeName ); modelClassName = mapping.models[ collectionClassName ] || collectionClassName; collectionClassName = mapping.collections[ collectionClassName ] || collectionClassName; loadingObjects.collections[ collectionClassName ] = wp.api.WPApiBaseCollection.extend( {