@automattic/newspack-blocks 4.25.3 → 4.26.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/blocks/author-profile/block.json +4 -0
  3. package/dist/blocks/carousel/block.json +4 -0
  4. package/dist/editor-rtl.css +1 -1
  5. package/dist/editor.asset.php +1 -1
  6. package/dist/editor.css +1 -1
  7. package/dist/editor.js +17 -17
  8. package/dist/modalCheckout-rtl.css +1 -1
  9. package/dist/modalCheckout.asset.php +1 -1
  10. package/dist/modalCheckout.css +1 -1
  11. package/dist/modalCheckout.js +1 -1
  12. package/includes/class-modal-checkout.php +42 -0
  13. package/languages/newspack-blocks-de_DE-37552bb09e2a9fceec1970e3c6d46557.json +1 -1
  14. package/languages/newspack-blocks-de_DE-53e2a1d5945b8d2b1c35e81ae1e532f3.json +1 -1
  15. package/languages/newspack-blocks-de_DE-eccbc51a43c04f59165364eda71e0be7.json +1 -1
  16. package/languages/newspack-blocks-de_DE-f5f8db76788e4079ddfa743333708b88.json +1 -0
  17. package/languages/newspack-blocks-de_DE-fcc93143c1f2b74671f595454b971f44.json +1 -0
  18. package/languages/newspack-blocks-de_DE.po +289 -260
  19. package/languages/newspack-blocks-es_ES-37552bb09e2a9fceec1970e3c6d46557.json +1 -1
  20. package/languages/newspack-blocks-es_ES-53e2a1d5945b8d2b1c35e81ae1e532f3.json +1 -1
  21. package/languages/newspack-blocks-es_ES-eccbc51a43c04f59165364eda71e0be7.json +1 -1
  22. package/languages/newspack-blocks-es_ES-f5f8db76788e4079ddfa743333708b88.json +1 -0
  23. package/languages/newspack-blocks-es_ES-fcc93143c1f2b74671f595454b971f44.json +1 -0
  24. package/languages/newspack-blocks-es_ES.po +289 -260
  25. package/languages/newspack-blocks-fr_BE-37552bb09e2a9fceec1970e3c6d46557.json +1 -1
  26. package/languages/newspack-blocks-fr_BE-53e2a1d5945b8d2b1c35e81ae1e532f3.json +1 -1
  27. package/languages/newspack-blocks-fr_BE-eccbc51a43c04f59165364eda71e0be7.json +1 -1
  28. package/languages/newspack-blocks-fr_BE-f5f8db76788e4079ddfa743333708b88.json +1 -0
  29. package/languages/newspack-blocks-fr_BE-fcc93143c1f2b74671f595454b971f44.json +1 -0
  30. package/languages/newspack-blocks-fr_BE.po +289 -260
  31. package/languages/newspack-blocks-nb_NO-37552bb09e2a9fceec1970e3c6d46557.json +1 -1
  32. package/languages/newspack-blocks-nb_NO-53e2a1d5945b8d2b1c35e81ae1e532f3.json +1 -1
  33. package/languages/newspack-blocks-nb_NO-eccbc51a43c04f59165364eda71e0be7.json +1 -1
  34. package/languages/newspack-blocks-nb_NO-f5f8db76788e4079ddfa743333708b88.json +1 -0
  35. package/languages/newspack-blocks-nb_NO-fcc93143c1f2b74671f595454b971f44.json +1 -0
  36. package/languages/newspack-blocks-nb_NO.po +289 -260
  37. package/languages/newspack-blocks-pt_PT-37552bb09e2a9fceec1970e3c6d46557.json +1 -1
  38. package/languages/newspack-blocks-pt_PT-53e2a1d5945b8d2b1c35e81ae1e532f3.json +1 -1
  39. package/languages/newspack-blocks-pt_PT-eccbc51a43c04f59165364eda71e0be7.json +1 -1
  40. package/languages/newspack-blocks-pt_PT-f5f8db76788e4079ddfa743333708b88.json +1 -0
  41. package/languages/newspack-blocks-pt_PT-fcc93143c1f2b74671f595454b971f44.json +1 -0
  42. package/languages/newspack-blocks-pt_PT.po +289 -260
  43. package/languages/newspack-blocks.pot +349 -311
  44. package/newspack-blocks.php +2 -2
  45. package/package.json +1 -1
  46. package/src/blocks/author-profile/README.md +105 -0
  47. package/src/blocks/author-profile/block.json +4 -0
  48. package/src/blocks/author-profile/edit.js +76 -165
  49. package/src/blocks/author-profile/editor.scss +1 -1
  50. package/src/blocks/author-profile/index.js +2 -0
  51. package/src/blocks/author-profile/templates.js +263 -0
  52. package/src/blocks/author-profile/variations.js +86 -0
  53. package/src/blocks/carousel/block.json +4 -0
  54. package/src/blocks/carousel/edit.js +2 -0
  55. package/src/blocks/carousel/view.php +3 -1
  56. package/src/blocks/donate/styles/editor.scss +1 -1
  57. package/src/blocks/homepage-articles/edit.tsx +2 -6
  58. package/src/components/query-controls.js +12 -0
  59. package/src/modal-checkout/checkout.scss +28 -0
  60. package/src/modal-checkout/index.js +1 -1
  61. package/src/modal-checkout/templates/form-checkout.php +13 -1
  62. package/vendor/composer/installed.php +2 -2
