Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
2b76be5
fix(avatar): show placeholder for text-only custom bylines in editor
rbcorrales Feb 5, 2026
3abcd69
feat(avatar): support as nested profile block content
rbcorrales Feb 2, 2026
8e3903c
fix(avatar): resolve guest author avatars in editor (#4460)
rbcorrales Feb 9, 2026
cf87cac
perf(avatar): register enriched newspack_author_info REST field
rbcorrales Feb 9, 2026
ed5a86b
fix(avatar): resolve context at render time and debounce image fetch
rbcorrales Feb 10, 2026
2e2710e
fix(avatar): respect avatarHideDefault and resolve at correct size
rbcorrales Feb 10, 2026
dd8de18
fix(avatar): increase image fetch debounce
rbcorrales Feb 10, 2026
ddfc1f8
feat(avatar): render default gravatar in nested mode fallback
rbcorrales Feb 10, 2026
9aee3ed
feat(blocks): add social blocks with shared circular button mixin
rbcorrales Feb 10, 2026
e8203d6
fix(author-profile-social): fix spacing and remove duplicate asset en…
rbcorrales Feb 10, 2026
43edb25
fix(avatar): add missing useDefaultAvatar mock in edit tests
rbcorrales Feb 10, 2026
6fb8489
fix(i18n): use correct text domain in social blocks
rbcorrales Feb 10, 2026
f3b945a
fix(a11y): add accessible labels to social link blocks
rbcorrales Feb 10, 2026
6645402
fix(author-social-link): sanitize REST API SVGs with safeHTML
rbcorrales Feb 10, 2026
f26814f
fix(author-social): pass icon size via block context
rbcorrales Feb 10, 2026
f5bcfdc
refactor(author-profile-social): replace wp globals with imports
rbcorrales Feb 10, 2026
24e442e
fix(avatar): remove double escaping of author name
rbcorrales Feb 10, 2026
25b9ba4
fix(use-coauthors): cache negative avatar lookup results
rbcorrales Feb 10, 2026
1c06d5f
fix(avatar): remove double escaping in standalone render path
rbcorrales Feb 10, 2026
455ea09
fix(author-social): respect saved inner blocks on frontend
rbcorrales Feb 17, 2026
6fc7714
fix(social-button): add hex fallbacks for theme color tokens
rbcorrales Feb 17, 2026
3d1fa9a
refactor(avatar): extract shared render_avatar_image helper
rbcorrales Feb 17, 2026
2373555
fix(author-social): remove unused website service
rbcorrales Feb 17, 2026
bf523ce
fix(social-icons): align myspace, soundcloud, wikipedia SVGs with theme
rbcorrales Feb 17, 2026
9339795
refactor(social-icons): make plugin the canonical SVG source
rbcorrales Feb 18, 2026
4e3a5ff
refactor(social-icons): deduplicate SVGs into shared class constant
rbcorrales Feb 18, 2026
7b82e97
fix(avatar): adapt overlap mask cutout to match border-radius
rbcorrales Feb 18, 2026
ef89878
refactor(blocks): modularize avatar, deduplicate context, add types
rbcorrales Feb 18, 2026
4ccbdb2
perf(blocks): fix useSelect memoization warnings and add apiVersion
rbcorrales Feb 18, 2026
0c9c6d3
fix(bylines): add missing ToggleControl deprecation prop
rbcorrales Feb 18, 2026
6e546c3
fix(test): use WP_Block instead of stdClass in avatar tests
rbcorrales Feb 18, 2026
9858470
feat(social-icons): add REST endpoint for SVG icon map
rbcorrales Feb 18, 2026
bb3b3f0
feat(avatar): extract overlap mask constants and adjust mask radius
rbcorrales Feb 19, 2026
d2a93eb
fix(social): use full service list for contextual auto-populate
rbcorrales Feb 20, 2026
ceb63f2
feat(author-social-link): add service label as block title
thomasguillot Feb 20, 2026
fa917a2
feat(author-profile-social): add padding and margin support
thomasguillot Feb 20, 2026
5325de2
feat: update reset link toolbar
thomasguillot Feb 20, 2026
620319a
fix(author-profile-social): remove duplicate wrapper class
rbcorrales Feb 20, 2026
0f439e7
refactor(avatar): simplify overlap mask to use pixel-based SVG math
rbcorrales Feb 21, 2026
301184c
feat(author-profile-social): use pre-defined sizes for icon size
thomasguillot Feb 23, 2026
d865c06
feat(author-profile-social): rename panelbody title
thomasguillot Feb 23, 2026
fca3579
feat(author-profile-social): add support for block gap, update block …
thomasguillot Feb 24, 2026
8922ba6
feat(author-profile-social): add support for text and background color
thomasguillot Feb 24, 2026
a51f325
feat(author-profile-social): rename text and background color labels
thomasguillot Feb 24, 2026
e5efd7f
feat(social-button): update mixin to use scale instead of color on :h…
thomasguillot Feb 24, 2026
465c2c9
feat(author-profile-social): add brand style
thomasguillot Feb 24, 2026
202511a
Merge trunk into update/author-profile-social
thomasguillot Feb 24, 2026
e6e9bb3
fix: messed up merge
thomasguillot Feb 24, 2026
fac1316
Update src/blocks/author-profile-social/edit.jsx
thomasguillot Mar 11, 2026
e0eddf9
fix: indentation
thomasguillot Mar 11, 2026
6e4d8ee
Update src/blocks/author-profile-social/edit.jsx
thomasguillot Mar 11, 2026
980b7e3
Update src/blocks/author-profile-social/class-author-profile-social-b…
thomasguillot Mar 11, 2026
acee80c
fix: php wrapper style error
thomasguillot Mar 11, 2026
5d10a0f
feat: improve colour controls for author profile social block
thomasguillot Mar 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/blocks/author-profile-social/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,20 @@
"default": 24
}
},
"styles": [
{ "name": "default", "label": "Default", "isDefault": true },
{ "name": "brand", "label": "Brand" }
],
"supports": {
"color": {
"enableContrastChecker": false,
"background": true,
"text": true
},
"html": false,
"spacing": {
"margin": true,
"padding": true
"blockGap": [ "horizontal", "vertical" ],
"margin": true
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ private static function render_social_flat( array $attributes, WP_Block $block,

foreach ( $social_links as $service => $social_data ) {
$service_label = ucfirst( $service );
$output .= '<li>';
$output .= '<li data-service="' . esc_attr( $service ) . '">';
$output .= sprintf( '<a href="%s" aria-label="%s">', esc_url( $social_data['url'] ), esc_attr( $service_label ) );

$svg = ! empty( $social_data['svg'] ) ? $social_data['svg'] : Social_Icons::get_svg( $service );
Expand Down Expand Up @@ -202,20 +202,95 @@ private static function get_block_wrapper_attributes( WP_Block $block, array $at
);

\WP_Block_Supports::$block_to_render = $previous;

// Strip color classes/styles from wrapper so only the icon link (via CSS vars) is colored.
$wrapper_attributes = preg_replace( '/\bhas-[\w-]+-(color|background-color)\b/', '', $wrapper_attributes );
$wrapper_attributes = preg_replace( '/\bhas-text-color\b/', '', $wrapper_attributes );
$wrapper_attributes = preg_replace( '/\bhas-background\b/', '', $wrapper_attributes );
$wrapper_attributes = preg_replace( '/\s+/', ' ', $wrapper_attributes );

return $wrapper_attributes;
}

/**
* Build wrapper style from block attributes.style (spacing, color, border, etc.) and block-specific vars.
* Uses the style engine so presets (e.g. var:preset|spacing|20) are converted to CSS.
* Convert a preset token (var:preset|type|slug) to a CSS variable reference.
*
* @param string $value Raw value, e.g. "var:preset|spacing|20" or "#fff".
* @return string CSS value, e.g. "var(--wp--preset--spacing--20)" or "#fff".
*/
private static function preset_to_css( string $value ): string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at Core's Social Links block, it uses the <ul> as the outer wrapper, rather than an extra <div> that the author profile social block uses.

I think we can rejig it a bit so the <ul> is the outermost container, then the block gap stuff should just work without this workaround (cc @rbcorrales in case I am horribly wrong)! This looks like it works, but it'd be nice to take advantage of the baked in way to do it if we can 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One issue with this approach is that it would leave us with two separate versions of the block that use different structures: the legacy version built with <div><ul>, and the InnerBlocks version that uses only <ul>.

if ( preg_match( '/^var:preset\|([^|]+)\|(.+)$/', $value, $matches ) ) {
return sprintf( 'var(--wp--preset--%s--%s)', $matches[1], $matches[2] );
}
return $value;
}

/**
* Resolve a color value from attributes (preset slug or custom style token).
*
* @param array $attributes Block attributes.
* @param string $preset_key Top-level preset attribute key (e.g. "textColor").
* @param string $style_key Key under style.color (e.g. "text").
* @return string|null CSS color value or null.
*/
private static function resolve_color( array $attributes, string $preset_key, string $style_key ): ?string {
if ( ! empty( $attributes[ $preset_key ] ) && is_string( $attributes[ $preset_key ] ) ) {
return sprintf( 'var(--wp--preset--color--%s)', $attributes[ $preset_key ] );
}
$custom = $attributes['style']['color'][ $style_key ] ?? null;
if ( ! empty( $custom ) && is_string( $custom ) ) {
return self::preset_to_css( $custom );
}
return null;
}

/**
* Resolve blockGap into row-gap and column-gap CSS values.
*
* @param array $attributes Block attributes.
* @return array{row: string|null, column: string|null}
*/
private static function resolve_block_gap( array $attributes ): array {
$block_gap = $attributes['style']['spacing']['blockGap'] ?? null;
$result = [
'row' => null,
'column' => null,
];

if ( empty( $block_gap ) ) {
return $result;
}

if ( is_string( $block_gap ) ) {
$val = self::preset_to_css( $block_gap );
$result['row'] = $val;
$result['column'] = $val;
return $result;
}

if ( is_array( $block_gap ) ) {
$row = $block_gap['vertical'] ?? $block_gap['top'] ?? null;
$col = $block_gap['horizontal'] ?? $block_gap['left'] ?? null;

$result['row'] = is_string( $row ) ? self::preset_to_css( $row ) : null;
$result['column'] = is_string( $col ) ? self::preset_to_css( $col ) : null;
}

return $result;
}

/**
* Build wrapper inline style with custom CSS variables for icon sizing, gap, and color.
* Block supports (margin, etc.) are handled by get_block_wrapper_attributes().
*
* @param array $attributes Block attributes.
* @param int $icon_size Icon size in pixels.
* @return string Inline style string.
* @return string Inline style string for the block wrapper.
*/
private static function get_wrapper_style( array $attributes, int $icon_size ): string {
$parts = [];
$parts = [];
$style = $attributes['style'] ?? null;

if ( ! empty( $style ) && is_array( $style ) ) {
$styles = wp_style_engine_get_styles(
$style,
Expand All @@ -225,7 +300,31 @@ private static function get_wrapper_style( array $attributes, int $icon_size ):
$parts[] = $styles['css'];
}
}

$is_brand = ! empty( $attributes['className'] ) && str_contains( $attributes['className'], 'is-style-brand' );
$gap = self::resolve_block_gap( $attributes );

if ( null !== $gap['row'] ) {
$parts[] = sprintf( '--icon-row-gap: %s;', $gap['row'] );
}
if ( null !== $gap['column'] ) {
$parts[] = sprintf( '--icon-column-gap: %s;', $gap['column'] );
}

if ( ! $is_brand ) {
$icon_color = self::resolve_color( $attributes, 'textColor', 'text' );
$icon_background = self::resolve_color( $attributes, 'backgroundColor', 'background' );

if ( null !== $icon_color ) {
$parts[] = sprintf( '--icon-color: %s;', $icon_color );
}
if ( null !== $icon_background ) {
$parts[] = sprintf( '--icon-background: %s;', $icon_background );
}
}

$parts[] = sprintf( '--icon-size: %dpx;', absint( $icon_size ) );

return implode( ' ', $parts );
}
}
Expand Down
115 changes: 108 additions & 7 deletions src/blocks/author-profile-social/edit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,55 @@ const fetchAllServiceKeys = () => {
return allServiceKeysCache;
};

const presetToVar = value => {
if ( typeof value !== 'string' ) {
return value;
}
return value.replace( /^var:preset\|([^|]+)\|(.+)$/, 'var(--wp--preset--$1--$2)' );
};

const resolveColor = ( presetSlug, customValue ) => {
if ( presetSlug ) {
return `var(--wp--preset--color--${ presetSlug })`;
}
if ( typeof customValue === 'string' ) {
return presetToVar( customValue ) || customValue;
}
return undefined;
};

const resolveBlockGap = blockGap => {
if ( ! blockGap ) {
return {};
}
if ( typeof blockGap === 'string' ) {
const val = presetToVar( blockGap ) || blockGap;
return { '--icon-row-gap': val, '--icon-column-gap': val };
}
const result = {};
const row = blockGap.vertical ?? blockGap.top;
const col = blockGap.horizontal ?? blockGap.left;
if ( typeof row === 'string' ) {
result[ '--icon-row-gap' ] = presetToVar( row ) || row;
}
if ( typeof col === 'string' ) {
result[ '--icon-column-gap' ] = presetToVar( col ) || col;
}
return result;
};

const COLOR_CLASS_RE = /^has-([\w-]+-)?(color|background-color)$|^has-text-color$|^has-background$/;

const stripColorFromBlockProps = rawBlockProps => {
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
const { color, backgroundColor: bg, ...cleanStyle } = rawBlockProps.style || {};
const cleanClassName = ( rawBlockProps.className || '' )
.split( ' ' )
.filter( c => ! COLOR_CLASS_RE.test( c ) )
.join( ' ' );
return { ...rawBlockProps, className: cleanClassName, style: cleanStyle };
};

/**
* Edit component for the Author Social Links inner block.
*
Expand All @@ -40,16 +89,68 @@ const fetchAllServiceKeys = () => {
export default function Edit( { attributes, setAttributes, clientId } ) {
const AuthorContext = getSharedAuthorContext();
const author = useContext( AuthorContext );
const { iconSize } = attributes;
const { iconSize, style: styleAttr, textColor, backgroundColor, className } = attributes;
const hasPopulated = useRef( false );
const [ allServiceKeys, setAllServiceKeys ] = useState( null ); // null = loading

const blockProps = useBlockProps( {
className: 'wp-block-newspack-author-profile-social',
const isBrand = ( className || '' ).split( ' ' ).includes( 'is-style-brand' );
const iconSizeValue = typeof iconSize === 'number' ? iconSize : parseInt( iconSize ?? 24, 10 ) || 24;
const iconColor = resolveColor( textColor, styleAttr?.color?.text );
const iconBackground = resolveColor( backgroundColor, styleAttr?.color?.background );

// Hide color panel when "Brand" is active; rename labels when "Default".
useEffect( () => {
const sidebar = document.querySelector( '.interface-complementary-area' );
if ( ! sidebar ) {
return;
}

const COLOR_LABEL_MAP = {
Text: __( 'Icon color', 'newspack-plugin' ),
Background: __( 'Icon background', 'newspack-plugin' ),
};

const updateColorPanel = container => {
container.querySelectorAll( '.color-block-support-panel' ).forEach( el => {
el.style.display = isBrand ? 'none' : '';
} );

if ( isBrand ) {
return;
}

container.querySelectorAll( '.block-editor-panel-color-gradient-settings__color-name' ).forEach( el => {
if ( COLOR_LABEL_MAP[ el.textContent ] ) {
el.textContent = COLOR_LABEL_MAP[ el.textContent ];
}
} );
container.querySelectorAll( '.components-menu-item__item' ).forEach( el => {
if ( COLOR_LABEL_MAP[ el.textContent ] ) {
el.textContent = COLOR_LABEL_MAP[ el.textContent ];
}
} );
};

updateColorPanel( sidebar );

const observer = new MutationObserver( () => updateColorPanel( sidebar ) );
observer.observe( sidebar, { childList: true, subtree: true } );

return () => observer.disconnect();
}, [ isBrand ] );
const gapVars = resolveBlockGap( styleAttr?.spacing?.blockGap );

const rawProps = stripColorFromBlockProps( useBlockProps( { className: 'wp-block-newspack-author-profile-social' } ) );
const blockProps = {
...rawProps,
style: {
'--icon-size': `${ roundIconSize( iconSize ) }px`,
...rawProps.style,
'--icon-size': `${ roundIconSize( iconSizeValue ) }px`,
...gapVars,
...( ! isBrand && iconColor && { '--icon-color': iconColor } ),
...( ! isBrand && iconBackground && { '--icon-background': iconBackground } ),
},
} );
};

// Get inner blocks (stable reference from the store).
const innerBlocks = useSelect( select => select( 'core/block-editor' ).getBlocks( clientId ), [ clientId ] );
Expand Down Expand Up @@ -118,9 +219,9 @@ export default function Edit( { attributes, setAttributes, clientId } ) {
<PanelBody title={ __( 'Settings', 'newspack-plugin' ) }>
<SelectControl
label={ __( 'Icon size', 'newspack-plugin' ) }
value={ iconSize ?? 24 }
value={ iconSizeValue }
options={ getIconSizeOptions() }
onChange={ value => setAttributes( { iconSize: value } ) }
onChange={ value => setAttributes( { iconSize: Number( value ) || 24 } ) }
__next40pxDefaultSize
/>
{ missingServices.length > 0 && (
Expand Down
3 changes: 2 additions & 1 deletion src/blocks/author-profile-social/editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
.block-editor-block-list__layout {
display: flex;
flex-wrap: wrap;
gap: var(--wp--preset--spacing--20, 0.5rem);
column-gap: var(--icon-column-gap, var(--wp--preset--spacing--20, 0.5rem));
row-gap: var(--icon-row-gap, var(--wp--preset--spacing--20, 0.5rem));
}

// Remove default block margins in horizontal layout
Expand Down
48 changes: 47 additions & 1 deletion src/blocks/author-profile-social/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
list-style: none;
margin: 0;
padding: 0;
gap: var(--wp--preset--spacing--20, 0.5rem);
column-gap: var(--icon-column-gap, var(--wp--preset--spacing--20, 0.5rem));
row-gap: var(--icon-row-gap, var(--wp--preset--spacing--20, 0.5rem));

li {
line-height: 0;
Expand All @@ -28,4 +29,49 @@
text-transform: uppercase;
}
}

// Brand style: per-service colors override --icon-background / --icon-color
&.is-style-brand .author-profile-social__list {
--icon-color: #fff;

li[data-service="bluesky"] {
--icon-background: #0a7aff;
}
li[data-service="email"] {
--icon-background: #1e1e1e;
}
li[data-service="facebook"] {
--icon-background: #0866ff;
}
li[data-service="instagram"] {
--icon-background: #f00075;
}
li[data-service="linkedin"] {
--icon-background: #0d66c2;
}
li[data-service="myspace"] {
--icon-background: #000;
}
li[data-service="phone"] {
--icon-background: #1e1e1e;
}
li[data-service="pinterest"] {
--icon-background: #e60122;
}
li[data-service="soundcloud"] {
--icon-background: #ff5600;
}
li[data-service="tumblr"] {
--icon-background: #011835;
}
li[data-service="twitter"] {
--icon-background: #000;
}
li[data-service="wikipedia"] {
--icon-background: #000;
}
li[data-service="youtube"] {
--icon-background: #f00;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public static function render_block( array $attributes, string $content, WP_Bloc
$svg = self::get_social_service_svg( $author, $service );
$service_label = ucfirst( $service );

$output = '<li class="wp-block-newspack-author-social-link">';
$output = '<li class="wp-block-newspack-author-social-link" data-service="' . esc_attr( $service ) . '">';
$output .= sprintf( '<a href="%s" aria-label="%s">', esc_url( $url ), esc_attr( $service_label ) );

if ( $svg ) {
Expand Down
1 change: 1 addition & 0 deletions src/blocks/author-social-link/edit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default function AuthorSocialLinkEdit( { attributes, context } ) {

const blockProps = useBlockProps( {
className: 'wp-block-newspack-author-social-link',
'data-service': service,
} );

const url = getServiceUrl( author, service );
Expand Down
Loading
Loading