@wordpress/block-library
Version:
Block library for the WordPress editor.
373 lines (343 loc) • 8.96 kB
JavaScript
/**
* External dependencies
*/
import { omit } from 'lodash';
/**
* WordPress dependencies
*/
import { __, _x } from '@wordpress/i18n';
import { Fragment } from '@wordpress/element';
import { createBlock, getPhrasingContentSchema } from '@wordpress/blocks';
import {
BlockControls,
AlignmentToolbar,
RichText,
} from '@wordpress/block-editor';
import { join, split, create, toHTMLString } from '@wordpress/rich-text';
import { Path, SVG } from '@wordpress/components';
const ATTRIBUTE_QUOTE = 'value';
const ATTRIBUTE_CITATION = 'citation';
const blockAttributes = {
[ ATTRIBUTE_QUOTE ]: {
type: 'string',
source: 'html',
selector: 'blockquote',
multiline: 'p',
default: '',
},
[ ATTRIBUTE_CITATION ]: {
type: 'string',
source: 'html',
selector: 'cite',
default: '',
},
align: {
type: 'string',
},
};
export const name = 'core/quote';
export const settings = {
title: __( 'Quote' ),
description: __( 'Give quoted text visual emphasis. "In quoting others, we cite ourselves." — Julio Cortázar' ),
icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><Path d="M18.62 18h-5.24l2-4H13V6h8v7.24L18.62 18zm-2-2h.76L19 12.76V8h-4v4h3.62l-2 4zm-8 2H3.38l2-4H3V6h8v7.24L8.62 18zm-2-2h.76L9 12.76V8H5v4h3.62l-2 4z" /></SVG>,
category: 'common',
keywords: [ __( 'blockquote' ) ],
attributes: blockAttributes,
styles: [
{ name: 'default', label: _x( 'Default', 'block style' ), isDefault: true },
{ name: 'large', label: _x( 'Large', 'block style' ) },
],
transforms: {
from: [
{
type: 'block',
isMultiBlock: true,
blocks: [ 'core/paragraph' ],
transform: ( attributes ) => {
return createBlock( 'core/quote', {
value: toHTMLString( {
value: join( attributes.map( ( { content } ) =>
create( { html: content } )
), '\u2028' ),
multilineTag: 'p',
} ),
} );
},
},
{
type: 'block',
blocks: [ 'core/heading' ],
transform: ( { content } ) => {
return createBlock( 'core/quote', {
value: `<p>${ content }</p>`,
} );
},
},
{
type: 'block',
blocks: [ 'core/pullquote' ],
transform: ( { value, citation } ) => createBlock( 'core/quote', {
value,
citation,
} ),
},
{
type: 'prefix',
prefix: '>',
transform: ( content ) => {
return createBlock( 'core/quote', {
value: `<p>${ content }</p>`,
} );
},
},
{
type: 'raw',
isMatch: ( node ) => {
const isParagraphOrSingleCite = ( () => {
let hasCitation = false;
return ( child ) => {
// Child is a paragraph.
if ( child.nodeName === 'P' ) {
return true;
}
// Child is a cite and no other cite child exists before it.
if (
! hasCitation &&
child.nodeName === 'CITE'
) {
hasCitation = true;
return true;
}
};
} )();
return node.nodeName === 'BLOCKQUOTE' &&
// The quote block can only handle multiline paragraph
// content with an optional cite child.
Array.from( node.childNodes ).every(
isParagraphOrSingleCite
);
},
schema: {
blockquote: {
children: {
p: {
children: getPhrasingContentSchema(),
},
cite: {
children: getPhrasingContentSchema(),
},
},
},
},
},
],
to: [
{
type: 'block',
blocks: [ 'core/paragraph' ],
transform: ( { value, citation } ) => {
const paragraphs = [];
if ( value && value !== '<p></p>' ) {
paragraphs.push(
...split( create( { html: value, multilineTag: 'p' } ), '\u2028' )
.map( ( piece ) =>
createBlock( 'core/paragraph', {
content: toHTMLString( { value: piece } ),
} )
)
);
}
if ( citation && citation !== '<p></p>' ) {
paragraphs.push(
createBlock( 'core/paragraph', {
content: citation,
} )
);
}
if ( paragraphs.length === 0 ) {
return createBlock( 'core/paragraph', {
content: '',
} );
}
return paragraphs;
},
},
{
type: 'block',
blocks: [ 'core/heading' ],
transform: ( { value, citation, ...attrs } ) => {
// If there is no quote content, use the citation as the
// content of the resulting heading. A nonexistent citation
// will result in an empty heading.
if ( value === '<p></p>' ) {
return createBlock( 'core/heading', {
content: citation,
} );
}
const pieces = split( create( { html: value, multilineTag: 'p' } ), '\u2028' );
const headingBlock = createBlock( 'core/heading', {
content: toHTMLString( { value: pieces[ 0 ] } ),
} );
if ( ! citation && pieces.length === 1 ) {
return headingBlock;
}
const quotePieces = pieces.slice( 1 );
const quoteBlock = createBlock( 'core/quote', {
...attrs,
citation,
value: toHTMLString( {
value: quotePieces.length ? join( pieces.slice( 1 ), '\u2028' ) : create(),
multilineTag: 'p',
} ),
} );
return [ headingBlock, quoteBlock ];
},
},
{
type: 'block',
blocks: [ 'core/pullquote' ],
transform: ( { value, citation } ) => {
return createBlock( 'core/pullquote', {
value,
citation,
} );
},
},
],
},
edit( { attributes, setAttributes, isSelected, mergeBlocks, onReplace, className } ) {
const { align, value, citation } = attributes;
return (
<Fragment>
<BlockControls>
<AlignmentToolbar
value={ align }
onChange={ ( nextAlign ) => {
setAttributes( { align: nextAlign } );
} }
/>
</BlockControls>
<blockquote className={ className } style={ { textAlign: align } }>
<RichText
identifier={ ATTRIBUTE_QUOTE }
multiline
value={ value }
onChange={
( nextValue ) => setAttributes( {
value: nextValue,
} )
}
onMerge={ mergeBlocks }
onRemove={ ( forward ) => {
const hasEmptyCitation = ! citation || citation.length === 0;
if ( ! forward && hasEmptyCitation ) {
onReplace( [] );
}
} }
placeholder={
// translators: placeholder text used for the quote
__( 'Write quote…' )
}
/>
{ ( ! RichText.isEmpty( citation ) || isSelected ) && (
<RichText
identifier={ ATTRIBUTE_CITATION }
value={ citation }
onChange={
( nextCitation ) => setAttributes( {
citation: nextCitation,
} )
}
placeholder={
// translators: placeholder text used for the citation
__( 'Write citation…' )
}
className="wp-block-quote__citation"
/>
) }
</blockquote>
</Fragment>
);
},
save( { attributes } ) {
const { align, value, citation } = attributes;
return (
<blockquote style={ { textAlign: align ? align : null } }>
<RichText.Content multiline value={ value } />
{ ! RichText.isEmpty( citation ) && <RichText.Content tagName="cite" value={ citation } /> }
</blockquote>
);
},
merge( attributes, { value, citation } ) {
if ( ! value || value === '<p></p>' ) {
return {
...attributes,
citation: attributes.citation + citation,
};
}
return {
...attributes,
value: attributes.value + value,
citation: attributes.citation + citation,
};
},
deprecated: [
{
attributes: {
...blockAttributes,
style: {
type: 'number',
default: 1,
},
},
migrate( attributes ) {
if ( attributes.style === 2 ) {
return {
...omit( attributes, [ 'style' ] ),
className: attributes.className ? attributes.className + ' is-style-large' : 'is-style-large',
};
}
return attributes;
},
save( { attributes } ) {
const { align, value, citation, style } = attributes;
return (
<blockquote
className={ style === 2 ? 'is-large' : '' }
style={ { textAlign: align ? align : null } }
>
<RichText.Content multiline value={ value } />
{ ! RichText.isEmpty( citation ) && <RichText.Content tagName="cite" value={ citation } /> }
</blockquote>
);
},
},
{
attributes: {
...blockAttributes,
citation: {
type: 'string',
source: 'html',
selector: 'footer',
default: '',
},
style: {
type: 'number',
default: 1,
},
},
save( { attributes } ) {
const { align, value, citation, style } = attributes;
return (
<blockquote
className={ `blocks-quote-style-${ style }` }
style={ { textAlign: align ? align : null } }
>
<RichText.Content multiline value={ value } />
{ ! RichText.isEmpty( citation ) && <RichText.Content tagName="footer" value={ citation } /> }
</blockquote>
);
},
},
],
};