diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index a42e86fa94037..6013bc2d79d07 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { filter, pick, map, get } from 'lodash'; +import { filter, map } from 'lodash'; /** * WordPress dependencies @@ -31,6 +31,7 @@ import { __, sprintf } from '@wordpress/i18n'; */ import GalleryImage from './gallery-image'; import icon from './icon'; +import { defaultColumnsNumber, pickRelevantMediaFiles } from './shared'; const MAX_COLUMNS = 8; const linkOptions = [ @@ -40,16 +41,6 @@ const linkOptions = [ ]; const ALLOWED_MEDIA_TYPES = [ 'image' ]; -export function defaultColumnsNumber( attributes ) { - return Math.min( 3, attributes.images.length ); -} - -export const pickRelevantMediaFiles = ( image ) => { - const imageProps = pick( image, [ 'alt', 'id', 'link', 'caption' ] ); - imageProps.url = get( image, [ 'sizes', 'large', 'url' ] ) || get( image, [ 'media_details', 'sizes', 'large', 'source_url' ] ) || image.url; - return imageProps; -}; - class GalleryEdit extends Component { constructor() { super( ...arguments ); diff --git a/packages/block-library/src/gallery/index.js b/packages/block-library/src/gallery/index.js index 7f8aaa79bb3f4..d6ea28fe051c3 100644 --- a/packages/block-library/src/gallery/index.js +++ b/packages/block-library/src/gallery/index.js @@ -1,38 +1,28 @@ /** * External dependencies */ -import { filter, every, map, some } from 'lodash'; +import { map, some } from 'lodash'; /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { createBlock } from '@wordpress/blocks'; import { RichText } from '@wordpress/block-editor'; -import { mediaUpload } from '@wordpress/editor'; -import { createBlobURL } from '@wordpress/blob'; /** * Internal dependencies */ -import { default as edit, defaultColumnsNumber, pickRelevantMediaFiles } from './edit'; +import edit from './edit'; import icon from './icon'; import metadata from './block.json'; +import save from './save'; +import transforms from './transforms'; +import { defaultColumnsNumber } from './shared'; const { name, attributes: blockAttributes } = metadata; export { metadata, name }; -const parseShortcodeIds = ( ids ) => { - if ( ! ids ) { - return []; - } - - return ids.split( ',' ).map( ( id ) => ( - parseInt( id, 10 ) - ) ); -}; - export const settings = { title: __( 'Gallery' ), description: __( 'Display multiple images in a rich gallery.' ), @@ -41,138 +31,9 @@ export const settings = { supports: { align: true, }, - - transforms: { - from: [ - { - type: 'block', - isMultiBlock: true, - blocks: [ 'core/image' ], - transform: ( attributes ) => { - // Init the align attribute from the first item which may be either the placeholder or an image. - let { align } = attributes[ 0 ]; - // Loop through all the images and check if they have the same align. - align = every( attributes, [ 'align', align ] ) ? align : undefined; - - const validImages = filter( attributes, ( { id, url } ) => id && url ); - - return createBlock( 'core/gallery', { - images: validImages.map( ( { id, url, alt, caption } ) => ( { id, url, alt, caption } ) ), - ids: validImages.map( ( { id } ) => id ), - align, - } ); - }, - }, - { - type: 'shortcode', - tag: 'gallery', - attributes: { - images: { - type: 'array', - shortcode: ( { named: { ids } } ) => { - return parseShortcodeIds( ids ).map( ( id ) => ( { - id, - } ) ); - }, - }, - ids: { - type: 'array', - shortcode: ( { named: { ids } } ) => { - return parseShortcodeIds( ids ); - }, - }, - columns: { - type: 'number', - shortcode: ( { named: { columns = '3' } } ) => { - return parseInt( columns, 10 ); - }, - }, - linkTo: { - type: 'string', - shortcode: ( { named: { link = 'attachment' } } ) => { - return link === 'file' ? 'media' : link; - }, - }, - }, - }, - { - // When created by drag and dropping multiple files on an insertion point - type: 'files', - isMatch( files ) { - return files.length !== 1 && every( files, ( file ) => file.type.indexOf( 'image/' ) === 0 ); - }, - transform( files, onChange ) { - const block = createBlock( 'core/gallery', { - images: files.map( ( file ) => pickRelevantMediaFiles( { - url: createBlobURL( file ), - } ) ), - } ); - mediaUpload( { - filesList: files, - onFileChange: ( images ) => { - const imagesAttr = images.map( - pickRelevantMediaFiles - ); - onChange( block.clientId, { - ids: map( imagesAttr, 'id' ), - images: imagesAttr, - } ); - }, - allowedTypes: [ 'image' ], - } ); - return block; - }, - }, - ], - to: [ - { - type: 'block', - blocks: [ 'core/image' ], - transform: ( { images, align } ) => { - if ( images.length > 0 ) { - return images.map( ( { id, url, alt, caption } ) => createBlock( 'core/image', { id, url, alt, caption, align } ) ); - } - return createBlock( 'core/image', { align } ); - }, - }, - ], - }, - + transforms, edit, - - save( { attributes } ) { - const { images, columns = defaultColumnsNumber( attributes ), imageCrop, linkTo } = attributes; - return ( - - ); - }, - + save, deprecated: [ { attributes: blockAttributes, diff --git a/packages/block-library/src/gallery/save.js b/packages/block-library/src/gallery/save.js new file mode 100644 index 0000000000000..49ff711aa6d63 --- /dev/null +++ b/packages/block-library/src/gallery/save.js @@ -0,0 +1,42 @@ +/** + * WordPress dependencies + */ +import { RichText } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import { defaultColumnsNumber } from './shared'; + +export default function save( { attributes } ) { + const { images, columns = defaultColumnsNumber( attributes ), imageCrop, linkTo } = attributes; + return ( + + ); +} diff --git a/packages/block-library/src/gallery/shared.js b/packages/block-library/src/gallery/shared.js new file mode 100644 index 0000000000000..15affe5c62039 --- /dev/null +++ b/packages/block-library/src/gallery/shared.js @@ -0,0 +1,14 @@ +/** + * External dependencies + */ +import { get, pick } from 'lodash'; + +export function defaultColumnsNumber( attributes ) { + return Math.min( 3, attributes.images.length ); +} + +export const pickRelevantMediaFiles = ( image ) => { + const imageProps = pick( image, [ 'alt', 'id', 'link', 'caption' ] ); + imageProps.url = get( image, [ 'sizes', 'large', 'url' ] ) || get( image, [ 'media_details', 'sizes', 'large', 'source_url' ] ) || image.url; + return imageProps; +}; diff --git a/packages/block-library/src/gallery/transforms.js b/packages/block-library/src/gallery/transforms.js new file mode 100644 index 0000000000000..af8d5d164c10e --- /dev/null +++ b/packages/block-library/src/gallery/transforms.js @@ -0,0 +1,135 @@ +/** + * External dependencies + */ +import { filter, every, map } from 'lodash'; + +/** + * WordPress dependencies + */ +import { createBlock } from '@wordpress/blocks'; +import { mediaUpload } from '@wordpress/editor'; +import { createBlobURL } from '@wordpress/blob'; + +/** + * Internal dependencies + */ +import { pickRelevantMediaFiles } from './shared'; + +const parseShortcodeIds = ( ids ) => { + if ( ! ids ) { + return []; + } + + return ids.split( ',' ).map( ( id ) => ( + parseInt( id, 10 ) + ) ); +}; + +const transforms = { + from: [ + { + type: 'block', + isMultiBlock: true, + blocks: [ 'core/image' ], + transform: ( attributes ) => { + // Init the align attribute from the first item which may be either the placeholder or an image. + let { align } = attributes[ 0 ]; + // Loop through all the images and check if they have the same align. + align = every( attributes, [ 'align', align ] ) ? align : undefined; + + const validImages = filter( attributes, ( { id, url } ) => id && url ); + + return createBlock( 'core/gallery', { + images: validImages.map( ( { id, url, alt, caption } ) => ( { + id, + url, + alt, + caption, + } ) ), + ids: validImages.map( ( { id } ) => id ), + align, + } ); + }, + }, + { + type: 'shortcode', + tag: 'gallery', + attributes: { + images: { + type: 'array', + shortcode: ( { named: { ids } } ) => { + return parseShortcodeIds( ids ).map( ( id ) => ( { + id, + } ) ); + }, + }, + ids: { + type: 'array', + shortcode: ( { named: { ids } } ) => { + return parseShortcodeIds( ids ); + }, + }, + columns: { + type: 'number', + shortcode: ( { named: { columns = '3' } } ) => { + return parseInt( columns, 10 ); + }, + }, + linkTo: { + type: 'string', + shortcode: ( { named: { link = 'attachment' } } ) => { + return link === 'file' ? 'media' : link; + }, + }, + }, + }, + { + // When created by drag and dropping multiple files on an insertion point + type: 'files', + isMatch( files ) { + return files.length !== 1 && every( files, ( file ) => file.type.indexOf( 'image/' ) === 0 ); + }, + transform( files, onChange ) { + const block = createBlock( 'core/gallery', { + images: files.map( ( file ) => pickRelevantMediaFiles( { + url: createBlobURL( file ), + } ) ), + } ); + mediaUpload( { + filesList: files, + onFileChange: ( images ) => { + const imagesAttr = images.map( + pickRelevantMediaFiles, + ); + onChange( block.clientId, { + ids: map( imagesAttr, 'id' ), + images: imagesAttr, + } ); + }, + allowedTypes: [ 'image' ], + } ); + return block; + }, + }, + ], + to: [ + { + type: 'block', + blocks: [ 'core/image' ], + transform: ( { images, align } ) => { + if ( images.length > 0 ) { + return images.map( ( { id, url, alt, caption } ) => createBlock( 'core/image', { + id, + url, + alt, + caption, + align, + } ) ); + } + return createBlock( 'core/image', { align } ); + }, + }, + ], +}; + +export default transforms; diff --git a/packages/block-library/src/heading/index.js b/packages/block-library/src/heading/index.js index 4cbe792e8ec8e..4dd2f16e086ce 100644 --- a/packages/block-library/src/heading/index.js +++ b/packages/block-library/src/heading/index.js @@ -7,11 +7,6 @@ import { omit } from 'lodash'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { - createBlock, - getPhrasingContentSchema, - getBlockAttributes, -} from '@wordpress/blocks'; import { RichText } from '@wordpress/block-editor'; /** @@ -20,22 +15,14 @@ import { RichText } from '@wordpress/block-editor'; import edit from './edit'; import icon from './icon'; import metadata from './block.json'; +import save from './save'; +import transforms from './transforms'; +import { getLevelFromHeadingNodeName } from './shared'; const { name, attributes: schema } = metadata; export { metadata, name }; -/** - * Given a node name string for a heading node, returns its numeric level. - * - * @param {string} nodeName Heading node name. - * - * @return {number} Heading level. - */ -export function getLevelFromHeadingNodeName( nodeName ) { - return Number( nodeName.substr( 1 ) ); -} - const supports = { className: false, anchor: true, @@ -43,71 +30,11 @@ const supports = { export const settings = { title: __( 'Heading' ), - description: __( 'Introduce new sections and organize content to help visitors (and search engines) understand the structure of your content.' ), - icon, - keywords: [ __( 'title' ), __( 'subtitle' ) ], - supports, - - transforms: { - from: [ - { - type: 'block', - blocks: [ 'core/paragraph' ], - transform: ( { content } ) => { - return createBlock( 'core/heading', { - content, - } ); - }, - }, - { - type: 'raw', - selector: 'h1,h2,h3,h4,h5,h6', - schema: { - h1: { children: getPhrasingContentSchema() }, - h2: { children: getPhrasingContentSchema() }, - h3: { children: getPhrasingContentSchema() }, - h4: { children: getPhrasingContentSchema() }, - h5: { children: getPhrasingContentSchema() }, - h6: { children: getPhrasingContentSchema() }, - }, - transform( node ) { - return createBlock( 'core/heading', { - ...getBlockAttributes( - 'core/heading', - node.outerHTML - ), - level: getLevelFromHeadingNodeName( node.nodeName ), - } ); - }, - }, - ...[ 2, 3, 4, 5, 6 ].map( ( level ) => ( { - type: 'prefix', - prefix: Array( level + 1 ).join( '#' ), - transform( content ) { - return createBlock( 'core/heading', { - level, - content, - } ); - }, - } ) ), - ], - to: [ - { - type: 'block', - blocks: [ 'core/paragraph' ], - transform: ( { content } ) => { - return createBlock( 'core/paragraph', { - content, - } ); - }, - }, - ], - }, - + transforms, deprecated: [ { supports, @@ -142,25 +69,11 @@ export const settings = { }, }, ], - merge( attributes, attributesToMerge ) { return { content: ( attributes.content || '' ) + ( attributesToMerge.content || '' ), }; }, - edit, - - save( { attributes } ) { - const { align, level, content } = attributes; - const tagName = 'h' + level; - - return ( - - ); - }, + save, }; diff --git a/packages/block-library/src/heading/save.js b/packages/block-library/src/heading/save.js new file mode 100644 index 0000000000000..522013158ee99 --- /dev/null +++ b/packages/block-library/src/heading/save.js @@ -0,0 +1,17 @@ +/** + * WordPress dependencies + */ +import { RichText } from '@wordpress/block-editor'; + +export default function save( { attributes } ) { + const { align, level, content } = attributes; + const tagName = 'h' + level; + + return ( + + ); +} diff --git a/packages/block-library/src/heading/shared.js b/packages/block-library/src/heading/shared.js new file mode 100644 index 0000000000000..604eaa9adba8f --- /dev/null +++ b/packages/block-library/src/heading/shared.js @@ -0,0 +1,10 @@ +/** + * Given a node name string for a heading node, returns its numeric level. + * + * @param {string} nodeName Heading node name. + * + * @return {number} Heading level. + */ +export function getLevelFromHeadingNodeName( nodeName ) { + return Number( nodeName.substr( 1 ) ); +} diff --git a/packages/block-library/src/heading/test/index.js b/packages/block-library/src/heading/test/shared.js similarity index 80% rename from packages/block-library/src/heading/test/index.js rename to packages/block-library/src/heading/test/shared.js index ffa9d2538674d..e601a3d98b3d7 100644 --- a/packages/block-library/src/heading/test/index.js +++ b/packages/block-library/src/heading/test/shared.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { getLevelFromHeadingNodeName } from '../'; +import { getLevelFromHeadingNodeName } from '../shared'; describe( 'getLevelFromHeadingNodeName()', () => { it( 'should return a numeric value from nodeName', () => { diff --git a/packages/block-library/src/heading/transforms.js b/packages/block-library/src/heading/transforms.js new file mode 100644 index 0000000000000..b043018a400ca --- /dev/null +++ b/packages/block-library/src/heading/transforms.js @@ -0,0 +1,71 @@ +/** + * WordPress dependencies + */ +import { + createBlock, + getPhrasingContentSchema, + getBlockAttributes, +} from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { getLevelFromHeadingNodeName } from './shared'; + +const transforms = { + from: [ + { + type: 'block', + blocks: [ 'core/paragraph' ], + transform: ( { content } ) => { + return createBlock( 'core/heading', { + content, + } ); + }, + }, + { + type: 'raw', + selector: 'h1,h2,h3,h4,h5,h6', + schema: { + h1: { children: getPhrasingContentSchema() }, + h2: { children: getPhrasingContentSchema() }, + h3: { children: getPhrasingContentSchema() }, + h4: { children: getPhrasingContentSchema() }, + h5: { children: getPhrasingContentSchema() }, + h6: { children: getPhrasingContentSchema() }, + }, + transform( node ) { + return createBlock( 'core/heading', { + ...getBlockAttributes( + 'core/heading', + node.outerHTML + ), + level: getLevelFromHeadingNodeName( node.nodeName ), + } ); + }, + }, + ...[ 2, 3, 4, 5, 6 ].map( ( level ) => ( { + type: 'prefix', + prefix: Array( level + 1 ).join( '#' ), + transform( content ) { + return createBlock( 'core/heading', { + level, + content, + } ); + }, + } ) ), + ], + to: [ + { + type: 'block', + blocks: [ 'core/paragraph' ], + transform: ( { content } ) => { + return createBlock( 'core/paragraph', { + content, + } ); + }, + }, + ], +}; + +export default transforms; diff --git a/packages/block-library/src/html/index.js b/packages/block-library/src/html/index.js index 603199158a292..bcfec8fb79f84 100644 --- a/packages/block-library/src/html/index.js +++ b/packages/block-library/src/html/index.js @@ -1,9 +1,7 @@ /** * WordPress dependencies */ -import { RawHTML } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { getPhrasingContentSchema } from '@wordpress/blocks'; /** * Internal dependencies @@ -11,6 +9,8 @@ import { getPhrasingContentSchema } from '@wordpress/blocks'; import edit from './edit'; import icon from './icon'; import metadata from './block.json'; +import save from './save'; +import transforms from './transforms'; const { name } = metadata; @@ -18,44 +18,15 @@ export { metadata, name }; export const settings = { title: __( 'Custom HTML' ), - description: __( 'Add custom HTML code and preview it as you edit.' ), - icon, - keywords: [ __( 'embed' ) ], - supports: { customClassName: false, className: false, html: false, }, - - transforms: { - from: [ - { - type: 'raw', - isMatch: ( node ) => node.nodeName === 'FIGURE' && !! node.querySelector( 'iframe' ), - schema: { - figure: { - require: [ 'iframe' ], - children: { - iframe: { - attributes: [ 'src', 'allowfullscreen', 'height', 'width' ], - }, - figcaption: { - children: getPhrasingContentSchema(), - }, - }, - }, - }, - }, - ], - }, - + transforms, edit, - - save( { attributes } ) { - return { attributes.content }; - }, + save, }; diff --git a/packages/block-library/src/html/save.js b/packages/block-library/src/html/save.js new file mode 100644 index 0000000000000..08d72be511bb2 --- /dev/null +++ b/packages/block-library/src/html/save.js @@ -0,0 +1,8 @@ +/** + * WordPress dependencies + */ +import { RawHTML } from '@wordpress/element'; + +export default function save( { attributes } ) { + return { attributes.content }; +} diff --git a/packages/block-library/src/html/transforms.js b/packages/block-library/src/html/transforms.js new file mode 100644 index 0000000000000..a54c17ef8f6de --- /dev/null +++ b/packages/block-library/src/html/transforms.js @@ -0,0 +1,28 @@ +/** + * WordPress dependencies + */ +import { getPhrasingContentSchema } from '@wordpress/blocks'; + +const transforms = { + from: [ + { + type: 'raw', + isMatch: ( node ) => node.nodeName === 'FIGURE' && !! node.querySelector( 'iframe' ), + schema: { + figure: { + require: [ 'iframe' ], + children: { + iframe: { + attributes: [ 'src', 'allowfullscreen', 'height', 'width' ], + }, + figcaption: { + children: getPhrasingContentSchema(), + }, + }, + }, + }, + }, + ], +}; + +export default transforms; diff --git a/packages/block-library/src/image/index.js b/packages/block-library/src/image/index.js index a61d51e0b4aba..cf8eb321c4700 100644 --- a/packages/block-library/src/image/index.js +++ b/packages/block-library/src/image/index.js @@ -6,14 +6,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { createBlobURL } from '@wordpress/blob'; -import { - createBlock, - getBlockAttributes, - getPhrasingContentSchema, -} from '@wordpress/blocks'; import { RichText } from '@wordpress/block-editor'; -import { Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** @@ -22,247 +15,30 @@ import { __ } from '@wordpress/i18n'; import edit from './edit'; import icon from './icon'; import metadata from './block.json'; +import save from './save'; +import transforms from './transforms'; const { name, attributes: blockAttributes } = metadata; export { metadata, name }; -const imageSchema = { - img: { - attributes: [ 'src', 'alt' ], - classes: [ 'alignleft', 'aligncenter', 'alignright', 'alignnone', /^wp-image-\d+$/ ], - }, -}; - -const schema = { - figure: { - require: [ 'img' ], - children: { - ...imageSchema, - a: { - attributes: [ 'href', 'rel', 'target' ], - children: imageSchema, - }, - figcaption: { - children: getPhrasingContentSchema(), - }, - }, - }, -}; - -function getFirstAnchorAttributeFormHTML( html, attributeName ) { - const { body } = document.implementation.createHTMLDocument( '' ); - - body.innerHTML = html; - - const { firstElementChild } = body; - - if ( - firstElementChild && - firstElementChild.nodeName === 'A' - ) { - return firstElementChild.getAttribute( attributeName ) || undefined; - } -} - -export function stripFirstImage( attributes, { shortcode } ) { - const { body } = document.implementation.createHTMLDocument( '' ); - - body.innerHTML = shortcode.content; - - let nodeToRemove = body.querySelector( 'img' ); - - // if an image has parents, find the topmost node to remove - while ( nodeToRemove && nodeToRemove.parentNode && nodeToRemove.parentNode !== body ) { - nodeToRemove = nodeToRemove.parentNode; - } - - if ( nodeToRemove ) { - nodeToRemove.parentNode.removeChild( nodeToRemove ); - } - - return body.innerHTML.trim(); -} - export const settings = { title: __( 'Image' ), - description: __( 'Insert an image to make a visual statement.' ), - icon, - keywords: [ 'img', // "img" is not translated as it is intended to reflect the HTML tag. __( 'photo' ), ], - - transforms: { - from: [ - { - type: 'raw', - isMatch: ( node ) => node.nodeName === 'FIGURE' && !! node.querySelector( 'img' ), - schema, - transform: ( node ) => { - // Search both figure and image classes. Alignment could be - // set on either. ID is set on the image. - const className = node.className + ' ' + node.querySelector( 'img' ).className; - const alignMatches = /(?:^|\s)align(left|center|right)(?:$|\s)/.exec( className ); - const align = alignMatches ? alignMatches[ 1 ] : undefined; - const idMatches = /(?:^|\s)wp-image-(\d+)(?:$|\s)/.exec( className ); - const id = idMatches ? Number( idMatches[ 1 ] ) : undefined; - const anchorElement = node.querySelector( 'a' ); - const linkDestination = anchorElement && anchorElement.href ? 'custom' : undefined; - const href = anchorElement && anchorElement.href ? anchorElement.href : undefined; - const rel = anchorElement && anchorElement.rel ? anchorElement.rel : undefined; - const linkClass = anchorElement && anchorElement.className ? anchorElement.className : undefined; - const attributes = getBlockAttributes( 'core/image', node.outerHTML, { align, id, linkDestination, href, rel, linkClass } ); - return createBlock( 'core/image', attributes ); - }, - }, - { - type: 'files', - isMatch( files ) { - return files.length === 1 && files[ 0 ].type.indexOf( 'image/' ) === 0; - }, - transform( files ) { - const file = files[ 0 ]; - // We don't need to upload the media directly here - // It's already done as part of the `componentDidMount` - // int the image block - const block = createBlock( 'core/image', { - url: createBlobURL( file ), - } ); - - return block; - }, - }, - { - type: 'shortcode', - tag: 'caption', - attributes: { - url: { - type: 'string', - source: 'attribute', - attribute: 'src', - selector: 'img', - }, - alt: { - type: 'string', - source: 'attribute', - attribute: 'alt', - selector: 'img', - }, - caption: { - shortcode: stripFirstImage, - }, - href: { - shortcode: ( attributes, { shortcode } ) => { - return getFirstAnchorAttributeFormHTML( shortcode.content, 'href' ); - }, - }, - rel: { - shortcode: ( attributes, { shortcode } ) => { - return getFirstAnchorAttributeFormHTML( shortcode.content, 'rel' ); - }, - }, - linkClass: { - shortcode: ( attributes, { shortcode } ) => { - return getFirstAnchorAttributeFormHTML( shortcode.content, 'class' ); - }, - }, - id: { - type: 'number', - shortcode: ( { named: { id } } ) => { - if ( ! id ) { - return; - } - - return parseInt( id.replace( 'attachment_', '' ), 10 ); - }, - }, - align: { - type: 'string', - shortcode: ( { named: { align = 'alignnone' } } ) => { - return align.replace( 'align', '' ); - }, - }, - }, - }, - ], - }, - + transforms, getEditWrapperProps( attributes ) { const { align, width } = attributes; if ( 'left' === align || 'center' === align || 'right' === align || 'wide' === align || 'full' === align ) { return { 'data-align': align, 'data-resized': !! width }; } }, - edit, - - save( { attributes } ) { - const { - url, - alt, - caption, - align, - href, - rel, - linkClass, - width, - height, - id, - linkTarget, - } = attributes; - - const classes = classnames( { - [ `align${ align }` ]: align, - 'is-resized': width || height, - } ); - - const image = ( - { - ); - - const figure = ( - - { href ? ( - - { image } - - ) : image } - { ! RichText.isEmpty( caption ) && } - - ); - - if ( 'left' === align || 'right' === align || 'center' === align ) { - return ( -
-
- { figure } -
-
- ); - } - - return ( -
- { figure } -
- ); - }, - + save, deprecated: [ { attributes: blockAttributes, diff --git a/packages/block-library/src/image/save.js b/packages/block-library/src/image/save.js new file mode 100644 index 0000000000000..032759c34c158 --- /dev/null +++ b/packages/block-library/src/image/save.js @@ -0,0 +1,73 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { RichText } from '@wordpress/block-editor'; +import { Fragment } from '@wordpress/element'; + +export default function save( { attributes } ) { + const { + url, + alt, + caption, + align, + href, + rel, + linkClass, + width, + height, + id, + linkTarget, + } = attributes; + + const classes = classnames( { + [ `align${ align }` ]: align, + 'is-resized': width || height, + } ); + + const image = ( + { + ); + + const figure = ( + + { href ? ( + + { image } + + ) : image } + { ! RichText.isEmpty( caption ) && } + + ); + + if ( 'left' === align || 'right' === align || 'center' === align ) { + return ( +
+
+ { figure } +
+
+ ); + } + + return ( +
+ { figure } +
+ ); +} diff --git a/packages/block-library/src/image/test/index.js b/packages/block-library/src/image/test/transforms.js similarity index 97% rename from packages/block-library/src/image/test/index.js rename to packages/block-library/src/image/test/transforms.js index 5b21dcefdf6eb..14f2f8dc42580 100644 --- a/packages/block-library/src/image/test/index.js +++ b/packages/block-library/src/image/test/transforms.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { stripFirstImage } from '../'; +import { stripFirstImage } from '../transforms'; describe( 'stripFirstImage', () => { test( 'should do nothing if no image is present', () => { diff --git a/packages/block-library/src/image/transforms.js b/packages/block-library/src/image/transforms.js new file mode 100644 index 0000000000000..2af42cc2244e9 --- /dev/null +++ b/packages/block-library/src/image/transforms.js @@ -0,0 +1,161 @@ +/** + * WordPress dependencies + */ +import { createBlobURL } from '@wordpress/blob'; +import { + createBlock, + getBlockAttributes, + getPhrasingContentSchema, +} from '@wordpress/blocks'; + +export function stripFirstImage( attributes, { shortcode } ) { + const { body } = document.implementation.createHTMLDocument( '' ); + + body.innerHTML = shortcode.content; + + let nodeToRemove = body.querySelector( 'img' ); + + // if an image has parents, find the topmost node to remove + while ( nodeToRemove && nodeToRemove.parentNode && nodeToRemove.parentNode !== body ) { + nodeToRemove = nodeToRemove.parentNode; + } + + if ( nodeToRemove ) { + nodeToRemove.parentNode.removeChild( nodeToRemove ); + } + + return body.innerHTML.trim(); +} + +function getFirstAnchorAttributeFormHTML( html, attributeName ) { + const { body } = document.implementation.createHTMLDocument( '' ); + + body.innerHTML = html; + + const { firstElementChild } = body; + + if ( + firstElementChild && + firstElementChild.nodeName === 'A' + ) { + return firstElementChild.getAttribute( attributeName ) || undefined; + } +} + +const imageSchema = { + img: { + attributes: [ 'src', 'alt' ], + classes: [ 'alignleft', 'aligncenter', 'alignright', 'alignnone', /^wp-image-\d+$/ ], + }, +}; + +const schema = { + figure: { + require: [ 'img' ], + children: { + ...imageSchema, + a: { + attributes: [ 'href', 'rel', 'target' ], + children: imageSchema, + }, + figcaption: { + children: getPhrasingContentSchema(), + }, + }, + }, +}; + +const transforms = { + from: [ + { + type: 'raw', + isMatch: ( node ) => node.nodeName === 'FIGURE' && !! node.querySelector( 'img' ), + schema, + transform: ( node ) => { + // Search both figure and image classes. Alignment could be + // set on either. ID is set on the image. + const className = node.className + ' ' + node.querySelector( 'img' ).className; + const alignMatches = /(?:^|\s)align(left|center|right)(?:$|\s)/.exec( className ); + const align = alignMatches ? alignMatches[ 1 ] : undefined; + const idMatches = /(?:^|\s)wp-image-(\d+)(?:$|\s)/.exec( className ); + const id = idMatches ? Number( idMatches[ 1 ] ) : undefined; + const anchorElement = node.querySelector( 'a' ); + const linkDestination = anchorElement && anchorElement.href ? 'custom' : undefined; + const href = anchorElement && anchorElement.href ? anchorElement.href : undefined; + const rel = anchorElement && anchorElement.rel ? anchorElement.rel : undefined; + const linkClass = anchorElement && anchorElement.className ? anchorElement.className : undefined; + const attributes = getBlockAttributes( 'core/image', node.outerHTML, { align, id, linkDestination, href, rel, linkClass } ); + return createBlock( 'core/image', attributes ); + }, + }, + { + type: 'files', + isMatch( files ) { + return files.length === 1 && files[ 0 ].type.indexOf( 'image/' ) === 0; + }, + transform( files ) { + const file = files[ 0 ]; + // We don't need to upload the media directly here + // It's already done as part of the `componentDidMount` + // int the image block + return createBlock( 'core/image', { + url: createBlobURL( file ), + } ); + }, + }, + { + type: 'shortcode', + tag: 'caption', + attributes: { + url: { + type: 'string', + source: 'attribute', + attribute: 'src', + selector: 'img', + }, + alt: { + type: 'string', + source: 'attribute', + attribute: 'alt', + selector: 'img', + }, + caption: { + shortcode: stripFirstImage, + }, + href: { + shortcode: ( attributes, { shortcode } ) => { + return getFirstAnchorAttributeFormHTML( shortcode.content, 'href' ); + }, + }, + rel: { + shortcode: ( attributes, { shortcode } ) => { + return getFirstAnchorAttributeFormHTML( shortcode.content, 'rel' ); + }, + }, + linkClass: { + shortcode: ( attributes, { shortcode } ) => { + return getFirstAnchorAttributeFormHTML( shortcode.content, 'class' ); + }, + }, + id: { + type: 'number', + shortcode: ( { named: { id } } ) => { + if ( ! id ) { + return; + } + + return parseInt( id.replace( 'attachment_', '' ), 10 ); + }, + }, + align: { + type: 'string', + shortcode: ( { named: { align = 'alignnone' } } ) => { + return align.replace( 'align', '' ); + }, + }, + }, + }, + ], +}; + +export default transforms; diff --git a/packages/block-library/src/latest-comments/index.js b/packages/block-library/src/latest-comments/index.js index 002ee39c6f49f..ba3214750be62 100644 --- a/packages/block-library/src/latest-comments/index.js +++ b/packages/block-library/src/latest-comments/index.js @@ -13,19 +13,13 @@ export const name = 'core/latest-comments'; export const settings = { title: __( 'Latest Comments' ), - description: __( 'Display a list of your most recent comments.' ), - icon, - category: 'widgets', - keywords: [ __( 'recent comments' ) ], - supports: { align: true, html: false, }, - edit, }; diff --git a/packages/block-library/src/latest-posts/index.js b/packages/block-library/src/latest-posts/index.js index 6532a934759f4..a8cdf38168036 100644 --- a/packages/block-library/src/latest-posts/index.js +++ b/packages/block-library/src/latest-posts/index.js @@ -13,19 +13,13 @@ export const name = 'core/latest-posts'; export const settings = { title: __( 'Latest Posts' ), - description: __( 'Display a list of your most recent posts.' ), - icon, - category: 'widgets', - keywords: [ __( 'recent posts' ) ], - supports: { align: true, html: false, }, - edit, }; diff --git a/packages/block-library/src/legacy-widget/index.js b/packages/block-library/src/legacy-widget/index.js index 0339aaba8f1fc..51d00539b7327 100644 --- a/packages/block-library/src/legacy-widget/index.js +++ b/packages/block-library/src/legacy-widget/index.js @@ -13,16 +13,11 @@ export const name = 'core/legacy-widget'; export const settings = { title: __( 'Legacy Widget (Experimental)' ), - description: __( 'Display a legacy widget.' ), - icon, - category: 'widgets', - supports: { html: false, }, - edit, }; diff --git a/packages/block-library/src/list/index.js b/packages/block-library/src/list/index.js index 0ff8478b3f53f..dcc1098e71b13 100644 --- a/packages/block-library/src/list/index.js +++ b/packages/block-library/src/list/index.js @@ -7,13 +7,7 @@ import { omit } from 'lodash'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { - createBlock, - getPhrasingContentSchema, - getBlockAttributes, -} from '@wordpress/blocks'; import { RichText } from '@wordpress/block-editor'; -import { replace, join, split, create, toHTMLString, __UNSTABLE_LINE_SEPARATOR } from '@wordpress/rich-text'; /** * Internal dependencies @@ -21,28 +15,13 @@ import { replace, join, split, create, toHTMLString, __UNSTABLE_LINE_SEPARATOR } import edit from './edit'; import icon from './icon'; import metadata from './block.json'; +import save from './save'; +import transforms from './transforms'; const { name, attributes: schema } = metadata; export { metadata, name }; -const listContentSchema = { - ...getPhrasingContentSchema(), - ul: {}, - ol: { attributes: [ 'type' ] }, -}; - -// Recursion is needed. -// Possible: ul > li > ul. -// Impossible: ul > ul. -[ 'ul', 'ol' ].forEach( ( tag ) => { - listContentSchema[ tag ].children = { - li: { - children: listContentSchema, - }, - }; -} ); - const supports = { className: false, }; @@ -52,118 +31,8 @@ export const settings = { description: __( 'Create a bulleted or numbered list.' ), icon, keywords: [ __( 'bullet list' ), __( 'ordered list' ), __( 'numbered list' ) ], - supports, - - transforms: { - from: [ - { - type: 'block', - isMultiBlock: true, - blocks: [ 'core/paragraph' ], - transform: ( blockAttributes ) => { - return createBlock( 'core/list', { - values: toHTMLString( { - value: join( blockAttributes.map( ( { content } ) => { - const value = create( { html: content } ); - - if ( blockAttributes.length > 1 ) { - return value; - } - - // When converting only one block, transform - // every line to a list item. - return replace( value, /\n/g, __UNSTABLE_LINE_SEPARATOR ); - } ), __UNSTABLE_LINE_SEPARATOR ), - multilineTag: 'li', - } ), - } ); - }, - }, - { - type: 'block', - blocks: [ 'core/quote' ], - transform: ( { value } ) => { - return createBlock( 'core/list', { - values: toHTMLString( { - value: create( { html: value, multilineTag: 'p' } ), - multilineTag: 'li', - } ), - } ); - }, - }, - { - type: 'raw', - selector: 'ol,ul', - schema: { - ol: listContentSchema.ol, - ul: listContentSchema.ul, - }, - transform( node ) { - return createBlock( 'core/list', { - ...getBlockAttributes( - 'core/list', - node.outerHTML - ), - ordered: node.nodeName === 'OL', - } ); - }, - }, - ...[ '*', '-' ].map( ( prefix ) => ( { - type: 'prefix', - prefix, - transform( content ) { - return createBlock( 'core/list', { - values: `
  • ${ content }
  • `, - } ); - }, - } ) ), - ...[ '1.', '1)' ].map( ( prefix ) => ( { - type: 'prefix', - prefix, - transform( content ) { - return createBlock( 'core/list', { - ordered: true, - values: `
  • ${ content }
  • `, - } ); - }, - } ) ), - ], - to: [ - { - type: 'block', - blocks: [ 'core/paragraph' ], - transform: ( { values } ) => - split( create( { - html: values, - multilineTag: 'li', - multilineWrapperTags: [ 'ul', 'ol' ], - } ), __UNSTABLE_LINE_SEPARATOR ) - .map( ( piece ) => - createBlock( 'core/paragraph', { - content: toHTMLString( { value: piece } ), - } ) - ), - }, - { - type: 'block', - blocks: [ 'core/quote' ], - transform: ( { values } ) => { - return createBlock( 'core/quote', { - value: toHTMLString( { - value: create( { - html: values, - multilineTag: 'li', - multilineWrapperTags: [ 'ul', 'ol' ], - } ), - multilineTag: 'p', - } ), - } ); - }, - }, - ], - }, - + transforms, deprecated: [ { supports, @@ -197,7 +66,6 @@ export const settings = { }, }, ], - merge( attributes, attributesToMerge ) { const { values } = attributesToMerge; @@ -210,15 +78,6 @@ export const settings = { values: attributes.values + values, }; }, - edit, - - save( { attributes } ) { - const { ordered, values } = attributes; - const tagName = ordered ? 'ol' : 'ul'; - - return ( - - ); - }, + save, }; diff --git a/packages/block-library/src/list/save.js b/packages/block-library/src/list/save.js new file mode 100644 index 0000000000000..9b0ff55b44cd5 --- /dev/null +++ b/packages/block-library/src/list/save.js @@ -0,0 +1,13 @@ +/** + * WordPress dependencies + */ +import { RichText } from '@wordpress/block-editor'; + +export default function save( { attributes } ) { + const { ordered, values } = attributes; + const tagName = ordered ? 'ol' : 'ul'; + + return ( + + ); +} diff --git a/packages/block-library/src/list/transforms.js b/packages/block-library/src/list/transforms.js new file mode 100644 index 0000000000000..baf09b1b945c6 --- /dev/null +++ b/packages/block-library/src/list/transforms.js @@ -0,0 +1,144 @@ +/** + * WordPress dependencies + */ +import { + createBlock, + getBlockAttributes, + getPhrasingContentSchema, +} from '@wordpress/blocks'; +import { + __UNSTABLE_LINE_SEPARATOR, + create, + join, + replace, + split, + toHTMLString, +} from '@wordpress/rich-text'; + +const listContentSchema = { + ...getPhrasingContentSchema(), + ul: {}, + ol: { attributes: [ 'type' ] }, +}; + +// Recursion is needed. +// Possible: ul > li > ul. +// Impossible: ul > ul. +[ 'ul', 'ol' ].forEach( ( tag ) => { + listContentSchema[ tag ].children = { + li: { + children: listContentSchema, + }, + }; +} ); + +const transforms = { + from: [ + { + type: 'block', + isMultiBlock: true, + blocks: [ 'core/paragraph' ], + transform: ( blockAttributes ) => { + return createBlock( 'core/list', { + values: toHTMLString( { + value: join( blockAttributes.map( ( { content } ) => { + const value = create( { html: content } ); + + if ( blockAttributes.length > 1 ) { + return value; + } + + // When converting only one block, transform + // every line to a list item. + return replace( value, /\n/g, __UNSTABLE_LINE_SEPARATOR ); + } ), __UNSTABLE_LINE_SEPARATOR ), + multilineTag: 'li', + } ), + } ); + }, + }, + { + type: 'block', + blocks: [ 'core/quote' ], + transform: ( { value } ) => { + return createBlock( 'core/list', { + values: toHTMLString( { + value: create( { html: value, multilineTag: 'p' } ), + multilineTag: 'li', + } ), + } ); + }, + }, + { + type: 'raw', + selector: 'ol,ul', + schema: { + ol: listContentSchema.ol, + ul: listContentSchema.ul, + }, + transform( node ) { + return createBlock( 'core/list', { + ...getBlockAttributes( + 'core/list', + node.outerHTML + ), + ordered: node.nodeName === 'OL', + } ); + }, + }, + ...[ '*', '-' ].map( ( prefix ) => ( { + type: 'prefix', + prefix, + transform( content ) { + return createBlock( 'core/list', { + values: `
  • ${ content }
  • `, + } ); + }, + } ) ), + ...[ '1.', '1)' ].map( ( prefix ) => ( { + type: 'prefix', + prefix, + transform( content ) { + return createBlock( 'core/list', { + ordered: true, + values: `
  • ${ content }
  • `, + } ); + }, + } ) ), + ], + to: [ + { + type: 'block', + blocks: [ 'core/paragraph' ], + transform: ( { values } ) => + split( create( { + html: values, + multilineTag: 'li', + multilineWrapperTags: [ 'ul', 'ol' ], + } ), __UNSTABLE_LINE_SEPARATOR ) + .map( ( piece ) => + createBlock( 'core/paragraph', { + content: toHTMLString( { value: piece } ), + } ) + ), + }, + { + type: 'block', + blocks: [ 'core/quote' ], + transform: ( { values } ) => { + return createBlock( 'core/quote', { + value: toHTMLString( { + value: create( { + html: values, + multilineTag: 'li', + multilineWrapperTags: [ 'ul', 'ol' ], + } ), + multilineTag: 'p', + } ), + } ); + }, + }, + ], +}; + +export default transforms;