Skip to content

Commit

Permalink
Editor: Expose custom class name hook for mobile (#8072)
Browse files Browse the repository at this point in the history
* Editor: Expose custom class name hook for mobile

* Use overrides in subfolders rathern in the entry point file

* Element: Reorganize code to make it fit better React Native needs

* Editor: Expose another custom class name hook needed for mobile that were not exposed in the previous commit

* Fix lint
  • Loading branch information
gziolo authored Jul 24, 2018
1 parent a1bc317 commit b6ca3c1
Show file tree
Hide file tree
Showing 14 changed files with 350 additions and 287 deletions.
117 changes: 117 additions & 0 deletions editor/hooks/custom-class-name.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/**
* External dependencies
*/
import { assign, difference, compact } from 'lodash';
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import { addFilter } from '@wordpress/hooks';
import {
hasBlockSupport,
getSaveContent,
parseWithAttributeSchema,
} from '@wordpress/blocks';

/**
* Filters registered block settings, extending attributes with anchor using ID
* of the first node.
*
* @param {Object} settings Original block settings.
*
* @return {Object} Filtered block settings.
*/
export function addAttribute( settings ) {
if ( hasBlockSupport( settings, 'customClassName', true ) ) {
// Use Lodash's assign to gracefully handle if attributes are undefined
settings.attributes = assign( settings.attributes, {
className: {
type: 'string',
},
} );
}

return settings;
}

/**
* Override props assigned to save component to inject anchor ID, if block
* supports anchor. This is only applied if the block's save result is an
* element and not a markup string.
*
* @param {Object} extraProps Additional props applied to save element.
* @param {Object} blockType Block type.
* @param {Object} attributes Current block attributes.
*
* @return {Object} Filtered props applied to save element.
*/
export function addSaveProps( extraProps, blockType, attributes ) {
if ( hasBlockSupport( blockType, 'customClassName', true ) && attributes.className ) {
extraProps.className = classnames( extraProps.className, attributes.className );
}

return extraProps;
}

/**
* Given an HTML string, returns an array of class names assigned to the root
* element in the markup.
*
* @param {string} innerHTML Markup string from which to extract classes.
*
* @return {string[]} Array of class names assigned to the root element.
*/
export function getHTMLRootElementClasses( innerHTML ) {
innerHTML = `<div data-custom-class-name>${ innerHTML }</div>`;

const parsed = parseWithAttributeSchema( innerHTML, {
type: 'string',
source: 'attribute',
selector: '[data-custom-class-name] > *',
attribute: 'class',
} );

return parsed ? parsed.trim().split( /\s+/ ) : [];
}

/**
* Given a parsed set of block attributes, if the block supports custom class
* names and an unknown class (per the block's serialization behavior) is
* found, the unknown classes are treated as custom classes. This prevents the
* block from being considered as invalid.
*
* @param {Object} blockAttributes Original block attributes.
* @param {Object} blockType Block type settings.
* @param {string} innerHTML Original block markup.
*
* @return {Object} Filtered block attributes.
*/
export function addParsedDifference( blockAttributes, blockType, innerHTML ) {
if ( hasBlockSupport( blockType, 'customClassName', true ) ) {
// To determine difference, serialize block given the known set of
// attributes. If there are classes which are mismatched with the
// incoming HTML of the block, add to filtered result.
const serialized = getSaveContent( blockType, blockAttributes );
const classes = getHTMLRootElementClasses( serialized );
const parsedClasses = getHTMLRootElementClasses( innerHTML );
const customClasses = difference( parsedClasses, classes );

const filteredClassName = compact( [
blockAttributes.className,
...customClasses,
] ).join( ' ' );

if ( filteredClassName ) {
blockAttributes.className = filteredClassName;
} else {
delete blockAttributes.className;
}
}

return blockAttributes;
}

addFilter( 'blocks.registerBlockType', 'core/custom-class-name/attribute', addAttribute );
addFilter( 'blocks.getSaveContent.extraProps', 'core/custom-class-name/save-props', addSaveProps );
addFilter( 'blocks.getBlockAttributes', 'core/custom-class-name/addParsedDifference', addParsedDifference );
5 changes: 5 additions & 0 deletions editor/hooks/index.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* Internal dependencies
*/
import './custom-class-name';
import './generated-class-name';
1 change: 0 additions & 1 deletion editor/index.native.js

This file was deleted.

Empty file added editor/store/index.native.js
Empty file.
Empty file added editor/utils/index.native.js
Empty file.
4 changes: 4 additions & 0 deletions packages/blocks/src/api/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ export {
} from './factory';
export {
default as parse,
parseWithAttributeSchema,
} from './parser';
export {
default as serialize,
getBlockContent,
getBlockDefaultClassName,
getSaveContent,
} from './serializer';
export {
registerBlockType,
getBlockType,
hasBlockSupport,
} from './registration';
200 changes: 5 additions & 195 deletions packages/element/src/index.js
Original file line number Diff line number Diff line change
@@ -1,197 +1,7 @@
/**
* External dependencies
*/
import {
createElement,
createContext,
createRef,
forwardRef,
Component,
cloneElement,
Children,
Fragment,
isValidElement,
StrictMode,
} from 'react';
import { render, findDOMNode, createPortal, unmountComponentAtNode } from 'react-dom';
import { isString } from 'lodash';
export * from './react';
export * from './react-platform';
export { default as renderToString } from './serialize';
export { default as RawHTML } from './raw-html';

/**
* Internal dependencies
*/
import serialize from './serialize';
import { RawHTML } from './index-common';
// deprecated
export * from './deprecated';

/**
* Returns a new element of given type. Type can be either a string tag name or
* another function which itself returns an element.
*
* @param {?(string|Function)} type Tag name or element creator
* @param {Object} props Element properties, either attribute
* set to apply to DOM node or values to
* pass through to element creator
* @param {...WPElement} children Descendant elements
*
* @return {WPElement} Element.
*/
export { createElement };

/**
* Returns an object tracking a reference to a rendered element via its
* `current` property as either a DOMElement or Element, dependent upon the
* type of element rendered with the ref attribute.
*
* @return {Object} Ref object.
*/
export { createRef };

/**
* Component enhancer used to enable passing a ref to its wrapped component.
* Pass a function argument which receives `props` and `ref` as its arguments,
* returning an element using the forwarded ref. The return value is a new
* component which forwards its ref.
*
* @param {Function} forwarder Function passed `props` and `ref`, expected to
* return an element.
*
* @return {WPComponent} Enhanced component.
*/
export { forwardRef };

/**
* Renders a given element into the target DOM node.
*
* @param {WPElement} element Element to render
* @param {Element} target DOM node into which element should be rendered
*/
export { render };

/**
* Removes any mounted element from the target DOM node.
*
* @param {Element} target DOM node in which element is to be removed
*/
export { unmountComponentAtNode };

/**
* A base class to create WordPress Components (Refs, state and lifecycle hooks)
*/
export { Component };

/**
* Creates a copy of an element with extended props.
*
* @param {WPElement} element Element
* @param {?Object} props Props to apply to cloned element
*
* @return {WPElement} Cloned element.
*/
export { cloneElement };

/**
* Finds the dom node of a React component
*
* @param {Component} component component's instance
* @param {Element} target DOM node into which element should be rendered
*/
export { findDOMNode };

export { Children };

export { StrictMode };

/**
* A component which renders its children without any wrapping element.
*/
export { Fragment };

/**
* Creates a context object containing two components: a provider and consumer.
*
* @param {Object} defaultValue A default data stored in the context.
*
* @return {Object} Context object.
*/
export { createContext };

/**
* Checks if an object is a valid WPElement
*
* @param {Object} objectToCheck The object to be checked.
*
* @return {boolean} true if objectToTest is a valid WPElement and false otherwise.
*/
export { isValidElement };

/**
* Creates a portal into which a component can be rendered.
*
* @see https://github.com/facebook/react/issues/10309#issuecomment-318433235
*
* @param {Component} component Component
* @param {Element} target DOM node into which element should be rendered
*/
export { createPortal };

/**
* Renders a given element into a string.
*
* @param {WPElement} element Element to render
*
* @return {string} HTML.
*/
export { serialize as renderToString };

/**
* Concatenate two or more React children objects.
*
* @param {...?Object} childrenArguments Array of children arguments (array of arrays/strings/objects) to concatenate.
*
* @return {Array} The concatenated value.
*/
export function concatChildren( ...childrenArguments ) {
return childrenArguments.reduce( ( memo, children, i ) => {
Children.forEach( children, ( child, j ) => {
if ( child && 'string' !== typeof child ) {
child = cloneElement( child, {
key: [ i, j ].join(),
} );
}

memo.push( child );
} );

return memo;
}, [] );
}

/**
* Switches the nodeName of all the elements in the children object.
*
* @param {?Object} children Children object.
* @param {string} nodeName Node name.
*
* @return {?Object} The updated children object.
*/
export function switchChildrenNodeName( children, nodeName ) {
return children && Children.map( children, ( elt, index ) => {
if ( isString( elt ) ) {
return createElement( nodeName, { key: index }, elt );
}
const { children: childrenProp, ...props } = elt.props;
return createElement( nodeName, { key: index, ...props }, childrenProp );
} );
}

/**
* Component used as equivalent of Fragment with unescaped HTML, in cases where
* it is desirable to render dangerous HTML without needing a wrapper element.
* To preserve additional props, a `div` wrapper _will_ be created if any props
* aside from `children` are passed.
*
* @param {string} props.children HTML to render.
*
* @return {WPElement} Dangerously-rendering element.
*/
export { RawHTML };
53 changes: 0 additions & 53 deletions packages/element/src/index.native.js

This file was deleted.

Loading

0 comments on commit b6ca3c1

Please sign in to comment.