diff --git a/AGENTS.md b/AGENTS.md index 7df720344f..f4c7c2ece8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,6 +26,8 @@ Changing a deep-module **interface** requires updating the matching docs and tes ## General guidelines +- Write user-facing replies for a beginner to intermediate developer. + Use plain words, explain a term the first time it appears, and keep the real file names, functions, and steps so the explanation stays accurate. - Never use the em dash "—". Use plain dash "-" instead, but if applicable, just use a comma. - When writing commit messages, NEVER auto-add your agent name as co-author. - Never manually modify CHANGELOG.md files or any files that are marked as auto-generated. diff --git a/src/block-components/block-link/index.js b/src/block-components/block-link/index.js index da8d354dc8..ec079ccc5e 100644 --- a/src/block-components/block-link/index.js +++ b/src/block-components/block-link/index.js @@ -8,6 +8,7 @@ import { Edit } from './edit' * External dependencies */ import { Link } from '~stackable/components' +import { CustomAttributes } from '../custom-attributes' export const BlockLink = () => { return null @@ -21,9 +22,11 @@ BlockLink.Content = props => { if ( ! attributes.blockLinkUrl ) { return null } + const customAttributes = CustomAttributes.getCustomAttributes( attributes, 'blockLinkCustomAttributes' ) return ( { + it( 'resolves a named custom attribute list', () => { + const attributes = { + text: 'Product details', + linkCustomAttributes: [ + [ 'aria-label', 'Open %text%' ], + [ 'data-link-id', 'details' ], + ], + } + + expect( CustomAttributes.getCustomAttributes( attributes, 'linkCustomAttributes' ) ).toEqual( { + 'aria-label': 'Open Product details', + 'data-link-id': 'details', + } ) + } ) + + it( 'does not resolve the custom attribute list into itself', () => { + const attributes = { + linkCustomAttributes: [ + [ 'data-value', '%linkCustomAttributes%' ], + ], + } + + expect( CustomAttributes.getCustomAttributes( attributes, 'linkCustomAttributes' ) ).toEqual( { + 'data-value': '%linkCustomAttributes%', + } ) + } ) +} ) diff --git a/src/block-components/custom-attributes/attributes.js b/src/block-components/custom-attributes/attributes.js index 52efd71c0f..b2f4b4ca82 100644 --- a/src/block-components/custom-attributes/attributes.js +++ b/src/block-components/custom-attributes/attributes.js @@ -1,4 +1,9 @@ -export const addAttributes = attrObject => { +export const addAttributes = ( attrObject, options = {} ) => { + const { + attrNameTemplate = '%s', + versionAdded = '3.0.0', + } = options + attrObject.add( { attributes: { customAttributes: { @@ -6,7 +11,8 @@ export const addAttributes = attrObject => { default: [], }, }, - versionAdded: '3.0.0', + attrNameTemplate, + versionAdded, versionDeprecated: '', } ) } diff --git a/src/block-components/custom-attributes/edit.js b/src/block-components/custom-attributes/edit.js index 8a0c01a524..0c73b70fb9 100644 --- a/src/block-components/custom-attributes/edit.js +++ b/src/block-components/custom-attributes/edit.js @@ -7,6 +7,7 @@ */ import { i18n } from 'stackable' import { + ControlSeparator, CustomAttributesControl, InspectorAdvancedControls, PanelAdvancedSettings, @@ -17,10 +18,36 @@ import { useBlockAttributesContext, useBlockSetAttributesContext } from '~stacka * WordPress dependencies */ import { __ } from '@wordpress/i18n' +import { ExternalLink } from '@wordpress/components' import { INVALID_HTML_ATTRIBUTES } from '.' -export const Edit = () => { - const customAttributes = useBlockAttributesContext( attributes => attributes.customAttributes ) +const CustomAttributesHelp = ( { hasLink } ) => { + return <> +

{ __( 'Add custom HTML attributes to enhance the functionality of your block or link. For example:', i18n ) }

