From 9be983e3b6a6bce9fb324c703f04729fa6ad04a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Sosa?= Date: Wed, 22 Oct 2025 14:57:20 +0100 Subject: [PATCH 1/4] added boxed/highlight/underline with colors option --- includes/BlockStyles.php | 22 ++++++++ src/blocks/inspector-control.js | 95 +++++++++++++++++++++++++++++++-- src/wonder-blocks.js | 1 + 3 files changed, 115 insertions(+), 3 deletions(-) diff --git a/includes/BlockStyles.php b/includes/BlockStyles.php index 8ea1d3c0..40bd38df 100644 --- a/includes/BlockStyles.php +++ b/includes/BlockStyles.php @@ -84,6 +84,21 @@ public function register_block_styles() { ), ); + $heading_styles = array( + array( + 'name' => 'nfd-heading-boxed', + 'label' => __('Boxed', 'nfd-wonder-blocks'), + ), + array( + 'name' => 'nfd-heading-highlight', + 'label' => __('Highlight', 'nfd-wonder-blocks'), + ), + array( + 'name' => 'nfd-heading-underline', + 'label' => __('Underline', 'nfd-wonder-blocks'), + ) + ); + foreach ( $image_styles as $image_style ) { register_block_style( array( 'core/group', 'core/image' ), @@ -97,5 +112,12 @@ public function register_block_styles() { $theme_style ); } + + foreach ( $heading_styles as $heading_style ) { + register_block_style( + 'core/heading', + $heading_style + ); + } } } diff --git a/src/blocks/inspector-control.js b/src/blocks/inspector-control.js index 0d35750e..6299d4ae 100644 --- a/src/blocks/inspector-control.js +++ b/src/blocks/inspector-control.js @@ -1,4 +1,7 @@ -import { InspectorControls } from "@wordpress/block-editor"; +import { + InspectorControls, + PanelColorSettings, +} from "@wordpress/block-editor"; import { Button, Notice, @@ -46,6 +49,18 @@ function addAttributes(settings, name) { }; } + if (name === "core/heading") { + settings.attributes = { + ...settings.attributes, + nfdHeadingBorderColor: { + type: 'string' + }, + nfdHeadingBorderCustom: { + type: 'string' + }, + }; + } + return { ...settings, attributes: { @@ -312,6 +327,15 @@ const withInspectorControls = createHigherOrderComponent((BlockEdit) => { [] ); + const onBorderChange = (value, meta = {}) => { + const { slug } = meta || {}; + if (slug) { + props.setAttributes({ nfdHeadingBorderColor: slug, nfdHeadingBorderCustom: undefined }); + } else { + props.setAttributes({ nfdHeadingBorderCustom: value, nfdHeadingBorderColor: undefined }); + } + }; + return ( <> @@ -491,11 +515,52 @@ const withInspectorControls = createHigherOrderComponent((BlockEdit) => { )} + + {name === "core/heading" && ( + + } + colorSettings={[ + { + label: __("Border Color", "nfd-wonder-blocks"), + value: props.attributes.nfdHeadingBorderCustom, + onChange: onBorderChange, + }, + ]} + /> + + )} ); }; }, "withInspectorControl"); + +const resolveHeadingColor = (slug, custom) => + slug ? `var(--wp--preset--color--${slug})` : (custom || undefined); + +function applyHeadingStylesInPlace(props, blockType, atts) { + if (!blockType || blockType.name !== 'core/heading') return; + + const usedPanel = + !!atts?.nfdHeadingBorderColor || + !!atts?.nfdHeadingBorderCustom; + + if (!usedPanel) return; + + const resolve = (slug, custom) => + slug ? `var(--wp--preset--color--${slug})` : (custom || undefined); + + const border = resolve(atts?.nfdHeadingBorderColor, atts?.nfdHeadingBorderCustom); + + if (!border) return; + + props.style = { + ...(props.style || {}), + ...(border ? { '--nfd-heading-border': border } : {}), + }; +} + function addSaveProps(saveElementProps, blockType, attributes) { const generatedClasses = saveElementProps?.className ?? []; const classes = [ @@ -530,9 +595,13 @@ function addSaveProps(saveElementProps, blockType, attributes) { ...normalizeAsArray(classes), ]); - return Object.assign({}, saveElementProps, { - className: [...classesCombined].join(" "), + const nextProps = Object.assign({}, saveElementProps, { + className: [...classesCombined].join(' ').trim(), }); + + applyHeadingStylesInPlace(nextProps, blockType, attributes); + + return nextProps; } addFilter("blocks.registerBlockType", "nfd-wonder-blocks/utilities/attributes", addAttributes); @@ -550,3 +619,23 @@ addFilter( "nfd-wonder-blocks/utilities/extraProps", addSaveProps ); + +const listHeadingBlock = createHigherOrderComponent((BlockListBlock) => { + return (props) => { + if (props.name !== 'core/heading') return ; + const attr = props.attributes; + const styleVars = { + ...(props.wrapperProps?.style || {}), + '--nfd-heading-border': resolveHeadingColor(attr.nfdHeadingBorderColor, attr.nfdHeadingBorderCustom), + }; + + return ( + + ); + }; +}, 'nfd-list-block'); + +addFilter('editor.BlockListBlock', 'nfd-wonder-blocks/utilities/listBlock', listHeadingBlock); diff --git a/src/wonder-blocks.js b/src/wonder-blocks.js index ea3c2ba6..956574a3 100644 --- a/src/wonder-blocks.js +++ b/src/wonder-blocks.js @@ -30,6 +30,7 @@ import { import "./blocks/block"; import "./blocks/inspector-control"; +import "./blocks/heading"; import "./blocks/register-category"; import Modal from "./components/Modal/Modal"; import ToolbarButton from "./components/ToolbarButton"; From 9545e3acb41b9c7be59a96dda1f5141b3adb21d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Sosa?= Date: Thu, 23 Oct 2025 15:39:02 +0100 Subject: [PATCH 2/4] added Heading style options --- src/blocks/heading.js | 131 ++++++++++++++++++++++++++++++++ src/blocks/inspector-control.js | 116 ++++++++++++++-------------- 2 files changed, 189 insertions(+), 58 deletions(-) create mode 100644 src/blocks/heading.js diff --git a/src/blocks/heading.js b/src/blocks/heading.js new file mode 100644 index 00000000..0d847839 --- /dev/null +++ b/src/blocks/heading.js @@ -0,0 +1,131 @@ +import { addFilter } from '@wordpress/hooks'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { useEffect } from '@wordpress/element'; +import { + create as createRT, + toHTMLString, + applyFormat, + registerFormatType +} from '@wordpress/rich-text'; +import {createHigherOrderComponent} from "@wordpress/compose"; +import { InspectorControls } from '@wordpress/block-editor'; +import { PanelBody } from '@wordpress/components'; +import { BorderControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +const STYLE_CLASS = 'is-style-nfd-heading-highlight'; +const FORMAT_TYPE = 'nfd/heading-highlight'; +const FORMAT_CLASS = 'nfd-heading-highlight__text'; +import TitleWithLogo from "../components/TitleWithLogo"; + +const resolveHeadingColor = (slug, custom) => + slug ? `var(--wp--preset--color--${slug})` : (custom || undefined); + +const withHeadingHighlightSync = ( BlockEdit ) => ( props ) => { + if ( props.name !== 'core/heading' ) return ; + + const { attributes } = props; + const { updateBlockAttributes } = useDispatch('core/block-editor'); + + const { className, content } = useSelect( ( select ) => { + const attrs = select('core/block-editor').getBlockAttributes( props.clientId ) || {}; + return { className: attrs.className || '', content: attrs.content || '' }; + }, [ props.clientId ] ); + + const styled = className.includes( STYLE_CLASS ); + + useEffect( () => { + const html = content || ''; + const parser = new DOMParser(); + const doc = parser.parseFromString( html, 'text/html' ); + let changed = false; + + doc.querySelectorAll('span.nfd-heading-highlight__text').forEach( (node) => { + const parent = node.parentNode; + while ( node.firstChild ) parent.insertBefore( node.firstChild, node ); + parent.removeChild( node ); + changed = true; + } ); + + let normalizedHTML = doc.body.innerHTML || ''; + + let value = createRT( { html: normalizedHTML } ); + + const styled = (className || '').includes( STYLE_CLASS ); + if ( styled && value.text.length > 0 ) { + value = applyFormat( value, { type: FORMAT_TYPE, attributes: { class: FORMAT_CLASS } }, 0, value.text.length ); + } + + const nextHTML = toHTMLString( { value } ); + + if ( nextHTML !== content ) { + updateBlockAttributes( props.clientId, { content: nextHTML } ); + } + }, [ className, content ]); + + return ; +}; + +addFilter( 'editor.BlockEdit', 'nfd/heading-highlight/sync', withHeadingHighlightSync ); + + +registerFormatType( FORMAT_TYPE, { + title: 'Heading Highlight', + tagName: 'span', + className: FORMAT_CLASS, + edit: () => null +} ); + + +const listHeadingBlock = createHigherOrderComponent((BlockListBlock) => { + return (props) => { + if (props.name !== 'core/heading') return ; + const attr = props.attributes; + const styleVars = { + ...(props.wrapperProps?.style || {}), + '--nfd-heading-border': resolveHeadingColor(attr.nfdHeadingBorderColor, attr.nfdHeadingBorderColor), + }; + + return ( + + ); + }; +}, 'nfd-list-block'); + +addFilter('editor.BlockListBlock', 'nfd-wonder-blocks/utilities/listBlock', listHeadingBlock); + +const HeadingExtras = ( props ) => { + const { attributes, setAttributes } = props; + return ( + + } + initialOpen={ true } + > + { + setAttributes({ + nfdHeadingBorderWidth: border?.width || '1px', + nfdHeadingBorderColor: border?.color || '', + nfdHeadingBorderStyle: border?.style || 'solid', + }); + } } + enableStyle={ true } + enableAlpha={ true } + withSlider={ true } + isCompact={ true } + /> + + + ); +}; + +export default HeadingExtras; diff --git a/src/blocks/inspector-control.js b/src/blocks/inspector-control.js index 6299d4ae..f4133a57 100644 --- a/src/blocks/inspector-control.js +++ b/src/blocks/inspector-control.js @@ -7,6 +7,7 @@ import { Notice, PanelBody, SelectControl, + BorderControl, // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalTruncate as Truncate, } from "@wordpress/components"; @@ -19,6 +20,7 @@ import { __ } from "@wordpress/i18n"; import classnames from "classnames"; import TitleWithLogo from "../components/TitleWithLogo"; +import HeadingExtras from "./heading"; // These block types do not support custom attributes. const skipBlockTypes = [ @@ -55,9 +57,12 @@ function addAttributes(settings, name) { nfdHeadingBorderColor: { type: 'string' }, - nfdHeadingBorderCustom: { + nfdHeadingBorderWidth: { type: 'string' }, + nfdHeadingBorderStyle: { + type: 'string' + } }; } @@ -104,6 +109,8 @@ const withInspectorControls = createHigherOrderComponent((BlockEdit) => { } }; + const allClassNames = props?.attributes?.className || ""; + const selectedGroupDivider = props?.attributes?.nfdGroupDivider ?? "default"; const selectedGroupTheme = props?.attributes?.nfdGroupTheme ?? ""; const selectedGroupEffect = props?.attributes?.nfdGroupEffect ?? ""; @@ -327,14 +334,10 @@ const withInspectorControls = createHigherOrderComponent((BlockEdit) => { [] ); - const onBorderChange = (value, meta = {}) => { - const { slug } = meta || {}; - if (slug) { - props.setAttributes({ nfdHeadingBorderColor: slug, nfdHeadingBorderCustom: undefined }); - } else { - props.setAttributes({ nfdHeadingBorderCustom: value, nfdHeadingBorderColor: undefined }); - } - }; + const isNfdHeadingStyle = + allClassNames.includes('is-style-nfd-heading-boxed') || + allClassNames.includes('is-style-nfd-heading-highlight') || + allClassNames.includes('is-style-nfd-heading-underline'); return ( <> @@ -516,51 +519,68 @@ const withInspectorControls = createHigherOrderComponent((BlockEdit) => { )} - {name === "core/heading" && ( - - } - colorSettings={[ - { - label: __("Border Color", "nfd-wonder-blocks"), - value: props.attributes.nfdHeadingBorderCustom, - onChange: onBorderChange, - }, - ]} - /> - + {name === "core/heading" && isNfdHeadingStyle && ( + )} ); }; }, "withInspectorControl"); +function applyHeadingStylesInPlace( props, blockType, atts ) { + if ( !blockType || blockType.name !== 'core/heading' ) return; // [web:106] -const resolveHeadingColor = (slug, custom) => - slug ? `var(--wp--preset--color--${slug})` : (custom || undefined); + const color = atts?.nfdHeadingBorderColor || ''; + const width = atts?.nfdHeadingBorderWidth || ''; + const style = atts?.nfdHeadingBorderStyle || ''; -function applyHeadingStylesInPlace(props, blockType, atts) { - if (!blockType || blockType.name !== 'core/heading') return; + const resolveColor = ( value ) => { + if ( typeof value !== 'string' || !value ) return undefined; + const isLiteral = + value.startsWith('#') || value.startsWith('rgb') || value.startsWith('hsl'); + return isLiteral ? value : `var(--wp--preset--color--${ value })`; + }; - const usedPanel = - !!atts?.nfdHeadingBorderColor || - !!atts?.nfdHeadingBorderCustom; + const resolveWidth = ( value ) => { + if ( typeof value !== 'string' || !value ) return undefined; + if ( /(px|em|rem|vh|vw|%)$/i.test(value.trim()) ) return value.trim(); + if ( /^-?\d+(\.\d+)?$/.test(value.trim()) ) return `${value.trim()}px`; + return undefined; + }; - if (!usedPanel) return; + const resolveStyle = ( value ) => { + const allowed = new Set([ 'solid', 'dashed', 'dotted', 'double', 'groove', 'ridge', 'inset', 'outset', 'none', 'hidden' ]); + if ( typeof value !== 'string' || !value ) return undefined; + const v = value.trim().toLowerCase(); + return allowed.has(v) ? v : undefined; + }; - const resolve = (slug, custom) => - slug ? `var(--wp--preset--color--${slug})` : (custom || undefined); + const borderColor = resolveColor(color); + const borderWidth = resolveWidth(width); + const borderStyle = resolveStyle(style) || 'solid'; - const border = resolve(atts?.nfdHeadingBorderColor, atts?.nfdHeadingBorderCustom); + const nextStyle = { ...(props.style || {}) }; - if (!border) return; + if ( borderColor ) { + nextStyle['--nfd-heading-border'] = borderColor; + } + if ( borderWidth ) { + nextStyle['--nfd-heading-border-size'] = borderWidth; + } + if ( borderStyle ) { + nextStyle['--nfd-heading-border-style'] = borderStyle; + } - props.style = { - ...(props.style || {}), - ...(border ? { '--nfd-heading-border': border } : {}), - }; + if ( + !nextStyle['--nfd-heading-border'] && + !nextStyle['--nfd-heading-border-size'] && + !nextStyle['--nfd-heading-border-style'] + ) return; + + props.style = nextStyle; } + function addSaveProps(saveElementProps, blockType, attributes) { const generatedClasses = saveElementProps?.className ?? []; const classes = [ @@ -619,23 +639,3 @@ addFilter( "nfd-wonder-blocks/utilities/extraProps", addSaveProps ); - -const listHeadingBlock = createHigherOrderComponent((BlockListBlock) => { - return (props) => { - if (props.name !== 'core/heading') return ; - const attr = props.attributes; - const styleVars = { - ...(props.wrapperProps?.style || {}), - '--nfd-heading-border': resolveHeadingColor(attr.nfdHeadingBorderColor, attr.nfdHeadingBorderCustom), - }; - - return ( - - ); - }; -}, 'nfd-list-block'); - -addFilter('editor.BlockListBlock', 'nfd-wonder-blocks/utilities/listBlock', listHeadingBlock); From efccf130a03c38bf1ae4c67efdef1bb9abbc54ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Sosa?= Date: Mon, 27 Oct 2025 07:21:19 +0000 Subject: [PATCH 3/4] wip --- src/blocks/heading.js | 56 ++++++++++++++++++++++++++++++ src/blocks/inspector-control.js | 60 ++------------------------------- 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/src/blocks/heading.js b/src/blocks/heading.js index 0d847839..acc9d4a1 100644 --- a/src/blocks/heading.js +++ b/src/blocks/heading.js @@ -97,6 +97,61 @@ const listHeadingBlock = createHigherOrderComponent((BlockListBlock) => { addFilter('editor.BlockListBlock', 'nfd-wonder-blocks/utilities/listBlock', listHeadingBlock); + +export const applyHeadingStylesInPlace = (props, blockType, atts) => { + if ( !blockType || blockType.name !== 'core/heading' ) return; + + const color = atts?.nfdHeadingBorderColor || ''; + const width = atts?.nfdHeadingBorderWidth || ''; + const style = atts?.nfdHeadingBorderStyle || ''; + + const resolveColor = ( value ) => { + if ( typeof value !== 'string' || !value ) return undefined; + const isLiteral = + value.startsWith('#') || value.startsWith('rgb') || value.startsWith('hsl'); + return isLiteral ? value : `var(--wp--preset--color--${ value })`; + }; + + const resolveWidth = ( value ) => { + if ( typeof value !== 'string' || !value ) return undefined; + if ( /(px|em|rem|vh|vw|%)$/i.test(value.trim()) ) return value.trim(); + if ( /^-?\d+(\.\d+)?$/.test(value.trim()) ) return `${value.trim()}px`; + return undefined; + }; + + const resolveStyle = ( value ) => { + const allowed = new Set([ 'solid', 'dashed', 'dotted', 'double', 'groove', 'ridge', 'inset', 'outset', 'none', 'hidden' ]); + if ( typeof value !== 'string' || !value ) return undefined; + const v = value.trim().toLowerCase(); + return allowed.has(v) ? v : undefined; + }; + + const borderColor = resolveColor(color); + const borderWidth = resolveWidth(width); + const borderStyle = resolveStyle(style) || 'solid'; + + const nextStyle = { ...(props.style || {}) }; + + if ( borderColor ) { + nextStyle['--nfd-heading-border'] = borderColor; + } + if ( borderWidth ) { + nextStyle['--nfd-heading-border-size'] = borderWidth; + } + if ( borderStyle ) { + nextStyle['--nfd-heading-border-style'] = borderStyle; + } + + if ( + !nextStyle['--nfd-heading-border'] && + !nextStyle['--nfd-heading-border-size'] && + !nextStyle['--nfd-heading-border-style'] + ) return; + + props.style = nextStyle; +} + + const HeadingExtras = ( props ) => { const { attributes, setAttributes } = props; return ( @@ -106,6 +161,7 @@ const HeadingExtras = ( props ) => { initialOpen={ true } > { }; }, "withInspectorControl"); -function applyHeadingStylesInPlace( props, blockType, atts ) { - if ( !blockType || blockType.name !== 'core/heading' ) return; // [web:106] - - const color = atts?.nfdHeadingBorderColor || ''; - const width = atts?.nfdHeadingBorderWidth || ''; - const style = atts?.nfdHeadingBorderStyle || ''; - - const resolveColor = ( value ) => { - if ( typeof value !== 'string' || !value ) return undefined; - const isLiteral = - value.startsWith('#') || value.startsWith('rgb') || value.startsWith('hsl'); - return isLiteral ? value : `var(--wp--preset--color--${ value })`; - }; - - const resolveWidth = ( value ) => { - if ( typeof value !== 'string' || !value ) return undefined; - if ( /(px|em|rem|vh|vw|%)$/i.test(value.trim()) ) return value.trim(); - if ( /^-?\d+(\.\d+)?$/.test(value.trim()) ) return `${value.trim()}px`; - return undefined; - }; - - const resolveStyle = ( value ) => { - const allowed = new Set([ 'solid', 'dashed', 'dotted', 'double', 'groove', 'ridge', 'inset', 'outset', 'none', 'hidden' ]); - if ( typeof value !== 'string' || !value ) return undefined; - const v = value.trim().toLowerCase(); - return allowed.has(v) ? v : undefined; - }; - - const borderColor = resolveColor(color); - const borderWidth = resolveWidth(width); - const borderStyle = resolveStyle(style) || 'solid'; - - const nextStyle = { ...(props.style || {}) }; - - if ( borderColor ) { - nextStyle['--nfd-heading-border'] = borderColor; - } - if ( borderWidth ) { - nextStyle['--nfd-heading-border-size'] = borderWidth; - } - if ( borderStyle ) { - nextStyle['--nfd-heading-border-style'] = borderStyle; - } - - if ( - !nextStyle['--nfd-heading-border'] && - !nextStyle['--nfd-heading-border-size'] && - !nextStyle['--nfd-heading-border-style'] - ) return; - - props.style = nextStyle; -} - - function addSaveProps(saveElementProps, blockType, attributes) { const generatedClasses = saveElementProps?.className ?? []; const classes = [ From 57b112ac24c4dc5d4b4605e3833ab66e2b79f027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Sosa?= Date: Tue, 28 Oct 2025 16:04:02 +0000 Subject: [PATCH 4/4] wip --- src/blocks/heading.js | 88 ++++++++++++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 23 deletions(-) diff --git a/src/blocks/heading.js b/src/blocks/heading.js index acc9d4a1..8aae1ca9 100644 --- a/src/blocks/heading.js +++ b/src/blocks/heading.js @@ -9,8 +9,12 @@ import { } from '@wordpress/rich-text'; import {createHigherOrderComponent} from "@wordpress/compose"; import { InspectorControls } from '@wordpress/block-editor'; -import { PanelBody } from '@wordpress/components'; -import { BorderControl } from '@wordpress/components'; +import { + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, + BorderControl, + PanelBody +} from '@wordpress/components'; import { __ } from '@wordpress/i18n'; const STYLE_CLASS = 'is-style-nfd-heading-highlight'; @@ -153,34 +157,72 @@ export const applyHeadingStylesInPlace = (props, blockType, atts) => { const HeadingExtras = ( props ) => { - const { attributes, setAttributes } = props; + const { attributes, setAttributes, clientId } = props; + const { nfdHeadingBorderWidth, nfdHeadingBorderColor, nfdHeadingBorderStyle } = attributes; + + const borderValue = { + width: nfdHeadingBorderWidth || '1px', + color: nfdHeadingBorderColor || undefined, + style: nfdHeadingBorderStyle || 'solid', + }; + + const hasBorderValue = Boolean( + nfdHeadingBorderWidth || nfdHeadingBorderColor || nfdHeadingBorderStyle + ); + + const onBorderChange = ( border ) => { + setAttributes({ + nfdHeadingBorderWidth: border?.width || '1px', + nfdHeadingBorderColor: border?.color || '', + nfdHeadingBorderStyle: border?.style || 'solid', + }); + }; + + const resetBorder = () => { + setAttributes({ + nfdHeadingBorderWidth: undefined, + nfdHeadingBorderColor: undefined, + nfdHeadingBorderStyle: undefined, + }); + }; + return ( } + title={ } initialOpen={ true } > - { - setAttributes({ - nfdHeadingBorderWidth: border?.width || '1px', - nfdHeadingBorderColor: border?.color || '', - nfdHeadingBorderStyle: border?.style || 'solid', - }); - } } - enableStyle={ true } - enableAlpha={ true } - withSlider={ true } - isCompact={ true } - /> + + hasBorderValue } + onDeselect={ resetBorder } + resetAllFilter={ () => ({ + nfdHeadingBorderWidth: undefined, + nfdHeadingBorderColor: undefined, + nfdHeadingBorderStyle: undefined, + }) } + isShownByDefault + > + + + + ); };