@@ -7,7 +7,7 @@
7
7
  * Author URI: https://newspack.com/
8
8
  * Text Domain: newspack-blocks
9
9
  * Domain Path: /languages
10
- * Version: 4.25.3
10
+ * Version: 4.26.0-alpha.2
11
11
  *
12
12
  * @package Newspack_Blocks
13
13
  */
@@ -15,7 +15,7 @@
15
15
  define( 'NEWSPACK_BLOCKS__PLUGIN_FILE', __FILE__ );
16
16
  define( 'NEWSPACK_BLOCKS__BLOCKS_DIRECTORY', 'dist/' );
17
17
  define( 'NEWSPACK_BLOCKS__PLUGIN_DIR', plugin_dir_path( NEWSPACK_BLOCKS__PLUGIN_FILE ) );
18
- define( 'NEWSPACK_BLOCKS__VERSION', '4.25.3' );
18
+ define( 'NEWSPACK_BLOCKS__VERSION', '4.26.0-alpha.2' );
19
19
 
20
20
  require_once NEWSPACK_BLOCKS__PLUGIN_DIR . 'includes/class-newspack-blocks.php';
21
21
  require_once NEWSPACK_BLOCKS__PLUGIN_DIR . 'includes/class-newspack-blocks-api.php';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/newspack-blocks",
3
- "version": "4.25.3",
3
+ "version": "4.26.0-alpha.2",
4
4
  "author": "Automattic",
5
5
  "description": "=== Newspack Blocks === Contributors: (this should be a list of wordpress.org userid's) Donate link: https://example.com/ Tags: comments, spam Requires at least: 4.5 Tested up to: 5.1.1 Stable tag: 0.1.0 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html",