+ data-id="my-title" + { hasLink && <> +
+ aria-label="Learn more" + } +
+
+ + { __( 'Learn more about Custom Attributes', i18n ) } + + +} + +export const Edit = props => { + const { + customAttributes, + linkCustomAttributes, + } = useBlockAttributesContext( attributes => ( { + customAttributes: attributes.customAttributes, + linkCustomAttributes: props.linkAttributeName ? attributes[ props.linkAttributeName ] : undefined, + } ) ) const setAttributes = useBlockSetAttributesContext() return ( @@ -31,12 +58,29 @@ export const Edit = () => { responsive={ false } > setAttributes( { customAttributes } ) } + help={ __( 'Attributes placed here will be added to the block element. Separate multiple attributes with a space.', i18n ) } + /> + { props.linkAttributeName && setAttributes( { [ props.linkAttributeName ]: linkCustomAttributes } ) } + help={ __( 'Attributes placed here will be added to the anchor tag if this block has a link. Separate multiple attributes with a space.', i18n ) } /> + } + + ) } + +Edit.defaultProps = { + linkAttributeName: '', +} diff --git a/src/block-components/custom-attributes/index.js b/src/block-components/custom-attributes/index.js index ca0c353348..c6e01727ca 100644 --- a/src/block-components/custom-attributes/index.js +++ b/src/block-components/custom-attributes/index.js @@ -26,12 +26,13 @@ CustomAttributes.addAttributes = addAttributes // CustomAttributes.Style = Style -CustomAttributes.getCustomAttributes = attributes => { - if ( ! Array.isArray( attributes.customAttributes ) || attributes.customAttributes.length === 0 ) { +CustomAttributes.getCustomAttributes = ( attributes, attributeName = 'customAttributes' ) => { + if ( ! Array.isArray( attributes[ attributeName ] ) || attributes[ attributeName ].length === 0 ) { return {} } - const customAttributes = Object.fromEntries( attributes.customAttributes ) + const customAttributes = Object.fromEntries( attributes[ attributeName ] ) + const invalidBlockAttributes = [ ...INVALID_BLOCK_ATTRIBUTES, attributeName ] Object.keys( customAttributes ).forEach( key => { // Unescape the value, since we're storing them as escaped strings. let value = unescape( customAttributes[ key ] ) @@ -44,7 +45,7 @@ CustomAttributes.getCustomAttributes = attributes => { dynamicAttributeMatch.forEach( _match => { const match = _match.substr( 1, _match.length - 2 ) if ( - ! INVALID_BLOCK_ATTRIBUTES.includes( match ) && + ! invalidBlockAttributes.includes( match ) && attributes.hasOwnProperty( match ) && ! isUndefined( attributes[ match ] ) ) { diff --git a/src/block-components/custom-attributes/readme.md b/src/block-components/custom-attributes/readme.md index f14bbf6034..6f8de06d25 100644 --- a/src/block-components/custom-attributes/readme.md +++ b/src/block-components/custom-attributes/readme.md @@ -1,10 +1,11 @@ # Custom Attributes Block Component -Gives the ability to add your own custom attributes which will be placed on the block. +Gives the ability to add custom attributes to a block or its generated link. ## Usage -The custom attribute implementation is already handled by the Block Div Block Component +The block custom attribute implementation is already handled by the Block Div Block Component. +For a link, render the attributes with `CustomAttributes.getCustomAttributes( attributes, 'linkCustomAttributes' )` or its block-link equivalent. ### Adding inspector controls in `edit.js` @@ -12,8 +13,16 @@ The custom attribute implementation is already handled by the Block Div Block Co ``` +For a block with a configurable link, pass the link attribute name to add a second field below the block attributes. + +``` + +``` + ### Adding attributes in `schema.js` ``` CustomAttributes.addAttributes( attrObject ) ``` + +Link attributes are registered through `Link.addAttributes` or `BlockLink.addAttributes`. diff --git a/src/block-components/helpers/link/attributes.js b/src/block-components/helpers/link/attributes.js index 4a47452857..bca9ee8b8b 100644 --- a/src/block-components/helpers/link/attributes.js +++ b/src/block-components/helpers/link/attributes.js @@ -1,3 +1,5 @@ +import { addAttributes as addCustomAttributes } from '../../custom-attributes/attributes' + export const linkAttributes = ( selector = 'a' ) => { return { hasLink: { @@ -50,4 +52,9 @@ export const addLinkAttributes = ( attrObject, attrNameTemplate = '%s', selector versionAdded: '3.0.0', versionDeprecated: '', } ) + + addCustomAttributes( attrObject, { + attrNameTemplate, + versionAdded: '3.20.4', + } ) } diff --git a/src/block-components/link/index.js b/src/block-components/link/index.js index 8ff56afbcf..01732ca01b 100644 --- a/src/block-components/link/index.js +++ b/src/block-components/link/index.js @@ -3,6 +3,7 @@ */ import { addAttributes } from './attributes' import { Edit } from './edit' +import { CustomAttributes } from '../custom-attributes' /*+ * External dependencies @@ -42,6 +43,7 @@ Link.Content = props => { linkProps = {}, attributes, } = props + const customAttributes = CustomAttributes.getCustomAttributes( attributes, 'linkCustomAttributes' ) if ( ! attributes.linkHasLink ) { return props.children @@ -49,6 +51,7 @@ Link.Content = props => { return ( { Link.InspectorControls = Edit Link.addAttributes = addAttributes - diff --git a/src/block/call-to-action/edit.js b/src/block/call-to-action/edit.js index 4245dcd3fd..c7e5037148 100644 --- a/src/block/call-to-action/edit.js +++ b/src/block/call-to-action/edit.js @@ -137,7 +137,7 @@ const InspectorControls = memo( () => { - + diff --git a/src/block/card/edit.js b/src/block/card/edit.js index b572b41610..9fe4799b5a 100644 --- a/src/block/card/edit.js +++ b/src/block/card/edit.js @@ -191,7 +191,7 @@ const InspectorControls = memo( props => { - + diff --git a/src/block/column/edit.js b/src/block/column/edit.js index c82c9ae5e1..e100a12ac9 100644 --- a/src/block/column/edit.js +++ b/src/block/column/edit.js @@ -201,7 +201,7 @@ const InspectorControls = memo( props => { - + diff --git a/src/block/icon-box/edit.js b/src/block/icon-box/edit.js index 19fcc3cc48..cf241b4206 100644 --- a/src/block/icon-box/edit.js +++ b/src/block/icon-box/edit.js @@ -138,7 +138,7 @@ const InspectorControls = memo( () => { - + diff --git a/src/block/icon/edit.js b/src/block/icon/edit.js index 8b452eee56..7d88557cc9 100644 --- a/src/block/icon/edit.js +++ b/src/block/icon/edit.js @@ -120,7 +120,7 @@ const InspectorControls = memo( props => { - + diff --git a/src/block/image-box/edit.js b/src/block/image-box/edit.js index 322bac4697..be66fb42f6 100644 --- a/src/block/image-box/edit.js +++ b/src/block/image-box/edit.js @@ -135,7 +135,7 @@ const InspectorControls = memo( () => { - + diff --git a/src/block/image/edit.js b/src/block/image/edit.js index 4cdd12fa77..93050a1736 100644 --- a/src/block/image/edit.js +++ b/src/block/image/edit.js @@ -153,7 +153,7 @@ const InspectorControls = memo( props => { - + diff --git a/src/block/notification/edit.js b/src/block/notification/edit.js index 9daf7e4e7d..0bbc6e2244 100644 --- a/src/block/notification/edit.js +++ b/src/block/notification/edit.js @@ -206,7 +206,7 @@ const InspectorControls = memo( props => { - + diff --git a/src/block/pricing-box/edit.js b/src/block/pricing-box/edit.js index 443656815f..9cc007a1aa 100644 --- a/src/block/pricing-box/edit.js +++ b/src/block/pricing-box/edit.js @@ -129,7 +129,7 @@ const InspectorControls = memo( () => { - + diff --git a/src/block/team-member/edit.js b/src/block/team-member/edit.js index 745f1582aa..5c4d1a967c 100644 --- a/src/block/team-member/edit.js +++ b/src/block/team-member/edit.js @@ -134,7 +134,7 @@ const InspectorControls = memo( () => { - + diff --git a/src/block/testimonial/edit.js b/src/block/testimonial/edit.js index b1757afdf3..e2a5110873 100644 --- a/src/block/testimonial/edit.js +++ b/src/block/testimonial/edit.js @@ -132,7 +132,7 @@ const InspectorControls = memo( () => { - + diff --git a/src/components/custom-attributes-control/__test__/index.test.js b/src/components/custom-attributes-control/__test__/index.test.js index 7c20bf5c95..c565faa072 100644 --- a/src/components/custom-attributes-control/__test__/index.test.js +++ b/src/components/custom-attributes-control/__test__/index.test.js @@ -22,10 +22,13 @@ describe( 'CustomAttributesControl', () => { } const { - getByTestId, getByText, + getByTestId, getByText, rerender, } = render( ) expect( getByText( 'Custom Attributes' ) ).toBeTruthy() + rerender( ) + expect( getByText( 'Link Custom Attributes' ) ).toBeTruthy() + const invalidInputs = [ `test`, `key=value`, diff --git a/src/components/custom-attributes-control/index.js b/src/components/custom-attributes-control/index.js index 0682985f67..785ce4330a 100644 --- a/src/components/custom-attributes-control/index.js +++ b/src/components/custom-attributes-control/index.js @@ -13,13 +13,11 @@ import { createRoot } from '~stackable/util' * WordPress dependencies */ import { - Fragment, useState, unmountComponentAtNode, useRef, } from '@wordpress/element' import { __ } from '@wordpress/i18n' -import { ExternalLink } from '@wordpress/components' import AdvancedTextControl from '../advanced-text-control' const sanitizeString = str => { @@ -87,45 +85,33 @@ const CustomAttributesControl = props => { setCustomAttributes( createAttributeString( props.value ) ) } } + const help = ( props.help || hasError ) && <> + { props.help } + { hasError && { __( 'There is an error in your custom attribute', i18n ) } } + return ( - { __( 'You can type in custom HTML attributes for this block in the field above.', i18n ) } -   - { __( 'Example:', i18n ) } -
- { - // eslint-disable-next-line react/jsx-curly-brace-presence - `data-id="my-title"` - } -
- - { __( 'Learn more about Custom Attributes', i18n ) } - - { hasError && { __( 'There is an error in your custom attribute', i18n ) } } - - ) } + help={ help } + placeholder={ props.placeholder } /> ) } CustomAttributesControl.defaultProps = { label: '', + help: null, value: [], invalidHtmlAttributes: [], onChange: () => {}, + placeholder: null, } export default CustomAttributesControl diff --git a/src/util/attributes/index.js b/src/util/attributes/index.js index c9758836d2..825bdb0016 100644 --- a/src/util/attributes/index.js +++ b/src/util/attributes/index.js @@ -232,6 +232,7 @@ export const CONTENT_ATTRIBUTES = [ 'linkRel', 'linkHasTitle', 'linkTitle', + 'linkCustomAttributes', // Block-level link 'blockLinkHasLink', 'blockLinkUrl', @@ -239,4 +240,5 @@ export const CONTENT_ATTRIBUTES = [ 'blockLinkRel', 'blockLinkHasTitle', 'blockLinkTitle', + 'blockLinkCustomAttributes', ] diff --git a/src/util/blocks.js b/src/util/blocks.js index 7d291976b0..57f1784cca 100644 --- a/src/util/blocks.js +++ b/src/util/blocks.js @@ -43,14 +43,14 @@ export const STACKABLE_FILTERS = { 'stackable/card-group': [], 'stackable/card': [ 'imageUrl', 'imageId', 'imageAlt' ], 'stackable/button-group': [], - 'stackable/button': [ 'text', 'icon', 'linkHasLink', 'linkUrl', 'linkNewTab', 'linkRel', 'linkHasTitle', 'linkTitle' ], + 'stackable/button': [ 'text', 'icon', 'linkHasLink', 'linkUrl', 'linkNewTab', 'linkRel', 'linkHasTitle', 'linkTitle', 'linkCustomAttributes' ], 'stackable/text': [ 'text' ], 'stackable/subtitle': [ 'text' ], 'stackable/heading': [ 'text' ], 'stackable/number-box': [ 'text' ], 'stackable/image': [ 'imageUrl', 'imageId', 'imageAlt' ], 'stackable/icon': [ 'icon' ], - 'stackable/icon-button': [ 'icon', 'linkHasLink', 'linkUrl', 'linkNewTab', 'linkRel', 'linkHasTitle', 'linkTitle' ], + 'stackable/icon-button': [ 'icon', 'linkHasLink', 'linkUrl', 'linkNewTab', 'linkRel', 'linkHasTitle', 'linkTitle', 'linkCustomAttributes' ], 'stackable/icon-list': [ 'icon', 'text' ], 'stackable/icon-list-item': [ 'text' ], 'stackable/progress-bar': [ 'text', 'progressValue', 'progressValuePrefix', 'progressValueSuffix', 'progressInnerText', 'progressMax' ],