6
6
  "repository": {
@@ -0,0 +1,105 @@
1
+ # Author Profile Block
2
+
3
+ A Gutenberg block that displays author bio cards with support for multiple authorship systems and two rendering modes.
4
+
5
+ ## Overview
6
+
7
+ The Author Profile block renders a complete author profile, including name, avatar, biography, social links, and a link to their archive page. It supports two operating modes (contextual and specific) and two rendering modes (nested and flat), adapting automatically to the theme type.
8
+
9
+ ## Operating modes
10
+
11
+ ### Contextual mode
12
+
13
+ Automatically detects authors for the current post. Resolution priority:
14
+
15
+ 1. **Newspack Custom Bylines:** If `_newspack_byline_active` meta is set and the byline contains `[Author id="X"]` shortcodes, profiles are shown for those authors. Plain-text-only bylines suppress the block entirely.
16
+ 2. **CoAuthors Plus:** If CAP is active, all co-authors are displayed (WordPress users and Guest Authors).
17
+ 3. **Default WordPress author:** Falls back to the standard post author.
18
+
19
+ ### Specific mode
20
+
21
+ Displays a single author selected via a search/autocomplete interface. The selected author ID and guest author flag are stored as block attributes.
22
+
23
+ ## Rendering modes
24
+
25
+ ### Nested mode (layoutVersion 2)
26
+
27
+ Used in block themes. The block saves `<InnerBlocks.Content />` and composes the profile from core blocks (Columns, Group, Heading, Paragraph) plus [`newspack/avatar`](https://github.com/Automattic/newspack-plugin/tree/trunk/src/blocks/avatar) and [`newspack/author-profile-social`](https://github.com/Automattic/newspack-plugin/tree/trunk/src/blocks/author-profile-social). Author data is injected via WordPress block bindings (`newspack-blocks/author` source) and block context.
28
+
29
+ **Layout variations:**
30
+
31
+ | Variation | Template constant | Description |
32
+ |-----------|------------------|-------------|
33
+ | `avatar-left` | `AVATAR_LEFT_TEMPLATE` | Two-column: avatar left, content right |
34
+ | `avatar-right` | `AVATAR_RIGHT_TEMPLATE` | Two-column: content left, avatar right |
35
+ | `centered` | `CENTERED_TEMPLATE` | Flex group: centered avatar, centered text |
36
+ | `compact` | `COMPACT_TEMPLATE` | Flex group: no avatar, vertical stack |
37
+
38
+ Variations are defined in [variations.js](./variations.js) and templates in [templates.js](./templates.js).
39
+
40
+ ### Flat mode (layoutVersion 1)
41
+
42
+ Used in classic themes. The block returns `save: () => null` and renders entirely via PHP using the [author-profile-card.php](../../templates/author-profile-card.php) template.
43
+
44
+ **Mode selection logic:** New blocks in block themes automatically get `layoutVersion: 2`. Existing blocks preserve their saved mode. A v2 block opened in a classic theme shows a warning in the editor and falls back to flat rendering on the frontend.
45
+
46
+ ## Block attributes
47
+
48
+ ### Core attributes
49
+
50
+ | Attribute | Type | Default | Description |
51
+ |-----------|------|---------|-------------|
52
+ | `authorId` | number | `0` | Selected author ID (specific mode) |
53
+ | `isContextual` | boolean | `false` | Whether to auto-detect authors from post context |
54
+ | `isGuestAuthor` | boolean | `true` | Whether the selected author is a CAP guest author |
55
+ | `layoutVersion` | number | `1` | `1` = flat, `2` = nested (see Rendering modes) |
56
+ | `variation` | string | `""` | Active layout variation name |
57
+ | `showEmptyBio` | boolean | `true` | Show profiles for authors without bios |
58
+ | `avatarHideDefault` | boolean | `false` | Hide default placeholder avatar |
59
+
60
+ ### Flat mode attributes
61
+
62
+ These only apply in flat mode (`layoutVersion: 1`). In nested mode, display is controlled by adding or removing inner blocks.
63
+
64
+ | Attribute | Type | Default | Description |
65
+ |-----------|------|---------|-------------|
66
+ | `showBio` | boolean | `true` | Display biography |
67
+ | `showSocial` | boolean | `true` | Display social links |
68
+ | `showEmail` | boolean | `true` | Display email link |
69
+ | `showArchiveLink` | boolean | `true` | Display "More by Author" link |
70
+ | `showAvatar` | boolean | `true` | Display avatar |
71
+ | `textSize` | string | `"medium"` | Text size: small, medium, large, extra-large |
72
+ | `avatarAlignment` | string | `"left"` | Avatar position: left or right |
73
+ | `avatarSize` | number | `128` | Avatar size in pixels |
74
+ | `avatarBorderRadius` | string | `"50%"` | Avatar border radius |
75
+
76
+ Additional `show*` attributes are dynamically added from Newspack Author Custom Fields when available (e.g., `shownewspack_job_title`, `shownewspack_role`, `shownewspack_employer`).
77
+
78
+ ## Block bindings
79
+
80
+ The `newspack-blocks/author` binding source is registered in both PHP ([view.php](./view.php)) and JS ([edit.js](./edit.js)). Supported keys:
81
+
82
+ | Key | Description |
83
+ |-----|-------------|
84
+ | `name` | Author display name |
85
+ | `bio` | Author biography |
86
+ | `url` / `archive_url` | Author archive URL |
87
+ | `archive_link_text` | "More by Author Name" text |
88
+ | `newspack_job_title` | Job title (custom field) |
89
+ | `newspack_role` | Role (custom field) |
90
+ | `newspack_employer` | Employer (custom field) |
91
+
92
+ In the editor, bindings resolve via a per-instance author map (`window.__newspackAuthorsByBlock`) keyed by `clientId`, so multiple Author Profile blocks on the same page don't conflict.
93
+
94
+ ## Editor behavior
95
+
96
+ - **Variation picker**: Shown when the block has no inner blocks (nested mode). Follows the same pattern as the core Columns block.
97
+ - **Author context**: The [AuthorContext](./context.js) React context passes author data to inner blocks. It's also exposed globally as `window.NewspackAuthorContext` for cross-package use (e.g., by the avatar block in newspack-plugin).
98
+ - **Template context**: In the Site Editor (templates/template parts), a placeholder author is shown with all supported social services populated.
99
+
100
+ ## Related
101
+
102
+ - [Author Social Links Block](https://github.com/Automattic/newspack-plugin/tree/trunk/src/blocks/author-profile-social) - Inner block for social media icons.
103
+ - [Avatar Block](https://github.com/Automattic/newspack-plugin/tree/trunk/src/blocks/avatar) - Inner block for author avatars.
104
+ - [Author Profile Card Template](../../templates/author-profile-card.php) - PHP template for flat-mode rendering.
105
+ - [Author List Block](../author-list/) - Directory-style author listing (different use case).
@@ -68,6 +68,10 @@
68
68
  "avatarHideDefault": {
69
69
  "type": "boolean",
70
70
  "default": false
71
+ },
72
+ "variation": {
73
+ "type": "string",
74
+ "default": ""
71
75
  }
72
76
  }
73
77
  }
@@ -2,8 +2,16 @@
2
2
  * WordPress dependencies
3
3
  */
4
4
  import apiFetch from '@wordpress/api-fetch';
5
- import { BlockControls, InnerBlocks, InspectorControls, useBlockProps } from '@wordpress/block-editor';
6
- import { createBlocksFromInnerBlocksTemplate, getBlockType, registerBlockBindingsSource } from '@wordpress/blocks';
5
+ import {
6
+ BlockControls,
7
+ InnerBlocks,
8
+ InspectorControls,
9
+ useBlockProps,
10
+ store as blockEditorStore,
11
+ // eslint-disable-next-line @wordpress/no-unsafe-wp-apis
12
+ __experimentalBlockVariationPicker as BlockVariationPicker,
13
+ } from '@wordpress/block-editor';
14
+ import { createBlocksFromInnerBlocksTemplate, getBlockType, registerBlockBindingsSource, store as blocksStore } from '@wordpress/blocks';
7
15
  import {
8
16
  Button,
9
17
  Card,
@@ -15,9 +23,6 @@ import {
15
23
  Spinner,
16
24
  ToggleControl,
17
25
  Toolbar,
18
- ToolbarButton,
19
- ToolbarGroup,
20
- Tooltip,
21
26
  // eslint-disable-next-line @wordpress/no-unsafe-wp-apis
22
27
  __experimentalUnitControl as UnitControl,
23
28
  // eslint-disable-next-line @wordpress/no-unsafe-wp-apis
@@ -172,151 +177,42 @@ export const avatarSizeOptions = [
172
177
  },
173
178
  ];
174
179
 
175
- // Helper to create a bound paragraph block with custom list view name.
176
- // If wrapInLink is true, the content will be wrapped in an anchor tag for editor preview.
177
- const createBoundParagraph = ( key, className, name, placeholder, wrapInLink = false ) => {
178
- const attributes = {
179
- metadata: {
180
- name, // Custom name shown in list view.
181
- bindings: {
182
- content: {
183
- source: 'newspack-blocks/author',
184
- args: { key },
185
- },
186
- },
180
+ /**
181
+ * Variation picker shown when the block has no inner blocks.
182
+ * Follows the same pattern as the core Columns block.
183
+ */
184
+ function VariationPlaceholder( { clientId, name, setAttributes } ) {
185
+ const { blockType, defaultVariation, variations } = useSelect(
186
+ select => {
187
+ const { getBlockVariations, getBlockType: _getBlockType, getDefaultBlockVariation } = select( blocksStore );
188
+ return {
189
+ blockType: _getBlockType( name ),
190
+ defaultVariation: getDefaultBlockVariation( name, 'block' ),
191
+ variations: getBlockVariations( name, 'block' ),
192
+ };
187
193
  },
188
- className,
189
- placeholder: placeholder || name,
190
- };
191
-
192
- // If wrapInLink is true, set initial content with link wrapper for editor preview.
193
- if ( wrapInLink ) {
194
- const linkText = placeholder || name;
195
- attributes.content = `<a href="#" class="no-op">${ linkText }</a>`;
196
- }
197
-
198
- return [ 'core/paragraph', attributes ];
199
- };
194
+ [ name ]
195
+ );
196
+ const { replaceInnerBlocks } = useDispatch( blockEditorStore );
197
+ const blockProps = useBlockProps();
200
198
 
201
- // Template for nested inner blocks.
202
- // Each author field is a separate block that can be reordered or removed.
203
- // Block bindings connect core block attributes to author data via 'newspack-blocks/author' source.
204
- const NESTED_TEMPLATE = [
205
- [
206
- 'core/columns',
207
- { isStackedOnMobile: true, className: 'author-profile-columns', templateLock: 'insert' },
208
- [
209
- [
210
- 'core/column',
211
- {
212
- className: 'author-profile-avatar-column',
213
- templateLock: 'insert',
214
- allowedBlocks: [ 'newspack/avatar' ],
215
- },
216
- [ [ 'newspack/avatar', { size: 128 } ] ],
217
- ],
218
- [
219
- 'core/column',
220
- {
221
- className: 'author-profile-content-column',
222
- templateLock: false,
223
- allowedBlocks: [ 'core/heading', 'core/paragraph', 'core/separator', 'core/spacer', 'newspack/author-profile-social' ],
224
- style: {
225
- spacing: {
226
- blockGap: 'var:preset|spacing|20',
227
- },
228
- elements: {
229
- link: {
230
- color: {
231
- text: 'var:preset|color|contrast-3',
232
- },
233
- },
234
- },
235
- },
236
- textColor: 'contrast-3',
237
- fontSize: 'small',
238
- },
239
- [
240
- [
241
- 'core/heading',
242
- {
243
- level: 3,
244
- metadata: {
245
- name: __( 'Author Name', 'newspack-blocks' ),
246
- bindings: {
247
- content: {
248
- source: 'newspack-blocks/author',
249
- args: { key: 'name' },
250
- },
251
- },
252
- },
253
- className: 'author-name',
254
- placeholder: __( 'Author Name', 'newspack-blocks' ),
255
- textColor: 'contrast',
256
- fontSize: 'large',
257
- },
258
- ],
259
- [
260
- 'core/paragraph',
261
- {
262
- metadata: {
263
- name: __( 'Job Title', 'newspack-blocks' ),
264
- bindings: {
265
- content: {
266
- source: 'newspack-blocks/author',
267
- args: { key: 'newspack_job_title' },
268
- },
269
- },
270
- },
271
- className: 'author-job-title',
272
- placeholder: __( 'Job Title', 'newspack-blocks' ),
273
- style: {
274
- typography: {
275
- fontStyle: 'normal',
276
- fontWeight: '600',
277
- },
278
- elements: {
279
- link: {
280
- color: {
281
- text: 'var:preset|color|contrast',
282
- },
283
- },
284
- },
285
- },
286
- textColor: 'contrast',
287
- },
288
- ],
289
- createBoundParagraph( 'newspack_role', 'author-role', __( 'Role', 'newspack-blocks' ) ),
290
- createBoundParagraph( 'newspack_employer', 'author-employer', __( 'Employer', 'newspack-blocks' ) ),
291
- createBoundParagraph( 'bio', 'author-bio', __( 'Biography', 'newspack-blocks' ) ),
292
- createBoundParagraph(
293
- 'archive_link_text',
294
- 'author-archive-link',
295
- sprintf(
296
- /* translators: %s: author name. */
297
- __( 'More by %s', 'newspack-blocks' ),
298
- __( 'Author Name', 'newspack-blocks' )
299
- ),
300
- undefined,
301
- true
302
- ),
303
- [
304
- 'newspack/author-profile-social',
305
- {
306
- style: {
307
- spacing: {
308
- padding: {
309
- top: 'var:preset|spacing|20',
310
- },
311
- },
312
- },
313
- },
314
- ],
315
- ],
316
- ],
317
- ],
318
- ],
319
- ];
199
+ return (
200
+ <div { ...blockProps }>
201
+ <BlockVariationPicker
202
+ icon={ blockType?.icon?.src }
203
+ label={ blockType?.title }
204
+ variations={ variations }
205
+ instructions={ __( 'Select a layout to start with:', 'newspack-blocks' ) }
206
+ onSelect={ ( nextVariation = defaultVariation ) => {
207
+ setAttributes( { ...nextVariation.attributes, variation: nextVariation.name } );
208
+ if ( nextVariation.innerBlocks ) {
209
+ replaceInnerBlocks( clientId, createBlocksFromInnerBlocksTemplate( nextVariation.innerBlocks ), true );
210
+ }
211
+ } }
212
+ />
213
+ </div>
214
+ );
215
+ }
320
216
 
321
217
  // Module-level cache for social icon SVGs so multiple block instances share one fetch.
322
218
  let socialIconSvgsCache = null;
@@ -360,7 +256,7 @@ const getPlaceholderAuthor = ( socialIconSvgs = {} ) => {
360
256
  };
361
257
 
362
258
  const AuthorProfile = ( { attributes, setAttributes, context, clientId } ) => {
363
- const { replaceInnerBlocks } = useDispatch( 'core/block-editor' );
259
+ const { replaceInnerBlocks } = useDispatch( blockEditorStore );
364
260
 
365
261
  // ALL HOOKS MUST BE CALLED UNCONDITIONALLY (React rules of hooks)
366
262
  const [ author, setAuthor ] = useState( null );
@@ -387,6 +283,7 @@ const AuthorProfile = ( { attributes, setAttributes, context, clientId } ) => {
387
283
  avatarSize,
388
284
  avatarHideDefault,
389
285
  showEmptyBio,
286
+ variation,
390
287
  } = attributes;
391
288
  const blockProps = useBlockProps();
392
289
 
@@ -435,6 +332,17 @@ const AuthorProfile = ( { attributes, setAttributes, context, clientId } ) => {
435
332
  return ( theme?.is_block_theme ?? false ) && !! getBlockType( 'newspack/avatar' ) && !! getBlockType( 'newspack/author-profile-social' );
436
333
  }, [] );
437
334
 
335
+ // Check for inner blocks (pattern picker shows when empty).
336
+ const hasInnerBlocks = useSelect( select => !! select( blockEditorStore ).getBlocks( clientId ).length, [ clientId ] );
337
+
338
+ // Look up the active variation's template from the store.
339
+ const blockVariations = useSelect( select => select( blocksStore ).getBlockVariations( 'newspack-blocks/author-profile', 'block' ), [] );
340
+ const defaultVariation = useMemo( () => blockVariations?.find( v => v.isDefault ), [ blockVariations ] );
341
+ const activeVariationTemplate = useMemo( () => {
342
+ const match = blockVariations?.find( v => v.name === variation );
343
+ return match?.innerBlocks || defaultVariation?.innerBlocks;
344
+ }, [ blockVariations, variation, defaultVariation ] );
345
+
438
346
  // Set layoutVersion to 2 for brand new blocks in block themes.
439
347
  // This persists the mode choice and enables InnerBlocks-based layout.
440
348
  // Only converts unconfigured blocks to preserve existing blocks created in classic themes.
@@ -445,6 +353,17 @@ const AuthorProfile = ( { attributes, setAttributes, context, clientId } ) => {
445
353
  }
446
354
  }, [ isNestedMode, layoutVersion, isUnconfiguredBlock, setAttributes ] );
447
355
 
356
+ // Auto-populate inner blocks from variation attribute on mount (e.g., when inserted from a pattern).
357
+ // Only runs once; subsequent empty states show the variation picker instead of auto-repopulating.
358
+ // Initialized with hasInnerBlocks so existing blocks (after page reload) don't re-populate.
359
+ const [ didAutoPopulate, setDidAutoPopulate ] = useState( hasInnerBlocks );
360
+ useEffect( () => {
361
+ if ( ! didAutoPopulate && variation && ! hasInnerBlocks && activeVariationTemplate ) {
362
+ replaceInnerBlocks( clientId, createBlocksFromInnerBlocksTemplate( activeVariationTemplate ), true );
363
+ setDidAutoPopulate( true );
364
+ }
365
+ }, [ didAutoPopulate, variation, hasInnerBlocks, activeVariationTemplate, clientId, replaceInnerBlocks ] );
366
+
448
367
  // Fetch author for specific mode
449
368
  useEffect( () => {
450
369
  if ( isContextual || 0 === authorId ) {
@@ -631,10 +550,6 @@ const AuthorProfile = ( { attributes, setAttributes, context, clientId } ) => {
631
550
  // In nested mode, hide field toggles since publishers control display by adding/removing blocks.
632
551
  const isNestedLayout = layoutVersion === 2;
633
552
 
634
- const resetLayout = () => {
635
- replaceInnerBlocks( clientId, createBlocksFromInnerBlocksTemplate( NESTED_TEMPLATE ), false );
636
- };
637
-
638
553
  // Inspector controls for display settings
639
554
  const inspectorControls = (
640
555
  <InspectorControls>
@@ -761,15 +676,6 @@ const AuthorProfile = ( { attributes, setAttributes, context, clientId } ) => {
761
676
  ] }
762
677
  />
763
678
  ) }
764
- { isNestedLayout && (
765
- <ToolbarGroup>
766
- <Tooltip text={ __( 'Reset layout', 'newspack-blocks' ) }>
767
- <ToolbarButton label={ __( 'Reset layout', 'newspack-blocks' ) } onClick={ resetLayout }>
768
- { __( 'Reset', 'newspack-blocks' ) }
769
- </ToolbarButton>
770
- </Tooltip>
771
- </ToolbarGroup>
772
- ) }
773
679
  </BlockControls>
774
680
  );
775
681
 
@@ -781,7 +687,7 @@ const AuthorProfile = ( { attributes, setAttributes, context, clientId } ) => {
781
687
  className="newspack-blocks-author-profile"
782
688
  icon={ postAuthor }
783
689
  label={ __( 'Author Profile', 'newspack-blocks' ) }
784
- instructions={ __( 'Select a type to start with.', 'newspack-blocks' ) }
690
+ instructions={ __( 'Select a type:', 'newspack-blocks' ) }
785
691
  >
786
692
  <Button variant="primary" onClick={ () => setAttributes( { isContextual: true } ) }>
787
693
  { __( 'Contextual', 'newspack-blocks' ) }
@@ -813,6 +719,11 @@ const AuthorProfile = ( { attributes, setAttributes, context, clientId } ) => {
813
719
  );
814
720
  }
815
721
 
722
+ // Variation picker: shown when block has no inner blocks.
723
+ if ( ! hasInnerBlocks ) {
724
+ return <VariationPlaceholder clientId={ clientId } name="newspack-blocks/author-profile" setAttributes={ setAttributes } />;
725
+ }
726
+
816
727
  // Mode selection for new blocks in nested mode
817
728
  if ( ! authorId && ! isContextual && ! showSpecificSelector ) {
818
729
  return modeSelectionPlaceholder;
@@ -958,9 +869,9 @@ const AuthorProfile = ( { attributes, setAttributes, context, clientId } ) => {
958
869
  { /* Key forces re-render when author changes, which re-evaluates bindings */ }
959
870
  <InnerBlocks
960
871
  key={ `author-${ previewAuthor?.id || 'none' }` }
961
- template={ NESTED_TEMPLATE }
872
+ template={ activeVariationTemplate }
962
873
  templateLock="insert"
963
- allowedBlocks={ [ 'core/columns' ] }
874
+ allowedBlocks={ [ 'core/columns', 'core/group' ] }
964
875
  />
965
876
  </div>
966
877
  </AuthorContext.Provider>
@@ -58,4 +58,4 @@
58
58
  [data-type="newspack-blocks/author-profile"]:not(.is-nested-mode) {
59
59
  display: block;
60
60
  }
61
- }
61
+ }
@@ -14,6 +14,7 @@ import { postAuthor } from '@wordpress/icons';
14
14
  * Internal dependencies
15
15
  */
16
16
  import edit from './edit';
17
+ import variations from './variations';
17
18
 
18
19
  /**
19
20
  * Style dependencies - will load in editor
@@ -53,6 +54,7 @@ export const settings = {
53
54
  default: '',
54
55
  },
55
56
  edit,
57
+ variations,
56
58
  // Save inner blocks for nested mode (layoutVersion 2).
57
59
  // For flat mode (layoutVersion 1), return null to use server-side rendering only.
58
60
  save: props => {