diff --git a/.eslintrc.js b/.eslintrc.js index 8a44b5ef74a1e6..122ec45369c224 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -331,6 +331,7 @@ module.exports = { message: 'Prefer page.locator instead.', }, ], + 'playwright/no-conditional-in-test': 'off', '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/no-floating-promises': 'error', '@typescript-eslint/no-misused-promises': 'error', diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 39f7caf6f806e7..65ba01d0b70e89 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -126,7 +126,7 @@ jobs: # dependency versions are installed and cached. ## - name: Set up PHP - uses: shivammathur/setup-php@7fdd3ece872ec7ec4c098ae5ab7637d5e0a96067 # v2.26.0 + uses: shivammathur/setup-php@a36e1e52ff4a1c9e9c9be31551ee4712a6cb6bd0 # v2.27.1 with: php-version: '${{ matrix.php }}' ini-file: development @@ -226,7 +226,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Set up PHP - uses: shivammathur/setup-php@7fdd3ece872ec7ec4c098ae5ab7637d5e0a96067 # v2.26.0 + uses: shivammathur/setup-php@a36e1e52ff4a1c9e9c9be31551ee4712a6cb6bd0 # v2.27.1 with: php-version: '7.4' coverage: none diff --git a/changelog.txt b/changelog.txt index 8159928bd84fb4..0f86c2d8f593b3 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,334 @@ == Changelog == += 17.1.0 = + + +## Changelog + +### Enhancements + +#### Block Library +- Navigation block: Fix Inaccurate description of the Show icon button setting. ([55429](https://github.com/WordPress/gutenberg/pull/55429)) +- Query Loop: Add accesibility markup at the end of the loop in all cases. ([55890](https://github.com/WordPress/gutenberg/pull/55890)) +- Template Part: Add fallback to the current theme when not provided. ([55965](https://github.com/WordPress/gutenberg/pull/55965)) +- Update components to use __next40pxDefaultSize. ([56022](https://github.com/WordPress/gutenberg/pull/56022)) + +#### Components +- Tabs: Improve focus behavior. ([55287](https://github.com/WordPress/gutenberg/pull/55287)) +- Tabs: Update subcomponents to accept full HTML element props. ([55860](https://github.com/WordPress/gutenberg/pull/55860)) +- TextControl: Add opt-in prop for 40px default size. ([55471](https://github.com/WordPress/gutenberg/pull/55471)) +- ToggleGroupControl: Add opt-in prop for 40px default size. ([55789](https://github.com/WordPress/gutenberg/pull/55789)) + +#### Patterns +- Move "Manage patterns" below "Detach pattern". ([56018](https://github.com/WordPress/gutenberg/pull/56018)) +- Show theme patterns from directory in site editor. ([55877](https://github.com/WordPress/gutenberg/pull/55877)) + +#### Global Styles +- Global Style Revisions: Ensure consistent back button behaviour. ([55881](https://github.com/WordPress/gutenberg/pull/55881)) +- Global Styles Revisions: More descriptive text timeline. ([55868](https://github.com/WordPress/gutenberg/pull/55868)) +- Global Styles Revisions: Add route for single styles revisions. ([55827](https://github.com/WordPress/gutenberg/pull/55827)) + +#### Block Locking +- Block Quick Navigation: Truncate text. ([56142](https://github.com/WordPress/gutenberg/pull/56142)) + +#### Block Editor +- Button block: Support double enter to skip to default block. ([56134](https://github.com/WordPress/gutenberg/pull/56134)) + +#### Design Tools +- Add block gap support to Quote block. ([56064](https://github.com/WordPress/gutenberg/pull/56064)) + +#### Post Editor +- "Detach" text change in template options. ([55870](https://github.com/WordPress/gutenberg/pull/55870)) + +#### Site Editor +- Site editor: Add edit page slug field. ([55767](https://github.com/WordPress/gutenberg/pull/55767)) + +#### Interactivity API +- Server directive processing: Process only root blocks. ([55739](https://github.com/WordPress/gutenberg/pull/55739)) + +#### Block settings menu +- Remove the extraneous template part title in replace control. ([55603](https://github.com/WordPress/gutenberg/pull/55603)) + +#### List View +- Add keyboard shortcut to select all blocks. ([54899](https://github.com/WordPress/gutenberg/pull/54899)) + + +### New APIs + +- Download blob: Remove downloadjs dependency. ([56024](https://github.com/WordPress/gutenberg/pull/56024)) + + +### Bug Fixes + +#### Block Library +- Background Image Support: Hide the background image reset button when there's no image. ([55973](https://github.com/WordPress/gutenberg/pull/55973)) +- Background image support: Fix focus loss when resetting background image. ([55984](https://github.com/WordPress/gutenberg/pull/55984)) +- Custom Link: Decode value in URL input field. ([55549](https://github.com/WordPress/gutenberg/pull/55549)) +- Fix lightbox trigger styles. ([55859](https://github.com/WordPress/gutenberg/pull/55859)) +- Form block: Use `type="submit"` for buttons. ([55690](https://github.com/WordPress/gutenberg/pull/55690)) +- Image block: Add check for lightbox values during image block migration. ([56057](https://github.com/WordPress/gutenberg/pull/56057)) +- Image block: Don't show pointer cursor on linked image in the editor. ([55882](https://github.com/WordPress/gutenberg/pull/55882)) +- Lightbox: Fix button misalignment in gallery image. ([56060](https://github.com/WordPress/gutenberg/pull/56060)) +- Lightbox: Fix close button position. ([56125](https://github.com/WordPress/gutenberg/pull/56125)) +- Missing block: Use raw source for originalContent. ([56014](https://github.com/WordPress/gutenberg/pull/56014)) +- Navigation Link block: Register variations on post type / taxonomy registration. ([54801](https://github.com/WordPress/gutenberg/pull/54801)) +- Pattern: Fix regression error in post type templates. ([55858](https://github.com/WordPress/gutenberg/pull/55858)) +- Pattern: Process embeds. ([55979](https://github.com/WordPress/gutenberg/pull/55979)) +- Post feature image block: Wrap images with hrefs in an A tag. ([55498](https://github.com/WordPress/gutenberg/pull/55498)) +- Quote Block: Fix the Quote block layout supports. ([55240](https://github.com/WordPress/gutenberg/pull/55240)) +- Read More block: Reduce text decoration specificity. ([56038](https://github.com/WordPress/gutenberg/pull/56038)) + +#### Data Views +- DataViews: Add missing key to `ResetFilters` component. ([56189](https://github.com/WordPress/gutenberg/pull/56189)) +- DataViews: Fix issue with irrelevant statuses. ([55967](https://github.com/WordPress/gutenberg/pull/55967)) +- DataViews: Fix nested button tags on sidebar. ([56089](https://github.com/WordPress/gutenberg/pull/56089)) +- DataViews: Fix pagination on manual input. ([55940](https://github.com/WordPress/gutenberg/pull/55940)) +- DataViews: Fix spacing issue in top-level bar. ([56151](https://github.com/WordPress/gutenberg/pull/56151)) +- DataViews: Fix status filter upon switching the default views from the sidebar. ([55856](https://github.com/WordPress/gutenberg/pull/55856)) +- DataViews: Make items per page an even number. ([55906](https://github.com/WordPress/gutenberg/pull/55906)) +- DataViews: Make used taxonomy private. ([55918](https://github.com/WordPress/gutenberg/pull/55918)) +- DataViews: Reset pagination upon filter change. ([55797](https://github.com/WordPress/gutenberg/pull/55797)) +- DataViews: Add a missing icon for the side by side view. ([55925](https://github.com/WordPress/gutenberg/pull/55925)) + +#### Components +- DropdownMenu: Remove extra vertical space around the toggle button. ([56136](https://github.com/WordPress/gutenberg/pull/56136)) +- DropdownMenuV2: Prevent default on Escape key presses. ([55962](https://github.com/WordPress/gutenberg/pull/55962)) +- DropdownMenuV2: Use the `Icon` component to render radio checks. ([55964](https://github.com/WordPress/gutenberg/pull/55964)) + +#### Typography +- Fix fatal error in WP_Fonts_Resolver::Get_settings(). ([55981](https://github.com/WordPress/gutenberg/pull/55981)) +- Font Library: Create fonts dir if a font face needs to use the filesystem. ([56120](https://github.com/WordPress/gutenberg/pull/56120)) +- Font Library: Fix font installation failure. ([55893](https://github.com/WordPress/gutenberg/pull/55893)) + +#### Block Editor +- Iframe: Bubble events from html element instead of body element to fix drag chip positioning. ([56099](https://github.com/WordPress/gutenberg/pull/56099)) +- Post Featured Image: Handling correctly when uploading a file without mime type. ([56133](https://github.com/WordPress/gutenberg/pull/56133)) +- Block Editor: Fix Block editor crash. ([56051](https://github.com/WordPress/gutenberg/pull/56051)) +- Move clientId key to BlockContextualToolbar. ([56008](https://github.com/WordPress/gutenberg/pull/56008)) + +#### Patterns +- Add context for translators to any unclear usage of "synced". ([55935](https://github.com/WordPress/gutenberg/pull/55935)) +- Use existing download function for JSON downloads to fix non-ASCII encoding. ([55912](https://github.com/WordPress/gutenberg/pull/55912)) + +#### Inspector Controls +- Global Styles: Don't show "Apply Styles Globally" button in non-block based themes. ([56033](https://github.com/WordPress/gutenberg/pull/56033)) + +#### Template Editor +- Templates: Update filter to call all of the individual methods. ([55980](https://github.com/WordPress/gutenberg/pull/55980)) + +#### Global Styles +- Global styles revisions: Load unsaved revision item into the revisions preview. ([55880](https://github.com/WordPress/gutenberg/pull/55880)) + +#### Post Editor +- Edit Post: Fix pattern modal reopening when making the title empty again. ([55873](https://github.com/WordPress/gutenberg/pull/55873)) + +#### Data Layer +- Core data: Fix wrong store results when page receives less items that what is stored. ([55832](https://github.com/WordPress/gutenberg/pull/55832)) + + +### Accessibility + +#### Data Views +- DataViews: Add labels to "in-filters". ([56001](https://github.com/WordPress/gutenberg/pull/56001)) +- DataViews: Show actions label. ([56027](https://github.com/WordPress/gutenberg/pull/56027)) + +#### Components +- Fix the image link button pressed state. ([56123](https://github.com/WordPress/gutenberg/pull/56123)) + +#### Block Editor +- Fix mismatching link control action buttons visual order and DOM order. ([56042](https://github.com/WordPress/gutenberg/pull/56042)) +- Escape on Block Toolbar returns focus to Editor Canvas. ([55712](https://github.com/WordPress/gutenberg/pull/55712)) + +#### Site Editor +- Prevent sidebar focus in site editor on small screens. ([55934](https://github.com/WordPress/gutenberg/pull/55934)) + +#### Block Library +- Heading level dropdown: Remove obtrusive tooltips in favor of visible text. ([56035](https://github.com/WordPress/gutenberg/pull/56035)) + + +### Performance + +#### Tooling +- Add a metric to trace template navigation in the site editor. ([55796](https://github.com/WordPress/gutenberg/pull/55796)) + +#### List View +- ListViewBlock: Combine 'useSelect' hooks. ([55889](https://github.com/WordPress/gutenberg/pull/55889)) + +#### Block Editor +- Block Editor: Optimize 'Block Hooks' inspector controls. ([56101](https://github.com/WordPress/gutenberg/pull/56101)) +- Block Editor: Optimize BlockListAppender. ([56116](https://github.com/WordPress/gutenberg/pull/56116)) + +#### Site Editor +- Avoid rerendering the sitehub unnecessarily. ([55818](https://github.com/WordPress/gutenberg/pull/55818)) + +#### Layout +- Block Editor: Optimize layout style renderer subscription. ([55762](https://github.com/WordPress/gutenberg/pull/55762)) + + +### Experiments + +#### Data Views +- DataViews: Add ability to create custom views. ([55773](https://github.com/WordPress/gutenberg/pull/55773)) +- DataViews: Add control to reset all filters at once. ([55955](https://github.com/WordPress/gutenberg/pull/55955)) +- DataViews: Add delete and restore actions. ([55781](https://github.com/WordPress/gutenberg/pull/55781)) +- DataViews: Add initial "Side by side" prototype. ([55343](https://github.com/WordPress/gutenberg/pull/55343)) +- DataViews: Add new page size option. ([56112](https://github.com/WordPress/gutenberg/pull/56112)) +- DataViews: Add rename functionality to custom views. ([55997](https://github.com/WordPress/gutenberg/pull/55997)) +- DataViews: Allow users to add filters dynamically. ([55992](https://github.com/WordPress/gutenberg/pull/55992)) +- DataViews: Update 'All pages' sidebar heading. ([56148](https://github.com/WordPress/gutenberg/pull/56148)) +- DataViews: Update 'View' button. ([56144](https://github.com/WordPress/gutenberg/pull/56144)) +- DataViews: Update `all templates` page. ([55848](https://github.com/WordPress/gutenberg/pull/55848)) +- DataViews: Update author and title fields in template's list. ([56029](https://github.com/WordPress/gutenberg/pull/56029)) +- DataViews: Update filters in view configuration. ([55735](https://github.com/WordPress/gutenberg/pull/55735)) +- DataViews: Add filters to table columns. ([55508](https://github.com/WordPress/gutenberg/pull/55508)) +- DataViews: Add: Ability to delete custom views. ([55924](https://github.com/WordPress/gutenberg/pull/55924)) +- DataViews: Add: Custom views header indication. ([55926](https://github.com/WordPress/gutenberg/pull/55926)) +- DataViews: Remove unnecessary label when no visible filters exist. ([55838](https://github.com/WordPress/gutenberg/pull/55838)) + + +### Documentation + +- Add a first block type page to the platform documentation. ([56109](https://github.com/WordPress/gutenberg/pull/56109)) +- Add new block development "Quick Start Guide" and update the `create-block-tutorial-template`. ([56056](https://github.com/WordPress/gutenberg/pull/56056)) +- Clean up DataViews docs: `filter.id` is not used. ([55833](https://github.com/WordPress/gutenberg/pull/55833)) +- DataViews: Document `enableSorting` and `enableHiding`. ([55988](https://github.com/WordPress/gutenberg/pull/55988)) +- DataViews: Document actions. ([55959](https://github.com/WordPress/gutenberg/pull/55959)) +- Doc: Corrected + updated links. ([56084](https://github.com/WordPress/gutenberg/pull/56084)) +- Doc: Fixes wrong link in #56084. ([56106](https://github.com/WordPress/gutenberg/pull/56106)) +- Docs: Changes imports from `wp.editor` to `wp.blockEditor` for PlainText and RichText. ([55841](https://github.com/WordPress/gutenberg/pull/55841)) +- Fix formatting issue in the "Get started with create-block" doc. ([55872](https://github.com/WordPress/gutenberg/pull/55872)) +- Fix: 404 Link on git workflow docs. ([55897](https://github.com/WordPress/gutenberg/pull/55897)) +- Fix: 404 link in get-started-with-create-block docs. ([55932](https://github.com/WordPress/gutenberg/pull/55932)) +- Fix: Create meta block link in block attributes documentation. ([55804](https://github.com/WordPress/gutenberg/pull/55804)) +- Fix: Filter duotone link on block-supports documentation. ([55896](https://github.com/WordPress/gutenberg/pull/55896)) +- Fix: Two invalid links on docs/contributors/documentation/README.md. ([55843](https://github.com/WordPress/gutenberg/pull/55843)) +- New additional resource for wp-env. ([55987](https://github.com/WordPress/gutenberg/pull/55987)) +- Update documentation to clarify workflow branch for release package publishing. ([56183](https://github.com/WordPress/gutenberg/pull/56183)) +- Update jest links to the new site. ([55802](https://github.com/WordPress/gutenberg/pull/55802)) + + +### Code Quality + +- Block lib: Remove multiline=false (deprecated). ([56113](https://github.com/WordPress/gutenberg/pull/56113)) +- Delete unused `SelectedBlockPopover` component. ([55821](https://github.com/WordPress/gutenberg/pull/55821)) +- Fix: Remove unrequired nullish coalescing. ([55854](https://github.com/WordPress/gutenberg/pull/55854)) +- Fix: Use of integer value in a conditional rendering condition on Gradients. ([55855](https://github.com/WordPress/gutenberg/pull/55855)) +- Give nice unique names to block controls HOCs. ([55795](https://github.com/WordPress/gutenberg/pull/55795)) +- Migrating `PatternTransformationsMenu`. ([56122](https://github.com/WordPress/gutenberg/pull/56122)) +- Migrating block inserter media tab components. ([56195](https://github.com/WordPress/gutenberg/pull/56195)) +- Move document tools motion to header-edit-mode layout level. ([55904](https://github.com/WordPress/gutenberg/pull/55904)) +- Only render block toolbar if blockType has value. ([55861](https://github.com/WordPress/gutenberg/pull/55861)) +- Refactor Edit Widgets Document Tools Navigation to own component. ([55778](https://github.com/WordPress/gutenberg/pull/55778)) +- Refactor Selected Block Tools. ([55737](https://github.com/WordPress/gutenberg/pull/55737)) +- Refactor Site Editor Document Tools Navigation to own component. ([55770](https://github.com/WordPress/gutenberg/pull/55770)) +- Remove BlockStyles.Slot empty component. ([55991](https://github.com/WordPress/gutenberg/pull/55991)) +- Remove obsolete `queryContext`. ([56034](https://github.com/WordPress/gutenberg/pull/56034)) +- Remove unnecessary empty className. ([55998](https://github.com/WordPress/gutenberg/pull/55998)) +- Rename Unforward to Unforwarded and export the named const. ([55820](https://github.com/WordPress/gutenberg/pull/55820)) +- Render Selected Block Tools in Header when using Top Toolbar. ([55787](https://github.com/WordPress/gutenberg/pull/55787)) +- Reusable Blocks: Unlock a private hook and a component at the file level. ([55809](https://github.com/WordPress/gutenberg/pull/55809)) +- Server directive processing: Improve how block references are saved. ([56107](https://github.com/WordPress/gutenberg/pull/56107)) +- Share the editor settings between the post and site editors. ([55970](https://github.com/WordPress/gutenberg/pull/55970)) +- Site Editor: Fix deprecation console error in top toolbar. ([55678](https://github.com/WordPress/gutenberg/pull/55678)) +- Site Editor: Unlock global styles' private hooks at the file level. ([55800](https://github.com/WordPress/gutenberg/pull/55800)) +- Site Editor: Update edited entity sync logic. ([55928](https://github.com/WordPress/gutenberg/pull/55928)) +- Site Editor: Use EditorProvider instead of custom logic. ([56000](https://github.com/WordPress/gutenberg/pull/56000)) +- SiteEditor: Optimize BackToPageNotification component. ([56102](https://github.com/WordPress/gutenberg/pull/56102)) +- SiteEditor: Refactor disable non page content blocks. ([56103](https://github.com/WordPress/gutenberg/pull/56103)) +- Unify the PageUrl and PageSlug components between site and post editors. ([56203](https://github.com/WordPress/gutenberg/pull/56203)) + +#### Data Views +- DataViews: Fix translatable string. ([56075](https://github.com/WordPress/gutenberg/pull/56075)) +- DataViews: Remove `filter.name`. ([55834](https://github.com/WordPress/gutenberg/pull/55834)) +- DataViews: Remove reset values from filters. ([55839](https://github.com/WordPress/gutenberg/pull/55839)) +- DataViews: Remove unnecessary `sortingFn` prop from field description. ([55989](https://github.com/WordPress/gutenberg/pull/55989)) +- DataViews: Simplify filters API. ([55917](https://github.com/WordPress/gutenberg/pull/55917)) +- DataViews: Update actions API. ([56026](https://github.com/WordPress/gutenberg/pull/56026)) + +#### Block Editor +- Rich text: Remove preserveWhiteSpace serialisation differences. ([55999](https://github.com/WordPress/gutenberg/pull/55999)) +- Rich text: highlight format: Gracefully handle old span format. ([56071](https://github.com/WordPress/gutenberg/pull/56071)) +- Update Link Control labels to use gray-900. ([55867](https://github.com/WordPress/gutenberg/pull/55867)) + +#### Components +- `DisclosureContent`: Migrate from `reakit` to `@ariakit/react`. ([55639](https://github.com/WordPress/gutenberg/pull/55639)) +- `Divider`: Migrate from `reakit` to `@ariakit/react`. ([55622](https://github.com/WordPress/gutenberg/pull/55622)) +- `RadioGroup`: Migrate from `reakit` to `ariakit`. ([55580](https://github.com/WordPress/gutenberg/pull/55580)) + +#### Site Editor +- Core Data: Move the template lookup to core-data selectors/resolvers. ([55883](https://github.com/WordPress/gutenberg/pull/55883)) +- Don't use 'useEntityRecord' to only dispatch actions. ([56076](https://github.com/WordPress/gutenberg/pull/56076)) + +#### Block Library +- Navigation: Refactor the PHP render function to make it easier to make changes in the future. ([55605](https://github.com/WordPress/gutenberg/pull/55605)) +- Update `blockEditor.__unstableCanInsertBlockType` hook namespace. ([55845](https://github.com/WordPress/gutenberg/pull/55845)) + +#### Data Layer +- Data: Fix ESLint warnings for the 'useSelect' hook. ([55916](https://github.com/WordPress/gutenberg/pull/55916)) + +#### Post Editor +- Edit Post: Use a single 'useSelect' hook for getting selectors. ([55902](https://github.com/WordPress/gutenberg/pull/55902)) + +#### Colors +- Add Unit testing for duotone enhanced pagination. ([55542](https://github.com/WordPress/gutenberg/pull/55542)) + +#### Patterns +- Split up the block editor inserter patterns tab into separate component files. ([55315](https://github.com/WordPress/gutenberg/pull/55315)) + +#### Design Tools +- Block styles: Remove __unstableElementContext in favour of useStyleOverride. ([54493](https://github.com/WordPress/gutenberg/pull/54493)) + + +### Tools + +- Issue Templates: Add default type labels to issue templates. ([55826](https://github.com/WordPress/gutenberg/pull/55826)) +- Label enforcer: Make the warning message less scary for new contributors. ([55900](https://github.com/WordPress/gutenberg/pull/55900)) +- Quote feature request label. ([55862](https://github.com/WordPress/gutenberg/pull/55862)) + +#### Testing +- Disable 'no-conditional-in-test' ESLint rule for Playwright. ([56088](https://github.com/WordPress/gutenberg/pull/56088)) +- Fix 'Block Switcher' test file name for Playwright end-to-end tests. ([55840](https://github.com/WordPress/gutenberg/pull/55840)) +- Fix flaky 'Meta boxes' end-to-end tests. ([56083](https://github.com/WordPress/gutenberg/pull/56083)) +- Migrate 'CPT locking' end-to-end tests to Playwright. ([55929](https://github.com/WordPress/gutenberg/pull/55929)) +- Migrate 'Meta boxes' end-to-end tests to Playwright. ([55915](https://github.com/WordPress/gutenberg/pull/55915)) +- Migrate 'Plugins API' end-to-end tests to Playwright. ([55958](https://github.com/WordPress/gutenberg/pull/55958)) +- Migrate 'annotations' end-to-end tests to Playwright. ([55966](https://github.com/WordPress/gutenberg/pull/55966)) +- Migrate 'container blocks' end-to-end tests to Playwright. ([56141](https://github.com/WordPress/gutenberg/pull/56141)) +- Migrate 'inner-blocks-prioritized-inserter-blocks' end-to-end tests to Playwright. ([55828](https://github.com/WordPress/gutenberg/pull/55828)) +- Migrate 'inner-blocks-render-appender' end-to-end tests to Playwright. ([55814](https://github.com/WordPress/gutenberg/pull/55814)) +- Migrate 'meta-attribute-block' end-to-end tests to Playwright. ([55830](https://github.com/WordPress/gutenberg/pull/55830)) +- Migrate Child Block Test to Playwright. ([55199](https://github.com/WordPress/gutenberg/pull/55199)) +- Migrate flaky PostPublishButton end-to-end tests to Playwright. ([52285](https://github.com/WordPress/gutenberg/pull/52285)) +- Perf Tests: Stabilise the Site Editor metrics. ([55922](https://github.com/WordPress/gutenberg/pull/55922)) +- Playwright Utils: Fix 'clickBlockOptionsMenuItem' helper. ([55923](https://github.com/WordPress/gutenberg/pull/55923)) +- Query block enhanced pagination: Simplify test setup. ([55805](https://github.com/WordPress/gutenberg/pull/55805)) +- Site editor template preview: Add end-to-end test and aria-pressed attribute to template preview toggle. ([56096](https://github.com/WordPress/gutenberg/pull/56096)) +- Upgrade Playwright to 1.39.0. ([54051](https://github.com/WordPress/gutenberg/pull/54051)) +- end-to-end Utils: Add setPreferences and editPost utils. ([55099](https://github.com/WordPress/gutenberg/pull/55099)) +- end-to-end Utils: Add support for web-vitals.js. ([55660](https://github.com/WordPress/gutenberg/pull/55660)) + +#### Build Tooling +- Package `@ariakit/test` should be a dev dependency. ([56091](https://github.com/WordPress/gutenberg/pull/56091)) + + +## First time contributors + +The following PRs were merged by first time contributors: + +- @joanrodas: Update Link Control labels to use gray-900. ([55867](https://github.com/WordPress/gutenberg/pull/55867)) +- @JorgeVilchez95: "Detach" text change in template options. ([55870](https://github.com/WordPress/gutenberg/pull/55870)) +- @sacerro: Styles: More descriptive text for revisions timeline. ([55868](https://github.com/WordPress/gutenberg/pull/55868)) + + +## Contributors + +The following contributors merged PRs in this release: + +@afercia @andrewhayward @andrewserong @anomiex @anton-vlasenko @aristath @artemiomorales @bph @brookewp @c4rl0sbr4v0 @chad1008 @ciampo @DAreRodz @dcalhoun @dsas @ellatrix @flootr @fluiddot @gaambo @glendaviesnz @gziolo @jameskoster @jeryj @jhnstn @joanrodas @jorgefilipecosta @JorgeVilchez95 @jsnajdr @juanmaguitar @kevin940726 @Mamaduka @masteradhoc @matiasbenedetto @ndiego @ntsekouras @oandregal @peterwilsoncc @pooja-muchandikar @priethor @ramonjd @renatho @richtabor @sacerro @scruffian @shimotmk @SiobhyB @Soean @swissspidy @t-hamano @talldan @tellthemachines @torounit @tyxla @WunderBart @youknowriad + + + + = 17.0.2 = diff --git a/docs/README.md b/docs/README.md index d04df59e957529..b94a8d78d41a75 100644 --- a/docs/README.md +++ b/docs/README.md @@ -59,7 +59,7 @@ This handbook should be considered the canonical resource for all things related - [**Learn WordPress**](https://learn.wordpress.org/) - The WordPress hub for learning resources where you can find courses like [Introduction to Block Development: Build your first custom block](https://learn.wordpress.org/course/introduction-to-block-development-build-your-first-custom-block/), [Converting a Shortcode to a Block](https://learn.wordpress.org/course/converting-a-shortcode-to-a-block/) or [Using the WordPress Data Layer](https://learn.wordpress.org/course/using-the-wordpress-data-layer/) - [**WordPress.tv**](https://wordpress.tv/) - A hub of WordPress-related videos (from talks at WordCamps to recordings of online workshops) curated and moderated by the WordPress.org community. You’re sure to find something to aid your learning about [block development](https://wordpress.tv/?s=block%20development&sort=newest) or the [block-editor](https://wordpress.tv/?s=block%20editor&sort=relevance) here. - [**Gutenberg repository**](https://github.com/WordPress/gutenberg/) - Development of the block editor project is carried out in this GitHub repository. It contains the code of interesting packages such as [`block-library`](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src) (core blocks) or [`components`](https://github.com/WordPress/gutenberg/tree/trunk/packages/components) (common UI elements). _The [gutenberg-examples](https://github.com/WordPress/gutenberg-examples) repository is another useful reference._ - +- [**End User Documentation**](https://wordpress.org/documentation/) - Documentation site targeted to the end user (not developers) where you can also find documentation about the [Block Editor](https://wordpress.org/documentation/category/block-editor/) and [working with blocks](https://wordpress.org/documentation/article/work-with-blocks/). ## Are you in the right place? @@ -70,4 +70,4 @@ This handbook should be considered the canonical resource for all things related - [/apis](https://developer.wordpress.org/apis) - Common APIs Handbook - [/advanced-administration](https://developer.wordpress.org/advanced-administration) - WP Advanced Administration Handbook - [/rest-api](https://developer.wordpress.org/rest-api/) - REST API Handbook -- [/coding-standards](https://developer.wordpress.org/coding-standards) - Best practices for WordPress developers \ No newline at end of file +- [/coding-standards](https://developer.wordpress.org/coding-standards) - Best practices for WordPress developers diff --git a/docs/contributors/code/release.md b/docs/contributors/code/release.md index 83868f95ce184d..8c8ed3ff3c334e 100644 --- a/docs/contributors/code/release.md +++ b/docs/contributors/code/release.md @@ -422,7 +422,7 @@ Now, the `wp/X.Y` branch is ready for publishing npm packages. In order to start ![Run workflow dropdown for npm publishing](https://developer.wordpress.org/files/2023/07/image-2.png) -To publish packages to npm for the WordPress major release, select `wp` from the "Release type" dropdown and enter `X.Y` (example `5.2`) in the "WordPress major release" input field. Finally, press the green "Run workflow" button. It triggers the npm publishing job, and this needs to be approved by a Gutenberg Core team member. Locate the ["Publish npm packages" action](https://github.com/WordPress/gutenberg/actions/workflows/publish-npm-packages.yml) for the current publishing, and have it [approved](https://docs.github.com/en/actions/managing-workflow-runs/reviewing-deployments#approving-or-rejecting-a-job). +To publish packages to npm for the WordPress major release, select `trunk` as the branch to run the workflow from (this means that the script used to run the workflow comes from the trunk branch, though the packages themselves will published from the release branch as long as the correct "Release type" is selected below), then select `wp` from the "Release type" dropdown and enter `X.Y` (example `5.2`) in the "WordPress major release" input field. Finally, press the green "Run workflow" button. It triggers the npm publishing job, and this needs to be approved by a Gutenberg Core team member. Locate the ["Publish npm packages" action](https://github.com/WordPress/gutenberg/actions/workflows/publish-npm-packages.yml) for the current publishing, and have it [approved](https://docs.github.com/en/actions/managing-workflow-runs/reviewing-deployments#approving-or-rejecting-a-job). For the record, the manual process would look like the following: diff --git a/docs/contributors/versions-in-wordpress.md b/docs/contributors/versions-in-wordpress.md index 649fe10d439aa6..4449f13996c629 100644 --- a/docs/contributors/versions-in-wordpress.md +++ b/docs/contributors/versions-in-wordpress.md @@ -6,6 +6,7 @@ If anything looks incorrect here, please bring it up in #core-editor in [WordPre | Gutenberg Versions | WordPress Version | | ------------------ | ----------------- | +| 16.2-16.7 | 6.4.1 | | 16.2-16.7 | 6.4 | | 15.2-16.1 | 6.3.1 | | 15.2-16.1 | 6.3 | diff --git a/docs/getting-started/create-block/README.md b/docs/getting-started/create-block/README.md index 2b87f09f1f903c..22a28560c76a81 100644 --- a/docs/getting-started/create-block/README.md +++ b/docs/getting-started/create-block/README.md @@ -8,24 +8,6 @@ The tutorial includes setting up your development environment, tools, and gettin The first thing you need is a development environment and tools. This includes setting up your WordPress environment, Node, NPM, and your code editor. If you need help, see the [setting up your development environment documentation](/docs/getting-started/devenv/README.md). -## Quick Start - -The `@wordpress/create-block` package exists to create the necessary block scaffolding to get you started. See [create-block package documentation](https://www.npmjs.com/package/@wordpress/create-block) for additional features. This quick start assumes you have a development environment with node installed, and a WordPress site. - -From your plugins directory, to create your block run: - -```sh -npx @wordpress/create-block gutenpride --template @wordpress/create-block-tutorial-template -``` - -> Remember that you should use Node.js v14. Other versions may result in an error in the terminal. See [Node Development Tools](https://developer.wordpress.org/block-editor/getting-started/devenv/#node-development-tools) for more info. - -The [npx command](https://docs.npmjs.com/cli/v8/commands/npx) runs a command from a remote package, in this case our create-block package that will create a new directory called `gutenpride`, installs the necessary files, and builds the block plugin. If you want an interactive mode that prompts you for details, run the command without the `gutenpride` name. - -You now need to activate the plugin from inside wp-admin plugins page. - -After activation, go to the block editor and use the inserter to search and add your new block. - ## Table of Contents The create a block tutorials breaks down to the following sections. diff --git a/docs/getting-started/devenv/get-started-with-wp-scripts.md b/docs/getting-started/devenv/get-started-with-wp-scripts.md index 8a7d100c8921fa..6416adc081e70a 100644 --- a/docs/getting-started/devenv/get-started-with-wp-scripts.md +++ b/docs/getting-started/devenv/get-started-with-wp-scripts.md @@ -20,7 +20,7 @@ The package abstracts away much of the initial setup, configuration, and boilerp ## Quick start
- If you want to build a custom block, the @wordpress/create-block package allows you to scaffold the structure of files needed to create and register a block. It generates all the necessary code to start a project and integrates a modern JavaScript build setup (using wp-scripts) with no configuration required. Refer to Get started with create-block for more details. + If you use @wordpress/create-block package to scaffold the structure of files needed to create and register a block, you'll also get a modern JavaScript build setup (using wp-scripts) with no configuration required, so you don't need to worry about installing wp-scripts or enqueuing assets. Refer to Get started with create-block for more details.
### Installation @@ -64,15 +64,25 @@ Once installed, you can run the predefined scripts provided with `wp-scripts` by } ``` -These scripts can then be run using the command `npm run {script name}`. The two scripts you will use most often are `start` and `build` since they handle the build step. See the [package documentation](https://developer.wordpress.org/block-editor/packages/packages-scripts/) for all options. +These scripts can then be run using the command `npm run {script name}`. + +### The build process with `wp-scripts` + +The two scripts you will use most often are `start` and `build` since they handle the build step. See the [package documentation](https://developer.wordpress.org/block-editor/packages/packages-scripts/) for all options. When working on your project, use the `npm run start` command. This will start a development server and automatically rebuild the project whenever any change is detected. Note that the compiled code in `build/index.js` will not be optimized. When you are ready to deploy your project, use the `npm run build` command. This optimizes your code and makes it production-ready. -After the build finishes, you will see the compiled JavaScript file created at `build/index.js`. A `build/index.asset.php` file will also be created, which contains an array of dependencies and a version number (for cache busting). +After the build finishes, you will see the compiled JavaScript file created at `build/index.js`. + +A `build/index.asset.php` file will also be created in the build process, which contains an array of dependencies and a version number (for cache busting). Please, note that to register a block without this `wp-scripts` build process you'll need to manually create `*.asset.php` dependencies files (see [example](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/minimal-block-no-build-e621a6)). -Enqueue the file in the Editor using PHP as you would any other JavaScript file. You can refer to the [Enqueueing assets in the Editor](https://developer.wordpress.org/block-editor/how-to-guides/enqueueing-assets-in-the-editor/) guide for more information, but here's a typical implementation. +### Enqueuing assets + +If you register a block via `register_block_type` the scripts defined in `block.json` will be automatically enqueued (see [example](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/minimal-block-ca6eda)) + +To manually enqueue files in the editor, in any other context, you can refer to the [Enqueueing assets in the Editor](https://developer.wordpress.org/block-editor/how-to-guides/enqueueing-assets-in-the-editor/) guide for more information, but here's a typical implementation. ```php /** @@ -91,6 +101,8 @@ function example_project_enqueue_editor_assets() { add_action( 'enqueue_block_editor_assets', 'example_project_enqueue_editor_assets' ); ``` +Here's [an example](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/data-basics-59c8f8) of manually enqueuing files in the editor. + ## Next steps While `start` and `build` will be the two most used scripts, several other useful tools come with `wp-scripts` that are worth exploring. Here's a look at a few. diff --git a/docs/getting-started/quick-start-guide.md b/docs/getting-started/quick-start-guide.md new file mode 100644 index 00000000000000..e978b250ab8aff --- /dev/null +++ b/docs/getting-started/quick-start-guide.md @@ -0,0 +1,44 @@ +# Quick Start Guide + +This guide is designed to demonstrate the basic principles of block development in WordPress using a hands-on approach. Following the steps below, you will create a custom block plugin that uses modern JavaScript (ESNext and JSX) in a matter of minutes. The example block displays the copyright symbol (©) and the current year, the perfect addition to any website's footer. + +## Scaffold the block plugin + +Start by ensuring you have Node.js and `npm` installed on your computer. Review the [Node.js development environment](https://developer.wordpress.org/block-editor/getting-started/devenv/nodejs-development-environment/) guide if not. + +Next, use the [`@wordpress/create-block`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/) package and the [`@wordpress/create-block-tutorial-template`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block-tutorial-template/) template to scaffold the complete “Copyright Date Block” plugin. + +
+

You can use create-block to scaffold a block just about anywhere and then use wp-env inside the generated plugin folder. This will create a local WordPress development environment with your new block plugin installed and activated.

+

If you already have your own local WordPress development environment, navigate to the plugins/ folder using the terminal.

+
+ +Choose the folder where you want to create the plugin, and then execute the following command in the terminal from within that folder: + +```sh +npx @wordpress/create-block copyright-date-block --template @wordpress/create-block-tutorial-template +``` + +The `slug` provided (`copyright-date-block`) defines the folder name for the scaffolded plugin and the internal block name. + +Navigate to the Plugins page of your local WordPress installation and activate the “Copyright Date Block” plugin. The example block will then be available in the Editor. + +## Basic usage + +With the plugin activated, you can explore how the block works. Use the following command to move into the newly created plugin folder and start the development process. + +```sh +cd copyright-date-block && npm start +``` + +When `create-block` scaffolds the block, it installs `wp-scripts` and adds the most common scripts to the block’s `package.json` file. Refer to the [Get started with wp-scripts](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-scripts/) article for an introduction to this package. + +The `npm start` command will start a development server and watch for changes in the block’s code, rebuilding the block whenever modifications are made. + +When you are finished making changes, run the `npm run build` command. This optimizes the block code and makes it production-ready. + +## Additional resources + +- [Get started with create-block](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-create-block/) +- [Get started with wp-scripts](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-scripts/) +- [Get started with wp-env](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-env/) diff --git a/docs/how-to-guides/block-tutorial/README.md b/docs/how-to-guides/block-tutorial/README.md index 95aa4182430c07..8688bd09416d7b 100644 --- a/docs/how-to-guides/block-tutorial/README.md +++ b/docs/how-to-guides/block-tutorial/README.md @@ -2,9 +2,9 @@ The purpose of this tutorial is to step through the fundamentals of creating a new block type. Beginning with the simplest possible example, each new section will incrementally build upon the last to include more of the common functionality you could expect to need when implementing your own block types. -To follow along with this tutorial, you can download the [accompanying WordPress plugin](https://github.com/WordPress/gutenberg-examples) which includes all of the examples for you to try on your own site. At each step along the way, experiment by modifying the examples with your own ideas, and observe the effects they have on the block's behavior. +To follow along with this tutorial, you can download the [accompanying WordPress plugin](https://github.com/WordPress/block-development-examples) which includes all of the examples for you to try on your own site. At each step along the way, experiment by modifying the examples with your own ideas, and observe the effects they have on the block's behavior. -> To find the latest version of the .zip file go to the repo's [releases page](https://github.com/WordPress/gutenberg-examples/releases) and look in the latest release under 'Assets'. +> To find the latest version of the .zip file go to the repo's [releases page](https://github.com/WordPress/block-development-examples/releases) and look in the latest release under 'Assets'. Code snippets are provided in two formats "JSX" and "Plain". JSX refers to JavaScript code that uses JSX syntax which requires a build step. Plain refers to "classic" JavaScript that does not require building. You can change between them using tabs found above each code example. Using JSX, does require you to run [the JavaScript build step](/docs/how-to-guides/javascript/js-build-setup/) to compile your code to a browser compatible format. diff --git a/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md b/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md index 2cd79198b70b9f..697984c9456e02 100644 --- a/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md +++ b/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md @@ -18,9 +18,6 @@ The first method shows adding the style inline. This transforms the defined styl The `useBlockProps` React hook is used to set and apply properties on the block's wrapper element. The following example shows how: -{% codetabs %} -{% JSX %} - ```jsx import { registerBlockType } from '@wordpress/blocks'; import { useBlockProps } from '@wordpress/block-editor'; @@ -55,49 +52,6 @@ registerBlockType( 'gutenberg-examples/example-02-stylesheets', { } ); ``` -{% Plain %} - -```js -( function ( blocks, React, blockEditor ) { - var el = React.createElement; - - blocks.registerBlockType( 'gutenberg-examples/example-02-stylesheets', { - edit: function ( props ) { - const greenBackground = { - backgroundColor: '#090', - color: '#fff', - padding: '20px', - }; - const blockProps = blockEditor.useBlockProps( { - style: greenBackground, - } ); - return el( - 'p', - blockProps, - 'Hello World (from the editor, in green).' - ); - }, - save: function () { - const redBackground = { - backgroundColor: '#090', - color: '#fff', - padding: '20px', - }; - const blockProps = blockEditor.useBlockProps.save( { - style: redBackground, - } ); - return el( - 'p', - blockProps, - 'Hello World (from the frontend, in red).' - ); - }, - } ); -} )( window.wp.blocks, window.React, window.wp.blockEditor ); -``` - -{% end %} - ## Method 2: Block classname The inline style works well for a small amount of CSS to apply. If you have much more than the above you will likely find that it is easier to manage with them in a separate stylesheet file. @@ -106,9 +60,6 @@ The `useBlockProps` hooks includes the classname for the block automatically, it For example the block name: `gutenberg-examples/example-02-stylesheets` would get the classname: `wp-block-gutenberg-examples-example-02-stylesheets`. It might be a bit long but best to avoid conflicts with other blocks. -{% codetabs %} -{% JSX %} - ```jsx import { registerBlockType } from '@wordpress/blocks'; import { useBlockProps } from '@wordpress/block-editor'; @@ -131,66 +82,15 @@ registerBlockType( 'gutenberg-examples/example-02-stylesheets', { } ); ``` -{% Plain %} - -```js -( function ( blocks, React, blockEditor ) { - var el = React.createElement; - - blocks.registerBlockType( 'gutenberg-examples/example-02-stylesheets', { - edit: function ( props ) { - var blockProps = blockEditor.useBlockProps(); - return el( - 'p', - blockProps, - 'Hello World (from the editor, in green).' - ); - }, - save: function () { - var blockProps = blockEditor.useBlockProps.save(); - return el( - 'p', - blockProps, - 'Hello World (from the frontend, in red).' - ); - }, - } ); -} )( window.wp.blocks, window.React, window.wp.blockEditor ); -``` - -{% end %} - ### Build or add dependency In order to include the blockEditor as a dependency, make sure to run the build step, or update the asset php file. -{% codetabs %} -{% JSX %} - Build the scripts and update the asset file which is used to keep track of dependencies and the build version. ```bash npm run build ``` -{% Plain %} - -Edit the asset file to include the block-editor dependency for the scripts. - -```php - - array( - 'react', - 'wp-blocks', - 'wp-block-editor', - 'wp-polyfill' - ), - 'version' => '0.1' - ); -``` - -{% end %} - ### Enqueue stylesheets Like scripts, you can enqueue your block's styles using the `block.json` file. @@ -199,7 +99,7 @@ Use the `editorStyle` property to a CSS file you want to load in the editor view It is worth noting that, if the editor content is iframed, both of these will load in the iframe. `editorStyle` will also load outside the iframe, so it can -be used for editor content as well as UI. +be used for editor content as well as UI. For example: @@ -249,4 +149,4 @@ The files will automatically be enqueued when specified in the block.json. This guide showed a couple of different ways to apply styles to your block, by either inline or in its own style sheet. Both of these methods use the `useBlockProps` hook, see the [block wrapper reference documentation](/docs/reference-guides/block-api/block-edit-save.md#block-wrapper-props) for additional details. -See the complete [example-02-stylesheets](https://github.com/WordPress/gutenberg-examples/tree/trunk/blocks-non-jsx/02-stylesheets) code in the [gutenberg-examples repository](https://github.com/WordPress/gutenberg-examples). +See the complete [stylesheets-79a4c3](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/stylesheets-79a4c3) code in the [block-development-examples repository](https://github.com/WordPress/block-development-examples). diff --git a/docs/how-to-guides/block-tutorial/block-controls-toolbar-and-sidebar.md b/docs/how-to-guides/block-tutorial/block-controls-toolbar-and-sidebar.md index 1b3a54592b9967..4436696b552619 100644 --- a/docs/how-to-guides/block-tutorial/block-controls-toolbar-and-sidebar.md +++ b/docs/how-to-guides/block-tutorial/block-controls-toolbar-and-sidebar.md @@ -10,9 +10,6 @@ When the user selects a block, a number of control buttons may be shown in a too You can also customize the toolbar to include controls specific to your block type. If the return value of your block type's `edit` function includes a `BlockControls` element, those controls will be shown in the selected block's toolbar. -{% codetabs %} -{% JSX %} - ```jsx import { registerBlockType } from '@wordpress/blocks'; @@ -92,95 +89,6 @@ registerBlockType( 'gutenberg-examples/example-04-controls-esnext', { } ); ``` -{% Plain %} - -```js -( function ( blocks, blockEditor, React ) { - var el = React.createElement; - var RichText = blockEditor.RichText; - var AlignmentToolbar = blockEditor.AlignmentToolbar; - var BlockControls = blockEditor.BlockControls; - var useBlockProps = blockEditor.useBlockProps; - - blocks.registerBlockType( 'gutenberg-examples/example-04-controls', { - title: 'Example: Controls', - icon: 'universal-access-alt', - category: 'design', - - attributes: { - content: { - type: 'string', - source: 'html', - selector: 'p', - }, - alignment: { - type: 'string', - default: 'none', - }, - }, - example: { - attributes: { - content: 'Hello World', - alignment: 'right', - }, - }, - edit: function ( props ) { - var content = props.attributes.content; - var alignment = props.attributes.alignment; - - function onChangeContent( newContent ) { - props.setAttributes( { content: newContent } ); - } - - function onChangeAlignment( newAlignment ) { - props.setAttributes( { - alignment: - newAlignment === undefined ? 'none' : newAlignment, - } ); - } - - return el( - 'div', - useBlockProps(), - el( - BlockControls, - { key: 'controls' }, - el( AlignmentToolbar, { - value: alignment, - onChange: onChangeAlignment, - } ) - ), - el( RichText, { - key: 'richtext', - tagName: 'p', - style: { textAlign: alignment }, - onChange: onChangeContent, - value: content, - } ) - ); - }, - - save: function ( props ) { - var blockProps = useBlockProps.save(); - - return el( - 'div', - blockProps, - el( RichText.Content, { - tagName: 'p', - className: - 'gutenberg-examples-align-' + - props.attributes.alignment, - value: props.attributes.content, - } ) - ); - }, - } ); -} )( window.wp.blocks, window.wp.blockEditor, window.React ); -``` - -{% end %} - Note that `BlockControls` is only visible when the block is currently selected and in visual editing mode. `BlockControls` are not shown when editing a block in HTML editing mode. ## Settings Sidebar diff --git a/docs/how-to-guides/block-tutorial/block-supports-in-static-blocks.md b/docs/how-to-guides/block-tutorial/block-supports-in-static-blocks.md index a6350470bb797c..47fa3a86b75eb9 100644 --- a/docs/how-to-guides/block-tutorial/block-supports-in-static-blocks.md +++ b/docs/how-to-guides/block-tutorial/block-supports-in-static-blocks.md @@ -8,8 +8,6 @@ Let's take the block we wrote in the previous chapter (example 3) and with just Here's the exact same code we used to register the block previously. -{% codetabs %} -{% JSX %} ```jsx import { registerBlockType } from '@wordpress/blocks'; @@ -64,65 +62,6 @@ registerBlockType( 'gutenberg-examples/example-03-editable-esnext', { } ); ``` -{% Plain %} - -```js -( function ( blocks, blockEditor, React ) { - var el = React.createElement; - var RichText = blockEditor.RichText; - var useBlockProps = blockEditor.useBlockProps; - - blocks.registerBlockType( 'gutenberg-examples/example-03-editable', { - apiVersion: 3, - title: 'Example: Basic with block supports', - icon: 'universal-access-alt', - category: 'design', - - attributes: { - content: { - type: 'string', - source: 'html', - selector: 'p', - }, - }, - example: { - attributes: { - content: 'Hello World', - }, - }, - edit: function ( props ) { - var blockProps = useBlockProps(); - var content = props.attributes.content; - function onChangeContent( newContent ) { - props.setAttributes( { content: newContent } ); - } - - return el( - RichText, - Object.assign( blockProps, { - tagName: 'p', - onChange: onChangeContent, - value: content, - } ) - ); - }, - - save: function ( props ) { - var blockProps = useBlockProps.save(); - return el( - RichText.Content, - Object.assign( blockProps, { - tagName: 'p', - value: props.attributes.content, - } ) - ); - }, - } ); -} )( window.wp.blocks, window.wp.blockEditor, window.React ); -``` - -{% end %} - Now, let's alter the block.json file for that block, and add the supports key. (If you're not using a block.json file, you can also add the key to the `registerBlockType` function call) ```json diff --git a/docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md b/docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md index f8d4041c4542e4..89ef666abe494f 100644 --- a/docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md +++ b/docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md @@ -17,8 +17,7 @@ Block attributes can be used for any content or setting you want to save for tha The following code example shows how to create a dynamic block that shows only the last post as a link. -{% codetabs %} -{% JSX %} + ```jsx import { registerBlockType } from '@wordpress/blocks'; @@ -52,47 +51,7 @@ registerBlockType( 'gutenberg-examples/example-dynamic', { } ); ``` -{% Plain %} - -```js -( function ( blocks, React, data, blockEditor ) { - var el = React.createElement, - registerBlockType = blocks.registerBlockType, - useSelect = data.useSelect, - useBlockProps = blockEditor.useBlockProps; - - registerBlockType( 'gutenberg-examples/example-dynamic', { - apiVersion: 3, - title: 'Example: last post', - icon: 'megaphone', - category: 'widgets', - edit: function () { - var content; - var blockProps = useBlockProps(); - var posts = useSelect( function ( select ) { - return select( 'core' ).getEntityRecords( 'postType', 'post' ); - }, [] ); - if ( ! posts ) { - content = 'Loading...'; - } else if ( posts.length === 0 ) { - content = 'No posts'; - } else { - var post = posts[ 0 ]; - content = el( 'a', { href: post.link }, post.title.rendered ); - } - - return el( 'div', blockProps, content ); - }, - } ); -} )( - window.wp.blocks, - window.React, - window.wp.data, - window.wp.blockEditor -); -``` -{% end %} Because it is a dynamic block it doesn't need to override the default `save` implementation on the client. Instead, it needs a server component. The contents in the front of your site depend on the function called by the `render_callback` property of `register_block_type`. @@ -156,8 +115,7 @@ Gutenberg 2.8 added the [``](/packages/server-side-render/READ _Server-side render is meant as a fallback; client-side rendering in JavaScript is always preferred (client rendering is faster and allows better editor manipulation)._ -{% codetabs %} -{% JSX %} + ```jsx import { registerBlockType } from '@wordpress/blocks'; @@ -184,41 +142,6 @@ registerBlockType( 'gutenberg-examples/example-dynamic', { } ); ``` -{% Plain %} - -```js -( function ( blocks, React, serverSideRender, blockEditor ) { - var el = React.createElement, - registerBlockType = blocks.registerBlockType, - ServerSideRender = serverSideRender, - useBlockProps = blockEditor.useBlockProps; - - registerBlockType( 'gutenberg-examples/example-dynamic', { - apiVersion: 3, - title: 'Example: last post', - icon: 'megaphone', - category: 'widgets', - - edit: function ( props ) { - var blockProps = useBlockProps(); - return el( - 'div', - blockProps, - el( ServerSideRender, { - block: 'gutenberg-examples/example-dynamic', - attributes: props.attributes, - } ) - ); - }, - } ); -} )( - window.wp.blocks, - window.React, - window.wp.serverSideRender, - window.wp.blockEditor -); -``` -{% end %} -Note that this code uses the `wp-server-side-render` package but not `wp-data`. Make sure to update the dependencies in the PHP code. You can use wp-scripts to automatically build dependencies (see the [gutenberg-examples repo](https://github.com/WordPress/gutenberg-examples/tree/trunk/blocks-jsx/01-basic-esnext) for PHP code setup). +Note that this code uses the `wp-server-side-render` package but not `wp-data`. Make sure to update the dependencies in the PHP code. You can use wp-scripts to automatically build dependencies (see the [block-development-examples repo](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/basic-esnext-a2ab62) for PHP code setup). diff --git a/docs/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields.md b/docs/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields.md index 7586081af4216d..3d8e10cae7ab2a 100644 --- a/docs/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields.md +++ b/docs/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields.md @@ -52,8 +52,6 @@ Because `RichText` allows for nested nodes, you'll most often use it in conjunct Here is the complete block definition for Example 03. -{% codetabs %} -{% JSX %} ```jsx import { registerBlockType } from '@wordpress/blocks'; @@ -107,62 +105,3 @@ registerBlockType( 'gutenberg-examples/example-03-editable-esnext', { }, } ); ``` - -{% Plain %} - -```js -( function ( blocks, blockEditor, React ) { - var el = React.createElement; - var RichText = blockEditor.RichText; - var useBlockProps = blockEditor.useBlockProps; - - blocks.registerBlockType( 'gutenberg-examples/example-03-editable', { - apiVersion: 3, - title: 'Example: Editable', - icon: 'universal-access-alt', - category: 'design', - - attributes: { - content: { - type: 'string', - source: 'html', - selector: 'p', - }, - }, - example: { - attributes: { - content: 'Hello World', - }, - }, - edit: function ( props ) { - var blockProps = useBlockProps(); - var content = props.attributes.content; - function onChangeContent( newContent ) { - props.setAttributes( { content: newContent } ); - } - - return el( - RichText, - Object.assign( blockProps, { - tagName: 'p', - onChange: onChangeContent, - value: content, - } ) - ); - }, - - save: function ( props ) { - var blockProps = useBlockProps.save(); - return el( - RichText.Content, - Object.assign( blockProps, { - tagName: 'p', - value: props.attributes.content, - } ) - ); - }, - } ); -} )( window.wp.blocks, window.wp.blockEditor, window.React ); -``` - -{% end %} diff --git a/docs/how-to-guides/block-tutorial/nested-blocks-inner-blocks.md b/docs/how-to-guides/block-tutorial/nested-blocks-inner-blocks.md index e2c37f341427e5..9dc7f1f324743f 100644 --- a/docs/how-to-guides/block-tutorial/nested-blocks-inner-blocks.md +++ b/docs/how-to-guides/block-tutorial/nested-blocks-inner-blocks.md @@ -6,8 +6,6 @@ Note: A single block can only contain one `InnerBlocks` component. Here is the basic InnerBlocks usage. -{% codetabs %} -{% JSX %} ```js import { registerBlockType } from '@wordpress/blocks'; @@ -38,35 +36,6 @@ registerBlockType( 'gutenberg-examples/example-06', { } ); ``` -{% Plain %} - -```js -( function ( blocks, React, blockEditor ) { - var el = React.createElement; - var InnerBlocks = blockEditor.InnerBlocks; - var useBlockProps = blockEditor.useBlockProps; - - blocks.registerBlockType( 'gutenberg-examples/example-06', { - title: 'Example: Inner Blocks', - category: 'design', - - edit: function () { - var blockProps = useBlockProps(); - - return el( 'div', blockProps, el( InnerBlocks ) ); - }, - - save: function () { - var blockProps = useBlockProps.save(); - - return el( 'div', blockProps, el( InnerBlocks.Content ) ); - }, - } ); -} )( window.wp.blocks, window.React, window.wp.blockEditor ); -``` - -{% end %} - ## Allowed Blocks Using the `allowedBlocks` property, you can define the set of blocks allowed in your InnerBlock. This restricts the blocks that can be included only to those listed, all other blocks will not show in the inserter. @@ -101,8 +70,6 @@ By default this behavior is disabled until the `directInsert` prop is set to `tr Use the template property to define a set of blocks that prefill the InnerBlocks component when inserted. You can set attributes on the blocks to define their use. The example below shows a book review template using InnerBlocks component and setting placeholders values to show the block usage. -{% codetabs %} -{% JSX %} ```js const MY_TEMPLATE = [ @@ -123,29 +90,6 @@ const MY_TEMPLATE = [ }, ``` -{% Plain %} - -```js -const MY_TEMPLATE = [ - [ 'core/image', {} ], - [ 'core/heading', { placeholder: 'Book Title' } ], - [ 'core/paragraph', { placeholder: 'Summary' } ], -]; - -//... - - edit: function( props ) { - return el( - InnerBlocks, - { - template: MY_TEMPLATE, - templateLock: "all", - } - ); - }, -``` - -{% end %} Use the `templateLock` property to lock down the template. Using `all` locks the template completely so no changes can be made. Using `insert` prevents additional blocks from being inserted, but existing blocks can be reordered. See [templateLock documentation](https://github.com/WordPress/gutenberg/tree/HEAD/packages/block-editor/src/components/inner-blocks/README.md#templatelock) for additional information. @@ -167,7 +111,7 @@ add_action( 'init', function() { ## Using Parent and Ancestor Relationships in Blocks -A common pattern for using InnerBlocks is to create a custom block that will be only be available if its parent block is inserted. This allows builders to establish a relationship between blocks, while limiting a nested block's discoverability. Currently, there are two relationships builders can use: `parent` and `ancestor`. The differences are: +A common pattern for using InnerBlocks is to create a custom block that will be only be available if its parent block is inserted. This allows builders to establish a relationship between blocks, while limiting a nested block's discoverability. Currently, there are two relationships builders can use: `parent` and `ancestor`. The differences are: - If you assign a `parent` then you’re stating that the nested block can only be used and inserted as a __direct descendant of the parent__. - If you assign an `ancestor` then you’re stating that the nested block can only be used and inserted as a __descendent of the parent__. @@ -214,8 +158,7 @@ The `useInnerBlocksProps` is exported from the `@wordpress/block-editor` package Here is the basic `useInnerBlocksProps` hook usage. -{% codetabs %} -{% JSX %} + ```js import { registerBlockType } from '@wordpress/blocks'; @@ -248,42 +191,9 @@ registerBlockType( 'gutenberg-examples/example-06', { } ); ``` -{% Plain %} - -```js -( function ( blocks, React, blockEditor ) { - var el = React.createElement; - var InnerBlocks = blockEditor.InnerBlocks; - var useBlockProps = blockEditor.useBlockProps; - var useInnerBlocksProps = blockEditor.useInnerBlocksProps; - - blocks.registerBlockType( 'gutenberg-examples/example-06', { - title: 'Example: Inner Blocks', - category: 'design', - - edit: function () { - var blockProps = useBlockProps(); - var innerBlocksProps = useInnerBlocksProps(); - - return el( 'div', blockProps, el( 'div', innerBlocksProps ) ); - }, - - save: function () { - var blockProps = useBlockProps.save(); - var innerBlocksProps = useInnerBlocksProps.save(); - - return el( 'div', blockProps, el( 'div', innerBlocksProps ) ); - }, - } ); -} )( window.wp.blocks, window.React, window.wp.blockEditor ); -``` - -{% end %} - This hook can also pass objects returned from the `useBlockProps` hook to the `useInnerBlocksProps` hook. This reduces the number of elements we need to create. -{% codetabs %} -{% JSX %} + ```js import { registerBlockType } from '@wordpress/blocks'; @@ -312,36 +222,6 @@ registerBlockType( 'gutenberg-examples/example-06', { } ); ``` -{% Plain %} - -```js -( function ( blocks, React, blockEditor ) { - var el = React.createElement; - var InnerBlocks = blockEditor.InnerBlocks; - var useBlockProps = blockEditor.useBlockProps; - var useInnerBlocksProps = blockEditor.useInnerBlocksProps; - - blocks.registerBlockType( 'gutenberg-examples/example-06', { - // ... - - edit: function () { - var blockProps = useBlockProps(); - var innerBlocksProps = useInnerBlocksProps(); - - return el( 'div', innerBlocksProps ); - }, - - save: function () { - var blockProps = useBlockProps.save(); - var innerBlocksProps = useInnerBlocksProps.save(); - - return el( 'div', innerBlocksProps ); - }, - } ); -} )( window.wp.blocks, window.React, window.wp.blockEditor ); -``` - -{% end %} The above code will render to the following markup in the editor: @@ -353,8 +233,6 @@ The above code will render to the following markup in the editor: Another benefit to using the hook approach is using the returned value, which is just an object, and deconstruct to get the react children from the object. This property contains the actual child inner blocks thus we can place elements on the same level as our inner blocks. -{% codetabs %} -{% JSX %} ```js import { registerBlockType } from '@wordpress/blocks'; @@ -379,39 +257,6 @@ registerBlockType( 'gutenberg-examples/example-06', { } ); ``` -{% Plain %} - -```js -( function ( blocks, React, blockEditor ) { - var el = React.createElement; - var InnerBlocks = blockEditor.InnerBlocks; - var useBlockProps = blockEditor.useBlockProps; - var useInnerBlocksProps = blockEditor.useInnerBlocksProps; - - blocks.registerBlockType( 'gutenberg-examples/example-06', { - // ... - - edit: function () { - var blockProps = useBlockProps(); - var { children, ...innerBlocksProps } = useInnerBlocksProps( blockProps ); - - return el( - 'div', - innerBlocksProps, - children, - el( - 'div', - {}, - '', - ) - ); - }, - // ... - } ); -} )( window.wp.blocks, window.React, window.wp.blockEditor ); -``` - -{% end %} ```html
diff --git a/docs/how-to-guides/block-tutorial/writing-your-first-block-type.md b/docs/how-to-guides/block-tutorial/writing-your-first-block-type.md index a9dfc0d51a682c..4a690984011e0f 100644 --- a/docs/how-to-guides/block-tutorial/writing-your-first-block-type.md +++ b/docs/how-to-guides/block-tutorial/writing-your-first-block-type.md @@ -64,8 +64,6 @@ The `block.json` file should be added to your plugin. To start a new plugin, cre Create a basic `block.json` file there: -{% codetabs %} -{% JSX %} ```json { @@ -77,22 +75,6 @@ Create a basic `block.json` file there: "editorScript": "file:./build/index.js" } ``` - -{% Plain %} - -```json -{ - "apiVersion": 3, - "title": "Example: Basic", - "name": "gutenberg-examples/example-01-basic", - "category": "layout", - "icon": "universal-access-alt", - "editorScript": "file:./block.js" -} -``` - -{% end %} - ### Step 2: Register block in plugin With the `block.json` in place, the registration for the block is a single function call in PHP, this will setup the block and JavaScript file specified in the `editorScript` property to load in the editor. @@ -118,8 +100,6 @@ The `edit` function is a component that is shown in the editor when the block is The `save` function is a component that defines the final markup returned by the block and saved in `post_content`. -{% codetabs %} -{% JSX %} Add the following in `src/index.js` @@ -140,33 +120,12 @@ registerBlockType( 'gutenberg-examples/example-01-basic-esnext', { } ); ``` -{% Plain %} - -Add the following to `block.js` - -```js -( function ( blocks, React ) { - var el = React.createElement; - - blocks.registerBlockType( 'gutenberg-examples/example-01-basic', { - edit: function () { - return el( 'p', {}, 'Hello World (from the editor).' ); - }, - save: function () { - return el( 'p', {}, 'Hola mundo (from the frontend).' ); - }, - } ); -} )( window.wp.blocks, window.React ); -``` - -{% end %} ### Step 4: Build or add dependency In order to register the block, an asset php file is required in the same directory as the directory used in `register_block_type()` and must begin with the script's filename. -{% codetabs %} -{% JSX %} + Build the scripts and asset file which is used to keep track of dependencies and the build version. @@ -174,23 +133,6 @@ Build the scripts and asset file which is used to keep track of dependencies and npm run build ``` -{% Plain %} - -Create the asset file to load the dependencies for the scripts. The name of this file should be the name of the js file then .asset.php. For this example, create `block.asset.php` with the following: - -```php - - array( - 'react', - 'wp-blocks', - 'wp-polyfill' - ), - 'version' => '0.1' - ); -``` - -{% end %} ### Step 5: Confirm @@ -207,11 +149,11 @@ When you save the post and view it published, you will see the `Hola mundo (from ## Conclusion -This shows the most basic static block. The [gutenberg-examples](https://github.com/WordPress/gutenberg-examples) repository has complete examples for both. +This shows the most basic static block. The [block-development-examples](https://github.com/WordPress/block-development-examples) repository has complete examples for both. -- [Basic Example with JSX build](https://github.com/WordPress/gutenberg-examples/tree/trunk/blocks-jsx/01-basic-esnext) +- [Basic Example with JSX build](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/basic-esnext-a2ab62) -- [Basic Example Plain JavaScript](https://github.com/WordPress/gutenberg-examples/tree/trunk/blocks-non-jsx/01-basic), +- [Basic Example Plain JavaScript](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/minimal-block-no-build-e621a6), **NOTE:** The examples include a more complete block setup with translation features included, it is recommended to follow those examples for a production block. The internationalization features were left out of this guide for simplicity and focusing on the very basics of a block. @@ -219,7 +161,7 @@ This shows the most basic static block. The [gutenberg-examples](https://github. A couple of things to note when creating your blocks: -- A block name must be prefixed with a namespace specific to your plugin. This helps prevent conflicts when more than one plugin registers a block with the same name. In this example, the namespace is `gutenberg-examples`. +- A block name must be prefixed with a namespace specific to your plugin. This helps prevent conflicts when more than one plugin registers a block with the same name. In this example, the namespace is `block-development-examples`. - Block names _must_ include only lowercase alphanumeric characters or dashes and start with a letter. Example: `my-plugin/my-custom-block`. diff --git a/docs/how-to-guides/data-basics/1-data-basics-setup.md b/docs/how-to-guides/data-basics/1-data-basics-setup.md index 3657b65791a658..e61db83c4ecbd5 100644 --- a/docs/how-to-guides/data-basics/1-data-basics-setup.md +++ b/docs/how-to-guides/data-basics/1-data-basics-setup.md @@ -212,4 +212,4 @@ Congratulations! You are now ready to start building the app! - Previous part: [Introduction](/docs/how-to-guides/data-basics/README.md) - Next part: [Building a basic list of pages](/docs/how-to-guides/data-basics/2-building-a-list-of-pages.md) -- (optional) Review the [finished app](https://github.com/WordPress/gutenberg-examples/tree/trunk/non-block-examples/09-code-data-basics-esnext) in the gutenberg-examples repository +- (optional) Review the [finished app](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/data-basics-59c8f8) in the block-development-examples repository diff --git a/docs/how-to-guides/data-basics/2-building-a-list-of-pages.md b/docs/how-to-guides/data-basics/2-building-a-list-of-pages.md index aee5575cdb5adb..8a0d172e45f453 100644 --- a/docs/how-to-guides/data-basics/2-building-a-list-of-pages.md +++ b/docs/how-to-guides/data-basics/2-building-a-list-of-pages.md @@ -446,4 +446,4 @@ All that’s left is to refresh the page and enjoy the brand new status indicato * **Previous part:** [Setup](/docs/how-to-guides/data-basics/1-data-basics-setup.md) * **Next part:** [Building an edit form](/docs/how-to-guides/data-basics/3-building-an-edit-form.md) -* (optional) Review the [finished app](https://github.com/WordPress/gutenberg-examples/tree/trunk/non-block-examples/09-code-data-basics-esnext) in the gutenberg-examples repository +* (optional) Review the [finished app](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/data-basics-59c8f8) in the block-development-examples repository diff --git a/docs/how-to-guides/data-basics/3-building-an-edit-form.md b/docs/how-to-guides/data-basics/3-building-an-edit-form.md index 754a31f1bc4921..68c87381701515 100644 --- a/docs/how-to-guides/data-basics/3-building-an-edit-form.md +++ b/docs/how-to-guides/data-basics/3-building-an-edit-form.md @@ -540,4 +540,4 @@ function EditPageForm( { pageId, onCancel, onSaveFinished } ) { * **Previous part:** [Building a list of pages](/docs/how-to-guides/data-basics/2-building-a-list-of-pages.md) * **Next part:** Building a *New Page* form (coming soon) -* (optional) Review the [finished app](https://github.com/WordPress/gutenberg-examples/tree/trunk/non-block-examples/09-code-data-basics-esnext) in the gutenberg-examples repository +* (optional) Review the [finished app](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/data-basics-59c8f8) in the block-development-examples repository diff --git a/docs/how-to-guides/data-basics/4-building-a-create-page-form.md b/docs/how-to-guides/data-basics/4-building-a-create-page-form.md index 19aada07c2fc78..33c6e9a5ccff5b 100644 --- a/docs/how-to-guides/data-basics/4-building-a-create-page-form.md +++ b/docs/how-to-guides/data-basics/4-building-a-create-page-form.md @@ -389,4 +389,4 @@ All that’s left is to refresh the page and enjoy the form: * **Next part:** [Adding a delete button](/docs/how-to-guides/data-basics/5-adding-a-delete-button.md) * **Previous part:** [Building an edit form](/docs/how-to-guides/data-basics/3-building-an-edit-form.md) -* (optional) Review the [finished app](https://github.com/WordPress/gutenberg-examples/tree/trunk/non-block-examples/09-code-data-basics-esnext) in the gutenberg-examples repository +* (optional) Review the [finished app](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/data-basics-59c8f8) in the block-development-examples repository diff --git a/docs/how-to-guides/data-basics/5-adding-a-delete-button.md b/docs/how-to-guides/data-basics/5-adding-a-delete-button.md index 07b10ac822c546..e0a0b0d1e93370 100644 --- a/docs/how-to-guides/data-basics/5-adding-a-delete-button.md +++ b/docs/how-to-guides/data-basics/5-adding-a-delete-button.md @@ -446,4 +446,4 @@ function DeletePageButton( { pageId } ) { ## What's next? * **Previous part:** [Building a *Create page form*](/docs/how-to-guides/data-basics/4-building-a-create-page-form.md) -* (optional) Review the [finished app](https://github.com/WordPress/gutenberg-examples/tree/trunk/non-block-examples/09-code-data-basics-esnext) in the gutenberg-examples repository +* (optional) Review the [finished app](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/data-basics-59c8f8) in the block-development-examples repository diff --git a/docs/how-to-guides/data-basics/README.md b/docs/how-to-guides/data-basics/README.md index 88e901a90e11db..3e92a216b60c55 100644 --- a/docs/how-to-guides/data-basics/README.md +++ b/docs/how-to-guides/data-basics/README.md @@ -2,9 +2,10 @@ This tutorial aims to get you comfortable with the Gutenberg data layer. It guides you through building a simple React application that enables the user to manage their WordPress pages. The finished app will look like this: -![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/list-of-pages/part1-finished.jpg) -You may review the [finished app](https://github.com/WordPress/gutenberg-examples/tree/trunk/non-block-examples/09-code-data-basics-esnext) in the gutenberg-examples repository. +[![Open demo in WordPress Playground](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/list-of-pages/part1-finished.jpg)](https://playground.wordpress.net/?blueprint-url=https://raw.githubusercontent.com/WordPress/block-development-examples/trunk/plugins/data-basics-59c8f8/_playground/blueprint.json "Opens demo in WordPress Playground") + +You may review the [finished app](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/data-basics-59c8f8) in the block-development-examples repository. ### Table of Contents diff --git a/docs/how-to-guides/format-api.md b/docs/how-to-guides/format-api.md index a23293bbb27e3f..00e1b82675c006 100644 --- a/docs/how-to-guides/format-api.md +++ b/docs/how-to-guides/format-api.md @@ -18,7 +18,7 @@ You will need: - A minimal plugin activated and setup ready to edit - JavaScript setup for building and enqueuing -The [complete format-api example](https://github.com/WordPress/gutenberg-examples/tree/trunk/non-block-examples/format-api) is available that you can use as a reference for your setup. +The [complete format-api example](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/format-api-f14b86) is available that you can use as a reference for your setup. ## Step-by-step guide @@ -234,4 +234,4 @@ Reference documentation used in this guide: The guide showed you how to add a button to the toolbar and have it apply a format to the selected text. Try it out and see what you can build with it in your next plugin. -Download the [format-api example](https://github.com/WordPress/gutenberg-examples/tree/trunk/non-block-examples/format-api) from the [gutenberg-examples](https://github.com/WordPress/gutenberg-examples) repository. +Download the [format-api example](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/format-api-f14b86) from the [block-development-examples](https://github.com/WordPress/block-development-examples) repository. diff --git a/docs/how-to-guides/internationalization.md b/docs/how-to-guides/internationalization.md index c3194f309fca63..08ce46edb3f581 100644 --- a/docs/how-to-guides/internationalization.md +++ b/docs/how-to-guides/internationalization.md @@ -37,9 +37,6 @@ add_action( 'init', 'myguten_block_init' ); In your code, you can include the i18n functions. The most common function is **\_\_** (a double underscore) which provides translation of a simple string. Here is a basic block example: -{% codetabs %} -{% JSX %} - ```js import { __ } from '@wordpress/i18n'; import { registerBlockType } from '@wordpress/blocks'; @@ -64,33 +61,6 @@ registerBlockType( 'myguten/simple', { } ); ``` -{% Plain %} - -```js -const el = React.createElement; -const { __ } = wp.i18n; -const { registerBlockType } = wp.blocks; -const { useBlockProps } = wp.blockEditor; - -registerBlockType( 'myguten/simple', { - title: __( 'Simple Block', 'myguten' ), - category: 'widgets', - - edit: function () { - const blockProps = useBlockProps( { style: { color: 'red' } } ); - - return el( 'p', blockProps, __( 'Hello World', 'myguten' ) ); - }, - - save: function () { - const blockProps = useBlockProps.save( { style: { color: 'red' } } ); - return el( 'p', blockProps, __( 'Hello World', 'myguten' ) ); - }, -} ); -``` - -{% end %} - In the above example, the function will use the first argument for the string to be translated. The second argument is the text domain which must match the text domain slug specified by your plugin. Common functions available, these mirror their PHP counterparts are: diff --git a/docs/how-to-guides/javascript/js-build-setup.md b/docs/how-to-guides/javascript/js-build-setup.md index b915f4dd444f90..cf49154b590a96 100644 --- a/docs/how-to-guides/javascript/js-build-setup.md +++ b/docs/how-to-guides/javascript/js-build-setup.md @@ -22,7 +22,7 @@ The [@wordpress/scripts](https://www.npmjs.com/package/@wordpress/scripts) packa ## Quick Start -If you prefer a quick start, you can use one of the examples from the [Gutenberg Examples repository](https://github.com/wordpress/gutenberg-examples/) and skip below. Each one of the `-esnext` directories in the examples repository contain the necessary files for working with ESNext and JSX. +If you prefer a quick start, you can use one of the examples from the [Block Development Examples repository](https://github.com/wordpress/block-development-examples/) and skip below. Each one of the `-esnext` directories in the examples repository contain the necessary files for working with ESNext and JSX. ## Setup @@ -168,7 +168,7 @@ wp_register_script( ); ``` -See [ESNext blocks in gutenberg-examples repo](https://github.com/WordPress/gutenberg-examples) for full examples. +See [blocks in the block-development-examples repo](https://github.com/WordPress/block-development-examples) for full examples. ## Summary diff --git a/docs/how-to-guides/metabox.md b/docs/how-to-guides/metabox.md index 7a8686968d2cf2..e0402b1180c1c3 100644 --- a/docs/how-to-guides/metabox.md +++ b/docs/how-to-guides/metabox.md @@ -26,7 +26,7 @@ You will need: - A minimal plugin activated and ready to edit - JavaScript setup for building and enqueuing -A [complete meta-block example](https://github.com/WordPress/gutenberg-examples/tree/trunk/blocks-jsx/meta-block) is available that you can use as a reference for your setup. +A [complete meta-block example](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/meta-block-bb1e55) is available that you can use as a reference for your setup. ## Step-by-step guide diff --git a/docs/how-to-guides/plugin-sidebar-0.md b/docs/how-to-guides/plugin-sidebar-0.md index 9543eaf8761549..bf084680c3d1b7 100644 --- a/docs/how-to-guides/plugin-sidebar-0.md +++ b/docs/how-to-guides/plugin-sidebar-0.md @@ -379,7 +379,7 @@ Functions used in this guide: You now have a custom sidebar that you can use to update `post_meta` content. -A complete example is available, download the [plugin-sidebar example](https://github.com/WordPress/gutenberg-examples/tree/trunk/blocks-non-jsx/plugin-sidebar) from the [gutenberg-examples](https://github.com/WordPress/gutenberg-examples) repository. +A complete example is available, download the [plugin-sidebar example](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/plugin-sidebar-9ee4a6) from the [block-development-examples](https://github.com/WordPress/block-development-examples) repository. ### Note @@ -407,4 +407,4 @@ return el( TextControl, { document.querySelector( {the-value-textarea} ).innerHTML = content; }, } ); -``` \ No newline at end of file +``` diff --git a/docs/how-to-guides/themes/theme-json.md b/docs/how-to-guides/themes/theme-json.md index 024564b7c9eaeb..2a1161827c1369 100644 --- a/docs/how-to-guides/themes/theme-json.md +++ b/docs/how-to-guides/themes/theme-json.md @@ -69,8 +69,7 @@ To address this need, we've started to experiment with CSS Custom Properties, ak - **Presets**: [color palettes](/docs/how-to-guides/themes/theme-support.md#block-color-palettes), [font sizes](/docs/how-to-guides/themes/theme-support.md#block-font-sizes), or [gradients](/docs/how-to-guides/themes/theme-support.md#block-gradient-presets) declared by the theme are converted to CSS Custom Properties and enqueued both the front-end and the editors. -{% codetabs %} -{% Input %} +#### Input ```json { @@ -94,7 +93,7 @@ To address this need, we've started to experiment with CSS Custom Properties, ak } ``` -{% Output %} +#### Output ```css body { @@ -103,12 +102,10 @@ body { } ``` -{% end %} - - **Custom properties**: there's also a mechanism to create your own CSS Custom Properties. -{% codetabs %} -{% Input %} + +#### Input ```json { @@ -124,7 +121,7 @@ body { } ``` -{% Output %} +#### Output ```css body { @@ -133,8 +130,6 @@ body { } ``` -{% end %} - ## Specification This specification is the same for the three different origins that use this format: core, themes, and users. Themes can override core's defaults by creating a file called `theme.json`. Users, via the site editor, will also be able to override theme's or core's preferences via an user interface that is being worked on. @@ -166,8 +161,8 @@ The tabs below show WordPress 5.8 supported settings and the ones supported by t The settings section has the following structure: -{% codetabs %} -{% WordPress %} + +#### WordPress ```json { @@ -231,7 +226,7 @@ The settings section has the following structure: } ``` -{% Gutenberg %} +#### Gutenberg ```json { @@ -310,8 +305,6 @@ The settings section has the following structure: } ``` -{% end %} - Each block can configure any of these settings separately, providing a more fine-grained control over what exists via `add_theme_support`. The settings declared at the top-level affect to all blocks, unless a particular block overwrites it. It's a way to provide inheritance and configure all blocks at once. Note, however, that not all settings are relevant for all blocks. The settings section provides an opt-in/opt-out mechanism for themes, but it's the block's responsibility to add support for the features that are relevant to it. For example, if a block doesn't implement the `dropCap` feature, a theme can't enable it for such a block through `theme.json`. @@ -376,8 +369,8 @@ The naming schema for the classes and the custom properties is as follows: - Custom Properties: `--wp--preset--{preset-category}--{preset-slug}` such as `--wp--preset--color--black` - Classes: `.has-{preset-slug}-{preset-category}` such as `.has-black-color`. -{% codetabs %} -{% Input %} + +#### Input ```json { @@ -490,7 +483,7 @@ The naming schema for the classes and the custom properties is as follows: } ``` -{% Output %} +#### Output ```css /* Top-level custom properties */ @@ -540,8 +533,6 @@ body { ``` -{% end %} - To maintain backward compatibility, the presets declared via `add_theme_support` will also generate the CSS Custom Properties. If the `theme.json` contains any presets, these will take precedence over the ones declared via `add_theme_support`. Preset classes are attached to the content of a post by some user action. That's why the engine will add `!important` to these, because user styles should take precedence over theme styles. @@ -552,8 +543,7 @@ In addition to create CSS Custom Properties for the presets, the `theme.json` al For example: -{% codetabs %} -{% Input %} +#### Input ```json { @@ -578,7 +568,7 @@ For example: } ``` -{% Output %} +#### Output ```css body { @@ -592,7 +582,6 @@ body { } ``` -{% end %} Note that the name of the variable is created by adding `--` in between each nesting level and `camelCase` fields are transformed to `kebab-case`. @@ -702,9 +691,9 @@ The tabs below show WordPress 5.8 supported styles and the ones supported by the Each block declares which style properties it exposes via the [block supports mechanism](/docs/reference-guides/block-api/block-supports.md). The support declarations are used to automatically generate the UI controls for the block in the editor. Themes can use any style property via the `theme.json` for any block ― it's the theme's responsibility to verify that it works properly according to the block markup, etc. -{% codetabs %} -{% WordPress %} + +#### WordPress ```json { @@ -784,7 +773,7 @@ Each block declares which style properties it exposes via the [block supports me } ``` -{% Gutenberg %} +#### Gutenberg ```json { @@ -873,14 +862,11 @@ Each block declares which style properties it exposes via the [block supports me } ``` -{% end %} - ### Top-level styles Styles found at the top-level will be enqueued using the `body` selector. -{% codetabs %} -{% Input %} +#### Input ```json { @@ -893,7 +879,7 @@ Styles found at the top-level will be enqueued using the `body` selector. } ``` -{% Output %} +#### Output ```css body { @@ -901,7 +887,6 @@ body { } ``` -{% end %} ### Block styles @@ -909,8 +894,7 @@ Styles found within a block will be enqueued using the block selector. By default, the block selector is generated based on its name such as `.wp-block-`. For example, `.wp-block-group` for the `core/group` block. There are some blocks that want to opt-out from this default behavior. They can do so by explicitly telling the system which selector to use for them via the `__experimentalSelector` key within the `supports` section of its `block.json` file. Note that the block needs to be registered server-side for the `__experimentalSelector` field to be available to the style engine. -{% codetabs %} -{% Input %} +#### Input ```json { @@ -935,7 +919,7 @@ By default, the block selector is generated based on its name such as `.wp-block } ``` -{% Output %} +#### Output ```css body { @@ -949,7 +933,6 @@ p { /* The core/paragraph opts out from the default behaviour and uses p as a se } ``` -{% end %} #### Referencing a style @@ -996,8 +979,8 @@ Supported by WordPress: If they're found in the top-level the element selector will be used. If they're found within a block, the selector to be used will be the element's appended to the corresponding block. -{% codetabs %} -{% Input %} + +#### Input ```json { @@ -1043,7 +1026,7 @@ If they're found in the top-level the element selector will be used. If they're } ``` -{% Output %} +#### Output ```css body { @@ -1066,8 +1049,6 @@ h3 { } ``` -{% end %} - ##### Element pseudo selectors Pseudo selectors `:hover`, `:focus`, `:visited`, `:active`, `:link`, `:any-link` are supported by Gutenberg. @@ -1236,8 +1217,8 @@ This is for clarity, but also because we want a mechanism to parse back a variab For example: -{% codetabs %} -{% Input %} + +#### Input ```json { @@ -1253,7 +1234,7 @@ For example: } ``` -{% Output %} +#### Output ```css body { @@ -1262,7 +1243,6 @@ body { } ``` -{% end %} A few notes about this process: diff --git a/docs/manifest.json b/docs/manifest.json index bc4d8bd7c3b28d..ba345e7716ee37 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -41,6 +41,12 @@ "markdown_source": "../docs/getting-started/devenv/get-started-with-wp-scripts.md", "parent": "devenv" }, + { + "title": "Quick Start Guide", + "slug": "quick-start-guide", + "markdown_source": "../docs/getting-started/quick-start-guide.md", + "parent": "getting-started" + }, { "title": "Create a Block Tutorial", "slug": "create-block", diff --git a/docs/reference-guides/block-api/block-attributes.md b/docs/reference-guides/block-api/block-attributes.md index 765d69584a6690..35ec1c1e7c64e4 100644 --- a/docs/reference-guides/block-api/block-attributes.md +++ b/docs/reference-guides/block-api/block-attributes.md @@ -375,7 +375,7 @@ Attribute definition: From here, meta attributes can be read and written by a block using the same interface as any attribute: -{% codetabs %} + {% JSX %} ```js @@ -388,22 +388,6 @@ edit( { attributes, setAttributes } ) { }, ``` -{% Plain %} - -```js -edit: function( props ) { - function onChange( event ) { - props.setAttributes( { author: event.target.value } ); - } - - return el( 'input', { - value: props.attributes.author, - onChange: onChange, - } ); -}, -``` - -{% end %} #### Considerations diff --git a/docs/reference-guides/block-api/block-deprecation.md b/docs/reference-guides/block-api/block-deprecation.md index a1497ec346936d..4d69d9d46843cd 100644 --- a/docs/reference-guides/block-api/block-deprecation.md +++ b/docs/reference-guides/block-api/block-deprecation.md @@ -61,9 +61,6 @@ It's important to note that attributes, supports, and ### Example: -{% codetabs %} -{% JSX %} - ```js const { registerBlockType } = wp.blocks; const attributes = { @@ -101,46 +98,6 @@ registerBlockType( 'gutenberg/block-with-deprecated-version', { } ); ``` -{% Plain %} - -```js -var el = React.createElement, - registerBlockType = wp.blocks.registerBlockType, - attributes = { - text: { - type: 'string', - default: 'some random value', - }, - }, - supports = { - className: false, - }; - -registerBlockType( 'gutenberg/block-with-deprecated-version', { - // ... other block properties go here - - attributes: attributes, - - supports: supports, - - save: function ( props ) { - return el( 'div', {}, props.attributes.text ); - }, - - deprecated: [ - { - attributes: attributes, - - save: function ( props ) { - return el( 'p', {}, props.attributes.text ); - }, - }, - ], -} ); -``` - -{% end %} - In the example above we updated the markup of the block to use a `div` instead of `p`. ## Changing the attributes set @@ -149,8 +106,6 @@ Sometimes, you need to update the attributes set to rename or modify old attribu ### Example: -{% codetabs %} -{% JSX %} ```js const { registerBlockType } = wp.blocks; @@ -192,50 +147,6 @@ registerBlockType( 'gutenberg/block-with-deprecated-version', { } ); ``` -{% Plain %} - -```js -var el = React.createElement, - registerBlockType = wp.blocks.registerBlockType; - -registerBlockType( 'gutenberg/block-with-deprecated-version', { - // ... other block properties go here - - attributes: { - content: { - type: 'string', - default: 'some random value', - }, - }, - - save: function ( props ) { - return el( 'div', {}, props.attributes.content ); - }, - - deprecated: [ - { - attributes: { - text: { - type: 'string', - default: 'some random value', - }, - }, - - migrate: function ( attributes ) { - return { - content: attributes.text, - }; - }, - - save: function ( props ) { - return el( 'p', {}, props.attributes.text ); - }, - }, - ], -} ); -``` - -{% end %} In the example above we updated the markup of the block to use a `div` instead of `p` and rename the `text` attribute to `content`. @@ -246,9 +157,6 @@ E.g: a block wants to migrate a title attribute to a paragraph innerBlock. ### Example: -{% codetabs %} -{% JSX %} - ```js const { registerBlockType } = wp.blocks; @@ -292,49 +200,6 @@ registerBlockType( 'gutenberg/block-with-deprecated-version', { } ); ``` -{% Plain %} - -```js -var el = React.createElement, - registerBlockType = wp.blocks.registerBlockType; - -registerBlockType( 'gutenberg/block-with-deprecated-version', { - // ... block properties go here - - deprecated: [ - { - attributes: { - title: { - type: 'string', - source: 'html', - selector: 'p', - }, - }, - - migrate: function ( attributes, innerBlocks ) { - const { title, ...restAttributes } = attributes; - - return [ - restAttributes, - [ - createBlock( 'core/paragraph', { - content: attributes.title, - fontSize: 'large', - } ), - ].concat( innerBlocks ), - ]; - }, - - save: function ( props ) { - return el( 'p', {}, props.attributes.title ); - }, - }, - ], -} ); -``` - -{% end %} - In the example above we updated the block to use an inner Paragraph block with a title instead of a title attribute. _Above are example cases of block deprecation. For more, real-world examples, check for deprecations in the [core block library](https://github.com/WordPress/gutenberg/tree/HEAD/packages/block-library/src). Core blocks have been updated across releases and contain simple and complex deprecations._ diff --git a/docs/reference-guides/block-api/block-edit-save.md b/docs/reference-guides/block-api/block-edit-save.md index 35bbd5ae13e1e0..a8b6f9171bdef3 100644 --- a/docs/reference-guides/block-api/block-edit-save.md +++ b/docs/reference-guides/block-api/block-edit-save.md @@ -6,8 +6,6 @@ When registering a block with JavaScript on the client, the `edit` and `save` fu The `edit` function describes the structure of your block in the context of the editor. This represents what the editor will render when the block is used. -{% codetabs %} -{% JSX %} ```jsx import { useBlockProps } from '@wordpress/block-editor'; @@ -26,32 +24,12 @@ const blockSettings = { }; ``` -{% Plain %} - -```js -var blockSettings = { - apiVersion: 3, - - // ... - - edit: function () { - var blockProps = wp.blockEditor.useBlockProps(); - - return React.createElement( 'div', blockProps, 'Your block.' ); - }, -}; -``` - -{% end %} - ### block wrapper props The first thing to notice here is the use of the `useBlockProps` React hook on the block wrapper element. In the example above, the block wrapper renders a "div" in the editor, but in order for the Gutenberg editor to know how to manipulate the block, add any extra classNames that are needed for the block... the block wrapper element should apply props retrieved from the `useBlockProps` react hook call. The block wrapper element should be a native DOM element, like `
` and ``, or a React component that forwards any additional props to native DOM elements. Using a `` or `` component, for instance, would be invalid. If the element wrapper needs any extra custom HTML attributes, these need to be passed as an argument to the `useBlockProps` hook. For example to add a `my-random-classname` className to the wrapper, you can use the following code: -{% codetabs %} -{% JSX %} ```jsx import { useBlockProps } from '@wordpress/block-editor'; @@ -72,25 +50,6 @@ const blockSettings = { }; ``` -{% Plain %} - -```js -var blockSettings = { - apiVersion: 3, - - // ... - - edit: function () { - var blockProps = wp.blockEditor.useBlockProps( { - className: 'my-random-classname', - } ); - - return React.createElement( 'div', blockProps, 'Your block.' ); - }, -}; -``` - -{% end %} ### attributes @@ -100,8 +59,6 @@ The `attributes` property surfaces all the available attributes and their corres In this case, assuming we had defined an attribute of `content` during block registration, we would receive and use that value in our edit function: -{% codetabs %} -{% JSX %} ```js edit: ( { attributes } ) => { @@ -111,21 +68,6 @@ edit: ( { attributes } ) => { }; ``` -{% Plain %} - -```js -edit: function( props ) { - var blockProps = wp.blockEditor.useBlockProps(); - - return React.createElement( - 'div', - blockProps, - props.attributes.content - ); -} -``` - -{% end %} The value of `attributes.content` will be displayed inside the `div` when inserting the block in the editor. @@ -133,8 +75,6 @@ The value of `attributes.content` will be displayed inside the `div` when insert The isSelected property is an boolean that communicates whether the block is currently selected. -{% codetabs %} -{% JSX %} ```jsx edit: ( { attributes, isSelected } ) => { @@ -151,35 +91,10 @@ edit: ( { attributes, isSelected } ) => { }; ``` -{% Plain %} - -```js -edit: function( props ) { - var blockProps = wp.blockEditor.useBlockProps(); - - return React.createElement( - 'div', - blockProps, - [ - 'Your block.', - props.isSelected ? React.createElement( - 'span', - null, - 'Shows only when the block is selected.' - ) - ] - ); -} -``` - -{% end %} - ### setAttributes This function allows the block to update individual attributes based on user interactions. -{% codetabs %} -{% JSX %} ```jsx edit: ( { attributes, setAttributes, isSelected } ) => { @@ -201,40 +116,8 @@ edit: ( { attributes, setAttributes, isSelected } ) => { }; ``` -{% Plain %} - -```js -edit: function( props ) { - var blockProps = wp.blockEditor.useBlockProps(); - - // Simplify access to attributes - let content = props.attributes.content; - let mySetting = props.attributes.mySetting; - - // Toggle a setting when the user clicks the button - let toggleSetting = () => props.setAttributes( { mySetting: ! mySetting } ); - return React.createElement( - 'div', - blockProps, - [ - content, - props.isSelected ? React.createElement( - 'button', - { onClick: toggleSetting }, - 'Toggle setting' - ) : null - ] - ); -}, -``` - -{% end %} - When using attributes that are objects or arrays it's a good idea to copy or clone the attribute prior to updating it: -{% codetabs %} -{% JSX %} - ```js // Good - a new array is created from the old list attribute and a new list item: const { list } = attributes; @@ -249,25 +132,6 @@ const addListItem = ( newListItem ) => { }; ``` -{% Plain %} - -```js -// Good - cloning the old list -var newList = attributes.list.slice(); - -var addListItem = function ( newListItem ) { - setAttributes( { list: newList.concat( [ newListItem ] ) } ); -}; - -// Bad - the list from the existing attribute is modified directly to add the new list item: -var list = attributes.list; -var addListItem = function ( newListItem ) { - list.push( newListItem ); - setAttributes( { list: list } ); -}; -``` - -{% end %} Why do this? In JavaScript, arrays and objects are passed by reference, so this practice ensures changes won't affect other code that might hold references to the same data. Furthermore, the Gutenberg project follows the philosophy of the Redux library that [state should be immutable](https://redux.js.org/faq/immutable-data#what-are-the-benefits-of-immutability)—data should not be changed directly, but instead a new version of the data created containing the changes. @@ -275,8 +139,6 @@ Why do this? In JavaScript, arrays and objects are passed by reference, so this The `save` function defines the way in which the different attributes should be combined into the final markup, which is then serialized into `post_content`. -{% codetabs %} -{% JSX %} ```jsx save: () => { @@ -286,21 +148,6 @@ save: () => { }; ``` -{% Plain %} - -```js -save: function() { - var blockProps = wp.blockEditor.useBlockProps.save(); - - return React.createElement( - 'div', - blockProps, - 'Your block.' - ); -} -``` - -{% end %} For most blocks, the return value of `save` should be an [instance of WordPress Element](/packages/element/README.md) representing how the block is to appear on the front of the site. @@ -326,8 +173,6 @@ Like the `edit` function, when rendering static blocks, it's important to add th As with `edit`, the `save` function also receives an object argument including attributes which can be inserted into the markup. -{% codetabs %} -{% JSX %} ```jsx save: ( { attributes } ) => { @@ -337,21 +182,7 @@ save: ( { attributes } ) => { }; ``` -{% Plain %} -```js -save: function( props ) { - var blockProps = wp.blockEditor.useBlockProps.save(); - - return React.createElement( - 'div', - blockProps, - props.attributes.content - ); -} -``` - -{% end %} When saving your block, you want to save the attributes in the same format specified by the attribute source definition. If no attribute source is specified, the attribute will be saved to the block's comment delimiter. See the [Block Attributes documentation](/docs/reference-guides/block-api/block-attributes.md) for more details. @@ -361,8 +192,6 @@ Here are a couple examples of using attributes, edit, and save all together. For ### Saving Attributes to Child Elements -{% codetabs %} -{% JSX %} ```jsx attributes: { @@ -396,46 +225,6 @@ save: ( { attributes } ) => { }, ``` -{% Plain %} - -```js -attributes: { - content: { - type: 'string', - source: 'html', - selector: 'p' - } -}, - -edit: function( props ) { - var blockProps = wp.blockEditor.useBlockProps(); - var updateFieldValue = function( val ) { - props.setAttributes( { content: val } ); - } - - return React.createElement( - 'div', - blockProps, - React.createElement( - wp.components.TextControl, - { - label: 'My Text Field', - value: props.attributes.content, - onChange: updateFieldValue, - - } - ) - ); -}, - -save: function( props ) { - var blockProps = wp.blockEditor.useBlockProps.save(); - - return React.createElement( 'div', blockProps, props.attributes.content ); -}, -``` - -{% end %} ### Saving Attributes via Serialization @@ -443,8 +232,6 @@ Ideally, the attributes saved should be included in the markup. However, there a This example could be for a dynamic block, such as the [Latest Posts block](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-library/src/latest-posts/index.js), which renders the markup server-side. The save function is still required, however in this case it simply returns null since the block is not saving content from the editor. -{% codetabs %} -{% JSX %} ```jsx attributes: { @@ -474,41 +261,6 @@ save: () => { } ``` -{% Plain %} - -```js -attributes: { - postsToShow: { - type: 'number', - } -}, - -edit: function( props ) { - var blockProps = wp.blockEditor.useBlockProps(); - - return React.createEleement( - 'div', - blockProps, - React.createElement( - wp.components.TextControl, - { - label: 'Number Posts to Show', - value: props.attributes.postsToShow, - onChange: function( val ) { - props.setAttributes( { postsToShow: parseInt( val ) } ); - }, - } - ) - ); -}, - -save: function() { - return null; -} -``` - -{% end %} - ## Validation When the editor loads, all blocks within post content are validated to determine their accuracy in order to protect against content loss. This is closely related to the saving implementation of a block, as a user may unintentionally remove or modify their content if the editor is unable to restore a block correctly. During editor initialization, the saved markup for each block is regenerated using the attributes that were parsed from the post's content. If the newly-generated markup does not match what was already stored in post content, the block is marked as invalid. This is because we assume that unless the user makes edits, the markup should remain identical to the saved content. diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 0ae5979b797047..10f0f76d9eb0d7 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -378,8 +378,8 @@ Insert an image to make a visual statement. ([Source](https://github.com/WordPre - **Name:** core/image - **Category:** media -- **Supports:** anchor, color (~~background~~, ~~text~~), filter (duotone) -- **Attributes:** align, alt, aspectRatio, caption, height, href, id, lightbox, linkClass, linkDestination, linkTarget, rel, scale, sizeSlug, title, url, width +- **Supports:** align (center, full, left, right, wide), anchor, color (~~background~~, ~~text~~), filter (duotone) +- **Attributes:** alt, aspectRatio, caption, height, href, id, lightbox, linkClass, linkDestination, linkTarget, rel, scale, sizeSlug, title, url, width ## Latest Comments diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md index ea97ce28e4d85c..b2a75638ace9fe 100644 --- a/docs/reference-guides/data/data-core.md +++ b/docs/reference-guides/data/data-core.md @@ -128,6 +128,8 @@ _Returns_ ### getCurrentThemeGlobalStylesRevisions +> **Deprecated** since WordPress 6.5.0. Callers should use `select( 'core' ).getRevisions( 'root', 'globalStyles', ${ recordKey } )` instead, where `recordKey` is the id of the global styles parent post. + Returns the revisions of the current global styles theme. _Parameters_ @@ -420,6 +422,39 @@ _Returns_ - A value whose reference will change only when an edit occurs. +### getRevision + +Returns a single, specific revision of a parent entity. + +_Parameters_ + +- _state_ `State`: State tree +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordKey_ `EntityRecordKey`: The key of the entity record whose revisions you want to fetch. +- _revisionKey_ `EntityRecordKey`: The revision's key. +- _query_ `GetRecordsHttpQuery`: Optional query. If requesting specific fields, fields must always include the ID. For valid query parameters see revisions schema in [the REST API Handbook](https://developer.wordpress.org/rest-api/reference/). Then see the arguments available "Retrieve a [entity kind]". + +_Returns_ + +- `RevisionRecord | Record< PropertyKey, never > | undefined`: Record. + +### getRevisions + +Returns an entity's revisions. + +_Parameters_ + +- _state_ `State`: State tree +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordKey_ `EntityRecordKey`: The key of the entity record whose revisions you want to fetch. +- _query_ `GetRecordsHttpQuery`: Optional query. If requesting specific fields, fields must always include the ID. For valid query parameters see revisions schema in [the REST API Handbook](https://developer.wordpress.org/rest-api/reference/). Then see the arguments available "Retrieve a [Entity kind]". + +_Returns_ + +- `RevisionRecord[] | null`: Record. + ### getThemeSupports Return theme supports data in the index. @@ -704,6 +739,24 @@ _Returns_ - `Object`: Action object. +### receiveRevisions + +Returns an action object used in signalling that revisions have been received. + +_Parameters_ + +- _kind_ `string`: Kind of the received entity record revisions. +- _name_ `string`: Name of the received entity record revisions. +- _recordKey_ `number|string`: The key of the entity record whose revisions you want to fetch. +- _records_ `Array|Object`: Revisions received. +- _query_ `?Object`: Query Object. +- _invalidateCache_ `?boolean`: Should invalidate query caches. +- _meta_ `?Object`: Meta information about pagination. + +_Returns_ + +- `Object`: Action object. + ### receiveThemeSupports > **Deprecated** since WP 5.9, this is not useful anymore, use the selector direclty. diff --git a/docs/reference-guides/filters/autocomplete-filters.md b/docs/reference-guides/filters/autocomplete-filters.md index 1ce47219529181..85581f62e4af01 100644 --- a/docs/reference-guides/filters/autocomplete-filters.md +++ b/docs/reference-guides/filters/autocomplete-filters.md @@ -8,8 +8,7 @@ The `Autocomplete` component found in `@wordpress/block-editor` applies this fil Here is an example of using the `editor.Autocomplete.completers` filter to add an acronym completer. You can find full documentation for the autocompleter interface with the `Autocomplete` component in the `@wordpress/components` package. -{% codetabs %} -{% JSX %} + ```jsx // Our completer @@ -45,48 +44,3 @@ wp.hooks.addFilter( appendAcronymCompleter ); ``` - -{% Plain %} - -```js -// Our completer -var acronymCompleter = { - name: 'acronyms', - triggerPrefix: '::', - options: [ - { letters: 'FYI', expansion: 'For Your Information' }, - { letters: 'AFAIK', expansion: 'As Far As I Know' }, - { letters: 'IIRC', expansion: 'If I Recall Correctly' }, - ], - getOptionKeywords: function ( abbr ) { - var expansionWords = abbr.expansion.split( /\s+/ ); - return [ abbr.letters ].concat( expansionWords ); - }, - getOptionLabel: function ( acronym ) { - return acronym.letters; - }, - getOptionCompletion: function ( abbr ) { - return React.createElement( - 'abbr', - { title: abbr.expansion }, - abbr.letters - ); - }, -}; - -// Our filter function -function appendAcronymCompleter( completers, blockName ) { - return blockName === 'my-plugin/foo' - ? completers.concat( acronymCompleter ) - : completers; -} - -// Adding the filter -wp.hooks.addFilter( - 'editor.Autocomplete.completers', - 'my-plugin/autocompleters/acronyms', - appendAcronymCompleter -); -``` - -{% end %} diff --git a/docs/reference-guides/filters/block-filters.md b/docs/reference-guides/filters/block-filters.md index 912403c4838941..e269ba9ef19917 100644 --- a/docs/reference-guides/filters/block-filters.md +++ b/docs/reference-guides/filters/block-filters.md @@ -179,8 +179,6 @@ Used to modify the block's `edit` component. It receives the original block `Blo _Example:_ -{% codetabs %} -{% JSX %} ```js const { createHigherOrderComponent } = wp.compose; @@ -207,36 +205,6 @@ wp.hooks.addFilter( ); ``` -{% Plain %} - -```js -var el = React.createElement; - -var withMyPluginControls = wp.compose.createHigherOrderComponent( function ( - BlockEdit -) { - return function ( props ) { - return el( - React.Fragment, - {}, - el( BlockEdit, props ), - el( - wp.blockEditor.InspectorControls, - {}, - el( wp.components.PanelBody, {}, 'My custom control' ) - ) - ); - }; -}, 'withMyPluginControls' ); - -wp.hooks.addFilter( - 'editor.BlockEdit', - 'my-plugin/with-inspector-controls', - withMyPluginControls -); -``` - -{% end %} Note that as this hook is run for _all blocks_, consuming it has potential for performance regressions particularly around block selection metrics. @@ -267,9 +235,6 @@ Used to modify the block's wrapper component containing the block's `edit` compo _Example:_ -{% codetabs %} -{% JSX %} - ```js const { createHigherOrderComponent } = wp.compose; @@ -294,39 +259,10 @@ wp.hooks.addFilter( ); ``` -{% Plain %} - -```js -var el = React.createElement; - -var withClientIdClassName = wp.compose.createHigherOrderComponent( function ( - BlockListBlock -) { - return function ( props ) { - var newProps = { - ...props, - className: 'block-' + props.clientId, - }; - - return el( BlockListBlock, newProps ); - }; -}, 'withClientIdClassName' ); - -wp.hooks.addFilter( - 'editor.BlockListBlock', - 'my-plugin/with-client-id-class-name', - withClientIdClassName -); -``` - -{% end %} - Adding new properties to the block's wrapper component can be achieved by adding them to the `wrapperProps` property of the returned component. _Example:_ -{% codetabs %} -{% JSX %} ```js const { createHigherOrderComponent } = wp.compose; @@ -346,32 +282,6 @@ wp.hooks.addFilter( ); ``` -{% Plain %} - -```js -var el = React.createElement; -var hoc = wp.compose.createHigherOrderComponent; - -var withMyWrapperProp = hoc( function ( BlockListBlock ) { - return function ( props ) { - var newProps = { - ...props, - wrapperProps: { - ...props.wrapperProps, - 'data-my-property': 'the-value', - }, - }; - return el( BlockListBlock, newProps ); - }; -}, 'withMyWrapperProp' ); -wp.hooks.addFilter( - 'editor.BlockListBlock', - 'my-plugin/with-my-wrapper-prop', - withMyWrapperProp -); -``` - -{% end %} ## Removing Blocks @@ -379,8 +289,6 @@ wp.hooks.addFilter( Adding blocks is easy enough, removing them is as easy. Plugin or theme authors have the possibility to "unregister" blocks. -{% codetabs %} -{% JSX %} ```js // my-plugin.js @@ -392,16 +300,6 @@ domReady( function () { } ); ``` -{% Plain %} - -```js -// my-plugin.js -wp.domReady( function () { - wp.blocks.unregisterBlockType( 'core/verse' ); -} ); -``` - -{% end %} and load this script in the Editor diff --git a/docs/reference-guides/richtext.md b/docs/reference-guides/richtext.md index f908c7585bc1b8..1a4509318b72b7 100644 --- a/docs/reference-guides/richtext.md +++ b/docs/reference-guides/richtext.md @@ -25,8 +25,7 @@ There are a number of core blocks using the RichText component. The JavaScript e ## Example -{% codetabs %} -{% JSX %} + ```jsx import { registerBlockType } from '@wordpress/blocks'; @@ -66,46 +65,6 @@ registerBlockType( /* ... */, { } ); ``` -{% Plain %} - -```js -wp.blocks.registerBlockType( /* ... */, { - // ... - - attributes: { - content: { - type: 'string', - source: 'html', - selector: 'h2', - }, - }, - - edit: function( props ) { - var blockProps = wp.blockEditor.useBlockProps(); - - return React.createElement( wp.blockEditor.RichText, Object.assign( blockProps, { - tagName: 'h2', // The tag here is the element output and editable in the admin - value: props.attributes.content, // Any existing content, either from the database or an attribute default - allowedFormats: [ 'core/bold', 'core/italic' ], // Allow the content to be made bold or italic, but do not allow other formatting options - onChange: function( content ) { - props.setAttributes( { content: content } ); // Store updated content as a block attribute - }, - placeholder: __( 'Heading...' ), // Display this text before any content has been added by the user - } ) ); - }, - - save: function( props ) { - var blockProps = wp.blockEditor.useBlockProps.save(); - - return React.createElement( wp.blockEditor.RichText.Content, Object.assign( blockProps, { - tagName: 'h2', value: props.attributes.content // Saves

Content added in the editor...

to the database for frontend display - } ) ); - } -} ); -``` - -{% end %} - ## Common Issues & Solutions While using the RichText component a number of common issues tend to appear. diff --git a/docs/toc.json b/docs/toc.json index cbabf3d3b737c6..8a29d2d4f10aff 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -20,6 +20,7 @@ } ] }, + { "docs/getting-started/quick-start-guide.md": [] }, { "docs/getting-started/create-block/README.md": [ { diff --git a/gutenberg.php b/gutenberg.php index 8a8b1db6f85b92..018c269f4f565d 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the block editor, site editor, and other future WordPress core functionality. * Requires at least: 6.2 * Requires PHP: 7.0 - * Version: 17.0.2 + * Version: 17.1.0 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index 80be02db68360e..d35c963d0bed48 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -617,6 +617,9 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { $processor->add_class( $class_name ); } return $processor->get_updated_html(); + } elseif ( ! $block_supports_layout ) { + // Ensure layout classnames are not injected if there is no layout support. + return $block_content; } $global_settings = gutenberg_get_global_settings(); @@ -865,17 +868,63 @@ function gutenberg_restore_group_inner_container( $block_content, $block ) { return $block_content; } - $replace_regex = sprintf( + /** + * This filter runs after the layout classnames have been added to the block, so they + * have to be removed from the outer wrapper and then added to the inner. + */ + $layout_classes = array(); + $processor = new WP_HTML_Tag_Processor( $block_content ); + + if ( $processor->next_tag( array( 'class_name' => 'wp-block-group' ) ) ) { + if ( method_exists( $processor, 'class_list' ) ) { + foreach ( $processor->class_list() as $class_name ) { + if ( str_contains( $class_name, 'layout' ) ) { + array_push( $layout_classes, $class_name ); + $processor->remove_class( $class_name ); + } + } + } else { + /** + * The class_list method was only added in 6.4 so this needs a temporary fallback. + * This fallback should be removed when the minimum supported version is 6.4. + */ + $classes = $processor->get_attribute( 'class' ); + if ( $classes ) { + $classes = explode( ' ', $classes ); + foreach ( $classes as $class_name ) { + if ( str_contains( $class_name, 'layout' ) ) { + array_push( $layout_classes, $class_name ); + $processor->remove_class( $class_name ); + } + } + } + } + } + + $content_without_layout_classes = $processor->get_updated_html(); + $replace_regex = sprintf( '/(^\s*<%1$s\b[^>]*wp-block-group[^>]*>)(.*)(<\/%1$s>\s*$)/ms', preg_quote( $tag_name, '/' ) ); - $updated_content = preg_replace_callback( + $updated_content = preg_replace_callback( $replace_regex, static function ( $matches ) { return $matches[1] . '
' . $matches[2] . '
' . $matches[3]; }, - $block_content + $content_without_layout_classes ); + + // Add layout classes to inner wrapper. + if ( ! empty( $layout_classes ) ) { + $processor = new WP_HTML_Tag_Processor( $updated_content ); + if ( $processor->next_tag( array( 'class_name' => 'wp-block-group__inner-container' ) ) ) { + foreach ( $layout_classes as $class_name ) { + $processor->add_class( $class_name ); + } + } + $updated_content = $processor->get_updated_html(); + } + return $updated_content; } diff --git a/lib/class-wp-duotone-gutenberg.php b/lib/class-wp-duotone-gutenberg.php index 11f6c6dcccdd93..93714e59740063 100644 --- a/lib/class-wp-duotone-gutenberg.php +++ b/lib/class-wp-duotone-gutenberg.php @@ -938,9 +938,17 @@ public static function output_footer_assets() { echo self::get_svg_definitions( self::$used_svg_filter_data ); } - // This is for classic themes - in block themes, the CSS is added in the head via wp_add_inline_style in the wp_enqueue_scripts action. - if ( ! wp_is_block_theme() && ! empty( self::$used_global_styles_presets ) ) { - wp_add_inline_style( 'core-block-supports', self::get_global_styles_presets( self::$used_global_styles_presets ) ); + // In block themes, the CSS is added in the head via wp_add_inline_style in the wp_enqueue_scripts action. + if ( ! wp_is_block_theme() ) { + $style_tag_id = 'core-block-supports-duotone'; + wp_register_style( $style_tag_id, false ); + if ( ! empty( self::$used_global_styles_presets ) ) { + wp_add_inline_style( $style_tag_id, self::get_global_styles_presets( self::$used_global_styles_presets ) ); + } + if ( ! empty( self::$block_css_declarations ) ) { + wp_add_inline_style( $style_tag_id, gutenberg_style_engine_get_stylesheet_from_css_rules( self::$block_css_declarations ) ); + } + wp_enqueue_style( $style_tag_id ); } } diff --git a/lib/experimental/fonts/font-library/class-wp-font-family.php b/lib/experimental/fonts/font-library/class-wp-font-family.php index 11cf1fcdbdc64c..d1203562220ce3 100644 --- a/lib/experimental/fonts/font-library/class-wp-font-family.php +++ b/lib/experimental/fonts/font-library/class-wp-font-family.php @@ -300,16 +300,20 @@ private function sanitize() { 'version' => '2', 'settings' => array( 'typography' => array( - 'fontFamilies' => array( $this->data ), + 'fontFamilies' => array( + 'custom' => array( + $this->data, + ), + ), ), ), ); // Creates a new WP_Theme_JSON object with the new fonts to // leverage sanitization and validation. - $theme_json = new WP_Theme_JSON_Gutenberg( $fonts_json ); - $theme_data = $theme_json->get_data(); - + $fonts_json = WP_Theme_JSON_Gutenberg::remove_insecure_properties( $fonts_json ); + $theme_json = new WP_Theme_JSON_Gutenberg( $fonts_json ); + $theme_data = $theme_json->get_data(); $sanitized_font = ! empty( $theme_data['settings']['typography']['fontFamilies'] ) ? $theme_data['settings']['typography']['fontFamilies'][0] : array(); @@ -398,6 +402,11 @@ private function download_or_move_font_faces( $files ) { continue; } + // If the font face requires the use of the filesystem, create the fonts dir if it doesn't exist. + if ( ! empty( $font_face['downloadFromUrl'] ) && ! empty( $font_face['uploadedFile'] ) ) { + wp_mkdir_p( WP_Font_Library::get_fonts_dir() ); + } + // If installing google fonts, download the font face assets. if ( ! empty( $font_face['downloadFromUrl'] ) ) { $new_font_face = $this->download_font_face_assets( $new_font_face ); diff --git a/lib/experimental/interactivity-api/class-wp-directive-processor.php b/lib/experimental/interactivity-api/class-wp-directive-processor.php index 7d9ccdca453b8f..e717b2e5539431 100644 --- a/lib/experimental/interactivity-api/class-wp-directive-processor.php +++ b/lib/experimental/interactivity-api/class-wp-directive-processor.php @@ -27,17 +27,26 @@ class WP_Directive_Processor extends Gutenberg_HTML_Tag_Processor_6_4 { * * @var array */ - public static $root_blocks = array(); + public static $root_block = null; /** - * Add a root block to the list. + * Add a root block to the variable. * * @param array $block The block to add. * * @return void */ - public static function add_root_block( $block ) { - self::$root_blocks[] = md5( serialize( $block ) ); + public static function mark_root_block( $block ) { + self::$root_block = md5( serialize( $block ) ); + } + + /** + * Remove a root block to the variable. + * + * @return void + */ + public static function unmark_root_block() { + self::$root_block = null; } /** @@ -47,8 +56,17 @@ public static function add_root_block( $block ) { * * @return bool True if block is a root block, false otherwise. */ - public static function is_root_block( $block ) { - return in_array( md5( serialize( $block ) ), self::$root_blocks, true ); + public static function is_marked_as_root_block( $block ) { + return md5( serialize( $block ) ) === self::$root_block; + } + + /** + * Check if a root block has already been defined. + * + * @return bool True if block is a root block, false otherwise. + */ + public static function has_root_block() { + return isset( self::$root_block ); } @@ -92,6 +110,75 @@ public function next_balanced_closer() { return false; } + /** + * Traverses the HTML searching for Interactivity API directives and processing + * them. + * + * @param WP_Directive_Processor $tags An instance of the WP_Directive_Processor. + * @param string $prefix Attribute prefix. + * @param string[] $directives Directives. + * + * @return WP_Directive_Processor The modified instance of the + * WP_Directive_Processor. + */ + public function process_rendered_html( $tags, $prefix, $directives ) { + $context = new WP_Directive_Context(); + $tag_stack = array(); + + while ( $tags->next_tag( array( 'tag_closers' => 'visit' ) ) ) { + $tag_name = $tags->get_tag(); + + // Is this a tag that closes the latest opening tag? + if ( $tags->is_tag_closer() ) { + if ( 0 === count( $tag_stack ) ) { + continue; + } + + list( $latest_opening_tag_name, $attributes ) = end( $tag_stack ); + if ( $latest_opening_tag_name === $tag_name ) { + array_pop( $tag_stack ); + + // If the matching opening tag didn't have any directives, we move on. + if ( 0 === count( $attributes ) ) { + continue; + } + } + } else { + $attributes = array(); + foreach ( $tags->get_attribute_names_with_prefix( $prefix ) as $name ) { + /* + * Removes the part after the double hyphen before looking for + * the directive processor inside `$directives`, e.g., "wp-bind" + * from "wp-bind--src" and "wp-context" from "wp-context" etc... + */ + list( $type ) = WP_Directive_Processor::parse_attribute_name( $name ); + if ( array_key_exists( $type, $directives ) ) { + $attributes[] = $type; + } + } + + /* + * If this is an open tag, and if it either has directives, or if + * we're inside a tag that does, take note of this tag and its + * directives so we can call its directive processor once we + * encounter the matching closing tag. + */ + if ( + ! WP_Directive_Processor::is_html_void_element( $tags->get_tag() ) && + ( 0 !== count( $attributes ) || 0 !== count( $tag_stack ) ) + ) { + $tag_stack[] = array( $tag_name, $attributes ); + } + } + + foreach ( $attributes as $attribute ) { + call_user_func( $directives[ $attribute ], $tags, $context ); + } + } + + return $tags; + } + /** * Return the content between two balanced tags. * diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index eae731e2438913..064fc8ea62cbb2 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -8,9 +8,9 @@ */ /** - * Process the Interactivity API directives using the root blocks of the - * outermost rendering, ignoring the root blocks of inner blocks like Patterns, - * Template Parts or Content. + * Mark if the block is a root block. Checks that there is already a root block + * in order not to mark template-parts or synced patterns as root blocks, where + * the parent is null. * * @param array $parsed_block The parsed block. * @param array $source_block The source block. @@ -18,121 +18,44 @@ * * @return array The parsed block. */ -function gutenberg_interactivity_process_directives( $parsed_block, $source_block, $parent_block ) { - static $is_inside_root_block = false; - static $process_directives_in_root_blocks = null; - - if ( ! isset( $process_directives_in_root_blocks ) ) { - /** - * Process directives in each root block. - * - * @param string $block_content The block content. - * @param array $block The full block. - * - * @return string Filtered block content. - */ - $process_directives_in_root_blocks = static function ( $block_content, $block ) use ( &$is_inside_root_block ) { - - if ( WP_Directive_Processor::is_root_block( $block ) ) { - - $directives = array( - 'data-wp-bind' => 'gutenberg_interactivity_process_wp_bind', - 'data-wp-context' => 'gutenberg_interactivity_process_wp_context', - 'data-wp-class' => 'gutenberg_interactivity_process_wp_class', - 'data-wp-style' => 'gutenberg_interactivity_process_wp_style', - 'data-wp-text' => 'gutenberg_interactivity_process_wp_text', - ); - - $tags = new WP_Directive_Processor( $block_content ); - $tags = gutenberg_interactivity_process_rendered_html( $tags, 'data-wp-', $directives ); - $is_inside_root_block = false; - return $tags->get_updated_html(); - - } - - return $block_content; - }; - add_filter( 'render_block', $process_directives_in_root_blocks, 10, 2 ); - } - - if ( ! isset( $parent_block ) && ! $is_inside_root_block ) { - WP_Directive_Processor::add_root_block( $parsed_block ); - $is_inside_root_block = true; +function gutenberg_interactivity_mark_root_blocks( $parsed_block, $source_block, $parent_block ) { + if ( ! isset( $parent_block ) && ! WP_Directive_Processor::has_root_block() ) { + WP_Directive_Processor::mark_root_block( $parsed_block ); } return $parsed_block; } -add_filter( 'render_block_data', 'gutenberg_interactivity_process_directives', 10, 3 ); - +add_filter( 'render_block_data', 'gutenberg_interactivity_mark_root_blocks', 10, 3 ); /** - * Traverses the HTML searching for Interactivity API directives and processing - * them. + * Process directives in each root block. * - * @param WP_Directive_Processor $tags An instance of the WP_Directive_Processor. - * @param string $prefix Attribute prefix. - * @param string[] $directives Directives. + * @param string $block_content The block content. + * @param array $block The full block. * - * @return WP_Directive_Processor The modified instance of the - * WP_Directive_Processor. + * @return string Filtered block content. */ -function gutenberg_interactivity_process_rendered_html( $tags, $prefix, $directives ) { - $context = new WP_Directive_Context(); - $tag_stack = array(); +function gutenberg_process_directives_in_root_blocks( $block_content, $block ) { + if ( WP_Directive_Processor::is_marked_as_root_block( $block ) ) { + WP_Directive_Processor::unmark_root_block(); + $directives = array( + 'data-wp-bind' => 'gutenberg_interactivity_process_wp_bind', + 'data-wp-context' => 'gutenberg_interactivity_process_wp_context', + 'data-wp-class' => 'gutenberg_interactivity_process_wp_class', + 'data-wp-style' => 'gutenberg_interactivity_process_wp_style', + 'data-wp-text' => 'gutenberg_interactivity_process_wp_text', + ); + + $tags = new WP_Directive_Processor( $block_content ); + $tags = $tags->process_rendered_html( $tags, 'data-wp-', $directives ); + return $tags->get_updated_html(); - while ( $tags->next_tag( array( 'tag_closers' => 'visit' ) ) ) { - $tag_name = $tags->get_tag(); - - // Is this a tag that closes the latest opening tag? - if ( $tags->is_tag_closer() ) { - if ( 0 === count( $tag_stack ) ) { - continue; - } - - list( $latest_opening_tag_name, $attributes ) = end( $tag_stack ); - if ( $latest_opening_tag_name === $tag_name ) { - array_pop( $tag_stack ); - - // If the matching opening tag didn't have any directives, we move on. - if ( 0 === count( $attributes ) ) { - continue; - } - } - } else { - $attributes = array(); - foreach ( $tags->get_attribute_names_with_prefix( $prefix ) as $name ) { - /* - * Removes the part after the double hyphen before looking for - * the directive processor inside `$directives`, e.g., "wp-bind" - * from "wp-bind--src" and "wp-context" from "wp-context" etc... - */ - list( $type ) = WP_Directive_Processor::parse_attribute_name( $name ); - if ( array_key_exists( $type, $directives ) ) { - $attributes[] = $type; - } - } - - /* - * If this is an open tag, and if it either has directives, or if - * we're inside a tag that does, take note of this tag and its - * directives so we can call its directive processor once we - * encounter the matching closing tag. - */ - if ( - ! WP_Directive_Processor::is_html_void_element( $tags->get_tag() ) && - ( 0 !== count( $attributes ) || 0 !== count( $tag_stack ) ) - ) { - $tag_stack[] = array( $tag_name, $attributes ); - } - } - - foreach ( $attributes as $attribute ) { - call_user_func( $directives[ $attribute ], $tags, $context ); - } } - return $tags; + return $block_content; } +add_filter( 'render_block', 'gutenberg_process_directives_in_root_blocks', 10, 2 ); + /** * Resolve the reference using the store and the context from the provided path. diff --git a/package-lock.json b/package-lock.json index 9439981c29d7dc..601aafc18b2684 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gutenberg", - "version": "17.0.2", + "version": "17.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "gutenberg", - "version": "17.0.2", + "version": "17.1.0", "hasInstallScript": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -84,6 +84,7 @@ "devDependencies": { "@actions/core": "1.9.1", "@actions/github": "5.0.0", + "@ariakit/test": "^0.3.0", "@babel/core": "7.16.0", "@babel/plugin-proposal-export-namespace-from": "7.18.9", "@babel/plugin-syntax-jsx": "7.16.0", @@ -1664,6 +1665,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/@ariakit/test/-/test-0.3.0.tgz", "integrity": "sha512-fngAzkzCl9zM4O/tzVaUf7ixWUAk779hjTegr/zcegoxaMS5SmaLhNQ7RU0AGx06LrhJse6GYGy8ZtK58HP/EQ==", + "dev": true, "dependencies": { "@ariakit/core": "0.3.3", "@testing-library/dom": "^8.0.0 || ^9.0.0" @@ -1681,6 +1683,12 @@ } } }, + "node_modules/@ariakit/test/node_modules/@ariakit/core": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.3.3.tgz", + "integrity": "sha512-8x77R0aE9O9pheygg+h/z0oU9Wx/Xdlr7nfkl4klGnkJma8/nAhJ2RrchCTQCUef4WMsRnq/doCz8m/sslP6CA==", + "dev": true + }, "node_modules/@aw-web-design/x-default-browser": { "version": "1.4.126", "resolved": "https://registry.npmjs.org/@aw-web-design/x-default-browser/-/x-default-browser-1.4.126.tgz", @@ -15282,6 +15290,7 @@ "version": "9.3.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.1.tgz", "integrity": "sha512-0DGPd9AR3+iDTjGoMpxIkAsUihHZ3Ai6CneU6bRRrffXMgzCdlNk43jTrD2/5LT6CBb3MWTP8v510JzYtahD2w==", + "dev": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -15300,6 +15309,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, "engines": { "node": ">=10" }, @@ -15311,6 +15321,7 @@ "version": "5.1.3", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, "dependencies": { "deep-equal": "^2.0.5" } @@ -15319,6 +15330,7 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.2.tgz", "integrity": "sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA==", + "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.0", "call-bind": "^1.0.2", @@ -15346,12 +15358,14 @@ "node_modules/@testing-library/dom/node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true }, "node_modules/@testing-library/dom/node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -15364,7 +15378,8 @@ "node_modules/@testing-library/dom/node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true }, "node_modules/@testing-library/jest-dom": { "version": "5.16.5", @@ -15567,7 +15582,8 @@ "node_modules/@types/aria-query": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz", - "integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==" + "integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==", + "dev": true }, "node_modules/@types/async-lock": { "version": "1.4.0", @@ -19893,6 +19909,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "is-array-buffer": "^3.0.1" @@ -20232,6 +20249,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -21670,6 +21688,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -25107,6 +25126,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, "dependencies": { "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" @@ -25687,7 +25707,8 @@ "node_modules/dom-accessibility-api": { "version": "0.5.14", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz", - "integrity": "sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==" + "integrity": "sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==", + "dev": true }, "node_modules/dom-converter": { "version": "0.2.0", @@ -25835,11 +25856,6 @@ "node": ">=12" } }, - "node_modules/downloadjs": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/downloadjs/-/downloadjs-1.4.7.tgz", - "integrity": "sha512-LN1gO7+u9xjU5oEScGFKvXhYf7Y/empUIIEAGBs1LzUq/rg5duiDrkuH5A2lQGd5jfMOb9X9usDa2oVXwJ0U/Q==" - }, "node_modules/downshift": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/downshift/-/downshift-6.1.0.tgz", @@ -26310,6 +26326,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", @@ -26328,7 +26345,8 @@ "node_modules/es-get-iterator/node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true }, "node_modules/es-module-lexer": { "version": "1.3.1", @@ -28716,6 +28734,7 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, "dependencies": { "is-callable": "^1.1.3" } @@ -29102,6 +29121,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -29335,6 +29355,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -29825,6 +29846,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -29986,6 +30008,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -30002,6 +30025,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, "dependencies": { "get-intrinsic": "^1.1.1" }, @@ -30013,6 +30037,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -30024,6 +30049,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -30035,6 +30061,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -31208,6 +31235,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, "dependencies": { "get-intrinsic": "^1.2.0", "has": "^1.0.3", @@ -31337,6 +31365,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -31352,6 +31381,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.0", @@ -31370,6 +31400,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, "dependencies": { "has-bigints": "^1.0.1" }, @@ -31393,6 +31424,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -31428,6 +31460,7 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -31490,6 +31523,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -31683,6 +31717,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -31730,6 +31765,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -31840,6 +31876,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -31864,6 +31901,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -31872,6 +31910,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -31900,6 +31939,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -31914,6 +31954,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -31940,6 +31981,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dev": true, "dependencies": { "which-typed-array": "^1.1.11" }, @@ -31971,6 +32013,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -31991,6 +32034,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" @@ -36655,6 +36699,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, "bin": { "lz-string": "bin/bin.js" } @@ -41607,6 +41652,7 @@ "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -41615,6 +41661,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3" @@ -41630,6 +41677,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, "engines": { "node": ">= 0.4" } @@ -41649,6 +41697,7 @@ "version": "4.1.4", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -46384,6 +46433,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -48147,6 +48197,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, "dependencies": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -48983,6 +49034,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, "dependencies": { "internal-slot": "^1.0.4" }, @@ -53621,6 +53673,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, "dependencies": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", @@ -53636,6 +53689,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, "dependencies": { "is-map": "^2.0.1", "is-set": "^2.0.1", @@ -53655,6 +53709,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -54349,7 +54404,7 @@ }, "packages/a11y": { "name": "@wordpress/a11y", - "version": "3.45.0", + "version": "3.46.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54362,7 +54417,7 @@ }, "packages/annotations": { "name": "@wordpress/annotations", - "version": "2.45.0", + "version": "2.46.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54390,7 +54445,7 @@ }, "packages/api-fetch": { "name": "@wordpress/api-fetch", - "version": "6.42.0", + "version": "6.43.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54403,7 +54458,7 @@ }, "packages/autop": { "name": "@wordpress/autop", - "version": "3.45.0", + "version": "3.46.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0" @@ -54414,7 +54469,7 @@ }, "packages/babel-plugin-import-jsx-pragma": { "name": "@wordpress/babel-plugin-import-jsx-pragma", - "version": "4.28.0", + "version": "4.29.0", "dev": true, "license": "GPL-2.0-or-later", "engines": { @@ -54426,7 +54481,7 @@ }, "packages/babel-plugin-makepot": { "name": "@wordpress/babel-plugin-makepot", - "version": "5.29.0", + "version": "5.30.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -54443,7 +54498,7 @@ }, "packages/babel-preset-default": { "name": "@wordpress/babel-preset-default", - "version": "7.29.0", + "version": "7.30.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -54466,13 +54521,13 @@ }, "packages/base-styles": { "name": "@wordpress/base-styles", - "version": "4.36.0", + "version": "4.37.0", "dev": true, "license": "GPL-2.0-or-later" }, "packages/blob": { "name": "@wordpress/blob", - "version": "3.45.0", + "version": "3.46.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0" @@ -54483,7 +54538,7 @@ }, "packages/block-directory": { "name": "@wordpress/block-directory", - "version": "4.22.0", + "version": "4.23.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54518,7 +54573,7 @@ }, "packages/block-editor": { "name": "@wordpress/block-editor", - "version": "12.13.0", + "version": "12.14.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54615,7 +54670,7 @@ }, "packages/block-library": { "name": "@wordpress/block-library", - "version": "8.22.0", + "version": "8.23.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54677,7 +54732,7 @@ }, "packages/block-serialization-default-parser": { "name": "@wordpress/block-serialization-default-parser", - "version": "4.45.0", + "version": "4.46.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0" @@ -54688,7 +54743,7 @@ }, "packages/block-serialization-spec-parser": { "name": "@wordpress/block-serialization-spec-parser", - "version": "4.45.0", + "version": "4.46.0", "license": "GPL-2.0-or-later", "dependencies": { "pegjs": "^0.10.0", @@ -54700,7 +54755,7 @@ }, "packages/blocks": { "name": "@wordpress/blocks", - "version": "12.22.0", + "version": "12.23.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54748,7 +54803,7 @@ }, "packages/browserslist-config": { "name": "@wordpress/browserslist-config", - "version": "5.28.0", + "version": "5.29.0", "dev": true, "license": "GPL-2.0-or-later", "engines": { @@ -54757,7 +54812,7 @@ }, "packages/commands": { "name": "@wordpress/commands", - "version": "0.16.0", + "version": "0.17.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54782,11 +54837,10 @@ }, "packages/components": { "name": "@wordpress/components", - "version": "25.11.0", + "version": "25.12.0", "license": "GPL-2.0-or-later", "dependencies": { "@ariakit/react": "^0.3.5", - "@ariakit/test": "^0.3.0", "@babel/runtime": "^7.16.0", "@emotion/cache": "^11.7.1", "@emotion/css": "^11.7.1", @@ -54889,7 +54943,7 @@ }, "packages/compose": { "name": "@wordpress/compose", - "version": "6.22.0", + "version": "6.23.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54915,7 +54969,7 @@ }, "packages/core-commands": { "name": "@wordpress/core-commands", - "version": "0.14.0", + "version": "0.15.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54940,7 +54994,7 @@ }, "packages/core-data": { "name": "@wordpress/core-data", - "version": "6.22.0", + "version": "6.23.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -54984,7 +55038,7 @@ }, "packages/create-block": { "name": "@wordpress/create-block", - "version": "4.29.0", + "version": "4.30.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -55012,13 +55066,13 @@ }, "packages/create-block-tutorial-template": { "name": "@wordpress/create-block-tutorial-template", - "version": "2.33.0", + "version": "3.0.0", "dev": true, "license": "GPL-2.0-or-later" }, "packages/customize-widgets": { "name": "@wordpress/customize-widgets", - "version": "4.22.0", + "version": "4.23.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55055,7 +55109,7 @@ }, "packages/data": { "name": "@wordpress/data", - "version": "9.15.0", + "version": "9.16.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55083,7 +55137,7 @@ }, "packages/data-controls": { "name": "@wordpress/data-controls", - "version": "3.14.0", + "version": "3.15.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55100,7 +55154,7 @@ }, "packages/date": { "name": "@wordpress/date", - "version": "4.45.0", + "version": "4.46.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55114,7 +55168,7 @@ }, "packages/dependency-extraction-webpack-plugin": { "name": "@wordpress/dependency-extraction-webpack-plugin", - "version": "4.28.0", + "version": "4.29.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -55130,7 +55184,7 @@ }, "packages/deprecated": { "name": "@wordpress/deprecated", - "version": "3.45.0", + "version": "3.46.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55142,7 +55196,7 @@ }, "packages/docgen": { "name": "@wordpress/docgen", - "version": "1.54.0", + "version": "1.55.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -55160,7 +55214,7 @@ }, "packages/dom": { "name": "@wordpress/dom", - "version": "3.45.0", + "version": "3.46.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55172,7 +55226,7 @@ }, "packages/dom-ready": { "name": "@wordpress/dom-ready", - "version": "3.45.0", + "version": "3.46.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0" @@ -55183,7 +55237,7 @@ }, "packages/e2e-test-utils": { "name": "@wordpress/e2e-test-utils", - "version": "10.16.0", + "version": "10.17.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -55205,7 +55259,7 @@ }, "packages/e2e-test-utils-playwright": { "name": "@wordpress/e2e-test-utils-playwright", - "version": "0.13.0", + "version": "0.14.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -55228,7 +55282,7 @@ }, "packages/e2e-tests": { "name": "@wordpress/e2e-tests", - "version": "7.16.0", + "version": "7.17.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -55266,7 +55320,7 @@ }, "packages/edit-post": { "name": "@wordpress/edit-post", - "version": "7.22.0", + "version": "7.23.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55314,13 +55368,14 @@ }, "packages/edit-site": { "name": "@wordpress/edit-site", - "version": "5.22.0", + "version": "5.23.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", "@tanstack/react-table": "^8.10.3", "@wordpress/a11y": "file:../a11y", "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", "@wordpress/block-editor": "file:../block-editor", "@wordpress/block-library": "file:../block-library", "@wordpress/blocks": "file:../blocks", @@ -55361,7 +55416,6 @@ "classnames": "^2.3.1", "colord": "^2.9.2", "deepmerge": "^4.3.0", - "downloadjs": "^1.4.7", "fast-deep-equal": "^3.1.3", "is-plain-object": "^5.0.0", "memize": "^2.1.0", @@ -55379,7 +55433,7 @@ }, "packages/edit-widgets": { "name": "@wordpress/edit-widgets", - "version": "5.22.0", + "version": "5.23.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55421,7 +55475,7 @@ }, "packages/editor": { "name": "@wordpress/editor", - "version": "13.22.0", + "version": "13.23.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55471,7 +55525,7 @@ }, "packages/element": { "name": "@wordpress/element", - "version": "5.22.0", + "version": "5.23.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55489,7 +55543,7 @@ }, "packages/env": { "name": "@wordpress/env", - "version": "8.11.0", + "version": "8.12.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -55602,7 +55656,7 @@ }, "packages/escape-html": { "name": "@wordpress/escape-html", - "version": "2.45.0", + "version": "2.46.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0" @@ -55613,7 +55667,7 @@ }, "packages/eslint-plugin": { "name": "@wordpress/eslint-plugin", - "version": "17.2.0", + "version": "17.3.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -55656,7 +55710,7 @@ }, "packages/format-library": { "name": "@wordpress/format-library", - "version": "4.22.0", + "version": "4.23.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55682,7 +55736,7 @@ }, "packages/hooks": { "name": "@wordpress/hooks", - "version": "3.45.0", + "version": "3.46.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0" @@ -55693,7 +55747,7 @@ }, "packages/html-entities": { "name": "@wordpress/html-entities", - "version": "3.45.0", + "version": "3.46.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0" @@ -55704,7 +55758,7 @@ }, "packages/i18n": { "name": "@wordpress/i18n", - "version": "4.45.0", + "version": "4.46.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55723,7 +55777,7 @@ }, "packages/icons": { "name": "@wordpress/icons", - "version": "9.36.0", + "version": "9.37.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55736,7 +55790,7 @@ }, "packages/interactivity": { "name": "@wordpress/interactivity", - "version": "2.6.0", + "version": "2.7.0", "license": "GPL-2.0-or-later", "dependencies": { "@preact/signals": "^1.1.3", @@ -55749,7 +55803,7 @@ }, "packages/interface": { "name": "@wordpress/interface", - "version": "5.22.0", + "version": "5.23.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55776,7 +55830,7 @@ }, "packages/is-shallow-equal": { "name": "@wordpress/is-shallow-equal", - "version": "4.45.0", + "version": "4.46.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0" @@ -55787,7 +55841,7 @@ }, "packages/jest-console": { "name": "@wordpress/jest-console", - "version": "7.16.0", + "version": "7.17.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -55803,7 +55857,7 @@ }, "packages/jest-preset-default": { "name": "@wordpress/jest-preset-default", - "version": "11.16.0", + "version": "11.17.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -55820,7 +55874,7 @@ }, "packages/jest-puppeteer-axe": { "name": "@wordpress/jest-puppeteer-axe", - "version": "6.16.0", + "version": "6.17.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -55842,7 +55896,7 @@ }, "packages/keyboard-shortcuts": { "name": "@wordpress/keyboard-shortcuts", - "version": "4.22.0", + "version": "4.23.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55860,7 +55914,7 @@ }, "packages/keycodes": { "name": "@wordpress/keycodes", - "version": "3.45.0", + "version": "3.46.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55873,7 +55927,7 @@ }, "packages/lazy-import": { "name": "@wordpress/lazy-import", - "version": "1.32.0", + "version": "1.33.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -55887,11 +55941,12 @@ }, "packages/list-reusable-blocks": { "name": "@wordpress/list-reusable-blocks", - "version": "4.22.0", + "version": "4.23.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", "@wordpress/components": "file:../components", "@wordpress/compose": "file:../compose", "@wordpress/element": "file:../element", @@ -55908,7 +55963,7 @@ }, "packages/media-utils": { "name": "@wordpress/media-utils", - "version": "4.36.0", + "version": "4.37.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55923,7 +55978,7 @@ }, "packages/notices": { "name": "@wordpress/notices", - "version": "4.13.0", + "version": "4.14.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55939,7 +55994,7 @@ }, "packages/npm-package-json-lint-config": { "name": "@wordpress/npm-package-json-lint-config", - "version": "4.30.0", + "version": "4.31.0", "dev": true, "license": "GPL-2.0-or-later", "engines": { @@ -55951,7 +56006,7 @@ }, "packages/nux": { "name": "@wordpress/nux", - "version": "8.7.0", + "version": "8.8.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -55974,7 +56029,7 @@ }, "packages/patterns": { "name": "@wordpress/patterns", - "version": "1.6.0", + "version": "1.7.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -56003,7 +56058,7 @@ }, "packages/plugins": { "name": "@wordpress/plugins", - "version": "6.13.0", + "version": "6.14.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -56025,7 +56080,7 @@ }, "packages/postcss-plugins-preset": { "name": "@wordpress/postcss-plugins-preset", - "version": "4.29.0", + "version": "4.30.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -56041,7 +56096,7 @@ }, "packages/postcss-themes": { "name": "@wordpress/postcss-themes", - "version": "5.28.0", + "version": "5.29.0", "dev": true, "license": "GPL-2.0-or-later", "engines": { @@ -56053,7 +56108,7 @@ }, "packages/preferences": { "name": "@wordpress/preferences", - "version": "3.22.0", + "version": "3.23.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -56075,7 +56130,7 @@ }, "packages/preferences-persistence": { "name": "@wordpress/preferences-persistence", - "version": "1.37.0", + "version": "1.38.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -56087,7 +56142,7 @@ }, "packages/prettier-config": { "name": "@wordpress/prettier-config", - "version": "3.2.0", + "version": "3.3.0", "dev": true, "license": "GPL-2.0-or-later", "engines": { @@ -56099,7 +56154,7 @@ }, "packages/primitives": { "name": "@wordpress/primitives", - "version": "3.43.0", + "version": "3.44.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -56112,7 +56167,7 @@ }, "packages/priority-queue": { "name": "@wordpress/priority-queue", - "version": "2.45.0", + "version": "2.46.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -56124,7 +56179,7 @@ }, "packages/private-apis": { "name": "@wordpress/private-apis", - "version": "0.27.0", + "version": "0.28.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0" @@ -56135,7 +56190,7 @@ }, "packages/project-management-automation": { "name": "@wordpress/project-management-automation", - "version": "1.44.0", + "version": "1.45.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -56148,7 +56203,7 @@ }, "packages/react-i18n": { "name": "@wordpress/react-i18n", - "version": "3.43.0", + "version": "3.44.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -56296,7 +56351,7 @@ }, "packages/readable-js-assets-webpack-plugin": { "name": "@wordpress/readable-js-assets-webpack-plugin", - "version": "2.28.0", + "version": "2.29.0", "dev": true, "license": "GPL-2.0-or-later", "engines": { @@ -56308,7 +56363,7 @@ }, "packages/redux-routine": { "name": "@wordpress/redux-routine", - "version": "4.45.0", + "version": "4.46.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -56352,7 +56407,7 @@ }, "packages/reusable-blocks": { "name": "@wordpress/reusable-blocks", - "version": "4.22.0", + "version": "4.23.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -56378,7 +56433,7 @@ }, "packages/rich-text": { "name": "@wordpress/rich-text", - "version": "6.22.0", + "version": "6.23.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -56402,7 +56457,7 @@ }, "packages/router": { "name": "@wordpress/router", - "version": "0.14.0", + "version": "0.15.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -56420,7 +56475,7 @@ }, "packages/scripts": { "name": "@wordpress/scripts", - "version": "26.16.0", + "version": "26.17.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -56510,7 +56565,7 @@ }, "packages/server-side-render": { "name": "@wordpress/server-side-render", - "version": "4.22.0", + "version": "4.23.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -56535,7 +56590,7 @@ }, "packages/shortcode": { "name": "@wordpress/shortcode", - "version": "3.45.0", + "version": "3.46.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -56547,7 +56602,7 @@ }, "packages/style-engine": { "name": "@wordpress/style-engine", - "version": "1.28.0", + "version": "1.29.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -56559,7 +56614,7 @@ }, "packages/stylelint-config": { "name": "@wordpress/stylelint-config", - "version": "21.28.0", + "version": "21.29.0", "dev": true, "license": "MIT", "dependencies": { @@ -56575,7 +56630,7 @@ }, "packages/sync": { "name": "@wordpress/sync", - "version": "0.7.0", + "version": "0.8.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -56595,7 +56650,7 @@ }, "packages/token-list": { "name": "@wordpress/token-list", - "version": "2.45.0", + "version": "2.46.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0" @@ -56606,7 +56661,7 @@ }, "packages/undo-manager": { "name": "@wordpress/undo-manager", - "version": "0.5.0", + "version": "0.6.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -56618,7 +56673,7 @@ }, "packages/url": { "name": "@wordpress/url", - "version": "3.46.0", + "version": "3.47.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -56630,7 +56685,7 @@ }, "packages/viewport": { "name": "@wordpress/viewport", - "version": "5.22.0", + "version": "5.23.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -56647,7 +56702,7 @@ }, "packages/warning": { "name": "@wordpress/warning", - "version": "2.45.0", + "version": "2.46.0", "license": "GPL-2.0-or-later", "engines": { "node": ">=12" @@ -56655,7 +56710,7 @@ }, "packages/widgets": { "name": "@wordpress/widgets", - "version": "3.22.0", + "version": "3.23.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", @@ -56679,7 +56734,7 @@ }, "packages/wordcount": { "name": "@wordpress/wordcount", - "version": "3.45.0", + "version": "3.46.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0" @@ -57746,9 +57801,18 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/@ariakit/test/-/test-0.3.0.tgz", "integrity": "sha512-fngAzkzCl9zM4O/tzVaUf7ixWUAk779hjTegr/zcegoxaMS5SmaLhNQ7RU0AGx06LrhJse6GYGy8ZtK58HP/EQ==", + "dev": true, "requires": { "@ariakit/core": "0.3.3", "@testing-library/dom": "^8.0.0 || ^9.0.0" + }, + "dependencies": { + "@ariakit/core": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.3.3.tgz", + "integrity": "sha512-8x77R0aE9O9pheygg+h/z0oU9Wx/Xdlr7nfkl4klGnkJma8/nAhJ2RrchCTQCUef4WMsRnq/doCz8m/sslP6CA==", + "dev": true + } } }, "@aw-web-design/x-default-browser": { @@ -67424,6 +67488,7 @@ "version": "9.3.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.1.tgz", "integrity": "sha512-0DGPd9AR3+iDTjGoMpxIkAsUihHZ3Ai6CneU6bRRrffXMgzCdlNk43jTrD2/5LT6CBb3MWTP8v510JzYtahD2w==", + "dev": true, "requires": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -67438,12 +67503,14 @@ "ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true }, "aria-query": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, "requires": { "deep-equal": "^2.0.5" } @@ -67452,6 +67519,7 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.2.tgz", "integrity": "sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA==", + "dev": true, "requires": { "array-buffer-byte-length": "^1.0.0", "call-bind": "^1.0.2", @@ -67476,12 +67544,14 @@ "isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true }, "pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, "requires": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -67491,7 +67561,8 @@ "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true } } }, @@ -67646,7 +67717,8 @@ "@types/aria-query": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz", - "integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==" + "integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==", + "dev": true }, "@types/async-lock": { "version": "1.4.0", @@ -70023,7 +70095,6 @@ "version": "file:packages/components", "requires": { "@ariakit/react": "^0.3.5", - "@ariakit/test": "^0.3.0", "@babel/runtime": "^7.16.0", "@emotion/cache": "^11.7.1", "@emotion/css": "^11.7.1", @@ -70398,6 +70469,7 @@ "@tanstack/react-table": "^8.10.3", "@wordpress/a11y": "file:../a11y", "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", "@wordpress/block-editor": "file:../block-editor", "@wordpress/block-library": "file:../block-library", "@wordpress/blocks": "file:../blocks", @@ -70438,7 +70510,6 @@ "classnames": "^2.3.1", "colord": "^2.9.2", "deepmerge": "^4.3.0", - "downloadjs": "^1.4.7", "fast-deep-equal": "^3.1.3", "is-plain-object": "^5.0.0", "memize": "^2.1.0", @@ -70785,6 +70856,7 @@ "requires": { "@babel/runtime": "^7.16.0", "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", "@wordpress/components": "file:../components", "@wordpress/compose": "file:../compose", "@wordpress/element": "file:../element", @@ -72282,6 +72354,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, "requires": { "call-bind": "^1.0.2", "is-array-buffer": "^3.0.1" @@ -72547,7 +72620,8 @@ "available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true }, "axe-core": { "version": "4.7.2", @@ -73681,6 +73755,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, "requires": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -76292,6 +76367,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, "requires": { "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" @@ -76737,7 +76813,8 @@ "dom-accessibility-api": { "version": "0.5.14", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz", - "integrity": "sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==" + "integrity": "sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==", + "dev": true }, "dom-converter": { "version": "0.2.0", @@ -76857,11 +76934,6 @@ "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", "dev": true }, - "downloadjs": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/downloadjs/-/downloadjs-1.4.7.tgz", - "integrity": "sha512-LN1gO7+u9xjU5oEScGFKvXhYf7Y/empUIIEAGBs1LzUq/rg5duiDrkuH5A2lQGd5jfMOb9X9usDa2oVXwJ0U/Q==" - }, "downshift": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/downshift/-/downshift-6.1.0.tgz", @@ -77247,6 +77319,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, "requires": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", @@ -77262,7 +77335,8 @@ "isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true } } }, @@ -79095,6 +79169,7 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, "requires": { "is-callable": "^1.1.3" } @@ -79394,7 +79469,8 @@ "functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true }, "gauge": { "version": "4.0.4", @@ -79573,6 +79649,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -79956,6 +80033,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, "requires": { "get-intrinsic": "^1.1.3" } @@ -80085,7 +80163,8 @@ "has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true }, "has-flag": { "version": "3.0.0", @@ -80096,6 +80175,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, "requires": { "get-intrinsic": "^1.1.1" } @@ -80103,17 +80183,20 @@ "has-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true }, "has-tostringtag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, "requires": { "has-symbols": "^1.0.2" } @@ -80996,6 +81079,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, "requires": { "get-intrinsic": "^1.2.0", "has": "^1.0.3", @@ -81098,6 +81182,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, "requires": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -81107,6 +81192,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, "requires": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.0", @@ -81122,6 +81208,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, "requires": { "has-bigints": "^1.0.1" } @@ -81139,6 +81226,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, "requires": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -81161,7 +81249,8 @@ "is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true }, "is-ci": { "version": "2.0.0", @@ -81210,6 +81299,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, "requires": { "has-tostringtag": "^1.0.0" } @@ -81337,7 +81427,8 @@ "is-map": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==" + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true }, "is-nan": { "version": "1.3.2", @@ -81377,6 +81468,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, "requires": { "has-tostringtag": "^1.0.0" } @@ -81446,6 +81538,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, "requires": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -81460,12 +81553,14 @@ "is-set": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==" + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true }, "is-shared-array-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, "requires": { "call-bind": "^1.0.2" } @@ -81488,6 +81583,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, "requires": { "has-tostringtag": "^1.0.0" } @@ -81496,6 +81592,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, "requires": { "has-symbols": "^1.0.2" } @@ -81513,6 +81610,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dev": true, "requires": { "which-typed-array": "^1.1.11" } @@ -81531,7 +81629,8 @@ "is-weakmap": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==" + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true }, "is-weakref": { "version": "1.0.2", @@ -81546,6 +81645,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, "requires": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" @@ -85122,7 +85222,8 @@ "lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", - "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==" + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true }, "macos-release": { "version": "2.2.0", @@ -89001,12 +89102,14 @@ "object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true }, "object-is": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3" @@ -89015,7 +89118,8 @@ "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true }, "object-visit": { "version": "1.0.1", @@ -89029,6 +89133,7 @@ "version": "4.1.4", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -92585,6 +92690,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -93977,6 +94083,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, "requires": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -94626,6 +94733,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, "requires": { "internal-slot": "^1.0.4" } @@ -98083,6 +98191,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, "requires": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", @@ -98095,6 +98204,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, "requires": { "is-map": "^2.0.1", "is-set": "^2.0.1", @@ -98111,6 +98221,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "dev": true, "requires": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", diff --git a/package.json b/package.json index 9f456f359a741b..43ca7f0858f609 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "17.0.2", + "version": "17.1.0", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", @@ -96,6 +96,7 @@ "devDependencies": { "@actions/core": "1.9.1", "@actions/github": "5.0.0", + "@ariakit/test": "^0.3.0", "@babel/core": "7.16.0", "@babel/plugin-proposal-export-namespace-from": "7.18.9", "@babel/plugin-syntax-jsx": "7.16.0", diff --git a/packages/a11y/CHANGELOG.md b/packages/a11y/CHANGELOG.md index 2be0c0b64bb149..ef2826ea2f87bc 100644 --- a/packages/a11y/CHANGELOG.md +++ b/packages/a11y/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.46.0 (2023-11-16) + ## 3.45.0 (2023-11-02) ## 3.44.0 (2023-10-18) diff --git a/packages/a11y/package.json b/packages/a11y/package.json index a48168ac575783..10964d4f47800a 100644 --- a/packages/a11y/package.json +++ b/packages/a11y/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/a11y", - "version": "3.45.0", + "version": "3.46.0", "description": "Accessibility (a11y) utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/annotations/CHANGELOG.md b/packages/annotations/CHANGELOG.md index a45627849418b3..ccb8a4eb3b22f9 100644 --- a/packages/annotations/CHANGELOG.md +++ b/packages/annotations/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 2.46.0 (2023-11-16) + ## 2.45.0 (2023-11-02) ## 2.44.0 (2023-10-18) diff --git a/packages/annotations/package.json b/packages/annotations/package.json index 32b3eee0e2ecb6..653414da29014e 100644 --- a/packages/annotations/package.json +++ b/packages/annotations/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/annotations", - "version": "2.45.0", + "version": "2.46.0", "description": "Annotate content in the Gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/api-fetch/CHANGELOG.md b/packages/api-fetch/CHANGELOG.md index 5806a41860d5c9..07e50ad2cb451f 100644 --- a/packages/api-fetch/CHANGELOG.md +++ b/packages/api-fetch/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 6.43.0 (2023-11-16) + ## 6.42.0 (2023-11-02) ## 6.41.0 (2023-10-18) diff --git a/packages/api-fetch/package.json b/packages/api-fetch/package.json index e5413887a61ceb..c572e343edab86 100644 --- a/packages/api-fetch/package.json +++ b/packages/api-fetch/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/api-fetch", - "version": "6.42.0", + "version": "6.43.0", "description": "Utility to make WordPress REST API requests.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/autop/CHANGELOG.md b/packages/autop/CHANGELOG.md index a3cba5718404ba..c3ffd7dd4bb171 100644 --- a/packages/autop/CHANGELOG.md +++ b/packages/autop/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.46.0 (2023-11-16) + ## 3.45.0 (2023-11-02) ## 3.44.0 (2023-10-18) diff --git a/packages/autop/package.json b/packages/autop/package.json index 13eb7cac02d54c..d444d2d5a4779f 100644 --- a/packages/autop/package.json +++ b/packages/autop/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/autop", - "version": "3.45.0", + "version": "3.46.0", "description": "WordPress's automatic paragraph functions `autop` and `removep`.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md b/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md index 292314b1c9fe10..8070de13c5955d 100644 --- a/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md +++ b/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.29.0 (2023-11-16) + ## 4.28.0 (2023-11-02) ## 4.27.0 (2023-10-18) diff --git a/packages/babel-plugin-import-jsx-pragma/package.json b/packages/babel-plugin-import-jsx-pragma/package.json index 00a621f31b1e06..a49af835b912fc 100644 --- a/packages/babel-plugin-import-jsx-pragma/package.json +++ b/packages/babel-plugin-import-jsx-pragma/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-plugin-import-jsx-pragma", - "version": "4.28.0", + "version": "4.29.0", "description": "Babel transform plugin for automatically injecting an import to be used as the pragma for the React JSX Transform plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-plugin-makepot/CHANGELOG.md b/packages/babel-plugin-makepot/CHANGELOG.md index 0b57ac2d9b0a1d..1b8137ab96e7fe 100644 --- a/packages/babel-plugin-makepot/CHANGELOG.md +++ b/packages/babel-plugin-makepot/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.30.0 (2023-11-16) + ## 5.29.0 (2023-11-02) ## 5.28.0 (2023-10-18) diff --git a/packages/babel-plugin-makepot/package.json b/packages/babel-plugin-makepot/package.json index 4acc61c4819e63..298a05f3f4425f 100644 --- a/packages/babel-plugin-makepot/package.json +++ b/packages/babel-plugin-makepot/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-plugin-makepot", - "version": "5.29.0", + "version": "5.30.0", "description": "WordPress Babel internationalization (i18n) plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-preset-default/CHANGELOG.md b/packages/babel-preset-default/CHANGELOG.md index e5a4c06b4c92b6..20ff5b49dbebe9 100644 --- a/packages/babel-preset-default/CHANGELOG.md +++ b/packages/babel-preset-default/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 7.30.0 (2023-11-16) + ## 7.29.0 (2023-11-02) ## 7.28.0 (2023-10-18) diff --git a/packages/babel-preset-default/package.json b/packages/babel-preset-default/package.json index dd38c349bb1716..d53a2603593233 100644 --- a/packages/babel-preset-default/package.json +++ b/packages/babel-preset-default/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-preset-default", - "version": "7.29.0", + "version": "7.30.0", "description": "Default Babel preset for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/base-styles/CHANGELOG.md b/packages/base-styles/CHANGELOG.md index b6135581dbd731..bac407def2873b 100644 --- a/packages/base-styles/CHANGELOG.md +++ b/packages/base-styles/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.37.0 (2023-11-16) + ## 4.36.0 (2023-11-02) ## 4.35.0 (2023-10-18) diff --git a/packages/base-styles/package.json b/packages/base-styles/package.json index fe604a7cb02115..8af7e6b7e3db17 100644 --- a/packages/base-styles/package.json +++ b/packages/base-styles/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/base-styles", - "version": "4.36.0", + "version": "4.37.0", "description": "Base SCSS utilities and variables for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blob/CHANGELOG.md b/packages/blob/CHANGELOG.md index 8d88b3cc4062a3..7fae8a61cabb1f 100644 --- a/packages/blob/CHANGELOG.md +++ b/packages/blob/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +## 3.46.0 (2023-11-16) + +### New feature + +- Add `downloadBlob` function and remove `downloadjs` dependency ([#56024](https://github.com/WordPress/gutenberg/pull/56024)). + ## 3.45.0 (2023-11-02) ## 3.44.0 (2023-10-18) diff --git a/packages/blob/README.md b/packages/blob/README.md index 0305c4784d6cbd..64520a98bd6a79 100644 --- a/packages/blob/README.md +++ b/packages/blob/README.md @@ -26,6 +26,31 @@ _Returns_ - `string`: The blob URL. +### downloadBlob + +Downloads a file, e.g., a text or readable stream, in the browser. Appropriate for downloading smaller file sizes, e.g., \< 5 MB. + +Example usage: + +```js +const fileContent = JSON.stringify( + { + title: 'My Post', + }, + null, + 2 +); +const filename = 'file.json'; + +downloadBlob( filename, fileContent, 'application/json' ); +``` + +_Parameters_ + +- _filename_ `string`: File name. +- _content_ `BlobPart`: File content (BufferSource | Blob | string). +- _contentType_ `string`: (Optional) File mime type. Default is `''`. + ### getBlobByURL Retrieve a file based on a blob URL. The file must have been created by `createBlobURL` and not removed by `revokeBlobURL`, otherwise it will return `undefined`. diff --git a/packages/blob/package.json b/packages/blob/package.json index c3e89fb9918237..ba0a355e19a47d 100644 --- a/packages/blob/package.json +++ b/packages/blob/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blob", - "version": "3.45.0", + "version": "3.46.0", "description": "Blob utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blob/src/index.js b/packages/blob/src/index.js index 496869703d2dac..2493f81fc4c65b 100644 --- a/packages/blob/src/index.js +++ b/packages/blob/src/index.js @@ -70,3 +70,43 @@ export function isBlobURL( url ) { } return url.indexOf( 'blob:' ) === 0; } + +/** + * Downloads a file, e.g., a text or readable stream, in the browser. + * Appropriate for downloading smaller file sizes, e.g., < 5 MB. + * + * Example usage: + * + * ```js + * const fileContent = JSON.stringify( + * { + * "title": "My Post", + * }, + * null, + * 2 + * ); + * const filename = 'file.json'; + * + * downloadBlob( filename, fileContent, 'application/json' ); + * ``` + * + * @param {string} filename File name. + * @param {BlobPart} content File content (BufferSource | Blob | string). + * @param {string} contentType (Optional) File mime type. Default is `''`. + */ +export function downloadBlob( filename, content, contentType = '' ) { + if ( ! filename || ! content ) { + return; + } + + const file = new window.Blob( [ content ], { type: contentType } ); + const url = window.URL.createObjectURL( file ); + const anchorElement = document.createElement( 'a' ); + anchorElement.href = url; + anchorElement.download = filename; + anchorElement.style.display = 'none'; + document.body.appendChild( anchorElement ); + anchorElement.click(); + document.body.removeChild( anchorElement ); + window.URL.revokeObjectURL( url ); +} diff --git a/packages/blob/src/test/index.js b/packages/blob/src/test/index.js index 4e59917522b519..47dcb5019ee25e 100644 --- a/packages/blob/src/test/index.js +++ b/packages/blob/src/test/index.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { isBlobURL, getBlobTypeByURL } from '../'; +import { isBlobURL, getBlobTypeByURL, downloadBlob } from '../'; describe( 'isBlobURL', () => { it( 'returns true if the url starts with "blob:"', () => { @@ -26,3 +26,60 @@ describe( 'getBlobTypeByURL', () => { expect( getBlobTypeByURL() ).toBe( undefined ); } ); } ); + +describe( 'downloadBlob', () => { + const originalURL = window.URL; + const createObjectURL = jest.fn().mockReturnValue( 'blob:pannacotta' ); + const revokeObjectURL = jest.fn().mockReturnValue( false ); + const mockAnchorElement = document.createElement( 'a' ); + mockAnchorElement.click = jest.fn(); + const createElementSpy = jest + .spyOn( global.document, 'createElement' ) + .mockReturnValue( mockAnchorElement ); + const mockBlob = jest.fn(); + const blobSpy = jest.spyOn( window, 'Blob' ).mockReturnValue( mockBlob ); + jest.spyOn( document.body, 'appendChild' ); + jest.spyOn( document.body, 'removeChild' ); + beforeEach( () => { + // Can't seem to spy on these static methods. They are `undefined`. + // Possibly overwritten: https://github.com/WordPress/gutenberg/blob/trunk/packages/jest-preset-default/scripts/setup-globals.js#L5 + window.URL = { + createObjectURL, + revokeObjectURL, + }; + } ); + + afterAll( () => { + window.URL = originalURL; + } ); + + it( 'requires a filename argument', () => { + downloadBlob( '', '{}', 'application/json' ); + expect( blobSpy ).not.toHaveBeenCalled(); + } ); + + it( 'requires a content argument', () => { + downloadBlob( 'text.txt', '', 'text/plain' ); + expect( blobSpy ).not.toHaveBeenCalled(); + } ); + + it( 'constructs an anchor element with attributes and removes it', () => { + downloadBlob( 'filename.json', '{}', 'application/json' ); + expect( blobSpy ).toHaveBeenCalledWith( [ '{}' ], { + type: 'application/json', + } ); + expect( createObjectURL ).toHaveBeenCalledWith( mockBlob ); + expect( createElementSpy ).toHaveBeenCalledWith( 'a' ); + expect( mockAnchorElement.download ).toBe( 'filename.json' ); + expect( mockAnchorElement.href ).toBe( 'blob:pannacotta' ); + expect( mockAnchorElement ).toHaveStyle( 'display:none' ); + expect( document.body.appendChild ).toHaveBeenCalledWith( + mockAnchorElement + ); + expect( mockAnchorElement.click ).toHaveBeenCalledTimes( 1 ); + expect( document.body.removeChild ).toHaveBeenCalledWith( + mockAnchorElement + ); + expect( revokeObjectURL ).toHaveBeenCalled(); + } ); +} ); diff --git a/packages/block-directory/CHANGELOG.md b/packages/block-directory/CHANGELOG.md index eb3a68dd24cc81..7cebd71db2fbc3 100644 --- a/packages/block-directory/CHANGELOG.md +++ b/packages/block-directory/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.23.0 (2023-11-16) + ## 4.22.0 (2023-11-02) ## 4.21.0 (2023-10-18) diff --git a/packages/block-directory/package.json b/packages/block-directory/package.json index 71018738fac083..438b0680db283b 100644 --- a/packages/block-directory/package.json +++ b/packages/block-directory/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-directory", - "version": "4.22.0", + "version": "4.23.0", "description": "Extend editor with block directory features to search, download and install blocks.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md index 94e0306e175659..497e419453f6a2 100644 --- a/packages/block-editor/CHANGELOG.md +++ b/packages/block-editor/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 12.14.0 (2023-11-16) + ## 12.13.0 (2023-11-02) - Deprecated the `useSetting` function in favor of new `useSettings` one that can retrieve multiple settings at once ([#55337](https://github.com/WordPress/gutenberg/pull/55337)). diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 9c7a72f0897143..2d6a5627a52a44 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -954,7 +954,7 @@ _Parameters_ ### useSetting -> **Deprecated** 6.4.0 Use useSettings instead. +> **Deprecated** 6.5.0 Use useSettings instead. Hook that retrieves the given setting for the block instance in use. diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index baebc824086525..a80fd2f9bca969 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-editor", - "version": "12.13.0", + "version": "12.14.0", "description": "Generic block editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-editor/src/components/block-heading-level-dropdown/index.js b/packages/block-editor/src/components/block-heading-level-dropdown/index.js index be580042bb85e1..a8296d48ad2683 100644 --- a/packages/block-editor/src/components/block-heading-level-dropdown/index.js +++ b/packages/block-editor/src/components/block-heading-level-dropdown/index.js @@ -56,7 +56,7 @@ export default function HeadingLevelDropdown( { isPressed={ isActive } /> ), - label: + title: targetLevel === 0 ? __( 'Paragraph' ) : sprintf( diff --git a/packages/block-editor/src/components/block-list-appender/index.js b/packages/block-editor/src/components/block-list-appender/index.js index 7b37b93d8be8d1..68f36f7dd25058 100644 --- a/packages/block-editor/src/components/block-list-appender/index.js +++ b/packages/block-editor/src/components/block-list-appender/index.js @@ -49,10 +49,6 @@ function useAppender( rootClientId, CustomAppender ) { getBlockEditingMode, } = select( blockEditorStore ); - if ( CustomAppender === false ) { - return false; - } - if ( ! CustomAppender ) { const selectedBlockClientId = getSelectedBlockClientId(); const isParentSelected = @@ -92,6 +88,26 @@ function BlockListAppender( { renderAppender, className, tagName: TagName = 'div', +} ) { + if ( renderAppender === false ) { + return null; + } + + return ( + + ); +} + +function BlockListAppenderInner( { + rootClientId, + renderAppender, + className, + tagName: TagName, } ) { const appender = useAppender( rootClientId, renderAppender ); const isDragOver = useSelect( diff --git a/packages/block-editor/src/components/block-pattern-setup/index.js b/packages/block-editor/src/components/block-pattern-setup/index.js index 22d51466b3b6e9..edd55e90dc3e27 100644 --- a/packages/block-editor/src/components/block-pattern-setup/index.js +++ b/packages/block-editor/src/components/block-pattern-setup/index.js @@ -5,9 +5,7 @@ import { useDispatch } from '@wordpress/data'; import { cloneBlock } from '@wordpress/blocks'; import { VisuallyHidden, - __unstableComposite as Composite, - __unstableUseCompositeState as useCompositeState, - __unstableCompositeItem as CompositeItem, + privateApis as componentsPrivateApis, } from '@wordpress/components'; import { useState } from '@wordpress/element'; @@ -22,6 +20,13 @@ import BlockPreview from '../block-preview'; import SetupToolbar from './setup-toolbar'; import usePatternsSetup from './use-patterns-setup'; import { VIEWMODES } from './constants'; +import { unlock } from '../../lock-unlock'; + +const { + CompositeV2: Composite, + CompositeItemV2: CompositeItem, + useCompositeStoreV2: useCompositeStore, +} = unlock( componentsPrivateApis ); const SetupContent = ( { viewMode, @@ -30,8 +35,9 @@ const SetupContent = ( { onBlockPatternSelect, showTitles, } ) => { - const composite = useCompositeState(); + const compositeStore = useCompositeStore(); const containerClass = 'block-editor-block-pattern-setup__container'; + if ( viewMode === VIEWMODES.carousel ) { const slideClass = new Map( [ [ activeSlide, 'active-slide' ], @@ -41,23 +47,25 @@ const SetupContent = ( { return (
-
    +
    { patterns.map( ( pattern, index ) => ( ) ) } -
+
); } + return (
) ) } @@ -76,7 +83,7 @@ const SetupContent = ( { ); }; -function BlockPattern( { pattern, onSelect, composite, showTitles } ) { +function BlockPattern( { pattern, onSelect, showTitles } ) { const baseClassName = 'block-editor-block-pattern-setup-list'; const { blocks, description, viewportWidth = 700 } = pattern; const descriptionId = useInstanceId( @@ -84,16 +91,19 @@ function BlockPattern( { pattern, onSelect, composite, showTitles } ) { `${ baseClassName }__item-description` ); return ( -
+
+ } + id={ `${ baseClassName }__pattern__${ pattern.name }` } role="option" - as="div" - { ...composite } - className={ `${ baseClassName }__item` } onClick={ () => onSelect( blocks ) } > ) } - +
); } @@ -178,10 +190,14 @@ const BlockPatternSetup = ( { activeSlide={ activeSlide } totalSlides={ patterns.length } handleNext={ () => { - setActiveSlide( ( active ) => active + 1 ); + setActiveSlide( ( active ) => + Math.min( active + 1, patterns.length - 1 ) + ); } } handlePrevious={ () => { - setActiveSlide( ( active ) => active - 1 ); + setActiveSlide( ( active ) => + Math.max( active - 1, 0 ) + ); } } onBlockPatternSelect={ () => { onPatternSelectCallback( diff --git a/packages/block-editor/src/components/block-pattern-setup/setup-toolbar.js b/packages/block-editor/src/components/block-pattern-setup/setup-toolbar.js index 69922f9560ab0b..91b68456cda71c 100644 --- a/packages/block-editor/src/components/block-pattern-setup/setup-toolbar.js +++ b/packages/block-editor/src/components/block-pattern-setup/setup-toolbar.js @@ -35,12 +35,14 @@ const CarouselNavigation = ( { label={ __( 'Previous pattern' ) } onClick={ handlePrevious } disabled={ activeSlide === 0 } + __experimentalIsFocusable />
); diff --git a/packages/block-editor/src/components/block-pattern-setup/style.scss b/packages/block-editor/src/components/block-pattern-setup/style.scss index 792e6872a2d594..3474eed5be5176 100644 --- a/packages/block-editor/src/components/block-pattern-setup/style.scss +++ b/packages/block-editor/src/components/block-pattern-setup/style.scss @@ -32,6 +32,8 @@ } .block-editor-block-pattern-setup-list__item { + scroll-margin: 5px 0; + &:hover .block-editor-block-preview__container { box-shadow: 0 0 0 2px var(--wp-admin-theme-color); } @@ -44,6 +46,7 @@ color: var(--wp-admin-theme-color); } } + .block-editor-block-pattern-setup-list__list-item { break-inside: avoid-column; margin-bottom: $grid-unit-30; @@ -85,7 +88,7 @@ align-items: center; justify-content: space-between; border-top: 1px solid $gray-300; - align-self: flex-end; + align-self: stretch; .block-editor-block-pattern-setup__display-controls { display: flex; diff --git a/packages/block-editor/src/components/block-patterns-list/index.js b/packages/block-editor/src/components/block-patterns-list/index.js index bebeb1f44184cd..daf07fa5c7fc05 100644 --- a/packages/block-editor/src/components/block-patterns-list/index.js +++ b/packages/block-editor/src/components/block-patterns-list/index.js @@ -25,6 +25,7 @@ import { Icon, symbol } from '@wordpress/icons'; import BlockPreview from '../block-preview'; import InserterDraggableBlocks from '../inserter-draggable-blocks'; import BlockPatternsPaging from '../block-patterns-paging'; +import { PATTERN_TYPES } from '../inserter/block-patterns-tab/utils'; const WithToolTip = ( { showTooltip, title, children } ) => { if ( showTooltip ) { @@ -71,7 +72,9 @@ function BlockPattern( { } } > { @@ -107,15 +111,17 @@ function BlockPattern( { /> - { pattern.id && ! pattern.syncStatus && ( -
- -
- ) } - { ( ! showTooltip || pattern.id ) && ( + { pattern.type === PATTERN_TYPES.user && + ! pattern.syncStatus && ( +
+ +
+ ) } + { ( ! showTooltip || + pattern.type === PATTERN_TYPES.user ) && (
{ pattern.title }
diff --git a/packages/block-editor/src/components/block-quick-navigation/index.js b/packages/block-editor/src/components/block-quick-navigation/index.js index de33c8a427f257..7a0e7984b83cb7 100644 --- a/packages/block-editor/src/components/block-quick-navigation/index.js +++ b/packages/block-editor/src/components/block-quick-navigation/index.js @@ -5,7 +5,9 @@ import { useSelect, useDispatch } from '@wordpress/data'; import { Button, __experimentalVStack as VStack, - __experimentalHStack as HStack, + __experimentalTruncate as Truncate, + Flex, + FlexBlock, FlexItem, } from '@wordpress/components'; import { @@ -72,10 +74,14 @@ function BlockQuickNavigationItem( { clientId } ) { isPressed={ isSelected } onClick={ () => selectBlock( clientId ) } > - - - { name } - + + + + + + { name } + + ); } diff --git a/packages/block-editor/src/components/block-switcher/pattern-transformations-menu.js b/packages/block-editor/src/components/block-switcher/pattern-transformations-menu.js index f9a4b7190a6dd8..84f2d4b6a7a950 100644 --- a/packages/block-editor/src/components/block-switcher/pattern-transformations-menu.js +++ b/packages/block-editor/src/components/block-switcher/pattern-transformations-menu.js @@ -11,9 +11,7 @@ import { MenuItem, Popover, VisuallyHidden, - __unstableComposite as Composite, - __unstableUseCompositeState as useCompositeState, - __unstableCompositeItem as CompositeItem, + privateApis as componentsPrivateApis, } from '@wordpress/components'; /** @@ -21,6 +19,13 @@ import { */ import BlockPreview from '../block-preview'; import useTransformedPatterns from './use-transformed-patterns'; +import { unlock } from '../../lock-unlock'; + +const { + CompositeV2: Composite, + CompositeItemV2: CompositeItem, + useCompositeStoreV2: useCompositeStore, +} = unlock( componentsPrivateApis ); function PatternTransformationsMenu( { blocks, @@ -73,10 +78,10 @@ function PreviewPatternsPopover( { patterns, onSelect } ) { } function BlockPatternsList( { patterns, onSelect } ) { - const composite = useCompositeState(); + const composite = useCompositeStore(); return ( ) ) } ); } -function BlockPattern( { pattern, onSelect, composite } ) { +function BlockPattern( { pattern, onSelect } ) { // TODO check pattern/preview width... const baseClassName = 'block-editor-block-switcher__preview-patterns-container'; @@ -104,14 +108,16 @@ function BlockPattern( { pattern, onSelect, composite } ) { return (
} - className={ `${ baseClassName }-list__item` } onClick={ () => onSelect( pattern.transformedBlocks ) } > 1 - ? __( 'Detach patterns' ) - : __( 'Detach pattern' ), + label: __( 'Detach' ), value: 'convertToRegularBlocksOption', onSelect: () => { /* translators: %s: name of the synced block */ diff --git a/packages/block-editor/src/components/block-types-list/index.js b/packages/block-editor/src/components/block-types-list/index.js index 40e04b040d5a80..0be6f82a653d18 100644 --- a/packages/block-editor/src/components/block-types-list/index.js +++ b/packages/block-editor/src/components/block-types-list/index.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { getBlockMenuDefaultClassName } from '@wordpress/blocks'; +import { useInstanceId } from '@wordpress/compose'; /** * Internal dependencies @@ -25,11 +26,10 @@ function BlockTypesList( { label, isDraggable = true, } ) { + const className = 'block-editor-block-types-list'; + const listId = useInstanceId( BlockTypesList, className ); return ( - + { chunk( items, 3 ).map( ( row, i ) => ( { row.map( ( item, j ) => ( @@ -43,6 +43,7 @@ function BlockTypesList( { onHover={ onHover } isDraggable={ isDraggable && ! item.isDisabled } isFirst={ i === 0 && j === 0 } + rowId={ `${ listId }-${ i }` } /> ) ) } diff --git a/packages/block-editor/src/components/button-block-appender/content.scss b/packages/block-editor/src/components/button-block-appender/content.scss index 941ccb7dd1ad3a..50d93234b93f5b 100644 --- a/packages/block-editor/src/components/button-block-appender/content.scss +++ b/packages/block-editor/src/components/button-block-appender/content.scss @@ -39,8 +39,8 @@ .is-layout-constrained.block-editor-block-list__block:not(.is-selected) > &, .is-layout-flow.block-editor-block-list__block:not(.is-selected) > &, // Legacy groups have an inner container so need to be targeted separately - .is-layout-constrained.block-editor-block-list__block:not(.is-selected) > .wp-block-group__inner-container > &, - .is-layout-flow.block-editor-block-list__block:not(.is-selected) > .wp-block-group__inner-container > & { + .block-editor-block-list__block:not(.is-selected) > .is-layout-constrained.wp-block-group__inner-container > &, + .block-editor-block-list__block:not(.is-selected) > .is-layout-flow.wp-block-group__inner-container > & { pointer-events: none; &::after { diff --git a/packages/block-editor/src/components/editable-text/README.md b/packages/block-editor/src/components/editable-text/README.md index 86607349ae8176..aa5a2f4b1962b8 100644 --- a/packages/block-editor/src/components/editable-text/README.md +++ b/packages/block-editor/src/components/editable-text/README.md @@ -47,40 +47,6 @@ _Optional._ Called when the block can be removed. `forward` is true when the sel ## Example -{% codetabs %} -{% ES5 %} - -```js -wp.blocks.registerBlockType( /* ... */, { - // ... - - attributes: { - content: { - source: 'html', - selector: 'div', - }, - }, - - edit: function( props ) { - return React.createElement( wp.editor.EditableText, { - className: props.className, - value: props.attributes.content, - onChange: function( content ) { - props.setAttributes( { content: content } ); - } - } ); - }, - - save: function( props ) { - return React.createElement( wp.editor.EditableText.Content, { - value: props.attributes.content - } ); - } -} ); -``` - -{% ESNext %} - ```js const { registerBlockType } = wp.blocks; const { EditableText } = wp.editor; @@ -110,5 +76,3 @@ registerBlockType( /* ... */, { } } ); ``` - -{% end %} diff --git a/packages/block-editor/src/components/iframe/index.js b/packages/block-editor/src/components/iframe/index.js index 28697324aa8b83..1939f75811c8c5 100644 --- a/packages/block-editor/src/components/iframe/index.js +++ b/packages/block-editor/src/components/iframe/index.js @@ -73,12 +73,13 @@ function bubbleEvent( event, Constructor, frame ) { * @param {Document} iframeDocument Document to attach listeners to. */ function useBubbleEvents( iframeDocument ) { - return useRefEffect( ( body ) => { + return useRefEffect( () => { const { defaultView } = iframeDocument; if ( ! defaultView ) { return; } const { frameElement } = defaultView; + const html = iframeDocument.documentElement; const eventTypes = [ 'dragover', 'mousemove' ]; const handlers = {}; for ( const name of eventTypes ) { @@ -88,12 +89,12 @@ function useBubbleEvents( iframeDocument ) { const Constructor = window[ constructorName ]; bubbleEvent( event, Constructor, frameElement ); }; - body.addEventListener( name, handlers[ name ] ); + html.addEventListener( name, handlers[ name ] ); } return () => { for ( const name of eventTypes ) { - body.removeEventListener( name, handlers[ name ] ); + html.removeEventListener( name, handlers[ name ] ); } }; } ); diff --git a/packages/block-editor/src/components/image-size-control/index.js b/packages/block-editor/src/components/image-size-control/index.js index d929b129313938..46e87de60f2fc8 100644 --- a/packages/block-editor/src/components/image-size-control/index.js +++ b/packages/block-editor/src/components/image-size-control/index.js @@ -8,7 +8,6 @@ import { __experimentalNumberControl as NumberControl, __experimentalHStack as HStack, } from '@wordpress/components'; -import deprecated from '@wordpress/deprecated'; import { __ } from '@wordpress/i18n'; /** @@ -31,11 +30,6 @@ export default function ImageSizeControl( { onChange, onChangeImage = noop, } ) { - deprecated( 'wp.blockEditor.__experimentalImageSizeControl', { - since: '6.3', - alternative: - 'wp.blockEditor.privateApis.DimensionsTool and wp.blockEditor.privateApis.ResolutionTool', - } ); const { currentHeight, currentWidth, updateDimension, updateDimensions } = useDimensionHandler( height, width, imageHeight, imageWidth, onChange ); diff --git a/packages/block-editor/src/components/inner-blocks/index.js b/packages/block-editor/src/components/inner-blocks/index.js index f5f216d6072e4b..d637a16f363602 100644 --- a/packages/block-editor/src/components/inner-blocks/index.js +++ b/packages/block-editor/src/components/inner-blocks/index.js @@ -169,8 +169,11 @@ const ForwardedInnerBlocks = forwardRef( ( props, ref ) => { * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/inner-blocks/README.md */ export function useInnerBlocksProps( props = {}, options = {} ) { - const { __unstableDisableLayoutClassNames, __unstableDisableDropZone } = - options; + const { + __unstableDisableLayoutClassNames, + __unstableDisableDropZone, + dropZoneElement, + } = options; const { clientId, layout = null, @@ -211,6 +214,7 @@ export function useInnerBlocksProps( props = {}, options = {} ) { ); const blockDropZoneRef = useBlockDropZone( { + dropZoneElement, rootClientId: clientId, } ); diff --git a/packages/block-editor/src/components/inserter-listbox/index.js b/packages/block-editor/src/components/inserter-listbox/index.js index 6345cb38c494ac..6af26a1d746bfb 100644 --- a/packages/block-editor/src/components/inserter-listbox/index.js +++ b/packages/block-editor/src/components/inserter-listbox/index.js @@ -1,26 +1,30 @@ /** * WordPress dependencies */ -import { __unstableUseCompositeState as useCompositeState } from '@wordpress/components'; +import { privateApis as componentsPrivateApis } from '@wordpress/components'; /** * Internal dependencies */ -import InserterListboxContext from './context'; +import { unlock } from '../../lock-unlock'; export { default as InserterListboxGroup } from './group'; export { default as InserterListboxRow } from './row'; export { default as InserterListboxItem } from './item'; +const { CompositeV2: Composite, useCompositeStoreV2: useCompositeStore } = + unlock( componentsPrivateApis ); + function InserterListbox( { children } ) { - const compositeState = useCompositeState( { - shift: true, - wrap: 'horizontal', + const store = useCompositeStore( { + focusShift: true, + focusWrap: 'horizontal', } ); + return ( - + }> { children } - + ); } diff --git a/packages/block-editor/src/components/inserter-listbox/item.js b/packages/block-editor/src/components/inserter-listbox/item.js index 50adb4a7880387..951eb86223ce8f 100644 --- a/packages/block-editor/src/components/inserter-listbox/item.js +++ b/packages/block-editor/src/components/inserter-listbox/item.js @@ -3,32 +3,31 @@ */ import { Button, - __unstableCompositeItem as CompositeItem, + privateApis as componentsPrivateApis, } from '@wordpress/components'; -import { forwardRef, useContext } from '@wordpress/element'; +import { forwardRef } from '@wordpress/element'; /** * Internal dependencies */ -import InserterListboxContext from './context'; +import { unlock } from '../../lock-unlock'; + +const { CompositeItemV2: CompositeItem } = unlock( componentsPrivateApis ); function InserterListboxItem( { isFirst, as: Component, children, ...props }, ref ) { - const state = useContext( InserterListboxContext ); return ( - { ( htmlProps ) => { + render={ ( htmlProps ) => { const propsWithTabIndex = { ...htmlProps, tabIndex: isFirst ? 0 : htmlProps.tabIndex, @@ -45,7 +44,7 @@ function InserterListboxItem( } return ; } } - + /> ); } diff --git a/packages/block-editor/src/components/inserter-listbox/row.js b/packages/block-editor/src/components/inserter-listbox/row.js index 710267660199d7..f9827f08b3fa39 100644 --- a/packages/block-editor/src/components/inserter-listbox/row.js +++ b/packages/block-editor/src/components/inserter-listbox/row.js @@ -1,24 +1,18 @@ /** * WordPress dependencies */ -import { forwardRef, useContext } from '@wordpress/element'; -import { __unstableCompositeGroup as CompositeGroup } from '@wordpress/components'; +import { forwardRef } from '@wordpress/element'; +import { privateApis as componentsPrivateApis } from '@wordpress/components'; /** * Internal dependencies */ -import InserterListboxContext from './context'; +import { unlock } from '../../lock-unlock'; + +const { CompositeGroupV2: CompositeGroup } = unlock( componentsPrivateApis ); function InserterListboxRow( props, ref ) { - const state = useContext( InserterListboxContext ); - return ( - - ); + return ; } export default forwardRef( InserterListboxRow ); diff --git a/packages/block-editor/src/components/inserter/block-patterns-explorer/pattern-list.js b/packages/block-editor/src/components/inserter/block-patterns-explorer/pattern-list.js index 7cd2320a4fd1f0..bf2867be5cdf3c 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-explorer/pattern-list.js +++ b/packages/block-editor/src/components/inserter/block-patterns-explorer/pattern-list.js @@ -18,6 +18,7 @@ import { searchItems } from '../search-items'; import BlockPatternsPaging from '../../block-patterns-paging'; import usePatternsPaging from '../hooks/use-patterns-paging'; import { + PATTERN_TYPES, allPatternsCategory, myPatternsCategory, } from '../block-patterns-tab/utils'; @@ -70,7 +71,10 @@ function PatternList( { searchValue, selectedCategory, patternCategories } ) { if ( selectedCategory === allPatternsCategory.name ) { return true; } - if ( selectedCategory === myPatternsCategory.name && pattern.id ) { + if ( + selectedCategory === myPatternsCategory.name && + pattern.type === PATTERN_TYPES.user + ) { return true; } if ( selectedCategory === 'uncategorized' ) { diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js b/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js index 2fef53cfa2a193..76864a6a00ccc5 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js +++ b/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js @@ -30,6 +30,7 @@ import { isPatternFiltered, allPatternsCategory, myPatternsCategory, + PATTERN_TYPES, } from './utils'; const noop = () => {}; @@ -69,7 +70,10 @@ export function PatternCategoryPreviews( { if ( category.name === allPatternsCategory.name ) { return true; } - if ( category.name === myPatternsCategory.name && pattern.id ) { + if ( + category.name === myPatternsCategory.name && + pattern.type === PATTERN_TYPES.user + ) { return true; } if ( category.name !== 'uncategorized' ) { diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab/use-pattern-categories.js b/packages/block-editor/src/components/inserter/block-patterns-tab/use-pattern-categories.js index 9c7a7a32a60c07..12e885954f4bf3 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-tab/use-pattern-categories.js +++ b/packages/block-editor/src/components/inserter/block-patterns-tab/use-pattern-categories.js @@ -14,6 +14,7 @@ import { isPatternFiltered, allPatternsCategory, myPatternsCategory, + PATTERN_TYPES, } from './utils'; export function usePatternCategories( rootClientId, sourceFilter = 'all' ) { @@ -69,7 +70,11 @@ export function usePatternCategories( rootClientId, sourceFilter = 'all' ) { label: _x( 'Uncategorized' ), } ); } - if ( filteredPatterns.some( ( pattern ) => pattern.id ) ) { + if ( + filteredPatterns.some( + ( pattern ) => pattern.type === PATTERN_TYPES.user + ) + ) { categories.unshift( myPatternsCategory ); } if ( filteredPatterns.length > 0 ) { diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab/utils.js b/packages/block-editor/src/components/inserter/block-patterns-tab/utils.js index 9f222c6a2f93cd..439955eca92b35 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-tab/utils.js +++ b/packages/block-editor/src/components/inserter/block-patterns-tab/utils.js @@ -53,9 +53,11 @@ export function isPatternFiltered( pattern, sourceFilter, syncFilter ) { return true; } - // If user source selected, filter out theme patterns. Any pattern without - // an id wasn't created by a user. - if ( sourceFilter === PATTERN_TYPES.user && ! pattern.id ) { + // If user source selected, filter out theme patterns. + if ( + sourceFilter === PATTERN_TYPES.user && + pattern.type !== PATTERN_TYPES.user + ) { return true; } diff --git a/packages/block-editor/src/components/inserter/hooks/use-patterns-state.js b/packages/block-editor/src/components/inserter/hooks/use-patterns-state.js index 1924187e04179f..576768c76abca9 100644 --- a/packages/block-editor/src/components/inserter/hooks/use-patterns-state.js +++ b/packages/block-editor/src/components/inserter/hooks/use-patterns-state.js @@ -11,6 +11,7 @@ import { store as noticesStore } from '@wordpress/notices'; * Internal dependencies */ import { store as blockEditorStore } from '../../../store'; +import { PATTERN_TYPES } from '../block-patterns-tab/utils'; /** * Retrieves the block patterns inserter state. @@ -57,7 +58,8 @@ const usePatternsState = ( onInsert, rootClientId ) => { const onClickPattern = useCallback( ( pattern, blocks ) => { const patternBlocks = - pattern.id && pattern.syncStatus !== 'unsynced' + pattern.type === PATTERN_TYPES.user && + pattern.syncStatus !== 'unsynced' ? [ createBlock( 'core/block', { ref: pattern.id } ) ] : blocks; onInsert( diff --git a/packages/block-editor/src/components/inserter/media-tab/media-list.js b/packages/block-editor/src/components/inserter/media-tab/media-list.js index b745a54e25e9c0..bfc858bc8c4de7 100644 --- a/packages/block-editor/src/components/inserter/media-tab/media-list.js +++ b/packages/block-editor/src/components/inserter/media-tab/media-list.js @@ -1,16 +1,17 @@ /** * WordPress dependencies */ -import { - __unstableComposite as Composite, - __unstableUseCompositeState as useCompositeState, -} from '@wordpress/components'; +import { privateApis as componentsPrivateApis } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ import { MediaPreview } from './media-preview'; +import { unlock } from '../../../lock-unlock'; + +const { CompositeV2: Composite, useCompositeStoreV2: useCompositeStore } = + unlock( componentsPrivateApis ); function MediaList( { mediaList, @@ -18,10 +19,10 @@ function MediaList( { onClick, label = __( 'Media List' ), } ) { - const composite = useCompositeState(); + const compositeStore = useCompositeStore(); return ( ) ) } diff --git a/packages/block-editor/src/components/inserter/media-tab/media-preview.js b/packages/block-editor/src/components/inserter/media-tab/media-preview.js index 88648bf96531b6..9efed229f0adf2 100644 --- a/packages/block-editor/src/components/inserter/media-tab/media-preview.js +++ b/packages/block-editor/src/components/inserter/media-tab/media-preview.js @@ -7,7 +7,6 @@ import classnames from 'classnames'; * WordPress dependencies */ import { - __unstableCompositeItem as CompositeItem, Tooltip, DropdownMenu, MenuGroup, @@ -17,6 +16,7 @@ import { Flex, FlexItem, Button, + privateApis as componentsPrivateApis, __experimentalVStack as VStack, } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; @@ -33,6 +33,7 @@ import { isBlobURL } from '@wordpress/blob'; import InserterDraggableBlocks from '../../inserter-draggable-blocks'; import { getBlockAndPreviewFromMedia } from './utils'; import { store as blockEditorStore } from '../../../store'; +import { unlock } from '../../../lock-unlock'; const ALLOWED_MEDIA_TYPES = [ 'image' ]; const MAXIMUM_TITLE_LENGTH = 25; @@ -42,6 +43,8 @@ const MEDIA_OPTIONS_POPOVER_PROPS = { 'block-editor-inserter__media-list__item-preview-options__popover', }; +const { CompositeItemV2: CompositeItem } = unlock( componentsPrivateApis ); + function MediaPreviewOptions( { category, media } ) { if ( ! category.getReportUrl ) { return null; @@ -113,7 +116,7 @@ function InsertExternalImageModal( { onClose, onSubmit } ) { ); } -export function MediaPreview( { media, onClick, composite, category } ) { +export function MediaPreview( { media, onClick, category } ) { const [ showExternalUploadModal, setShowExternalUploadModal ] = useState( false ); const [ isHovered, setIsHovered ] = useState( false ); @@ -216,20 +219,22 @@ export function MediaPreview( { media, onClick, composite, category } ) { onDragStart={ onDragStart } onDragEnd={ onDragEnd } > - - { /* Adding `is-hovered` class to the wrapper element is needed - because the options Popover is rendered outside of this node. */ } -
+ { /* Adding `is-hovered` class to the wrapper element is needed + because the options Popover is rendered outside of this node. */ } +
+ + } onClick={ () => onMediaInsert( block ) } - aria-label={ title } >
{ preview } @@ -240,14 +245,14 @@ export function MediaPreview( { media, onClick, composite, category } ) { ) }
- { ! isInserting && ( - - ) } -
- + + { ! isInserting && ( + + ) } +
) } diff --git a/packages/block-editor/src/components/list-view/block-select-button.js b/packages/block-editor/src/components/list-view/block-select-button.js index cec5d4699c7a2f..25de5483f5192e 100644 --- a/packages/block-editor/src/components/list-view/block-select-button.js +++ b/packages/block-editor/src/components/list-view/block-select-button.js @@ -19,6 +19,7 @@ import { SPACE, ENTER, BACKSPACE, DELETE } from '@wordpress/keycodes'; import { useSelect, useDispatch } from '@wordpress/data'; import { __unstableUseShortcutEventMatch as useShortcutEventMatch } from '@wordpress/keyboard-shortcuts'; import { __, sprintf } from '@wordpress/i18n'; +import isShallowEqual from '@wordpress/is-shallow-equal'; /** * Internal dependencies @@ -30,6 +31,7 @@ import ListViewExpander from './expander'; import { useBlockLock } from '../block-lock'; import { store as blockEditorStore } from '../../store'; import useListViewImages from './use-list-view-images'; +import { useListViewContext } from './context'; function ListViewBlockSelectButton( { @@ -64,10 +66,12 @@ function ListViewBlockSelectButton( getBlocksByClientId, canRemoveBlocks, } = useSelect( blockEditorStore ); - const { duplicateBlocks, removeBlocks } = useDispatch( blockEditorStore ); + const { duplicateBlocks, multiSelect, removeBlocks } = + useDispatch( blockEditorStore ); const isMatch = useShortcutEventMatch(); const isSticky = blockInformation?.positionType === 'sticky'; const images = useListViewImages( { clientId, isExpanded } ); + const { rootClientId } = useListViewContext(); const positionLabel = blockInformation?.positionLabel ? sprintf( @@ -183,6 +187,45 @@ function ListViewBlockSelectButton( updateFocusAndSelection( updatedBlocks[ 0 ], false ); } } + } else if ( isMatch( 'core/block-editor/select-all', event ) ) { + if ( event.defaultPrevented ) { + return; + } + event.preventDefault(); + + const { firstBlockRootClientId, selectedBlockClientIds } = + getBlocksToUpdate(); + const blockClientIds = getBlockOrder( firstBlockRootClientId ); + if ( ! blockClientIds.length ) { + return; + } + + // If we have selected all sibling nested blocks, try selecting up a level. + // This is a similar implementation to that used by `useSelectAll`. + // `isShallowEqual` is used for the list view instead of a length check, + // as the array of siblings of the currently focused block may be a different + // set of blocks from the current block selection if the user is focused + // on a different part of the list view from the block selection. + if ( isShallowEqual( selectedBlockClientIds, blockClientIds ) ) { + // Only select up a level if the first block is not the root block. + // This ensures that the block selection can't break out of the root block + // used by the list view, if the list view is only showing a partial hierarchy. + if ( + firstBlockRootClientId && + firstBlockRootClientId !== rootClientId + ) { + updateFocusAndSelection( firstBlockRootClientId, true ); + return; + } + } + + // Select all while passing `null` to skip focusing to the editor canvas, + // and retain focus within the list view. + multiSelect( + blockClientIds[ 0 ], + blockClientIds[ blockClientIds.length - 1 ], + null + ); } } diff --git a/packages/block-editor/src/components/list-view/index.js b/packages/block-editor/src/components/list-view/index.js index 1d21c28643a73c..315a6153839d16 100644 --- a/packages/block-editor/src/components/list-view/index.js +++ b/packages/block-editor/src/components/list-view/index.js @@ -222,6 +222,7 @@ function ListViewComponent( insertedBlock, setInsertedBlock, treeGridElementRef: elementRef, + rootClientId, } ), [ draggedClientIds, @@ -233,6 +234,7 @@ function ListViewComponent( AdditionalBlockContent, insertedBlock, setInsertedBlock, + rootClientId, ] ); diff --git a/packages/block-editor/src/components/plain-text/README.md b/packages/block-editor/src/components/plain-text/README.md index 4e59789fd612c7..aa15758118afdc 100644 --- a/packages/block-editor/src/components/plain-text/README.md +++ b/packages/block-editor/src/components/plain-text/README.md @@ -20,33 +20,6 @@ _Optional._ The component forwards the `ref` property to the `TextareaAutosize` ## Example -{% codetabs %} -{% ES5 %} - -```js -wp.blocks.registerBlockType( /* ... */, { - // ... - - attributes: { - content: { - type: 'string', - }, - }, - - edit: function( props ) { - return React.createElement( wp.blockEditor.PlainText, { - className: props.className, - value: props.attributes.content, - onChange: function( content ) { - props.setAttributes( { content: content } ); - }, - } ); - }, -} ); -``` - -{% ESNext %} - ```js import { registerBlockType } from '@wordpress/blocks'; import { PlainText } from '@wordpress/block-editor'; @@ -72,4 +45,3 @@ registerBlockType( /* ... */, { } ); ``` -{% end %} diff --git a/packages/block-editor/src/components/provider/use-block-sync.js b/packages/block-editor/src/components/provider/use-block-sync.js index 58aca847d80de0..4f2300f380892e 100644 --- a/packages/block-editor/src/components/provider/use-block-sync.js +++ b/packages/block-editor/src/components/provider/use-block-sync.js @@ -76,18 +76,11 @@ export default function useBlockSync( { resetBlocks, resetSelection, replaceInnerBlocks, - selectBlock, setHasControlledInnerBlocks, __unstableMarkNextChangeAsNotPersistent, } = registry.dispatch( blockEditorStore ); - const { - hasSelectedBlock, - getBlockName, - getBlocks, - getSelectionStart, - getSelectionEnd, - getBlock, - } = registry.select( blockEditorStore ); + const { getBlockName, getBlocks, getSelectionStart, getSelectionEnd } = + registry.select( blockEditorStore ); const isControlled = useSelect( ( select ) => { return ( @@ -180,9 +173,6 @@ export default function useBlockSync( { // bound sync, unset the outbound value to avoid considering it in // subsequent renders. pendingChanges.current.outgoing = []; - const hadSelection = hasSelectedBlock(); - const selectionAnchor = getSelectionStart(); - const selectionFocus = getSelectionEnd(); setControlledBlocks(); if ( controlledSelection ) { @@ -191,15 +181,6 @@ export default function useBlockSync( { controlledSelection.selectionEnd, controlledSelection.initialPosition ); - } else { - const selectionStillExists = getBlock( - selectionAnchor.clientId - ); - if ( hadSelection && ! selectionStillExists ) { - selectBlock( clientId ); - } else { - resetSelection( selectionAnchor, selectionFocus ); - } } } }, [ controlledBlocks, clientId ] ); diff --git a/packages/block-editor/src/components/rich-text/README.md b/packages/block-editor/src/components/rich-text/README.md index d17f987a34cf0e..4251debfa16c54 100644 --- a/packages/block-editor/src/components/rich-text/README.md +++ b/packages/block-editor/src/components/rich-text/README.md @@ -80,41 +80,6 @@ trimmed. ## Example -{% codetabs %} -{% ES5 %} - -```js -wp.blocks.registerBlockType( /* ... */, { - // ... - - attributes: { - content: { - source: 'html', - selector: 'h2', - }, - }, - - edit: function( props ) { - return React.createElement( wp.blockEditor.RichText, { - tagName: 'h2', - className: props.className, - value: props.attributes.content, - onChange: function( content ) { - props.setAttributes( { content: content } ); - } - } ); - }, - - save: function( props ) { - return React.createElement( wp.blockEditor.RichText.Content, { - tagName: 'h2', value: props.attributes.content - } ); - } -} ); -``` - -{% ESNext %} - ```js import { registerBlockType } from '@wordpress/blocks'; import { RichText } from '@wordpress/block-editor'; @@ -146,7 +111,6 @@ registerBlockType( /* ... */, { } ); ``` -{% end %} ## RichTextToolbarButton @@ -154,26 +118,6 @@ Slot to extend the format toolbar. Use it in the edit function of a `registerFor ### Example -{% codetabs %} -{% ES5 %} - -```js -wp.richText.registerFormatType( /* ... */, { - /* ... */ - edit: function( props ) { - return React.createElement( - wp.blockEditor.RichTextToolbarButton, { - icon: 'editor-code', - title: 'My formatting button', - onClick: function() { /* ... */ } - isActive: props.isActive, - } ); - }, - /* ... */ -} ); -``` - -{% ESNext %} ```js import { registerFormatType } from '@wordpress/rich-text'; @@ -194,5 +138,3 @@ registerFormatType( /* ... */, { /* ... */ } ); ``` - -{% end %} diff --git a/packages/block-editor/src/components/rich-text/use-input-rules.js b/packages/block-editor/src/components/rich-text/use-input-rules.js index 58432c01f9683b..5aa47e7c7b4d74 100644 --- a/packages/block-editor/src/components/rich-text/use-input-rules.js +++ b/packages/block-editor/src/components/rich-text/use-input-rules.js @@ -3,7 +3,7 @@ */ import { useRef } from '@wordpress/element'; import { useRefEffect } from '@wordpress/compose'; -import { insert, toHTMLString } from '@wordpress/rich-text'; +import { insert, isCollapsed, toHTMLString } from '@wordpress/rich-text'; import { getBlockTransforms, findTransform } from '@wordpress/blocks'; import { useDispatch } from '@wordpress/data'; @@ -42,6 +42,34 @@ function findSelection( blocks ) { return []; } +/** + * An input rule that replaces two spaces with an en space, and an en space + * followed by a space with an em space. + * + * @param {Object} value Value to replace spaces in. + * + * @return {Object} Value with spaces replaced. + */ +function replacePrecedingSpaces( value ) { + if ( ! isCollapsed( value ) ) { + return value; + } + + const { text, start } = value; + const lastTwoCharacters = text.slice( start - 2, start ); + + // Replace two spaces with an em space. + if ( lastTwoCharacters === ' ' ) { + return insert( value, '\u2002', start - 2, start ); + } + // Replace an en space followed by a space with an em space. + else if ( lastTwoCharacters === '\u2002 ' ) { + return insert( value, '\u2003', start - 2, start ); + } + + return value; +} + export function useInputRules( props ) { const { __unstableMarkLastChangeAsPersistent, @@ -122,7 +150,7 @@ export function useInputRules( props ) { return accumlator; }, - preventEventDiscovery( value ) + preventEventDiscovery( replacePrecedingSpaces( value ) ) ); if ( transformed !== value ) { diff --git a/packages/block-editor/src/components/spacing-sizes-control/input-controls/spacing-input-control.js b/packages/block-editor/src/components/spacing-sizes-control/input-controls/spacing-input-control.js index a7efd10bce7125..7ef5c17f82943c 100644 --- a/packages/block-editor/src/components/spacing-sizes-control/input-controls/spacing-input-control.js +++ b/packages/block-editor/src/components/spacing-sizes-control/input-controls/spacing-input-control.js @@ -139,7 +139,7 @@ export default function SpacingInputControl( { useMemo( () => parseQuantityAndUnitFromRawValue( currentValue ), [ currentValue ] - )[ 1 ] || units[ 0 ].value; + )[ 1 ] || units[ 0 ]?.value; const setInitialValue = () => { if ( value === undefined ) { diff --git a/packages/block-editor/src/components/url-input/README.md b/packages/block-editor/src/components/url-input/README.md index 46f673ecd35545..9d3e340371a608 100644 --- a/packages/block-editor/src/components/url-input/README.md +++ b/packages/block-editor/src/components/url-input/README.md @@ -36,41 +36,6 @@ This prop is passed directly to the `URLInput` component. ## Example -{% codetabs %} -{% ES5 %} - -```js -wp.blocks.registerBlockType( /* ... */, { - // ... - - attributes: { - url: { - type: 'string' - }, - text: { - type: 'string' - } - }, - - edit: function( props ) { - return React.createElement( wp.blockEditor.URLInputButton, { - className: props.className, - url: props.attributes.url, - onChange: function( url, post ) { - props.setAttributes( { url: url, text: (post && post.title) || 'Click here' } ); - } - } ); - }, - - save: function( props ) { - return React.createElement( 'a', { - href: props.attributes.url, - }, props.attributes.text ); - } -} ); -``` - -{% ESNext %} ```js import { registerBlockType } from '@wordpress/blocks'; @@ -103,7 +68,6 @@ registerBlockType( /* ... */, { } ); ``` -{% end %} # `URLInput` @@ -139,7 +103,7 @@ _Required._ Called when the value changes. The second parameter is `null` unless } ``` -### `onKeydown`: `( event: KeyboardEvent ) => void` +### `onKeyDown`: `( event: KeyboardEvent ) => void` A callback invoked on the keydown event. @@ -172,41 +136,6 @@ Start opting into the new margin-free styles that will become the default in a f ## Example -{% codetabs %} -{% ES5 %} - -```js -wp.blocks.registerBlockType( /* ... */, { - // ... - - attributes: { - url: { - type: 'string' - }, - text: { - type: 'string' - } - }, - - edit: function( props ) { - return React.createElement( wp.blockEditor.URLInput, { - className: props.className, - value: props.attributes.url, - onChange: function( url, post ) { - props.setAttributes( { url: url, text: (post && post.title) || 'Click here' } ); - } - } ); - }, - - save: function( props ) { - return React.createElement( 'a', { - href: props.attributes.url, - }, props.attributes.text ); - } -} ); -``` - -{% ESNext %} ```js import { registerBlockType } from '@wordpress/blocks'; @@ -240,5 +169,3 @@ registerBlockType( /* ... */, { } } ); ``` - -{% end %} diff --git a/packages/block-editor/src/components/url-popover/image-url-input-ui.js b/packages/block-editor/src/components/url-popover/image-url-input-ui.js index 2f849eaad78847..7caa218658b24c 100644 --- a/packages/block-editor/src/components/url-popover/image-url-input-ui.js +++ b/packages/block-editor/src/components/url-popover/image-url-input-ui.js @@ -249,6 +249,7 @@ const ImageURLInputUI = ( { aria-expanded={ isOpen } onClick={ openLinkUI } ref={ setPopoverAnchor } + isActive={ !! url } /> { isOpen && ( { const result = runHook( () => useSetting( 'layout.contentSize' ) ); expect( result ).toBe( '840px' ); expect( console ).toHaveWarnedWith( - 'wp.blockEditor.useSetting is deprecated since version 6.4. Please use wp.blockEditor.useSettings instead.' + 'wp.blockEditor.useSetting is deprecated since version 6.5. Please use wp.blockEditor.useSettings instead.' ); } ); } ); diff --git a/packages/block-editor/src/hooks/anchor.js b/packages/block-editor/src/hooks/anchor.js index 7a8f507d9674e0..3d404c4a868116 100644 --- a/packages/block-editor/src/hooks/anchor.js +++ b/packages/block-editor/src/hooks/anchor.js @@ -59,6 +59,7 @@ function BlockEditAnchorControl( { blockName, attributes, setAttributes } ) { const textControl = ( { return ( props ) => { - const blockEdit = ; return ( <> - { blockEdit } - + + { props.isSelected && ( + + ) } ); }; diff --git a/packages/block-editor/src/hooks/block-rename-ui.js b/packages/block-editor/src/hooks/block-rename-ui.js index 6af17da6fad7f4..836df953256c10 100644 --- a/packages/block-editor/src/hooks/block-rename-ui.js +++ b/packages/block-editor/src/hooks/block-rename-ui.js @@ -92,6 +92,7 @@ function RenameModal( { blockName, originalBlockName, onClose, onSave } ) { +"
" `; exports[`Align options for media block sets Align left option 1`] = ` -" +"
" `; exports[`Align options for media block sets Align right option 1`] = ` -" +"
" `; exports[`Align options for media block sets Full width option 1`] = ` -" +"
" `; @@ -49,7 +49,7 @@ exports[`Align options for media block sets None option 1`] = ` `; exports[`Align options for media block sets Wide width option 1`] = ` -" +"
" `; diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 548ad71664b5e6..c0441cd3b3755e 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -27,6 +27,7 @@ import { createRegistrySelector } from '@wordpress/data'; * Internal dependencies */ import { orderBy } from '../utils/sorting'; +import { PATTERN_TYPES } from '../components/inserter/block-patterns-tab/utils'; /** * A block selection object. @@ -2287,6 +2288,7 @@ function getUserPatterns( state ) { return { name: `core/block/${ userPattern.id }`, id: userPattern.id, + type: PATTERN_TYPES.user, title: userPattern.title.raw, categories: userPattern.wp_pattern_category.map( ( catId ) => categories && categories.get( catId ) diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index 22f02cfcd62885..da6f0fa75152cf 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 8.23.0 (2023-11-16) + ## 8.22.0 (2023-11-02) ## 8.21.0 (2023-10-18) diff --git a/packages/block-library/package.json b/packages/block-library/package.json index 5fdec495443efa..558566bccaae0a 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-library", - "version": "8.22.0", + "version": "8.23.0", "description": "Block library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index 293f09640569d2..979ae04c62282c 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -114,33 +114,28 @@ export default function ReusableBlockEdit( { : InnerBlocks.ButtonBlockAppender, } ); + let children = null; if ( hasAlreadyRendered ) { - return ( -
- - { __( 'Block cannot be rendered inside itself.' ) } - -
+ children = ( + + { __( 'Block cannot be rendered inside itself.' ) } + ); } if ( isMissing ) { - return ( -
- - { __( 'Block has been deleted or is unavailable.' ) } - -
+ children = ( + + { __( 'Block has been deleted or is unavailable.' ) } + ); } if ( ! hasResolved ) { - return ( -
- - - -
+ children = ( + + + ); } @@ -149,14 +144,19 @@ export default function ReusableBlockEdit( { -
+ { children === null ? ( +
+ ) : ( +
{ children }
+ ) } ); } diff --git a/packages/block-library/src/block/edit.native.js b/packages/block-library/src/block/edit.native.js index ddc22c01e40def..9ab6ccf86a1e19 100644 --- a/packages/block-library/src/block/edit.native.js +++ b/packages/block-library/src/block/edit.native.js @@ -78,7 +78,7 @@ export default function ReusableBlockEdit( { styles.spinnerDark ); - const { hasResolved, isEditing, isMissing, innerBlockCount } = useSelect( + const { hasResolved, isEditing, isMissing } = useSelect( ( select ) => { const persistedBlock = select( coreStore ).getEntityRecord( 'postType', @@ -176,20 +176,12 @@ export default function ReusableBlockEdit( { { infoTitle } - { innerBlockCount > 1 - ? __( - 'Alternatively, you can detach and edit these blocks separately by tapping “Detach patterns”.' - ) - : __( - 'Alternatively, you can detach and edit this block separately by tapping “Detach pattern”.' - ) } + { __( + 'Alternatively, you can detach and edit this block separately by tapping “Detach”.' + ) } 1 - ? __( 'Detach patterns' ) - : __( 'Detach pattern' ) - } + label={ __( 'Detach' ) } separatorType="topFullWidth" onPress={ onConvertToRegularBlocks } labelStyle={ actionButtonStyle } diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index 81861e44997a4a..8400dabddad348 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -33,11 +33,17 @@ import { __experimentalGetSpacingClassesAndStyles as useSpacingProps, __experimentalLinkControl as LinkControl, __experimentalGetElementClassName, + store as blockEditorStore, } from '@wordpress/block-editor'; -import { displayShortcut, isKeyboardEvent } from '@wordpress/keycodes'; +import { displayShortcut, isKeyboardEvent, ENTER } from '@wordpress/keycodes'; import { link, linkOff } from '@wordpress/icons'; -import { createBlock } from '@wordpress/blocks'; -import { useMergeRefs } from '@wordpress/compose'; +import { + createBlock, + cloneBlock, + getDefaultBlockName, +} from '@wordpress/blocks'; +import { useMergeRefs, useRefEffect } from '@wordpress/compose'; +import { useSelect, useDispatch } from '@wordpress/data'; const LINK_SETTINGS = [ ...LinkControl.DEFAULT_LINK_SETTINGS, @@ -47,6 +53,62 @@ const LINK_SETTINGS = [ }, ]; +function useEnter( props ) { + const { replaceBlocks, selectionChange } = useDispatch( blockEditorStore ); + const { getBlock, getBlockRootClientId, getBlockIndex } = + useSelect( blockEditorStore ); + const propsRef = useRef( props ); + propsRef.current = props; + return useRefEffect( ( element ) => { + function onKeyDown( event ) { + if ( event.defaultPrevented || event.keyCode !== ENTER ) { + return; + } + const { content, clientId } = propsRef.current; + if ( content.length ) { + return; + } + event.preventDefault(); + const topParentListBlock = getBlock( + getBlockRootClientId( clientId ) + ); + const blockIndex = getBlockIndex( clientId ); + const head = cloneBlock( { + ...topParentListBlock, + innerBlocks: topParentListBlock.innerBlocks.slice( + 0, + blockIndex + ), + } ); + const middle = createBlock( getDefaultBlockName() ); + const after = topParentListBlock.innerBlocks.slice( + blockIndex + 1 + ); + const tail = after.length + ? [ + cloneBlock( { + ...topParentListBlock, + innerBlocks: after, + } ), + ] + : []; + replaceBlocks( + topParentListBlock.clientId, + [ head, middle, ...tail ], + 1 + ); + // We manually change the selection here because we are replacing + // a different block than the selected one. + selectionChange( middle.clientId ); + } + + element.addEventListener( 'keydown', onKeyDown ); + return () => { + element.removeEventListener( 'keydown', onKeyDown ); + }; + }, [] ); +} + function WidthPanel( { selectedWidth, setAttributes } ) { function handleChange( newWidth ) { // Check if we are toggling the width off @@ -88,6 +150,7 @@ function ButtonEdit( props ) { isSelected, onReplace, mergeBlocks, + clientId, } = props; const { tagName, @@ -164,6 +227,9 @@ function ButtonEdit( props ) { [ url, opensInNewTab, nofollow ] ); + const useEnterRef = useEnter( { content: text, clientId } ); + const mergedRef = useMergeRefs( [ useEnterRef, richTextRef ] ); + return ( <>
)' ), value: 'div' }, diff --git a/packages/block-library/src/cover/edit/inspector-controls.js b/packages/block-library/src/cover/edit/inspector-controls.js index 3ed0ae872f9337..38757f90c2deec 100644 --- a/packages/block-library/src/cover/edit/inspector-controls.js +++ b/packages/block-library/src/cover/edit/inspector-controls.js @@ -319,6 +319,7 @@ export default function CoverInspectorControls( { )' ), value: 'div' }, diff --git a/packages/block-library/src/cover/style.scss b/packages/block-library/src/cover/style.scss index d9312a71f0faad..837e3834e2e1ba 100644 --- a/packages/block-library/src/cover/style.scss +++ b/packages/block-library/src/cover/style.scss @@ -123,7 +123,7 @@ h4, h5, h6 { - &:not(.has-text-color) { + &:where(:not(.has-text-color)) { color: inherit; } } diff --git a/packages/block-library/src/details/edit.js b/packages/block-library/src/details/edit.js index 81e4d7a52056a7..0d1d675893900f 100644 --- a/packages/block-library/src/details/edit.js +++ b/packages/block-library/src/details/edit.js @@ -72,7 +72,6 @@ function DetailsEdit( { attributes, setAttributes, clientId } ) { onChange={ ( newSummary ) => setAttributes( { summary: newSummary } ) } - multiline={ false } /> { innerBlocksProps.children } diff --git a/packages/block-library/src/group/edit.js b/packages/block-library/src/group/edit.js index 4d5354eff0180f..9c8690c4e0e8e2 100644 --- a/packages/block-library/src/group/edit.js +++ b/packages/block-library/src/group/edit.js @@ -52,6 +52,7 @@ function GroupEditControls( { tagName, onSelectTagName } ) { )' ), value: 'div' }, @@ -70,13 +71,7 @@ function GroupEditControls( { tagName, onSelectTagName } ) { ); } -function GroupEdit( { - attributes, - name, - setAttributes, - clientId, - __unstableLayoutClassNames: layoutClassNames, -} ) { +function GroupEdit( { attributes, name, setAttributes, clientId } ) { const { hasInnerBlocks, themeSupportsLayout } = useSelect( ( select ) => { const { getBlock, getSettings } = select( blockEditorStore ); @@ -102,9 +97,8 @@ function GroupEdit( { themeSupportsLayout || type === 'flex' || type === 'grid'; // Hooks. - const blockProps = useBlockProps( { - className: ! layoutSupportEnabled ? layoutClassNames : null, - } ); + const blockProps = useBlockProps(); + const [ showPlaceholder, setShowPlaceholder ] = useShouldShowPlaceHolder( { attributes, usedLayoutType: type, @@ -133,7 +127,6 @@ function GroupEdit( { templateLock, allowedBlocks, renderAppender, - __unstableDisableLayoutClassNames: ! layoutSupportEnabled, } ); diff --git a/packages/block-library/src/image/block.json b/packages/block-library/src/image/block.json index d665a8a8f77085..b46829e5059a25 100644 --- a/packages/block-library/src/image/block.json +++ b/packages/block-library/src/image/block.json @@ -9,9 +9,6 @@ "keywords": [ "img", "photo", "picture" ], "textdomain": "default", "attributes": { - "align": { - "type": "string" - }, "url": { "type": "string", "source": "attribute", @@ -95,6 +92,7 @@ } }, "supports": { + "align": [ "left", "center", "right", "wide", "full" ], "anchor": true, "color": { "text": false, diff --git a/packages/block-library/src/image/deprecated.js b/packages/block-library/src/image/deprecated.js index 4205da8e117b91..0365ddcfff5d17 100644 --- a/packages/block-library/src/image/deprecated.js +++ b/packages/block-library/src/image/deprecated.js @@ -1047,6 +1047,14 @@ const v8 = { }, }, migrate( { width, height, ...attributes } ) { + // We need to perform a check here because in cases + // where attributes are added dynamically to blocks, + // block invalidation overrides the isEligible() method + // and forces the migration to run, so it's not guaranteed + // that `behaviors` or `behaviors.lightbox` will be defined. + if ( ! attributes.behaviors?.lightbox ) { + return attributes; + } const { behaviors: { lightbox: { enabled }, diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index c58a96e5949112..d189af32efcbec 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -10,8 +10,6 @@ import { getBlobByURL, isBlobURL, revokeBlobURL } from '@wordpress/blob'; import { Placeholder } from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; import { - BlockAlignmentControl, - BlockControls, BlockIcon, MediaPlaceholder, useBlockProps, @@ -106,13 +104,13 @@ export function ImageEdit( { url = '', alt, caption, - align, id, width, height, sizeSlug, aspectRatio, scale, + align, } = attributes; const [ temporaryURL, setTemporaryURL ] = useState(); @@ -126,6 +124,21 @@ export function ImageEdit( { captionRef.current = caption; }, [ caption ] ); + const { __unstableMarkNextChangeAsNotPersistent } = + useDispatch( blockEditorStore ); + + useEffect( () => { + if ( [ 'wide', 'full' ].includes( align ) ) { + __unstableMarkNextChangeAsNotPersistent(); + setAttributes( { + width: undefined, + height: undefined, + aspectRatio: undefined, + scale: undefined, + } ); + } + }, [ align ] ); + const ref = useRef(); const { imageDefaultSize, mediaUpload } = useSelect( ( select ) => { const { getSettings } = select( blockEditorStore ); @@ -255,16 +268,6 @@ export function ImageEdit( { } } - function updateAlignment( nextAlign ) { - const extraUpdatedAttributes = [ 'wide', 'full' ].includes( nextAlign ) - ? { width: undefined, height: undefined } - : {}; - setAttributes( { - ...extraUpdatedAttributes, - align: nextAlign, - } ); - } - let isTemp = isTemporaryImage( id, url ); // Upload a temporary image on mount. @@ -375,14 +378,6 @@ export function ImageEdit( { clientId={ clientId } blockEditingMode={ blockEditingMode } /> - { ! url && blockEditingMode === 'default' && ( - - - - ) } } onSelect={ onSelectImage } diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 804ae9e1671f6e..44ebfda67d8750 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -44,7 +44,6 @@ import { MEDIA_TYPE_IMAGE, BlockControls, InspectorControls, - BlockAlignmentToolbar, BlockStyles, store as blockEditorStore, blockSettingsScreens, @@ -212,7 +211,6 @@ export class ImageEdit extends Component { this.onSetFeatured = this.onSetFeatured.bind( this ); this.onFocusCaption = this.onFocusCaption.bind( this ); this.onSelectURL = this.onSelectURL.bind( this ); - this.updateAlignment = this.updateAlignment.bind( this ); this.accessibilityLabelCreator = this.accessibilityLabelCreator.bind( this ); this.setMappedAttributes = this.setMappedAttributes.bind( this ); @@ -305,6 +303,20 @@ export class ImageEdit extends Component { this.replacedFeaturedImage = false; setFeaturedImage( id ); } + + const { align } = attributes; + const { __unstableMarkNextChangeAsNotPersistent } = this.props; + + // Update the attributes if the align is wide or full + if ( [ 'wide', 'full' ].includes( align ) ) { + __unstableMarkNextChangeAsNotPersistent(); + setAttributes( { + width: undefined, + height: undefined, + aspectRatio: undefined, + scale: undefined, + } ); + } } static getDerivedStateFromProps( props, state ) { @@ -391,18 +403,6 @@ export class ImageEdit extends Component { } ); } - updateAlignment( nextAlign ) { - const extraUpdatedAttributes = Object.values( - WIDE_ALIGNMENTS.alignments - ).includes( nextAlign ) - ? { width: undefined, height: undefined } - : {}; - this.props.setAttributes( { - ...extraUpdatedAttributes, - align: nextAlign, - } ); - } - onSetNewTab( value ) { const updatedLinkTarget = getUpdatedLinkTargetSettings( value, @@ -711,10 +711,6 @@ export class ImageEdit extends Component { onClick={ open } /> - ); @@ -941,8 +937,11 @@ export default compose( [ } ), withDispatch( ( dispatch ) => { const { createErrorNotice } = dispatch( noticesStore ); + const { __unstableMarkNextChangeAsNotPersistent } = + dispatch( blockEditorStore ); return { + __unstableMarkNextChangeAsNotPersistent, createErrorNotice, closeSettingsBottomSheet() { dispatch( editPostStore ).closeGeneralSidebar(); diff --git a/packages/block-library/src/image/editor.scss b/packages/block-library/src/image/editor.scss index e1721928362149..934682ed91b7de 100644 --- a/packages/block-library/src/image/editor.scss +++ b/packages/block-library/src/image/editor.scss @@ -62,13 +62,6 @@ figure.wp-block-image:not(.wp-block) { left: 50%; transform: translate(-50%, -50%); } - - // When the Image block is linked, - // it's wrapped with a disabled tag. - // Restore cursor style so it doesn't appear 'clickable'. - > a { - cursor: default; - } } // This is necessary for the editor resize handles to accurately work on a non-floated, non-resized, small image. diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index ae5f749fff3b5f..11d460efd472cb 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -25,7 +25,6 @@ import { MediaReplaceFlow, store as blockEditorStore, useSettings, - BlockAlignmentControl, __experimentalImageEditor as ImageEditor, __experimentalGetElementClassName, __experimentalUseBorderProps as useBorderProps, @@ -83,9 +82,29 @@ const scaleOptions = [ }, ]; -const disabledClickProps = { - onClick: ( event ) => event.preventDefault(), - 'aria-disabled': true, +// If the image has a href, wrap in an tag to trigger any inherited link element styles. +const ImageWrapper = ( { href, children } ) => { + if ( ! href ) { + return children; + } + return ( + event.preventDefault() } + aria-disabled={ true } + style={ { + // When the Image block is linked, + // it's wrapped with a disabled tag. + // Restore cursor style so it doesn't appear 'clickable' + // and remove pointer events. Safari needs the display property. + pointerEvents: 'none', + cursor: 'default', + display: 'inline', + } } + > + { children } + + ); }; export default function Image( { @@ -333,21 +352,6 @@ export default function Image( { } ); } - function updateAlignment( nextAlign ) { - const extraUpdatedAttributes = [ 'wide', 'full' ].includes( nextAlign ) - ? { - width: undefined, - height: undefined, - aspectRatio: undefined, - scale: undefined, - } - : {}; - setAttributes( { - ...extraUpdatedAttributes, - align: nextAlign, - } ); - } - useEffect( () => { if ( ! isSelected ) { setIsEditingImage( false ); @@ -435,12 +439,6 @@ export default function Image( { const controls = ( <> - { hasNonContentControls && ( - - ) } { hasNonContentControls && ( { @@ -653,25 +651,31 @@ export default function Image( { if ( canEditImage && isEditingImage ) { img = ( - - setAttributes( imageAttributes ) - } - onFinishEditing={ () => { - setIsEditingImage( false ); - } } - borderProps={ isRounded ? undefined : borderProps } - /> + + + setAttributes( imageAttributes ) + } + onFinishEditing={ () => { + setIsEditingImage( false ); + } } + borderProps={ isRounded ? undefined : borderProps } + /> + ); } else if ( ! isResizable ) { - img =
{ img }
; + img = ( +
+ { img } +
+ ); } else { const numericRatio = aspectRatio && evalAspectRatio( aspectRatio ); const customRatio = numericWidth / numericHeight; @@ -774,7 +778,7 @@ export default function Image( { } } resizeRatio={ align === 'center' ? 2 : 1 } > - { img } + { img } ); } @@ -788,14 +792,7 @@ export default function Image( { { /* Hide controls during upload to avoid component remount, which causes duplicated image upload. */ } { ! temporaryURL && controls } - { /* If the image has a href, wrap in an tag to trigger any inherited link element styles */ } - { !! href ? ( - - { img } - - ) : ( - img - ) } + { img } { showCaption && ( ! RichText.isEmpty( caption ) || isSelected ) && ( { ); const currentThemeStylesheet = useSelect( - ( select ) => select( coreStore ).getCurrentTheme()?.stylesheet + ( select ) => select( coreStore ).getCurrentTheme()?.stylesheet, + [] ); - const { replaceBlocks, __unstableMarkNextChangeAsNotPersistent } = - useDispatch( blockEditorStore ); - const { setBlockEditingMode } = useDispatch( blockEditorStore ); + const { + replaceBlocks, + setBlockEditingMode, + __unstableMarkNextChangeAsNotPersistent, + } = useDispatch( blockEditorStore ); const { getBlockRootClientId, getBlockEditingMode } = useSelect( blockEditorStore ); diff --git a/packages/block-library/src/pattern/index.php b/packages/block-library/src/pattern/index.php index f05bb333bd186d..436452f6853001 100644 --- a/packages/block-library/src/pattern/index.php +++ b/packages/block-library/src/pattern/index.php @@ -48,7 +48,12 @@ function render_block_core_pattern( $attributes ) { $content = gutenberg_serialize_blocks( $blocks ); } - return do_blocks( $content ); + $content = do_blocks( $content ); + + global $wp_embed; + $content = $wp_embed->autoembed( $content ); + + return $content; } add_action( 'init', 'register_block_core_pattern' ); diff --git a/packages/block-library/src/post-author/edit.js b/packages/block-library/src/post-author/edit.js index 4ee353fdd9bdc0..05797fcc8250a1 100644 --- a/packages/block-library/src/post-author/edit.js +++ b/packages/block-library/src/post-author/edit.js @@ -196,7 +196,6 @@ function PostAuthorEdit( { { ( ! RichText.isEmpty( byline ) || isSelected ) && ( )' ), value: 'div' }, diff --git a/packages/block-library/src/template-part/edit/advanced-controls.js b/packages/block-library/src/template-part/edit/advanced-controls.js index b879b46638face..8ad4b4bdbeb1da 100644 --- a/packages/block-library/src/template-part/edit/advanced-controls.js +++ b/packages/block-library/src/template-part/edit/advanced-controls.js @@ -95,6 +95,7 @@ export function TemplatePartAdvancedControls( { ) } select( coreStore ).getCurrentTheme()?.stylesheet, + [] + ); + const { slug, theme = currentTheme, tagName, layout = {} } = attributes; const templatePartId = createTemplatePartId( theme, slug ); const hasAlreadyRendered = useHasRecursion( templatePartId ); const [ isTemplatePartSelectionOpen, setIsTemplatePartSelectionOpen ] = @@ -174,17 +177,7 @@ export default function TemplatePartEdit( { } aria-haspopup="dialog" > - { createInterpolateElement( - __( 'Replace ' ), - { - BlockTitle: ( - - ), - } - ) } + { __( 'Replace' ) } ); } } diff --git a/packages/block-library/src/template-part/index.js b/packages/block-library/src/template-part/index.js index a68dd2301be22c..c9b5e33a1c9598 100644 --- a/packages/block-library/src/template-part/index.js +++ b/packages/block-library/src/template-part/index.js @@ -32,10 +32,11 @@ export const settings = { return; } - const entity = select( coreDataStore ).getEntityRecord( + const { getCurrentTheme, getEntityRecord } = select( coreDataStore ); + const entity = getEntityRecord( 'postType', 'wp_template_part', - theme + '//' + slug + ( theme || getCurrentTheme()?.stylesheet ) + '//' + slug ); if ( ! entity ) { return; diff --git a/packages/block-library/src/template-part/variations.js b/packages/block-library/src/template-part/variations.js index 866cf15d56c125..79881ee5f89e4c 100644 --- a/packages/block-library/src/template-part/variations.js +++ b/packages/block-library/src/template-part/variations.js @@ -35,10 +35,12 @@ export function enhanceTemplatePartVariations( settings, name ) { // Find a matching variation from the created template part // by checking the entity's `area` property. if ( ! slug ) return false; - const entity = select( coreDataStore ).getEntityRecord( + const { getCurrentTheme, getEntityRecord } = + select( coreDataStore ); + const entity = getEntityRecord( 'postType', 'wp_template_part', - `${ theme }//${ slug }` + `${ theme || getCurrentTheme()?.stylesheet }//${ slug }` ); if ( entity?.slug ) { diff --git a/packages/block-serialization-default-parser/CHANGELOG.md b/packages/block-serialization-default-parser/CHANGELOG.md index 7d8b2b6b582ae7..38fbb2b327d63b 100644 --- a/packages/block-serialization-default-parser/CHANGELOG.md +++ b/packages/block-serialization-default-parser/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.46.0 (2023-11-16) + ## 4.45.0 (2023-11-02) ## 4.44.0 (2023-10-18) diff --git a/packages/block-serialization-default-parser/package.json b/packages/block-serialization-default-parser/package.json index 3072546e446d19..93fcef93acdd97 100644 --- a/packages/block-serialization-default-parser/package.json +++ b/packages/block-serialization-default-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-default-parser", - "version": "4.45.0", + "version": "4.46.0", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-serialization-spec-parser/CHANGELOG.md b/packages/block-serialization-spec-parser/CHANGELOG.md index f4a3dea0b491bf..5e44a000908e5d 100644 --- a/packages/block-serialization-spec-parser/CHANGELOG.md +++ b/packages/block-serialization-spec-parser/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.46.0 (2023-11-16) + ## 4.45.0 (2023-11-02) ## 4.44.0 (2023-10-18) diff --git a/packages/block-serialization-spec-parser/package.json b/packages/block-serialization-spec-parser/package.json index 1056d871489c33..d5818b926323bc 100644 --- a/packages/block-serialization-spec-parser/package.json +++ b/packages/block-serialization-spec-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-spec-parser", - "version": "4.45.0", + "version": "4.46.0", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md index a0f9d61e0b4c2e..3b04d680cb3aa2 100644 --- a/packages/blocks/CHANGELOG.md +++ b/packages/blocks/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 12.23.0 (2023-11-16) + ## 12.22.0 (2023-11-02) ## 12.21.0 (2023-10-18) diff --git a/packages/blocks/package.json b/packages/blocks/package.json index 813272be7ef6e3..abfed4b763e7e4 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blocks", - "version": "12.22.0", + "version": "12.23.0", "description": "Block API for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/browserslist-config/CHANGELOG.md b/packages/browserslist-config/CHANGELOG.md index 28f88c40569bf8..1b32704cfd5776 100644 --- a/packages/browserslist-config/CHANGELOG.md +++ b/packages/browserslist-config/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.29.0 (2023-11-16) + ## 5.28.0 (2023-11-02) ## 5.27.0 (2023-10-18) diff --git a/packages/browserslist-config/package.json b/packages/browserslist-config/package.json index c7813290dedf5d..315594a8bda1bc 100644 --- a/packages/browserslist-config/package.json +++ b/packages/browserslist-config/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/browserslist-config", - "version": "5.28.0", + "version": "5.29.0", "description": "WordPress Browserslist shared configuration.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/commands/CHANGELOG.md b/packages/commands/CHANGELOG.md index a858b5af26543b..6641cb29ca6724 100644 --- a/packages/commands/CHANGELOG.md +++ b/packages/commands/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 0.17.0 (2023-11-16) + ## 0.16.0 (2023-11-02) ## 0.15.0 (2023-10-18) diff --git a/packages/commands/package.json b/packages/commands/package.json index 0acd14f3de8ad7..3d18b9f47a7ed7 100644 --- a/packages/commands/package.json +++ b/packages/commands/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/commands", - "version": "0.16.0", + "version": "0.17.0", "description": "Handles the commands menu.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 0ca8fb69cde4fd..eb6e595e304ecf 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +### Experimental + +- `Tabs`: Memoize and expose the component context ([#56224](https://github.com/WordPress/gutenberg/pull/56224)). + +## 25.12.0 (2023-11-16) + +### Bug Fix + +- `Toolbar`: Remove CSS rule that prevented focus outline to be visible for toolbar buttons in the `:active` state. ([#56123](https://github.com/WordPress/gutenberg/pull/56123)). + ### Internal - Migrate `Divider` from `reakit` to `ariakit` ([#55622](https://github.com/WordPress/gutenberg/pull/55622)) @@ -20,6 +30,11 @@ - `ToggleGroupControl`: Add opt-in prop for 40px default size ([#55789](https://github.com/WordPress/gutenberg/pull/55789)). - `TextControl`: Add opt-in prop for 40px default size ([#55471](https://github.com/WordPress/gutenberg/pull/55471)). +### Bug Fix + +- `DropdownMenu`: remove extra vertical space around the toggle button ([#56136](https://github.com/WordPress/gutenberg/pull/56136)). +- Package should not depend on `@ariakit/test`, that package is only needed for testing ([#56091](https://github.com/WordPress/gutenberg/pull/56091)). + ## 25.11.0 (2023-11-02) ### Enhancements diff --git a/packages/components/package.json b/packages/components/package.json index f616b261c8ec4f..7e8c237b700244 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/components", - "version": "25.11.0", + "version": "25.12.0", "description": "UI components for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,7 +31,6 @@ "types": "build-types", "dependencies": { "@ariakit/react": "^0.3.5", - "@ariakit/test": "^0.3.0", "@babel/runtime": "^7.16.0", "@emotion/cache": "^11.7.1", "@emotion/css": "^11.7.1", diff --git a/packages/components/src/angle-picker-control/index.tsx b/packages/components/src/angle-picker-control/index.tsx index f90394b12078f4..06178e0b401015 100644 --- a/packages/components/src/angle-picker-control/index.tsx +++ b/packages/components/src/angle-picker-control/index.tsx @@ -41,7 +41,6 @@ function UnforwardedAnglePickerControl( 'Bottom margin styles for wp.components.AnglePickerControl', { since: '6.1', - version: '6.4', hint: 'Set the `__nextHasNoMarginBottom` prop to true to start opting into the new styles, which will become the default in a future version.', } ); diff --git a/packages/components/src/combobox-control/README.md b/packages/components/src/combobox-control/README.md index 30f1f47e653e87..cc15248678d275 100644 --- a/packages/components/src/combobox-control/README.md +++ b/packages/components/src/combobox-control/README.md @@ -47,9 +47,7 @@ function MyComboboxControl() { onFilterValueChange={ ( inputValue ) => setFilteredOptions( options.filter( ( option ) => - option.label - .toLowerCase() - .startsWith( inputValue.toLowerCase() ) + option.value === inputValue ) ) } diff --git a/packages/components/src/dropdown-menu/style.scss b/packages/components/src/dropdown-menu/style.scss index 7d9e1b997f7804..261662ab7256dc 100644 --- a/packages/components/src/dropdown-menu/style.scss +++ b/packages/components/src/dropdown-menu/style.scss @@ -1,3 +1,7 @@ +.components-dropdown-menu__toggle { + vertical-align: top; +} + .components-dropdown-menu__menu { width: 100%; font-family: $default-font; diff --git a/packages/components/src/index.native.js b/packages/components/src/index.native.js index 9a66a62f98e187..f88399fbee2878 100644 --- a/packages/components/src/index.native.js +++ b/packages/components/src/index.native.js @@ -141,4 +141,5 @@ export { getGlobalStyles, getColorsAndGradients, useMobileGlobalStylesColors, + useEditorColorScheme, } from './mobile/global-styles-context/utils'; diff --git a/packages/components/src/mobile/audio-player/index.native.js b/packages/components/src/mobile/audio-player/index.native.js index ad8f0dcb2e9e8d..c5d7c0e8132c1e 100644 --- a/packages/components/src/mobile/audio-player/index.native.js +++ b/packages/components/src/mobile/audio-player/index.native.js @@ -14,7 +14,7 @@ import { default as VideoPlayer } from 'react-native-video'; * WordPress dependencies */ import { View } from '@wordpress/primitives'; -import { Icon } from '@wordpress/components'; +import { Icon, useEditorColorScheme } from '@wordpress/components'; import { withPreferredColorScheme } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; import { audio, warning } from '@wordpress/icons'; @@ -34,7 +34,6 @@ import { parseAudioUrl } from './audio-url-parser.native'; const isIOS = Platform.OS === 'ios'; function Player( { - getStylesFromColorScheme, isUploadInProgress, isUploadFailed, attributes, @@ -70,14 +69,14 @@ function Player( { } }; - const containerStyle = getStylesFromColorScheme( + const containerStyle = useEditorColorScheme( styles.container, styles.containerDark ); - const iconStyle = getStylesFromColorScheme( styles.icon, styles.iconDark ); + const iconStyle = useEditorColorScheme( styles.icon, styles.iconDark ); - const iconDisabledStyle = getStylesFromColorScheme( + const iconDisabledStyle = useEditorColorScheme( styles.iconDisabled, styles.iconDisabledDark ); @@ -89,7 +88,7 @@ function Player( { ...( isDisabled && iconDisabledStyle ), }; - const iconContainerStyle = getStylesFromColorScheme( + const iconContainerStyle = useEditorColorScheme( styles.iconContainer, styles.iconContainerDark ); @@ -99,17 +98,14 @@ function Player( { ...( isIOS ? styles.titleContainerIOS : styles.titleContainerAndroid ), }; - const titleStyle = getStylesFromColorScheme( - styles.title, - styles.titleDark - ); + const titleStyle = useEditorColorScheme( styles.title, styles.titleDark ); - const uploadFailedStyle = getStylesFromColorScheme( + const uploadFailedStyle = useEditorColorScheme( styles.uploadFailed, styles.uploadFailedDark ); - const subtitleStyle = getStylesFromColorScheme( + const subtitleStyle = useEditorColorScheme( styles.subtitle, styles.subtitleDark ); @@ -119,7 +115,7 @@ function Player( { ...( isUploadFailed && uploadFailedStyle ), }; - const buttonBackgroundStyle = getStylesFromColorScheme( + const buttonBackgroundStyle = useEditorColorScheme( styles.buttonBackground, styles.buttonBackgroundDark ); diff --git a/packages/components/src/mobile/global-styles-context/utils.native.js b/packages/components/src/mobile/global-styles-context/utils.native.js index f7ce110060eb4a..f2cbcae9c3f3e7 100644 --- a/packages/components/src/mobile/global-styles-context/utils.native.js +++ b/packages/components/src/mobile/global-styles-context/utils.native.js @@ -3,6 +3,7 @@ */ import { camelCase } from 'change-case'; import { Dimensions } from 'react-native'; +import { colord } from 'colord'; /** * WordPress dependencies @@ -13,6 +14,12 @@ import { useMultipleOriginColorsAndGradients, SETTINGS_DEFAULTS, } from '@wordpress/block-editor'; +import { usePreferredColorSchemeStyle } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import { useGlobalStyles } from './index.native'; export const BLOCK_STYLE_ATTRIBUTES = [ 'textColor', @@ -441,3 +448,40 @@ export function getGlobalStyles( rawStyles, rawFeatures ) { __experimentalGlobalStylesBaseStyles: globalStyles, }; } + +/** + * Determine and apply appropriate color scheme based on global styles or device's light/dark mode. + * + * The function first attempts to retrieve the editor's background color from global styles. + * If the detected background color is light, light styles are applied, and dark styles otherwise. + * If no custom background color is defined, styles are applied using the device's dark/light setting. + * + * @param {Object} baseStyle - An object representing the base (light theme) styles for the editor. + * @param {Object} darkStyle - An object representing the additional styles to apply when the editor is in dark mode. + * + * @return {Object} - The combined style object that should be applied to the editor. + */ +export const useEditorColorScheme = ( baseStyle, darkStyle ) => { + const globalStyles = useGlobalStyles(); + + const deviceColorScheme = usePreferredColorSchemeStyle( + baseStyle, + darkStyle + ); + + const editorColors = globalStyles?.baseColors?.color; + const editorBackgroundColor = editorColors?.background; + + const isBackgroundColorDefined = + typeof editorBackgroundColor !== 'undefined' && + editorBackgroundColor !== 'undefined'; + + if ( isBackgroundColorDefined ) { + const isEditorBackgroundDark = colord( editorBackgroundColor ).isDark(); + return isEditorBackgroundDark + ? { ...baseStyle, ...darkStyle } + : baseStyle; + } + + return deviceColorScheme; +}; diff --git a/packages/components/src/mobile/utils/alignments.native.js b/packages/components/src/mobile/utils/alignments.native.js index 98635ea4681ada..bc42385988a5d1 100644 --- a/packages/components/src/mobile/utils/alignments.native.js +++ b/packages/components/src/mobile/utils/alignments.native.js @@ -10,6 +10,7 @@ export const WIDE_ALIGNMENTS = { }, excludeBlocks: [ 'core/heading' ], notInnerContainers: [ + 'core/image', 'core/separator', 'core/media-text', 'core/pullquote', diff --git a/packages/components/src/tabs/index.tsx b/packages/components/src/tabs/index.tsx index 59eb83f5240590..12dd0b4fcc83f4 100644 --- a/packages/components/src/tabs/index.tsx +++ b/packages/components/src/tabs/index.tsx @@ -8,7 +8,7 @@ import * as Ariakit from '@ariakit/react'; * WordPress dependencies */ import { useInstanceId } from '@wordpress/compose'; -import { useLayoutEffect, useRef } from '@wordpress/element'; +import { useLayoutEffect, useMemo, useRef } from '@wordpress/element'; /** * Internal dependencies @@ -154,8 +154,16 @@ function Tabs( { setSelectedId, ] ); + const contextValue = useMemo( + () => ( { + store, + instanceId, + } ), + [ store, instanceId ] + ); + return ( - + { children } ); @@ -164,4 +172,6 @@ function Tabs( { Tabs.TabList = TabList; Tabs.Tab = Tab; Tabs.TabPanel = TabPanel; +Tabs.Context = TabsContext; + export default Tabs; diff --git a/packages/components/src/tabs/stories/index.story.tsx b/packages/components/src/tabs/stories/index.story.tsx index 08e29589881707..ce8c8324edaee4 100644 --- a/packages/components/src/tabs/stories/index.story.tsx +++ b/packages/components/src/tabs/stories/index.story.tsx @@ -20,6 +20,14 @@ import Button from '../../button'; const meta: Meta< typeof Tabs > = { title: 'Components (Experimental)/Tabs', component: Tabs, + subcomponents: { + // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 + 'Tabs.TabList': Tabs.TabList, + // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 + 'Tabs.Tab': Tabs.Tab, + // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 + 'Tabs.TabPanel': Tabs.TabPanel, + }, parameters: { actions: { argTypesRegex: '^on.*' }, controls: { expanded: true }, diff --git a/packages/components/src/tabs/tab.tsx b/packages/components/src/tabs/tab.tsx index 03e5d80871c56a..4bfc99e8ef43b1 100644 --- a/packages/components/src/tabs/tab.tsx +++ b/packages/components/src/tabs/tab.tsx @@ -2,14 +2,14 @@ * WordPress dependencies */ -import { useContext, forwardRef } from '@wordpress/element'; +import { forwardRef } from '@wordpress/element'; /** * Internal dependencies */ import type { TabProps } from './types'; import warning from '@wordpress/warning'; -import { TabsContext } from './context'; +import { useTabsContext } from './context'; import { Tab as StyledTab } from './styles'; import type { WordPressComponentProps } from '../context'; @@ -17,9 +17,9 @@ export const Tab = forwardRef< HTMLButtonElement, WordPressComponentProps< TabProps, 'button', false > >( function Tab( { children, id, disabled, render, ...otherProps }, ref ) { - const context = useContext( TabsContext ); + const context = useTabsContext(); if ( ! context ) { - warning( '`Tabs.TabList` must be wrapped in a `Tabs` component.' ); + warning( '`Tabs.Tab` must be wrapped in a `Tabs` component.' ); return null; } const { store, instanceId } = context; diff --git a/packages/components/src/tabs/tabpanel.tsx b/packages/components/src/tabs/tabpanel.tsx index f477d1d3b4b437..8e8d72280a4935 100644 --- a/packages/components/src/tabs/tabpanel.tsx +++ b/packages/components/src/tabs/tabpanel.tsx @@ -6,7 +6,7 @@ * WordPress dependencies */ -import { forwardRef, useContext } from '@wordpress/element'; +import { forwardRef } from '@wordpress/element'; /** * Internal dependencies @@ -15,14 +15,14 @@ import type { TabPanelProps } from './types'; import { TabPanel as StyledTabPanel } from './styles'; import warning from '@wordpress/warning'; -import { TabsContext } from './context'; +import { useTabsContext } from './context'; import type { WordPressComponentProps } from '../context'; export const TabPanel = forwardRef< HTMLDivElement, WordPressComponentProps< TabPanelProps, 'div', false > >( function TabPanel( { children, id, focusable = true, ...otherProps }, ref ) { - const context = useContext( TabsContext ); + const context = useTabsContext(); if ( ! context ) { warning( '`Tabs.TabPanel` must be wrapped in a `Tabs` component.' ); return null; diff --git a/packages/components/src/tabs/test/index.tsx b/packages/components/src/tabs/test/index.tsx index d2a035e436c194..fac8127c4cc0d8 100644 --- a/packages/components/src/tabs/test/index.tsx +++ b/packages/components/src/tabs/test/index.tsx @@ -524,6 +524,15 @@ describe( 'Tabs', () => { await screen.findByRole( 'tab', { name: 'Alpha' } ) ).toHaveFocus(); + // This assertion ensures the component has had time to fully + // render, preventing flakiness. + // see https://github.com/WordPress/gutenberg/pull/55950 + await waitFor( () => + expect( + screen.getByRole( 'tab', { name: 'Beta' } ) + ).toHaveAttribute( 'tabindex', '-1' ) + ); + // Because all other tabs should have `tabindex=-1`, pressing Tab // should NOT move the focus to the next tab, which is Beta. // Instead, focus should go to the currently selected tabpanel (alpha). @@ -847,6 +856,16 @@ describe( 'Tabs', () => { // onSelect should not be called since the disabled tab is // highlighted, but not selected. await user.keyboard( '[Tab]' ); + + // This assertion ensures focus has time to move to the first + // tab before the test proceeds, preventing flakiness. + // see https://github.com/WordPress/gutenberg/pull/55950 + await waitFor( () => + expect( + screen.getByRole( 'tab', { name: 'Alpha' } ) + ).toHaveFocus() + ); + await user.keyboard( '[ArrowLeft]' ); expect( mockOnSelect ).toHaveBeenCalledTimes( 1 ); diff --git a/packages/components/src/toggle-control/README.md b/packages/components/src/toggle-control/README.md index 5e2732fb07416f..3a77ef6942d2e5 100644 --- a/packages/components/src/toggle-control/README.md +++ b/packages/components/src/toggle-control/README.md @@ -22,8 +22,8 @@ const MyToggleControl = () => { : 'No fixed background.' } checked={ hasFixedBackground } - onChange={ () => { - setHasFixedBackground( ( state ) => ! state ); + onChange={ (newValue) => { + setHasFixedBackground( newValue ); } } /> ); diff --git a/packages/components/src/toolbar/toolbar-button/style.scss b/packages/components/src/toolbar/toolbar-button/style.scss index 1f295c94a2c672..b90b1bec49dfdb 100644 --- a/packages/components/src/toolbar/toolbar-button/style.scss +++ b/packages/components/src/toolbar/toolbar-button/style.scss @@ -17,11 +17,6 @@ bottom: 10px; } - // Hide focus rectangle on click. - &:active::before { - display: none; - } - &:not(:disabled).is-pressed[data-subscript]::after { color: $white; } diff --git a/packages/compose/CHANGELOG.md b/packages/compose/CHANGELOG.md index 519a4b7b98d9c1..550b2ab057bd60 100644 --- a/packages/compose/CHANGELOG.md +++ b/packages/compose/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 6.23.0 (2023-11-16) + ## 6.22.0 (2023-11-02) ## 6.21.0 (2023-10-18) diff --git a/packages/compose/package.json b/packages/compose/package.json index 17c5ae01ccfb5a..24576ec7f2b501 100644 --- a/packages/compose/package.json +++ b/packages/compose/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/compose", - "version": "6.22.0", + "version": "6.23.0", "description": "WordPress higher-order components (HOCs).", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/core-commands/CHANGELOG.md b/packages/core-commands/CHANGELOG.md index 6da22ab229627d..420ab39a97fd14 100644 --- a/packages/core-commands/CHANGELOG.md +++ b/packages/core-commands/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 0.15.0 (2023-11-16) + ## 0.14.0 (2023-11-02) ## 0.13.0 (2023-10-18) diff --git a/packages/core-commands/package.json b/packages/core-commands/package.json index 1ca3d5d25d4d6a..c46789e5fc6987 100644 --- a/packages/core-commands/package.json +++ b/packages/core-commands/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/core-commands", - "version": "0.14.0", + "version": "0.15.0", "description": "WordPress core reusable commands.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/core-data/CHANGELOG.md b/packages/core-data/CHANGELOG.md index d6ee7486934332..b54e8e01e48fb1 100644 --- a/packages/core-data/CHANGELOG.md +++ b/packages/core-data/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 6.23.0 (2023-11-16) + ## 6.22.0 (2023-11-02) ## 6.21.0 (2023-10-18) @@ -9,6 +11,7 @@ ## Enhancements - Add `getEntityRecordsTotalItems` and `getEntityRecordsTotalPages` selectors. [#55164](https://github.com/WordPress/gutenberg/pull/55164). +- Revisions: add new selectors, `getRevisions` and `getRevision`, to fetch entity revisions. [#54046](https://github.com/WordPress/gutenberg/pull/54046). ## 6.20.0 (2023-10-05) diff --git a/packages/core-data/README.md b/packages/core-data/README.md index ef5d9c1197f099..ebc467f7fede2d 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -123,7 +123,7 @@ The package provides general methods to interact with the entities (`getEntityRe ```js // Get the record collection for the user entity. -wp.data.select( 'core' ).getEntityRecords( 'root' 'user' ); +wp.data.select( 'core' ).getEntityRecords( 'root', 'user' ); // Get a single record for the user entity. wp.data.select( 'core' ).getEntityRecord( 'root', 'user', recordId ); @@ -138,7 +138,7 @@ In addition to the general utilities (`getEntityRecords`, `getEntityRecord`, etc ```js // Collection -wp.data.select( 'core' ).getEntityRecords( 'root' 'user' ); +wp.data.select( 'core' ).getEntityRecords( 'root', 'user' ); wp.data.select( 'core' ).getUsers(); // Single record @@ -248,6 +248,24 @@ _Returns_ - `Object`: Action object. +### receiveRevisions + +Returns an action object used in signalling that revisions have been received. + +_Parameters_ + +- _kind_ `string`: Kind of the received entity record revisions. +- _name_ `string`: Name of the received entity record revisions. +- _recordKey_ `number|string`: The key of the entity record whose revisions you want to fetch. +- _records_ `Array|Object`: Revisions received. +- _query_ `?Object`: Query Object. +- _invalidateCache_ `?boolean`: Should invalidate query caches. +- _meta_ `?Object`: Meta information about pagination. + +_Returns_ + +- `Object`: Action object. + ### receiveThemeSupports > **Deprecated** since WP 5.9, this is not useful anymore, use the selector direclty. @@ -435,6 +453,8 @@ _Returns_ ### getCurrentThemeGlobalStylesRevisions +> **Deprecated** since WordPress 6.5.0. Callers should use `select( 'core' ).getRevisions( 'root', 'globalStyles', ${ recordKey } )` instead, where `recordKey` is the id of the global styles parent post. + Returns the revisions of the current global styles theme. _Parameters_ @@ -727,6 +747,39 @@ _Returns_ - A value whose reference will change only when an edit occurs. +### getRevision + +Returns a single, specific revision of a parent entity. + +_Parameters_ + +- _state_ `State`: State tree +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordKey_ `EntityRecordKey`: The key of the entity record whose revisions you want to fetch. +- _revisionKey_ `EntityRecordKey`: The revision's key. +- _query_ `GetRecordsHttpQuery`: Optional query. If requesting specific fields, fields must always include the ID. For valid query parameters see revisions schema in [the REST API Handbook](https://developer.wordpress.org/rest-api/reference/). Then see the arguments available "Retrieve a [entity kind]". + +_Returns_ + +- `RevisionRecord | Record< PropertyKey, never > | undefined`: Record. + +### getRevisions + +Returns an entity's revisions. + +_Parameters_ + +- _state_ `State`: State tree +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordKey_ `EntityRecordKey`: The key of the entity record whose revisions you want to fetch. +- _query_ `GetRecordsHttpQuery`: Optional query. If requesting specific fields, fields must always include the ID. For valid query parameters see revisions schema in [the REST API Handbook](https://developer.wordpress.org/rest-api/reference/). Then see the arguments available "Retrieve a [Entity kind]". + +_Returns_ + +- `RevisionRecord[] | null`: Record. + ### getThemeSupports Return theme supports data in the index. diff --git a/packages/core-data/package.json b/packages/core-data/package.json index 6ab22b7ca9a69d..0d8fa19ec6be91 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/core-data", - "version": "6.22.0", + "version": "6.23.0", "description": "Access to and manipulation of core WordPress entities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index 9e7277f35a62a7..4c5622ac780589 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -218,6 +218,8 @@ export function receiveThemeSupports() { * Returns an action object used in signalling that the theme global styles CPT post revisions have been received. * Ignored from documentation as it's internal to the data store. * + * @deprecated since WordPress 6.5.0. Callers should use `dispatch( 'core' ).receiveRevision` instead. + * * @ignore * * @param {number} currentId The post id. @@ -226,6 +228,13 @@ export function receiveThemeSupports() { * @return {Object} Action object. */ export function receiveThemeGlobalStyleRevisions( currentId, revisions ) { + deprecated( + "wp.data.dispatch( 'core' ).receiveThemeGlobalStyleRevisions()", + { + since: '6.5.0', + alternative: "wp.data.dispatch( 'core' ).receiveRevisions", + } + ); return { type: 'RECEIVE_THEME_GLOBAL_STYLE_REVISIONS', currentId, @@ -924,3 +933,36 @@ export function receiveDefaultTemplateId( query, templateId ) { templateId, }; } + +/** + * Returns an action object used in signalling that revisions have been received. + * + * @param {string} kind Kind of the received entity record revisions. + * @param {string} name Name of the received entity record revisions. + * @param {number|string} recordKey The key of the entity record whose revisions you want to fetch. + * @param {Array|Object} records Revisions received. + * @param {?Object} query Query Object. + * @param {?boolean} invalidateCache Should invalidate query caches. + * @param {?Object} meta Meta information about pagination. + * @return {Object} Action object. + */ +export function receiveRevisions( + kind, + name, + recordKey, + records, + query, + invalidateCache = false, + meta +) { + return { + type: 'RECEIVE_ITEM_REVISIONS', + items: Array.isArray( records ) ? records : [ records ], + recordKey, + meta, + query, + kind, + name, + invalidateCache, + }; +} diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index af8829d0bc852c..e85673492ef56e 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -19,6 +19,10 @@ export const DEFAULT_ENTITY_KEY = 'id'; const POST_RAW_ATTRIBUTES = [ 'title', 'excerpt', 'content' ]; +// A hardcoded list of post types that support revisions. +// @TODO: Ideally this should be fetched from the `/types` REST API's view context. +const POST_TYPES_WITH_REVISIONS_SUPPORT = [ 'post', 'page' ]; + export const rootEntitiesConfig = [ { label: __( 'Base' ), @@ -207,6 +211,12 @@ export const rootEntitiesConfig = [ baseURLParams: { context: 'edit' }, plural: 'globalStylesVariations', // Should be different than name. getTitle: ( record ) => record?.title?.rendered || record?.title, + getRevisionsUrl: ( parentId ) => + `/wp/v2/global-styles/${ parentId }/revisions`, + supports: { + revisions: true, + }, + supportsPagination: true, }, { label: __( 'Themes' ), @@ -295,6 +305,11 @@ async function loadPostTypeEntities() { selection: true, }, mergedEdits: { meta: true }, + supports: { + revisions: POST_TYPES_WITH_REVISIONS_SUPPORT.includes( + postType?.slug + ), + }, rawAttributes: POST_RAW_ATTRIBUTES, getTitle: ( record ) => record?.title?.rendered || @@ -328,6 +343,12 @@ async function loadPostTypeEntities() { syncObjectType: 'postType/' + postType.name, getSyncObjectId: ( id ) => id, supportsPagination: true, + getRevisionsUrl: ( parentId, revisionId ) => + `/${ namespace }/${ + postType.rest_base + }/${ parentId }/revisions${ + revisionId ? '/' + revisionId : '' + }`, }; } ); } diff --git a/packages/core-data/src/entity-provider.js b/packages/core-data/src/entity-provider.js index e2274629006ee2..4b82b62e318bc9 100644 --- a/packages/core-data/src/entity-provider.js +++ b/packages/core-data/src/entity-provider.js @@ -155,6 +155,9 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) { const id = _id ?? providerId; const { content, editedBlocks, meta } = useSelect( ( select ) => { + if ( ! id ) { + return {}; + } const { getEditedEntityRecord } = select( STORE_NAME ); const editedRecord = getEditedEntityRecord( kind, name, id ); return { @@ -169,6 +172,10 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) { useDispatch( STORE_NAME ); const blocks = useMemo( () => { + if ( ! id ) { + return undefined; + } + if ( editedBlocks ) { return editedBlocks; } @@ -176,7 +183,7 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) { return content && typeof content !== 'function' ? parse( content ) : EMPTY_ARRAY; - }, [ editedBlocks, content ] ); + }, [ id, editedBlocks, content ] ); const updateFootnotes = useCallback( ( _blocks ) => updateFootnotesFromMeta( _blocks, meta ), diff --git a/packages/core-data/src/entity-types/global-styles-revision.ts b/packages/core-data/src/entity-types/global-styles-revision.ts new file mode 100644 index 00000000000000..1a89c164e313b0 --- /dev/null +++ b/packages/core-data/src/entity-types/global-styles-revision.ts @@ -0,0 +1,47 @@ +/** + * Internal dependencies + */ +import type { Context, ContextualField, OmitNevers } from './helpers'; + +import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; + +declare module './base-entity-records' { + export namespace BaseEntityRecords { + export interface GlobalStylesRevision< C extends Context > { + /** + * The ID for the author of the global styles revision. + */ + author: number; + /** + * The date the post global styles revision published, in the site's timezone. + */ + date: string | null; + /** + * The date the global styles revision was published, as GMT. + */ + date_gmt: ContextualField< string | null, 'view' | 'edit', C >; + /** + * Unique identifier for the revision. + */ + id: number; + /** + * The date the global styles revision was last modified, in the site's timezone. + */ + modified: ContextualField< string, 'view' | 'edit', C >; + /** + * The date the global styles revision was last modified, as GMT. + */ + modified_gmt: ContextualField< string, 'view' | 'edit', C >; + /** + * Identifier for the parent of the revision. + */ + parent: number; + styles: Record< string, Object >; + settings: Record< string, Object >; + } + } +} + +export type GlobalStylesRevision< C extends Context = 'view' > = OmitNevers< + _BaseEntityRecords.GlobalStylesRevision< C > +>; diff --git a/packages/core-data/src/entity-types/index.ts b/packages/core-data/src/entity-types/index.ts index 19d10a28ad698e..0e601137cbcb6c 100644 --- a/packages/core-data/src/entity-types/index.ts +++ b/packages/core-data/src/entity-types/index.ts @@ -4,12 +4,14 @@ import type { Context, Updatable } from './helpers'; import type { Attachment } from './attachment'; import type { Comment } from './comment'; +import type { GlobalStylesRevision } from './global-styles-revision'; import type { MenuLocation } from './menu-location'; import type { NavMenu } from './nav-menu'; import type { NavMenuItem } from './nav-menu-item'; import type { Page } from './page'; import type { Plugin } from './plugin'; import type { Post } from './post'; +import type { PostRevision } from './post-revision'; import type { Settings } from './settings'; import type { Sidebar } from './sidebar'; import type { Taxonomy } from './taxonomy'; @@ -27,12 +29,14 @@ export type { Attachment, Comment, Context, + GlobalStylesRevision, MenuLocation, NavMenu, NavMenuItem, Page, Plugin, Post, + PostRevision, Settings, Sidebar, Taxonomy, @@ -82,12 +86,14 @@ export interface PerPackageEntityRecords< C extends Context > { core: | Attachment< C > | Comment< C > + | GlobalStylesRevision< C > | MenuLocation< C > | NavMenu< C > | NavMenuItem< C > | Page< C > | Plugin< C > | Post< C > + | PostRevision< C > | Settings< C > | Sidebar< C > | Taxonomy< C > diff --git a/packages/core-data/src/entity-types/post-revision.ts b/packages/core-data/src/entity-types/post-revision.ts new file mode 100644 index 00000000000000..354a3fc02af704 --- /dev/null +++ b/packages/core-data/src/entity-types/post-revision.ts @@ -0,0 +1,93 @@ +/** + * Internal dependencies + */ +import type { + Context, + ContextualField, + RenderedText, + OmitNevers, +} from './helpers'; + +import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; + +declare module './base-entity-records' { + export namespace BaseEntityRecords { + export interface PostRevision< C extends Context > { + /** + * The ID for the author of the post revision. + */ + author: number; + /** + * The content for the post. + */ + content: ContextualField< + RenderedText< C > & { + /** + * Whether the content is protected with a password. + */ + is_protected: boolean; + /** + * Version of the content block format used by the post. + */ + block_version: ContextualField< string, 'edit', C >; + }, + 'view' | 'edit', + C + >; + /** + * The date the post was published, in the site's timezone. + */ + date: string | null; + /** + * The date the post was published, as GMT. + */ + date_gmt: ContextualField< string | null, 'view' | 'edit', C >; + /** + * The excerpt for the post revision. + */ + excerpt: RenderedText< C > & { + protected: boolean; + }; + /** + * The globally unique identifier for the post. + */ + guid: ContextualField< RenderedText< C >, 'view' | 'edit', C >; + /** + * Unique identifier for the revision. + */ + id: number; + /** + * Meta fields. + */ + meta: ContextualField< + Record< string, string >, + 'view' | 'edit', + C + >; + /** + * The date the post was last modified, in the site's timezone. + */ + modified: ContextualField< string, 'view' | 'edit', C >; + /** + * The date the post revision was last modified, as GMT. + */ + modified_gmt: ContextualField< string, 'view' | 'edit', C >; + /** + * Identifier for the parent of the revision. + */ + parent: number; + /** + * An alphanumeric identifier for the post unique to its type. + */ + slug: string; + /** + * The title for the post revision. + */ + title: RenderedText< C >; + } + } +} + +export type PostRevision< C extends Context = 'view' > = OmitNevers< + _BaseEntityRecords.PostRevision< C > +>; diff --git a/packages/core-data/src/hooks/test/use-entity-record.js b/packages/core-data/src/hooks/test/use-entity-record.js index d78a64d5f2b628..1fe68c84a951b7 100644 --- a/packages/core-data/src/hooks/test/use-entity-record.js +++ b/packages/core-data/src/hooks/test/use-entity-record.js @@ -23,6 +23,7 @@ describe( 'useEntityRecord', () => { beforeEach( () => { registry = createRegistry(); registry.register( coreDataStore ); + triggerFetch.mockReset(); } ); const TEST_RECORD = { id: 1, hello: 'world' }; @@ -113,4 +114,41 @@ describe( 'useEntityRecord', () => { expect( widget.editedRecord ).toEqual( { hello: 'foo', id: 1 } ); expect( widget.edits ).toEqual( { hello: 'foo' } ); } ); + + it( 'does not resolve entity record when disabled via options', async () => { + // Provide response + triggerFetch.mockImplementation( () => TEST_RECORD ); + + let data; + const TestComponent = () => { + data = useEntityRecord( 'root', 'widget', 2, { + options: { enabled: false }, + } ); + return
; + }; + render( + + + + ); + + expect( data ).toEqual( { + edit: expect.any( Function ), + editedRecord: {}, + hasEdits: false, + edits: {}, + record: null, + save: expect.any( Function ), + } ); + + // Fetch request should have been issued. + await waitFor( () => { + expect( triggerFetch ).not.toHaveBeenCalled(); + } ); + await waitFor( () => + expect( triggerFetch ).not.toHaveBeenCalledWith( { + path: '/wp/v2/widgets/2?context=edit', + } ) + ); + } ); } ); diff --git a/packages/core-data/src/hooks/use-entity-record.ts b/packages/core-data/src/hooks/use-entity-record.ts index 6c755c7bad6a60..60228893e5102e 100644 --- a/packages/core-data/src/hooks/use-entity-record.ts +++ b/packages/core-data/src/hooks/use-entity-record.ts @@ -56,6 +56,8 @@ export interface Options { enabled: boolean; } +const EMPTY_OBJECT = {}; + /** * Resolves the specified entity record. * @@ -167,24 +169,34 @@ export default function useEntityRecord< RecordType >( ); const { editedRecord, hasEdits, edits } = useSelect( - ( select ) => ( { - editedRecord: select( coreStore ).getEditedEntityRecord( - kind, - name, - recordId - ), - hasEdits: select( coreStore ).hasEditsForEntityRecord( - kind, - name, - recordId - ), - edits: select( coreStore ).getEntityRecordNonTransientEdits( - kind, - name, - recordId - ), - } ), - [ kind, name, recordId ] + ( select ) => { + if ( ! options.enabled ) { + return { + editedRecord: EMPTY_OBJECT, + hasEdits: false, + edits: EMPTY_OBJECT, + }; + } + + return { + editedRecord: select( coreStore ).getEditedEntityRecord( + kind, + name, + recordId + ), + hasEdits: select( coreStore ).hasEditsForEntityRecord( + kind, + name, + recordId + ), + edits: select( coreStore ).getEntityRecordNonTransientEdits( + kind, + name, + recordId + ), + }; + }, + [ kind, name, recordId, options.enabled ] ); const { data: record, ...querySelectRest } = useQuerySelect( diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js index a21623d8ba89d3..34558fcfbb142e 100644 --- a/packages/core-data/src/reducer.js +++ b/packages/core-data/src/reducer.js @@ -245,7 +245,6 @@ function entity( entityConfig ) { ] )( combineReducers( { queriedData: queriedDataReducer, - edits: ( state = {}, action ) => { switch ( action.type ) { case 'RECEIVE_ITEMS': @@ -355,6 +354,52 @@ function entity( entityConfig ) { return state; }, + + // Add revisions to the state tree if the post type supports it. + ...( entityConfig?.supports?.revisions + ? { + revisions: ( state = {}, action ) => { + // Use the same queriedDataReducer shape for revisions. + if ( action.type === 'RECEIVE_ITEM_REVISIONS' ) { + const recordKey = action.recordKey; + delete action.recordKey; + const newState = queriedDataReducer( + state[ recordKey ], + { + ...action, + type: 'RECEIVE_ITEMS', + } + ); + return { + ...state, + [ recordKey ]: newState, + }; + } + + if ( action.type === 'REMOVE_ITEMS' ) { + return Object.fromEntries( + Object.entries( state ).filter( + ( [ id ] ) => + ! action.itemIds.some( + ( itemId ) => { + if ( + Number.isInteger( + itemId + ) + ) { + return itemId === +id; + } + return itemId === id; + } + ) + ) + ); + } + + return state; + }, + } + : {} ), } ) ); } diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index cd2a65a60b0139..8735764a880b8b 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -244,7 +244,7 @@ export const getEntityRecords = // If we request fields but the result doesn't contain the fields, // explicitly set these fields as "undefined" - // that way we consider the query "fullfilled". + // that way we consider the query "fulfilled". if ( query._fields ) { records = records.map( ( record ) => { query._fields.split( ',' ).forEach( ( field ) => { @@ -322,7 +322,7 @@ export const getCurrentTheme = export const getThemeSupports = forwardResolver( 'getCurrentTheme' ); /** - * Requests a preview from the from the Embed API. + * Requests a preview from the Embed API. * * @param {string} url URL to get the preview for. */ @@ -718,3 +718,146 @@ export const getDefaultTemplateId = dispatch.receiveDefaultTemplateId( query, template.id ); } }; + +/** + * Requests an entity's revisions from the REST API. + * + * @param {string} kind Entity kind. + * @param {string} name Entity name. + * @param {number|string} recordKey The key of the entity record whose revisions you want to fetch. + * @param {Object|undefined} query Optional object of query parameters to + * include with request. If requesting specific + * fields, fields must always include the ID. + */ +export const getRevisions = + ( kind, name, recordKey, query = {} ) => + async ( { dispatch } ) => { + const configs = await dispatch( getOrLoadEntitiesConfig( kind ) ); + const entityConfig = configs.find( + ( config ) => config.name === name && config.kind === kind + ); + + if ( + ! entityConfig || + entityConfig?.__experimentalNoFetch || + ! entityConfig?.supports?.revisions + ) { + return; + } + + if ( query._fields ) { + // If requesting specific fields, items and query association to said + // records are stored by ID reference. Thus, fields must always include + // the ID. + query = { + ...query, + _fields: [ + ...new Set( [ + ...( getNormalizedCommaSeparable( query._fields ) || + [] ), + DEFAULT_ENTITY_KEY, + ] ), + ].join(), + }; + } + + const path = addQueryArgs( + entityConfig.getRevisionsUrl( recordKey ), + query + ); + + let records, meta; + if ( entityConfig.supportsPagination && query.per_page !== -1 ) { + const response = await apiFetch( { path, parse: false } ); + records = Object.values( await response.json() ); + meta = { + totalItems: parseInt( response.headers.get( 'X-WP-Total' ) ), + }; + } else { + records = Object.values( await apiFetch( { path } ) ); + } + + // If we request fields but the result doesn't contain the fields, + // explicitly set these fields as "undefined" + // that way we consider the query "fulfilled". + if ( query._fields ) { + records = records.map( ( record ) => { + query._fields.split( ',' ).forEach( ( field ) => { + if ( ! record.hasOwnProperty( field ) ) { + record[ field ] = undefined; + } + } ); + + return record; + } ); + } + + dispatch.receiveRevisions( + kind, + name, + recordKey, + records, + query, + false, + meta + ); + }; + +// Invalidate cache when a new revision is created. +getRevisions.shouldInvalidate = ( action, kind, name, recordKey ) => + action.type === 'SAVE_ENTITY_RECORD_FINISH' && + name === action.name && + kind === action.kind && + ! action.error && + recordKey === action.recordId; + +/** + * Requests a specific Entity revision from the REST API. + * + * @param {string} kind Entity kind. + * @param {string} name Entity name. + * @param {number|string} recordKey The key of the entity record whose revisions you want to fetch. + * @param {number|string} revisionKey The revision's key. + * @param {Object|undefined} query Optional object of query parameters to + * include with request. If requesting specific + * fields, fields must always include the ID. + */ +export const getRevision = + ( kind, name, recordKey, revisionKey, query = {} ) => + async ( { dispatch } ) => { + const configs = await dispatch( getOrLoadEntitiesConfig( kind ) ); + const entityConfig = configs.find( + ( config ) => config.name === name && config.kind === kind + ); + + if ( + ! entityConfig || + entityConfig?.__experimentalNoFetch || + ! entityConfig?.supports?.revisions + ) { + return; + } + + if ( query !== undefined && query._fields ) { + // If requesting specific fields, items and query association to said + // records are stored by ID reference. Thus, fields must always include + // the ID. + query = { + ...query, + _fields: [ + ...new Set( [ + ...( getNormalizedCommaSeparable( query._fields ) || + [] ), + DEFAULT_ENTITY_KEY, + ] ), + ].join(), + }; + } + const path = addQueryArgs( + entityConfig.getRevisionsUrl( recordKey, revisionKey ), + query + ); + + const record = await apiFetch( { path } ); + dispatch.receiveRevisions( kind, name, recordKey, record, query ); + }; diff --git a/packages/core-data/src/selectors.ts b/packages/core-data/src/selectors.ts index 2a046941611c7d..5751a80b9106cd 100644 --- a/packages/core-data/src/selectors.ts +++ b/packages/core-data/src/selectors.ts @@ -62,6 +62,16 @@ interface QueriedData { queries: Record< ET.Context, Record< string, Array< number > > >; } +type RevisionRecord = + | Record< ET.Context, Record< number, ET.PostRevision > > + | Record< ET.Context, Record< number, ET.GlobalStylesRevision > >; + +interface RevisionsQueriedData { + items: RevisionRecord; + itemIsComplete: Record< ET.Context, Record< number, boolean > >; + queries: Record< ET.Context, Record< string, Array< number > > >; +} + interface EntityState< EntityRecord extends ET.EntityRecord > { edits: Record< string, Partial< EntityRecord > >; saving: Record< @@ -70,6 +80,7 @@ interface EntityState< EntityRecord extends ET.EntityRecord > { >; deleting: Record< string, Partial< { pending: boolean; error: Error } > >; queriedData: QueriedData; + revisions?: RevisionsQueriedData; } interface EntityConfig { @@ -1342,13 +1353,20 @@ export function getUserPatternCategories( /** * Returns the revisions of the current global styles theme. * - * @param state Data state. + * @deprecated since WordPress 6.5.0. Callers should use `select( 'core' ).getRevisions( 'root', 'globalStyles', ${ recordKey } )` instead, where `recordKey` is the id of the global styles parent post. + * + * @param state Data state. * * @return The current global styles. */ export function getCurrentThemeGlobalStylesRevisions( state: State ): Array< object > | null { + deprecated( "select( 'core' ).getCurrentThemeGlobalStylesRevisions()", { + since: '6.5.0', + alternative: + "select( 'core' ).getRevisions( 'root', 'globalStyles', ${ recordKey } )", + } ); const currentGlobalStylesId = __experimentalGetCurrentGlobalStylesId( state ); @@ -1373,3 +1391,103 @@ export function getDefaultTemplateId( ): string { return state.defaultTemplates[ JSON.stringify( query ) ]; } + +/** + * Returns an entity's revisions. + * + * @param state State tree + * @param kind Entity kind. + * @param name Entity name. + * @param recordKey The key of the entity record whose revisions you want to fetch. + * @param query Optional query. If requesting specific + * fields, fields must always include the ID. For valid query parameters see revisions schema in [the REST API Handbook](https://developer.wordpress.org/rest-api/reference/). Then see the arguments available "Retrieve a [Entity kind]". + * + * @return Record. + */ +export const getRevisions = ( + state: State, + kind: string, + name: string, + recordKey: EntityRecordKey, + query?: GetRecordsHttpQuery +): RevisionRecord[] | null => { + const queriedStateRevisions = + state.entities.records?.[ kind ]?.[ name ]?.revisions?.[ recordKey ]; + if ( ! queriedStateRevisions ) { + return null; + } + + return getQueriedItems( queriedStateRevisions, query ); +}; + +/** + * Returns a single, specific revision of a parent entity. + * + * @param state State tree + * @param kind Entity kind. + * @param name Entity name. + * @param recordKey The key of the entity record whose revisions you want to fetch. + * @param revisionKey The revision's key. + * @param query Optional query. If requesting specific + * fields, fields must always include the ID. For valid query parameters see revisions schema in [the REST API Handbook](https://developer.wordpress.org/rest-api/reference/). Then see the arguments available "Retrieve a [entity kind]". + * + * @return Record. + */ +export const getRevision = createSelector( + ( + state: State, + kind: string, + name: string, + recordKey: EntityRecordKey, + revisionKey: EntityRecordKey, + query?: GetRecordsHttpQuery + ): RevisionRecord | Record< PropertyKey, never > | undefined => { + const queriedState = + state.entities.records?.[ kind ]?.[ name ]?.revisions?.[ + recordKey + ]; + + if ( ! queriedState ) { + return undefined; + } + + const context = query?.context ?? 'default'; + + if ( query === undefined ) { + // If expecting a complete item, validate that completeness. + if ( ! queriedState.itemIsComplete[ context ]?.[ revisionKey ] ) { + return undefined; + } + + return queriedState.items[ context ][ revisionKey ]; + } + + const item = queriedState.items[ context ]?.[ revisionKey ]; + if ( item && query._fields ) { + const filteredItem = {}; + const fields = getNormalizedCommaSeparable( query._fields ) ?? []; + + for ( let f = 0; f < fields.length; f++ ) { + const field = fields[ f ].split( '.' ); + let value = item; + field.forEach( ( fieldName ) => { + value = value?.[ fieldName ]; + } ); + setNestedValue( filteredItem, field, value ); + } + + return filteredItem; + } + + return item; + }, + ( state: State, kind, name, recordKey, revisionKey, query ) => { + const context = query?.context ?? 'default'; + return [ + state.entities.records?.[ kind ]?.[ name ]?.revisions?.[ recordKey ] + ?.items?.[ context ]?.[ revisionKey ], + state.entities.records?.[ kind ]?.[ name ]?.revisions?.[ recordKey ] + ?.itemIsComplete?.[ context ]?.[ revisionKey ], + ]; + } +); diff --git a/packages/core-data/src/test/entities.js b/packages/core-data/src/test/entities.js index 9afbbb8de055e1..c9a432c92c1442 100644 --- a/packages/core-data/src/test/entities.js +++ b/packages/core-data/src/test/entities.js @@ -80,6 +80,9 @@ describe( 'getKindEntities', () => { labels: { singular_name: 'post', }, + supports: { + revisions: true, + }, }, ]; const dispatch = jest.fn(); @@ -95,6 +98,12 @@ describe( 'getKindEntities', () => { expect( dispatch.mock.calls[ 0 ][ 0 ].entities[ 0 ].baseURL ).toBe( '/wp/v2/posts' ); + expect( + dispatch.mock.calls[ 0 ][ 0 ].entities[ 0 ].getRevisionsUrl( 1 ) + ).toBe( '/wp/v2/posts/1/revisions' ); + expect( + dispatch.mock.calls[ 0 ][ 0 ].entities[ 0 ].getRevisionsUrl( 1, 2 ) + ).toBe( '/wp/v2/posts/1/revisions/2' ); } ); } ); diff --git a/packages/core-data/src/test/reducer.js b/packages/core-data/src/test/reducer.js index 4142f65af4c7c4..d5d5bc5c8692fa 100644 --- a/packages/core-data/src/test/reducer.js +++ b/packages/core-data/src/test/reducer.js @@ -139,6 +139,241 @@ describe( 'entities', () => { .map( ( [ , cfg ] ) => cfg ) ).toEqual( [ { kind: 'postType', name: 'posts' } ] ); } ); + + describe( 'entity revisions', () => { + const stateWithConfig = entities( undefined, { + type: 'ADD_ENTITIES', + entities: [ + { + kind: 'root', + name: 'postType', + supports: { revisions: true }, + }, + ], + } ); + it( 'appends revisions state', () => { + expect( stateWithConfig.records.root.postType ).toHaveProperty( + 'revisions', + {} + ); + } ); + + it( 'returns with received revisions', () => { + const initialState = deepFreeze( { + config: stateWithConfig.config, + records: {}, + } ); + const state = entities( initialState, { + type: 'RECEIVE_ITEM_REVISIONS', + items: [ { id: 1, parent: 2 } ], + kind: 'root', + name: 'postType', + recordKey: 2, + } ); + expect( state.records.root.postType.revisions ).toEqual( { + 2: { + items: { + default: { + 1: { id: 1, parent: 2 }, + }, + }, + itemIsComplete: { + default: { + 1: true, + }, + }, + queries: {}, + }, + } ); + } ); + + it( 'returns with appended received revisions at the parent level', () => { + const initialState = deepFreeze( { + config: stateWithConfig.config, + records: { + root: { + postType: { + revisions: { + 2: { + items: { + default: { + 1: { id: 1, parent: 2 }, + }, + }, + itemIsComplete: { + default: { + 1: true, + }, + }, + queries: {}, + }, + }, + }, + }, + }, + } ); + const state = entities( initialState, { + type: 'RECEIVE_ITEM_REVISIONS', + items: [ { id: 3, parent: 4 } ], + kind: 'root', + name: 'postType', + recordKey: 4, + } ); + expect( state.records.root.postType.revisions ).toEqual( { + 2: { + items: { + default: { + 1: { id: 1, parent: 2 }, + }, + }, + itemIsComplete: { + default: { + 1: true, + }, + }, + queries: {}, + }, + 4: { + items: { + default: { + 3: { id: 3, parent: 4 }, + }, + }, + itemIsComplete: { + default: { + 3: true, + }, + }, + queries: {}, + }, + } ); + } ); + + it( 'returns with appended received revision items', () => { + const initialState = deepFreeze( { + config: stateWithConfig.config, + records: { + root: { + postType: { + revisions: { + 2: { + items: { + default: { + 1: { id: 1, parent: 2 }, + }, + }, + itemIsComplete: { + default: { + 1: true, + }, + }, + queries: {}, + }, + }, + }, + }, + }, + } ); + const state = entities( initialState, { + type: 'RECEIVE_ITEM_REVISIONS', + items: [ { id: 7, parent: 2 } ], + kind: 'root', + name: 'postType', + recordKey: 2, + } ); + expect( state.records.root.postType.revisions ).toEqual( { + 2: { + items: { + default: { + 1: { id: 1, parent: 2 }, + 7: { id: 7, parent: 2 }, + }, + }, + itemIsComplete: { + default: { + 1: true, + 7: true, + }, + }, + queries: {}, + }, + } ); + } ); + + it( 'returns with removed revision items', () => { + const initialState = deepFreeze( { + config: stateWithConfig.config, + records: { + root: { + postType: { + revisions: { + 2: { + items: { + default: { + 1: { id: 1, parent: 2 }, + }, + }, + itemIsComplete: { + default: { + 1: true, + }, + }, + queries: {}, + }, + 4: { + items: { + default: { + 3: { id: 3, parent: 4 }, + }, + }, + itemIsComplete: { + default: { + 3: true, + }, + }, + queries: {}, + }, + 6: { + items: { + default: { + 9: { id: 11, parent: 6 }, + }, + }, + itemIsComplete: { + default: { + 9: true, + }, + }, + queries: {}, + }, + }, + }, + }, + }, + } ); + const state = entities( initialState, { + type: 'REMOVE_ITEMS', + itemIds: [ 4, 6 ], + kind: 'root', + name: 'postType', + } ); + expect( state.records.root.postType.revisions ).toEqual( { + 2: { + items: { + default: { + 1: { id: 1, parent: 2 }, + }, + }, + itemIsComplete: { + default: { + 1: true, + }, + }, + queries: {}, + }, + } ); + } ); + } ); } ); describe( 'embedPreviews()', () => { diff --git a/packages/core-data/src/test/selectors.js b/packages/core-data/src/test/selectors.js index b7c583098c4a5c..43c84a3e978917 100644 --- a/packages/core-data/src/test/selectors.js +++ b/packages/core-data/src/test/selectors.js @@ -22,6 +22,8 @@ import { getAutosave, getAutosaves, getCurrentUser, + getRevisions, + getRevision, } from '../selectors'; // getEntityRecord and __experimentalGetEntityRecordNoResolver selectors share the same tests. describe.each( [ @@ -896,3 +898,97 @@ describe( 'getCurrentUser', () => { expect( getCurrentUser( state ) ).toEqual( currentUser ); } ); } ); + +describe( 'getRevisions', () => { + it( 'should return revisions', () => { + const state = deepFreeze( { + entities: { + records: { + postType: { + post: { + revisions: { + 1: { + items: { + default: { + 10: { + id: 10, + content: 'chicken', + author: 'bob', + parent: 1, + }, + }, + }, + itemIsComplete: { + default: { + 10: true, + }, + }, + queries: { + default: { + '': { itemIds: [ 10 ] }, + }, + }, + }, + }, + }, + }, + }, + }, + } ); + + expect( getRevisions( state, 'postType', 'post', 1 ) ).toEqual( [ + { + id: 10, + content: 'chicken', + author: 'bob', + parent: 1, + }, + ] ); + } ); +} ); + +describe( 'getRevision', () => { + it( 'should return a specific revision', () => { + const state = deepFreeze( { + entities: { + records: { + postType: { + post: { + revisions: { + 1: { + items: { + default: { + 10: { + id: 10, + content: 'chicken', + author: 'bob', + parent: 1, + }, + }, + }, + itemIsComplete: { + default: { + 10: true, + }, + }, + queries: { + default: { + '': [ 10 ], + }, + }, + }, + }, + }, + }, + }, + }, + } ); + + expect( getRevision( state, 'postType', 'post', 1, 10 ) ).toEqual( { + id: 10, + content: 'chicken', + author: 'bob', + parent: 1, + } ); + } ); +} ); diff --git a/packages/create-block-interactive-template/CHANGELOG.md b/packages/create-block-interactive-template/CHANGELOG.md index 9a446ed2cfa7ec..46e2a04014e6c0 100644 --- a/packages/create-block-interactive-template/CHANGELOG.md +++ b/packages/create-block-interactive-template/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 1.9.0 (2023-11-16) + ## 1.8.0 (2023-11-02) ## 1.7.0 (2023-10-18) diff --git a/packages/create-block-interactive-template/package.json b/packages/create-block-interactive-template/package.json index 579ad04f6c2513..fa196823e5ad6c 100644 --- a/packages/create-block-interactive-template/package.json +++ b/packages/create-block-interactive-template/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/create-block-interactive-template", - "version": "1.8.0", + "version": "1.9.0", "description": "Template for @wordpress/create-block to create interactive blocks with the Interactivity API.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/create-block-tutorial-template/CHANGELOG.md b/packages/create-block-tutorial-template/CHANGELOG.md index d395b408d86c56..12d8de954281d0 100644 --- a/packages/create-block-tutorial-template/CHANGELOG.md +++ b/packages/create-block-tutorial-template/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +## 3.0.0 (2023-11-16) + +### Breaking Change + +- Update the block example scaffolded by the template. + ## 2.33.0 (2023-11-02) ## 2.32.0 (2023-10-18) diff --git a/packages/create-block-tutorial-template/README.md b/packages/create-block-tutorial-template/README.md index a4143312948e50..f883ff5fb79d47 100644 --- a/packages/create-block-tutorial-template/README.md +++ b/packages/create-block-tutorial-template/README.md @@ -1,6 +1,6 @@ # Create Block Tutorial Template -This is a template for [`@wordpress/create-block`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/create-block/README.md) that is the finished version of the block in the official [WordPress Tutorial](https://github.com/WordPress/gutenberg/tree/HEAD/docs/getting-started/create-block/README.md) for the block editor. +This is a template for [`@wordpress/create-block`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/create-block/README.md) that creates an example "Copyright Date" block. This block is used in the official WordPress block development [Quick Start Guide](https://developer.wordpress.org/block-editor/getting-started/quick-start-guide). ## Usage @@ -10,6 +10,8 @@ This block template can be used by running the following command: npx @wordpress/create-block --template @wordpress/create-block-tutorial-template ``` +Use the default options when prompted in the terminal. + ## Contributing to this package This is an individual package that's part of the Gutenberg project. The project is organized as a monorepo. It's made up of multiple self-contained software packages, each with a specific purpose. The packages in this monorepo are published to [npm](https://www.npmjs.com/) and used by [WordPress](https://make.wordpress.org/core/) as well as other software projects. diff --git a/packages/create-block-tutorial-template/assets/gilbert-color.otf b/packages/create-block-tutorial-template/assets/gilbert-color.otf deleted file mode 100644 index f21f9a173f2fe0..00000000000000 Binary files a/packages/create-block-tutorial-template/assets/gilbert-color.otf and /dev/null differ diff --git a/packages/create-block-tutorial-template/block-templates/edit.js.mustache b/packages/create-block-tutorial-template/block-templates/edit.js.mustache index fbb6864d869df6..69a93b70c35565 100644 --- a/packages/create-block-tutorial-template/block-templates/edit.js.mustache +++ b/packages/create-block-tutorial-template/block-templates/edit.js.mustache @@ -1,17 +1,40 @@ /** - * WordPress components that create the necessary UI elements for the block + * Retrieves the translation of text. * - * @see https://developer.wordpress.org/block-editor/packages/packages-components/ + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-i18n/ */ -import { TextControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; /** - * React hook that is used to mark the block wrapper element. - * It provides all the necessary props like the class name. + * Imports the InspectorControls component, which is used to wrap + * the block's custom controls that will appear in in the Settings + * Sidebar when the block is selected. + * + * Also imports the React hook that is used to mark the block wrapper + * element. It provides all the necessary props like the class name. * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#inspectorcontrols * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#useblockprops */ -import { useBlockProps } from '@wordpress/block-editor'; +import { InspectorControls, useBlockProps } from '@wordpress/block-editor'; + +/** + * Imports the necessary components that will be used to create + * the user interface for the block's settings. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/components/panel/#panelbody + * @see https://developer.wordpress.org/block-editor/reference-guides/components/text-control/ + * @see https://developer.wordpress.org/block-editor/reference-guides/components/toggle-control/ + */ +import { PanelBody, TextControl, ToggleControl } from '@wordpress/components'; + +/** + * Imports the useEffect React Hook. This is used to set an attribute when the + * block is loaded in the Editor. + * + * @see https://react.dev/reference/react/useEffect + */ +import { useEffect } from 'react'; /** * The edit function describes the structure of your block in the context of the @@ -26,13 +49,59 @@ import { useBlockProps } from '@wordpress/block-editor'; * @return {Element} Element to render. */ export default function Edit( { attributes, setAttributes } ) { - const blockProps = useBlockProps(); + const { fallbackCurrentYear, showStartingYear, startingYear } = attributes; + + // Get the current year and make sure it's a string. + const currentYear = new Date().getFullYear().toString(); + + // When the block loads, set the fallbackCurrentYear attribute to the + // current year if it's not already set. + useEffect( () => { + if ( currentYear !== fallbackCurrentYear ) { + setAttributes( { fallbackCurrentYear: currentYear } ); + } + }, [ currentYear, fallbackCurrentYear, setAttributes ] ); + + let displayDate; + + // Display the starting year as well if supplied by the user. + if ( showStartingYear && startingYear ) { + displayDate = startingYear + '–' + currentYear; + } else { + displayDate = currentYear; + } + return ( -
- setAttributes( { message: val } ) } - /> -
+ <> + + + + setAttributes( { + showStartingYear: ! showStartingYear, + } ) + } + /> + { showStartingYear && ( + + setAttributes( { startingYear: value } ) + } + /> + ) } + + +

© { displayDate }

+ ); } diff --git a/packages/create-block-tutorial-template/block-templates/editor.scss.mustache b/packages/create-block-tutorial-template/block-templates/editor.scss.mustache deleted file mode 100644 index 5d0b2deea6c10e..00000000000000 --- a/packages/create-block-tutorial-template/block-templates/editor.scss.mustache +++ /dev/null @@ -1,13 +0,0 @@ -/** - * The following styles get applied inside the editor only. - * - * Replace them with your own styles or remove the file completely. - */ - -.wp-block-{{namespace}}-{{slug}} input[type="text"] { - font-family: Gilbert, sans-serif; - font-size: 64px; - color: inherit; - background: inherit; - border: 0; -} diff --git a/packages/create-block-tutorial-template/block-templates/index.js.mustache b/packages/create-block-tutorial-template/block-templates/index.js.mustache index fa35fbf2729220..117d08b8669807 100644 --- a/packages/create-block-tutorial-template/block-templates/index.js.mustache +++ b/packages/create-block-tutorial-template/block-templates/index.js.mustache @@ -5,49 +5,41 @@ */ import { registerBlockType } from '@wordpress/blocks'; -/** - * Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files. - * All files containing `style` keyword are bundled together. The code used - * gets applied both to the front of your site and to the editor. All other files - * get applied to the editor only. - * - * @see https://www.npmjs.com/package/@wordpress/scripts#using-css - */ -import './style.scss'; -import './editor.scss'; - /** * Internal dependencies */ import Edit from './edit'; -{{#isStaticVariant}} import save from './save'; -{{/isStaticVariant}} import metadata from './block.json'; +/** + * Define a custom SVG icon for the block. This icon will appear in + * the Inserter and when the user selects the block in the Editor. + */ +const calendarIcon = ( + +); + /** * Every block starts by registering a new block type definition. * * @see https://developer.wordpress.org/block-editor/developers/block-api/#registering-a-block */ registerBlockType( metadata.name, { - /** - * Used to construct a preview for the block to be shown in the block inserter. - */ - example: { - attributes: { - message: '{{title}}', - }, - }, + icon: calendarIcon, /** * @see ./edit.js */ edit: Edit, - {{#isStaticVariant}} - /** * @see ./save.js */ save, - {{/isStaticVariant}} } ); diff --git a/packages/create-block-tutorial-template/block-templates/render.php.mustache b/packages/create-block-tutorial-template/block-templates/render.php.mustache index b971a023985e55..1d31cdbed2836b 100644 --- a/packages/create-block-tutorial-template/block-templates/render.php.mustache +++ b/packages/create-block-tutorial-template/block-templates/render.php.mustache @@ -1,10 +1,33 @@ -{{#isDynamicVariant}} -

> - -

-{{/isDynamicVariant}} + +// Get the current year. +$current_year = date( "Y" ); + +// Determine which content to display. +if ( isset( $attributes['fallbackCurrentYear'] ) && $attributes['fallbackCurrentYear'] === $current_year ) { + + // The current year is the same as the fallback, so use the block content saved in the database (by the save.js function). + $block_content = $content; +} else { + + // The current year is different from the fallback, so render the updated block content. + if ( ! empty( $attributes['startingYear'] ) && ! empty( $attributes['showStartingYear'] ) ) { + $display_date = $attributes['startingYear'] . '–' . $current_year; + } else { + $display_date = $current_year; + } + + $block_content = '

© ' . esc_html( $display_date ) . '

'; +} + +echo wp_kses_post( $block_content ); diff --git a/packages/create-block-tutorial-template/block-templates/save.js.mustache b/packages/create-block-tutorial-template/block-templates/save.js.mustache index a11ee432a4d442..944ae4c29254c4 100644 --- a/packages/create-block-tutorial-template/block-templates/save.js.mustache +++ b/packages/create-block-tutorial-template/block-templates/save.js.mustache @@ -1,4 +1,3 @@ -{{#isStaticVariant}} /** * React hook that is used to mark the block wrapper element. * It provides all the necessary props like the class name. @@ -16,10 +15,27 @@ import { useBlockProps } from '@wordpress/block-editor'; * * @param {Object} props Properties passed to the function. * @param {Object} props.attributes Available block attributes. + * * @return {Element} Element to render. */ export default function save( { attributes } ) { - const blockProps = useBlockProps.save(); - return
{ attributes.message }
; + const { fallbackCurrentYear, showStartingYear, startingYear } = attributes; + + // If there is no fallbackCurrentYear, which could happen if the block + // is loaded from a template/pattern, return null. In this case, block + // rendering will be handled by the render.php file. + if ( ! fallbackCurrentYear ) { + return null; + } + + let displayDate; + + // Display the starting year as well if supplied by the user. + if ( showStartingYear && startingYear ) { + displayDate = startingYear + '–' + fallbackCurrentYear; + } else { + displayDate = fallbackCurrentYear; + } + + return

© { displayDate }

; } -{{/isStaticVariant}} diff --git a/packages/create-block-tutorial-template/block-templates/style.scss.mustache b/packages/create-block-tutorial-template/block-templates/style.scss.mustache deleted file mode 100644 index b1f1241345cbb9..00000000000000 --- a/packages/create-block-tutorial-template/block-templates/style.scss.mustache +++ /dev/null @@ -1,17 +0,0 @@ -/** - * The following styles get applied both on the front of your site - * and in the editor. - * - * Replace them with your own styles or remove the file completely. - */ - -@font-face { - font-family: Gilbert; - src: url(../assets/gilbert-color.otf); - font-weight: 700; -} - -.wp-block-{{namespace}}-{{slug}} { - font-family: Gilbert, sans-serif; - font-size: 64px; -} diff --git a/packages/create-block-tutorial-template/index.js b/packages/create-block-tutorial-template/index.js index 514918e70ad8b9..12c073d841fe55 100644 --- a/packages/create-block-tutorial-template/index.js +++ b/packages/create-block-tutorial-template/index.js @@ -5,35 +5,35 @@ const { join } = require( 'path' ); module.exports = { defaultValues: { - slug: 'gutenpride', - category: 'text', - title: 'Gutenpride', - description: - 'A Gutenberg block to show your pride! This block enables you to type text and style it with the color font Gilbert from Type with Pride.', - dashicon: 'flag', + slug: 'copyright-date-block', + title: 'Copyright Date', + description: "Display your site's copyright date.", attributes: { - message: { + fallbackCurrentYear: { + type: 'string', + }, + showStartingYear: { + type: 'boolean', + }, + startingYear: { type: 'string', - source: 'text', - selector: 'div', }, }, supports: { + color: { + background: false, + text: true, + }, html: false, - }, - }, - variants: { - static: {}, - dynamic: { - attributes: { - message: { - type: 'string', - }, + typography: { + fontSize: true, }, - render: 'file:./render.php', }, + editorScript: 'file:./index.js', + render: 'file:./render.php', + example: {}, + wpEnv: true, }, pluginTemplatesPath: join( __dirname, 'plugin-templates' ), blockTemplatesPath: join( __dirname, 'block-templates' ), - assetsPath: join( __dirname, 'assets' ), }; diff --git a/packages/create-block-tutorial-template/package.json b/packages/create-block-tutorial-template/package.json index 5fd4d71fd4cd8c..c180069b8263e4 100644 --- a/packages/create-block-tutorial-template/package.json +++ b/packages/create-block-tutorial-template/package.json @@ -1,15 +1,16 @@ { "name": "@wordpress/create-block-tutorial-template", - "version": "2.33.0", - "description": "Template for @wordpress/create-block used in the official WordPress tutorial.", + "version": "3.0.0", + "description": "This is a template for @wordpress/create-block that creates an example 'Copyright Date' block. This block is used in the official WordPress block development Quick Start Guide.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", "keywords": [ "wordpress", "create block", - "block template" + "block template", + "quick start" ], - "homepage": "https://github.com/WordPress/gutenberg/tree/HEAD/docs/getting-started/create-block", + "homepage": "https://github.com/WordPress/gutenberg/tree/HEAD/docs/getting-started/quick-start-guide", "repository": { "type": "git", "url": "https://github.com/WordPress/gutenberg.git", diff --git a/packages/create-block-tutorial-template/plugin-templates/$slug.php.mustache b/packages/create-block-tutorial-template/plugin-templates/$slug.php.mustache index 52c9c4966646fa..6de24df94c8d83 100644 --- a/packages/create-block-tutorial-template/plugin-templates/$slug.php.mustache +++ b/packages/create-block-tutorial-template/plugin-templates/$slug.php.mustache @@ -8,7 +8,7 @@ * Description: {{description}} {{/description}} * Version: {{version}} - * Requires at least: 6.1 + * Requires at least: 6.2 * Requires PHP: 7.0 {{#author}} * Author: {{author}} @@ -41,7 +41,7 @@ if ( ! defined( 'ABSPATH' ) ) { * * @see https://developer.wordpress.org/reference/functions/register_block_type/ */ -function {{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init() { +function {{namespaceSnakeCase}}_{{slugSnakeCase}}_init() { register_block_type( __DIR__ . '/build' ); } -add_action( 'init', '{{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init' ); +add_action( 'init', '{{namespaceSnakeCase}}_{{slugSnakeCase}}_init' ); diff --git a/packages/create-block-tutorial-template/plugin-templates/readme.txt.mustache b/packages/create-block-tutorial-template/plugin-templates/readme.txt.mustache index f069906fb19fa5..d2b31bed6c0e60 100644 --- a/packages/create-block-tutorial-template/plugin-templates/readme.txt.mustache +++ b/packages/create-block-tutorial-template/plugin-templates/readme.txt.mustache @@ -3,7 +3,7 @@ Contributors: {{author}} {{/author}} Tags: block -Tested up to: 6.1 +Tested up to: 6.4 Stable tag: {{version}} {{#license}} License: {{license}} diff --git a/packages/create-block/CHANGELOG.md b/packages/create-block/CHANGELOG.md index 7b48fe15b9f2ba..2a3a522feebdf1 100644 --- a/packages/create-block/CHANGELOG.md +++ b/packages/create-block/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.30.0 (2023-11-16) + ## 4.29.0 (2023-11-02) ## 4.28.0 (2023-10-18) diff --git a/packages/create-block/README.md b/packages/create-block/README.md index 88ac97c34c2cf7..20bb6c62ccf66c 100644 --- a/packages/create-block/README.md +++ b/packages/create-block/README.md @@ -15,7 +15,7 @@ _It is largely inspired by [create-react-app](https://create-react-app.dev/docs/ - [Interactive Mode](#interactive-mode) - [`slug`](#slug) - [`options`](#options) -- [Available Commands](#available-commands) +- [Available Commands](#available-commands-in-the-scaffolded-project) - [External Project Templates](#external-project-templates) - [Contributing to this package](#contributing-to-this-package) @@ -141,7 +141,7 @@ For example, running the `start` script from inside the generated folder (`npm s ## External Project Templates -[Click here](https://github.com/WordPress/gutenberg/tree/HEAD/packages/create-block/docs/external-template.md) for information on External Project Templates +[Click here](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/packages-create-block-external-template/) for information on External Project Templates ## Contributing to this package diff --git a/packages/create-block/package.json b/packages/create-block/package.json index aad739e1038043..94249456e48c94 100644 --- a/packages/create-block/package.json +++ b/packages/create-block/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/create-block", - "version": "4.29.0", + "version": "4.30.0", "description": "Generates PHP, JS and CSS code for registering a block for a WordPress plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/customize-widgets/CHANGELOG.md b/packages/customize-widgets/CHANGELOG.md index 3165744b7a6a19..80eb09e9b4fe79 100644 --- a/packages/customize-widgets/CHANGELOG.md +++ b/packages/customize-widgets/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.23.0 (2023-11-16) + ## 4.22.0 (2023-11-02) ## 4.21.0 (2023-10-18) diff --git a/packages/customize-widgets/package.json b/packages/customize-widgets/package.json index 5f84578f6c6ca6..be21e250b7f06b 100644 --- a/packages/customize-widgets/package.json +++ b/packages/customize-widgets/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/customize-widgets", - "version": "4.22.0", + "version": "4.23.0", "description": "Widgets blocks in Customizer Module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/data-controls/CHANGELOG.md b/packages/data-controls/CHANGELOG.md index 3068808b6e0284..48ed94498657cf 100644 --- a/packages/data-controls/CHANGELOG.md +++ b/packages/data-controls/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.15.0 (2023-11-16) + ## 3.14.0 (2023-11-02) ## 3.13.0 (2023-10-18) diff --git a/packages/data-controls/package.json b/packages/data-controls/package.json index 58ffc687664147..919e3479f326ec 100644 --- a/packages/data-controls/package.json +++ b/packages/data-controls/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/data-controls", - "version": "3.14.0", + "version": "3.15.0", "description": "A set of common controls for the @wordpress/data api.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/data/CHANGELOG.md b/packages/data/CHANGELOG.md index 630a9b01d0dda4..4c7ec318971058 100644 --- a/packages/data/CHANGELOG.md +++ b/packages/data/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 9.16.0 (2023-11-16) + ## 9.15.0 (2023-11-02) ## 9.14.0 (2023-10-18) diff --git a/packages/data/package.json b/packages/data/package.json index b3f4c6b0dd9224..ebfccdc75819fd 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/data", - "version": "9.15.0", + "version": "9.16.0", "description": "Data module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/date/CHANGELOG.md b/packages/date/CHANGELOG.md index 5d7eca2da82e47..b43273077d2e81 100644 --- a/packages/date/CHANGELOG.md +++ b/packages/date/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.46.0 (2023-11-16) + ## 4.45.0 (2023-11-02) ## 4.44.0 (2023-10-18) diff --git a/packages/date/package.json b/packages/date/package.json index 6b2e2dfe4280cf..092eb7a1f234f3 100644 --- a/packages/date/package.json +++ b/packages/date/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/date", - "version": "4.45.0", + "version": "4.46.0", "description": "Date module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/dependency-extraction-webpack-plugin/CHANGELOG.md b/packages/dependency-extraction-webpack-plugin/CHANGELOG.md index 4bc08d6a386b37..29bf83987880e6 100644 --- a/packages/dependency-extraction-webpack-plugin/CHANGELOG.md +++ b/packages/dependency-extraction-webpack-plugin/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.29.0 (2023-11-16) + ## 4.28.0 (2023-11-02) ## 4.27.0 (2023-10-18) diff --git a/packages/dependency-extraction-webpack-plugin/package.json b/packages/dependency-extraction-webpack-plugin/package.json index 36eadbaff63350..87267a8a593ad4 100644 --- a/packages/dependency-extraction-webpack-plugin/package.json +++ b/packages/dependency-extraction-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dependency-extraction-webpack-plugin", - "version": "4.28.0", + "version": "4.29.0", "description": "Extract WordPress script dependencies from webpack bundles.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/deprecated/CHANGELOG.md b/packages/deprecated/CHANGELOG.md index 6bd3185175ee7c..841f3cfd5dcfd7 100644 --- a/packages/deprecated/CHANGELOG.md +++ b/packages/deprecated/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.46.0 (2023-11-16) + ## 3.45.0 (2023-11-02) ## 3.44.0 (2023-10-18) diff --git a/packages/deprecated/package.json b/packages/deprecated/package.json index 5f13accb2e27bc..f0a823074c0475 100644 --- a/packages/deprecated/package.json +++ b/packages/deprecated/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/deprecated", - "version": "3.45.0", + "version": "3.46.0", "description": "Deprecation utility for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/docgen/CHANGELOG.md b/packages/docgen/CHANGELOG.md index 0b45b27df3ccbc..d72a476531feea 100644 --- a/packages/docgen/CHANGELOG.md +++ b/packages/docgen/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 1.55.0 (2023-11-16) + ## 1.54.0 (2023-11-02) ## 1.53.0 (2023-10-18) diff --git a/packages/docgen/package.json b/packages/docgen/package.json index c8610b86d2cb8e..87d92d25e80535 100644 --- a/packages/docgen/package.json +++ b/packages/docgen/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/docgen", - "version": "1.54.0", + "version": "1.55.0", "description": "Autogenerate public API documentation from exports and JSDoc comments.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/dom-ready/CHANGELOG.md b/packages/dom-ready/CHANGELOG.md index 61b414f50d8c3f..e0ae85e3cac5cf 100644 --- a/packages/dom-ready/CHANGELOG.md +++ b/packages/dom-ready/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.46.0 (2023-11-16) + ## 3.45.0 (2023-11-02) ## 3.44.0 (2023-10-18) diff --git a/packages/dom-ready/package.json b/packages/dom-ready/package.json index fc7043e2c950f5..542be436e09c6c 100644 --- a/packages/dom-ready/package.json +++ b/packages/dom-ready/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dom-ready", - "version": "3.45.0", + "version": "3.46.0", "description": "Execute callback after the DOM is loaded.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/dom/CHANGELOG.md b/packages/dom/CHANGELOG.md index 5a1567021b1eaf..df938d2fa57996 100644 --- a/packages/dom/CHANGELOG.md +++ b/packages/dom/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.46.0 (2023-11-16) + ## 3.45.0 (2023-11-02) ## 3.44.0 (2023-10-18) diff --git a/packages/dom/package.json b/packages/dom/package.json index b40a161d683b43..263ee6d5a04aec 100644 --- a/packages/dom/package.json +++ b/packages/dom/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dom", - "version": "3.45.0", + "version": "3.46.0", "description": "DOM utilities module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/e2e-test-utils-playwright/CHANGELOG.md b/packages/e2e-test-utils-playwright/CHANGELOG.md index cd7425c68eeff2..9f5c2a199ff0f7 100644 --- a/packages/e2e-test-utils-playwright/CHANGELOG.md +++ b/packages/e2e-test-utils-playwright/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 0.14.0 (2023-11-16) + ## 0.13.0 (2023-11-02) ## 0.12.0 (2023-10-18) diff --git a/packages/e2e-test-utils-playwright/package.json b/packages/e2e-test-utils-playwright/package.json index f8d9b7cd2f0171..9c7401c5346f14 100644 --- a/packages/e2e-test-utils-playwright/package.json +++ b/packages/e2e-test-utils-playwright/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-test-utils-playwright", - "version": "0.13.0", + "version": "0.14.0", "description": "End-To-End (E2E) test utils for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/e2e-test-utils/CHANGELOG.md b/packages/e2e-test-utils/CHANGELOG.md index bf5d32bb6cc94d..617315dbfaa6c1 100644 --- a/packages/e2e-test-utils/CHANGELOG.md +++ b/packages/e2e-test-utils/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 10.17.0 (2023-11-16) + ## 10.16.0 (2023-11-02) ## 10.15.0 (2023-10-18) diff --git a/packages/e2e-test-utils/package.json b/packages/e2e-test-utils/package.json index f4bd86e15f1ee3..8fad38623a6855 100644 --- a/packages/e2e-test-utils/package.json +++ b/packages/e2e-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-test-utils", - "version": "10.16.0", + "version": "10.17.0", "description": "End-To-End (E2E) test utils for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/e2e-tests/CHANGELOG.md b/packages/e2e-tests/CHANGELOG.md index 66ab34e8267590..76363c3fda1023 100644 --- a/packages/e2e-tests/CHANGELOG.md +++ b/packages/e2e-tests/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 7.17.0 (2023-11-16) + ## 7.16.0 (2023-11-02) ## 7.15.0 (2023-10-18) diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index 001f395dec6083..f3cabf4d6a2c92 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-tests", - "version": "7.16.0", + "version": "7.17.0", "description": "End-To-End (E2E) tests for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/e2e-tests/plugins/interactive-blocks.php b/packages/e2e-tests/plugins/interactive-blocks.php index 956508a11361e4..c551127548e801 100644 --- a/packages/e2e-tests/plugins/interactive-blocks.php +++ b/packages/e2e-tests/plugins/interactive-blocks.php @@ -40,7 +40,7 @@ function () { if ( 'true' === $_GET['disable_directives_ssr'] ) { remove_filter( 'render_block_data', - 'gutenberg_interactivity_process_directives' + 'gutenberg_interactivity_mark_root_blocks' ); } } diff --git a/packages/e2e-tests/specs/editor/plugins/__snapshots__/container-blocks.test.js.snap b/packages/e2e-tests/specs/editor/plugins/__snapshots__/container-blocks.test.js.snap deleted file mode 100644 index cbcbf0402f8c96..00000000000000 --- a/packages/e2e-tests/specs/editor/plugins/__snapshots__/container-blocks.test.js.snap +++ /dev/null @@ -1,58 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Container block without paragraph support ensures we can use the alternative block appender properly 1`] = ` -" - -
- -" -`; - -exports[`InnerBlocks Template Sync Ensure inner block writing flow works as expected without additional paragraphs added 1`] = ` -" - -

Test Paragraph

- -" -`; - -exports[`InnerBlocks Template Sync Ensures blocks without locking are kept intact even if they do not match the template 1`] = ` -" - -

Content…

- - - -

added paragraph

- - -" -`; - -exports[`InnerBlocks Template Sync Removes blocks that are not expected by the template if a lock all exists 1`] = ` -" - -

Content…

- -" -`; - -exports[`InnerBlocks Template Sync Synchronizes blocks if lock 'all' is set and the template prop is changed 1`] = ` -" - -

Content…

- -" -`; - -exports[`InnerBlocks Template Sync Synchronizes blocks if lock 'all' is set and the template prop is changed 2`] = ` -" - -

Content…

- - - -

Two

- -" -`; diff --git a/packages/e2e-tests/specs/editor/plugins/__snapshots__/plugins-api.test.js.snap b/packages/e2e-tests/specs/editor/plugins/__snapshots__/plugins-api.test.js.snap deleted file mode 100644 index 9e4a5ac3ad6f61..00000000000000 --- a/packages/e2e-tests/specs/editor/plugins/__snapshots__/plugins-api.test.js.snap +++ /dev/null @@ -1,7 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Using Plugins API Document Setting Custom Panel Should render a custom panel inside Document Setting sidebar 1`] = `"My Custom Panel"`; - -exports[`Using Plugins API Sidebar Medium screen Should open plugins sidebar using More Menu item and render content 1`] = `"
(no title)
Plugin title
"`; - -exports[`Using Plugins API Sidebar Should open plugins sidebar using More Menu item and render content 1`] = `"
(no title)
Plugin title
"`; diff --git a/packages/e2e-tests/specs/editor/plugins/container-blocks.test.js b/packages/e2e-tests/specs/editor/plugins/container-blocks.test.js deleted file mode 100644 index 7393b2700ff2b7..00000000000000 --- a/packages/e2e-tests/specs/editor/plugins/container-blocks.test.js +++ /dev/null @@ -1,130 +0,0 @@ -/** - * WordPress dependencies - */ -import { - activatePlugin, - createNewPost, - deactivatePlugin, - getEditedPostContent, - insertBlock, - switchEditorModeTo, - pressKeyWithModifier, - canvas, -} from '@wordpress/e2e-test-utils'; - -describe( 'InnerBlocks Template Sync', () => { - beforeAll( async () => { - await activatePlugin( 'gutenberg-test-innerblocks-templates' ); - } ); - - beforeEach( async () => { - await createNewPost(); - } ); - - afterAll( async () => { - await deactivatePlugin( 'gutenberg-test-innerblocks-templates' ); - } ); - - const insertBlockAndAddParagraphInside = async ( blockName, blockSlug ) => { - const paragraphToAdd = ` - -

added paragraph

- - `; - await insertBlock( blockName ); - await switchEditorModeTo( 'Code' ); - await page.$eval( - '.editor-post-text-editor', - ( element, _paragraph, _blockSlug ) => { - const blockDelimiter = ``; - element.value = element.value.replace( - blockDelimiter, - `${ _paragraph }${ blockDelimiter }` - ); - }, - paragraphToAdd, - blockSlug - ); - // Press "Enter" inside the code editor to fire the `onChange` event for the new value. - await page.click( '.editor-post-text-editor' ); - await pressKeyWithModifier( 'primary', 'A' ); - await page.keyboard.press( 'ArrowRight' ); - await page.keyboard.press( 'Enter' ); - await switchEditorModeTo( 'Visual' ); - }; - - it( 'Ensures blocks without locking are kept intact even if they do not match the template', async () => { - await insertBlockAndAddParagraphInside( - 'Test Inner Blocks no locking', - 'test/test-inner-blocks-no-locking' - ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - - it( 'Removes blocks that are not expected by the template if a lock all exists', async () => { - await insertBlockAndAddParagraphInside( - 'Test InnerBlocks locking all', - 'test/test-inner-blocks-locking-all' - ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - - // Test for regressions of https://github.com/WordPress/gutenberg/issues/27897. - it( `Synchronizes blocks if lock 'all' is set and the template prop is changed`, async () => { - // Insert the template and assert that the template has its initial value. - await insertBlock( 'Test Inner Blocks update locked template' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - - // Trigger a template update and assert that a second block is now present. - const [ button ] = await canvas().$x( - `//button[contains(text(), 'Update template')]` - ); - await button.click(); - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - - it( 'Ensure inner block writing flow works as expected without additional paragraphs added', async () => { - const TEST_BLOCK_NAME = 'Test Inner Blocks Paragraph Placeholder'; - - await insertBlock( TEST_BLOCK_NAME ); - await page.keyboard.type( 'Test Paragraph' ); - - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); -} ); - -describe( 'Container block without paragraph support', () => { - beforeAll( async () => { - await activatePlugin( - 'gutenberg-test-container-block-without-paragraph' - ); - } ); - - beforeEach( async () => { - await createNewPost(); - } ); - - afterAll( async () => { - await deactivatePlugin( - 'gutenberg-test-container-block-without-paragraph' - ); - } ); - - it( 'ensures we can use the alternative block appender properly', async () => { - await insertBlock( 'Container without paragraph' ); - - // Open the specific appender used when there's no paragraph support. - await page.click( - '.block-editor-inner-blocks .block-list-appender .block-list-appender__toggle' - ); - - // Insert an image block. - const insertButton = ( - await page.$x( `//button//span[contains(text(), 'Image')]` ) - )[ 0 ]; - await insertButton.click(); - - // Check the inserted content. - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/e2e-tests/specs/editor/plugins/plugins-api.test.js b/packages/e2e-tests/specs/editor/plugins/plugins-api.test.js deleted file mode 100644 index 4ad8d0e634204f..00000000000000 --- a/packages/e2e-tests/specs/editor/plugins/plugins-api.test.js +++ /dev/null @@ -1,189 +0,0 @@ -/** - * WordPress dependencies - */ -import { - activatePlugin, - clickBlockAppender, - clickOnMoreMenuItem, - createNewPost, - deactivatePlugin, - openDocumentSettingsSidebar, - openPublishPanel, - publishPost, - setBrowserViewport, -} from '@wordpress/e2e-test-utils'; - -describe( 'Using Plugins API', () => { - beforeAll( async () => { - await activatePlugin( 'gutenberg-test-plugin-plugins-api' ); - } ); - - afterAll( async () => { - await deactivatePlugin( 'gutenberg-test-plugin-plugins-api' ); - } ); - - beforeEach( async () => { - await createNewPost(); - } ); - - describe( 'Post Status Info', () => { - it( 'Should render post status info inside Document Setting sidebar', async () => { - await openDocumentSettingsSidebar(); - - const pluginPostStatusInfoText = await page.$eval( - '.edit-post-post-status .my-post-status-info-plugin', - ( el ) => el.innerText - ); - expect( pluginPostStatusInfoText ).toBe( 'My post status info' ); - } ); - } ); - - describe( 'Publish Panel', () => { - beforeEach( async () => { - // Type something first to activate Publish button. - await clickBlockAppender(); - await page.keyboard.type( 'First paragraph' ); - } ); - - it( 'Should render publish panel inside Pre-publish sidebar', async () => { - await openPublishPanel(); - - const pluginPublishPanelText = await page.$eval( - '.editor-post-publish-panel .my-publish-panel-plugin__pre', - ( el ) => el.innerText - ); - expect( pluginPublishPanelText ).toMatch( 'My pre publish panel' ); - } ); - - it( 'Should render publish panel inside Post-publish sidebar', async () => { - await publishPost(); - const pluginPublishPanel = await page.waitForSelector( - '.editor-post-publish-panel .my-publish-panel-plugin__post' - ); - const pluginPublishPanelText = await pluginPublishPanel.evaluate( - ( node ) => node.innerText - ); - expect( pluginPublishPanelText ).toMatch( 'My post publish panel' ); - } ); - } ); - - describe( 'Sidebar', () => { - const SIDEBAR_PINNED_ITEM_BUTTON = - '.interface-pinned-items button[aria-label="Plugin title"]'; - const SIDEBAR_PANEL_SELECTOR = '.sidebar-title-plugin-panel'; - it( 'Should open plugins sidebar using More Menu item and render content', async () => { - await clickOnMoreMenuItem( 'Plugin more menu title' ); - - const pluginSidebarContent = await page.$eval( - '.edit-post-sidebar', - ( el ) => el.innerHTML - ); - expect( pluginSidebarContent ).toMatchSnapshot(); - } ); - - it( 'Should be pinned by default and can be opened and closed using pinned items', async () => { - const sidebarPinnedItem = await page.$( - SIDEBAR_PINNED_ITEM_BUTTON - ); - expect( sidebarPinnedItem ).not.toBeNull(); - await sidebarPinnedItem.click(); - expect( await page.$( SIDEBAR_PANEL_SELECTOR ) ).not.toBeNull(); - await sidebarPinnedItem.click(); - expect( await page.$( SIDEBAR_PANEL_SELECTOR ) ).toBeNull(); - } ); - - it( 'Can be pinned and unpinned', async () => { - await ( await page.$( SIDEBAR_PINNED_ITEM_BUTTON ) ).click(); - const unpinButton = await page.$( - 'button[aria-label="Unpin from toolbar"]' - ); - await unpinButton.click(); - expect( await page.$( SIDEBAR_PINNED_ITEM_BUTTON ) ).toBeNull(); - await page.click( - '.interface-complementary-area-header button[aria-label="Close plugin"]' - ); - await page.reload(); - await page.waitForSelector( '.edit-post-layout' ); - expect( await page.$( SIDEBAR_PINNED_ITEM_BUTTON ) ).toBeNull(); - await clickOnMoreMenuItem( 'Plugin more menu title' ); - await page.click( 'button[aria-label="Pin to toolbar"]' ); - expect( await page.$( SIDEBAR_PINNED_ITEM_BUTTON ) ).not.toBeNull(); - await page.reload(); - await page.waitForSelector( '.edit-post-layout' ); - expect( await page.$( SIDEBAR_PINNED_ITEM_BUTTON ) ).not.toBeNull(); - } ); - - it( 'Should close plugins sidebar using More Menu item', async () => { - await clickOnMoreMenuItem( 'Plugin more menu title' ); - - const pluginSidebarOpened = await page.$( '.edit-post-sidebar' ); - expect( pluginSidebarOpened ).not.toBeNull(); - - await clickOnMoreMenuItem( 'Plugin more menu title' ); - - const pluginSidebarClosed = await page.$( '.edit-post-sidebar' ); - expect( pluginSidebarClosed ).toBeNull(); - } ); - - describe( 'Medium screen', () => { - beforeAll( async () => { - await setBrowserViewport( 'medium' ); - } ); - - afterAll( async () => { - await setBrowserViewport( 'large' ); - } ); - - it( 'Should open plugins sidebar using More Menu item and render content', async () => { - await clickOnMoreMenuItem( 'Plugin more menu title' ); - - const pluginSidebarContent = await page.$eval( - '.edit-post-sidebar', - ( el ) => el.innerHTML - ); - expect( pluginSidebarContent ).toMatchSnapshot(); - } ); - } ); - } ); - - describe( 'Document Setting Custom Panel', () => { - it( 'Should render a custom panel inside Document Setting sidebar', async () => { - await openDocumentSettingsSidebar(); - const pluginDocumentSettingsText = await page.$eval( - '.edit-post-sidebar .my-document-setting-plugin', - ( el ) => el.innerText - ); - expect( pluginDocumentSettingsText ).toMatchSnapshot(); - } ); - } ); - - describe( 'Error Boundary', () => { - beforeAll( async () => { - await activatePlugin( - 'gutenberg-test-plugin-plugins-error-boundary' - ); - } ); - - afterAll( async () => { - await deactivatePlugin( - 'gutenberg-test-plugin-plugins-error-boundary' - ); - } ); - - it( 'Should create notice using plugin error boundary callback', async () => { - const noticeContent = await page.waitForSelector( - '.is-error .components-notice__content' - ); - expect( - await page.evaluate( - ( _noticeContent ) => _noticeContent.firstChild.nodeValue, - noticeContent - ) - ).toEqual( - 'The "my-error-plugin" plugin has encountered an error and cannot be rendered.' - ); - - expect( console ).toHaveErrored(); - } ); - } ); -} ); diff --git a/packages/e2e-tests/specs/editor/various/datepicker.test.js b/packages/e2e-tests/specs/editor/various/datepicker.test.js index b0fc3bf898d4a4..6838fd56a2ba9a 100644 --- a/packages/e2e-tests/specs/editor/various/datepicker.test.js +++ b/packages/e2e-tests/specs/editor/various/datepicker.test.js @@ -56,7 +56,7 @@ function formatDatePickerValues( async function getPublishingDate() { return page.$eval( - '.edit-post-post-schedule__toggle', + '.editor-post-schedule__dialog-toggle', ( dateLabel ) => dateLabel.textContent ); } @@ -83,7 +83,7 @@ describe.each( [ [ 'UTC-10' ], [ 'UTC' ], [ 'UTC+10' ] ] )( it( 'should show the publishing date if the date is in the past', async () => { // Open the datepicker. - await page.click( '.edit-post-post-schedule__toggle' ); + await page.click( '.editor-post-schedule__dialog-toggle' ); // Change the publishing date to a year in the past. await page.click( '.components-datetime__time-field-year' ); @@ -91,7 +91,7 @@ describe.each( [ [ 'UTC-10' ], [ 'UTC' ], [ 'UTC+10' ] ] )( const datePickerValues = await getDatePickerValues(); // Close the datepicker. - await page.click( '.edit-post-post-schedule__toggle' ); + await page.click( '.editor-post-schedule__dialog-toggle' ); const publishingDate = await getPublishingDate(); @@ -102,7 +102,7 @@ describe.each( [ [ 'UTC-10' ], [ 'UTC' ], [ 'UTC+10' ] ] )( it( 'should show the publishing date if the date is in the future', async () => { // Open the datepicker. - await page.click( '.edit-post-post-schedule__toggle' ); + await page.click( '.editor-post-schedule__dialog-toggle' ); // Change the publishing date to a year in the future. await page.click( '.components-datetime__time-field-year' ); @@ -110,7 +110,7 @@ describe.each( [ [ 'UTC-10' ], [ 'UTC' ], [ 'UTC+10' ] ] )( const datePickerValues = await getDatePickerValues(); // Close the datepicker. - await page.click( '.edit-post-post-schedule__toggle' ); + await page.click( '.editor-post-schedule__dialog-toggle' ); const publishingDate = await getPublishingDate(); @@ -123,17 +123,17 @@ describe.each( [ [ 'UTC-10' ], [ 'UTC' ], [ 'UTC+10' ] ] )( it( `should show the publishing date as "Immediately" if the date is cleared`, async () => { // Open the datepicker. - await page.click( '.edit-post-post-schedule__toggle' ); + await page.click( '.editor-post-schedule__dialog-toggle' ); // Change the publishing date to a year in the future. await page.click( '.components-datetime__time-field-year' ); await page.keyboard.press( 'ArrowUp' ); // Close the datepicker. - await page.click( '.edit-post-post-schedule__toggle' ); + await page.click( '.editor-post-schedule__dialog-toggle' ); // Open the datepicker. - await page.click( '.edit-post-post-schedule__toggle' ); + await page.click( '.editor-post-schedule__dialog-toggle' ); // Clear the date. await page.click( diff --git a/packages/e2e-tests/specs/editor/various/pattern-blocks.test.js b/packages/e2e-tests/specs/editor/various/pattern-blocks.test.js index 442ed2b21915a5..cb5187a390e8b1 100644 --- a/packages/e2e-tests/specs/editor/various/pattern-blocks.test.js +++ b/packages/e2e-tests/specs/editor/various/pattern-blocks.test.js @@ -112,7 +112,7 @@ describe( 'Pattern blocks', () => { // Convert block to a regular block. await clickBlockToolbarButton( 'Options' ); - await clickMenuItem( 'Detach pattern' ); + await clickMenuItem( 'Detach' ); // Check that we have a paragraph block on the page. const paragraphBlock = await canvas().$( @@ -219,7 +219,7 @@ describe( 'Pattern blocks', () => { // Convert block to a regular block. await clickBlockToolbarButton( 'Options' ); - await clickMenuItem( 'Detach patterns' ); + await clickMenuItem( 'Detach' ); // Check that we have two paragraph blocks on the page. expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -352,7 +352,7 @@ describe( 'Pattern blocks', () => { // Convert back to regular blocks. await clickBlockToolbarButton( 'Select Edited block' ); await clickBlockToolbarButton( 'Options' ); - await clickMenuItem( 'Detach pattern' ); + await clickMenuItem( 'Detach' ); await page.waitForXPath( selector, { hidden: true, } ); diff --git a/packages/e2e-tests/specs/editor/various/scheduling.test.js b/packages/e2e-tests/specs/editor/various/scheduling.test.js index 61c3541c1f47e5..df75dcb92f2820 100644 --- a/packages/e2e-tests/specs/editor/various/scheduling.test.js +++ b/packages/e2e-tests/specs/editor/various/scheduling.test.js @@ -46,7 +46,7 @@ describe( 'Scheduling', () => { await page.keyboard.press( 'ArrowUp' ); // Close the datepicker. - await page.click( '.edit-post-post-schedule__toggle' ); + await page.click( '.editor-post-schedule__dialog-toggle' ); expect( await getPublishButtonText() ).toBe( 'Schedule…' ); } ); diff --git a/packages/e2e-tests/specs/editor/various/sidebar-permalink.test.js b/packages/e2e-tests/specs/editor/various/sidebar-permalink.test.js deleted file mode 100644 index e23bd830cee4fe..00000000000000 --- a/packages/e2e-tests/specs/editor/various/sidebar-permalink.test.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * WordPress dependencies - */ -import { - activatePlugin, - createNewPost, - deactivatePlugin, - publishPost, - canvas, -} from '@wordpress/e2e-test-utils'; - -const urlButtonSelector = '*[aria-label^="Change URL"]'; - -// This tests are not together with the remaining sidebar tests, -// because we need to publish/save a post, to correctly test the permalink row. -// The sidebar test suit enforces that focus is never lost, but during save operations -// the focus is lost and a new element is focused once the save is completed. -describe( 'Sidebar Permalink', () => { - beforeAll( async () => { - await activatePlugin( 'gutenberg-test-custom-post-types' ); - } ); - - afterAll( async () => { - await deactivatePlugin( 'gutenberg-test-custom-post-types' ); - } ); - - it( 'should not render URL when post is publicly queryable but not public', async () => { - await createNewPost( { postType: 'public_q_not_public' } ); - await page.keyboard.type( 'aaaaa' ); - await publishPost(); - // Start editing again. - await canvas().type( '.editor-post-title__input', ' (Updated)' ); - expect( await page.$( urlButtonSelector ) ).toBeNull(); - } ); - - it( 'should not render URL when post is public but not publicly queryable', async () => { - await createNewPost( { postType: 'not_public_q_public' } ); - await page.keyboard.type( 'aaaaa' ); - await publishPost(); - // Start editing again. - await canvas().type( '.editor-post-title__input', ' (Updated)' ); - expect( await page.$( urlButtonSelector ) ).toBeNull(); - } ); - - it( 'should render URL when post is public and publicly queryable', async () => { - await createNewPost( { postType: 'public_q_public' } ); - await page.keyboard.type( 'aaaaa' ); - await publishPost(); - // Start editing again. - await canvas( 0 ).type( '.editor-post-title__input', ' (Updated)' ); - expect( await page.$( urlButtonSelector ) ).not.toBeNull(); - } ); -} ); diff --git a/packages/edit-post/CHANGELOG.md b/packages/edit-post/CHANGELOG.md index 6417fcc4454c6f..e8e0fa1632a7da 100644 --- a/packages/edit-post/CHANGELOG.md +++ b/packages/edit-post/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 7.23.0 (2023-11-16) + ## 7.22.0 (2023-11-02) ## 7.21.0 (2023-10-18) diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index ed9b4541103c94..31c5c209fa96c2 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-post", - "version": "7.22.0", + "version": "7.23.0", "description": "Edit Post module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-post/src/components/header/writing-menu/index.js b/packages/edit-post/src/components/header/writing-menu/index.js index c0d6ff994815e6..de6acf67c19834 100644 --- a/packages/edit-post/src/components/header/writing-menu/index.js +++ b/packages/edit-post/src/components/header/writing-menu/index.js @@ -31,7 +31,7 @@ function WritingMenu() { const toggleDistractionFree = () => { registry.batch( () => { - setPreference( 'core/edit-post', 'fixedToolbar', false ); + setPreference( 'core/edit-post', 'fixedToolbar', true ); setIsInserterOpened( false ); setIsListViewOpened( false ); closeGeneralSidebar(); diff --git a/packages/edit-post/src/components/preferences-modal/index.js b/packages/edit-post/src/components/preferences-modal/index.js index bb0c8b5cd12715..c08dda81f8e594 100644 --- a/packages/edit-post/src/components/preferences-modal/index.js +++ b/packages/edit-post/src/components/preferences-modal/index.js @@ -70,7 +70,7 @@ export default function EditPostPreferencesModal() { const { set: setPreference } = useDispatch( preferencesStore ); const toggleDistractionFree = () => { - setPreference( 'core/edit-post', 'fixedToolbar', false ); + setPreference( 'core/edit-post', 'fixedToolbar', true ); setIsInserterOpened( false ); setIsListViewOpened( false ); closeGeneralSidebar(); diff --git a/packages/edit-post/src/components/sidebar/post-author/index.js b/packages/edit-post/src/components/sidebar/post-author/index.js deleted file mode 100644 index 8f5eca9aea46b4..00000000000000 --- a/packages/edit-post/src/components/sidebar/post-author/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * WordPress dependencies - */ -import { PanelRow } from '@wordpress/components'; -import { - PostAuthor as PostAuthorForm, - PostAuthorCheck, -} from '@wordpress/editor'; - -export function PostAuthor() { - return ( - - - - - - ); -} - -export default PostAuthor; diff --git a/packages/edit-post/src/components/sidebar/post-author/style.scss b/packages/edit-post/src/components/sidebar/post-author/style.scss deleted file mode 100644 index bfc80bdd2cf702..00000000000000 --- a/packages/edit-post/src/components/sidebar/post-author/style.scss +++ /dev/null @@ -1,5 +0,0 @@ -.edit-post-post-author { - display: flex; - flex-direction: column; - align-items: stretch; -} diff --git a/packages/edit-post/src/components/sidebar/post-pending-status/index.js b/packages/edit-post/src/components/sidebar/post-pending-status/index.js index 739aff6034b509..de1f02b00d746b 100644 --- a/packages/edit-post/src/components/sidebar/post-pending-status/index.js +++ b/packages/edit-post/src/components/sidebar/post-pending-status/index.js @@ -1,18 +1,25 @@ /** * WordPress dependencies */ -import { PanelRow } from '@wordpress/components'; import { PostPendingStatus as PostPendingStatusForm, PostPendingStatusCheck, + privateApis as editorPrivateApis, } from '@wordpress/editor'; +/** + * Internal dependencies + */ +import { unlock } from '../../../lock-unlock'; + +const { PostPanelRow } = unlock( editorPrivateApis ); + export function PostPendingStatus() { return ( - + - + ); } diff --git a/packages/edit-post/src/components/sidebar/post-schedule/index.js b/packages/edit-post/src/components/sidebar/post-schedule/index.js deleted file mode 100644 index dca0dd12c6d6be..00000000000000 --- a/packages/edit-post/src/components/sidebar/post-schedule/index.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * WordPress dependencies - */ -import { __, sprintf } from '@wordpress/i18n'; -import { PanelRow, Dropdown, Button } from '@wordpress/components'; -import { useState, useMemo } from '@wordpress/element'; -import { - PostSchedule as PostScheduleForm, - PostScheduleCheck, - usePostScheduleLabel, -} from '@wordpress/editor'; - -export default function PostSchedule() { - // Use internal state instead of a ref to make sure that the component - // re-renders when the popover's anchor updates. - const [ popoverAnchor, setPopoverAnchor ] = useState( null ); - // Memoize popoverProps to avoid returning a new object every time. - const popoverProps = useMemo( - () => ( { anchor: popoverAnchor, placement: 'bottom-end' } ), - [ popoverAnchor ] - ); - - return ( - - - { __( 'Publish' ) } - ( - - ) } - renderContent={ ( { onClose } ) => ( - - ) } - /> - - - ); -} - -function PostScheduleToggle( { isOpen, onClick } ) { - const label = usePostScheduleLabel(); - const fullLabel = usePostScheduleLabel( { full: true } ); - return ( - - ); -} diff --git a/packages/edit-post/src/components/sidebar/post-schedule/style.scss b/packages/edit-post/src/components/sidebar/post-schedule/style.scss deleted file mode 100644 index 056c2e68dbb5f1..00000000000000 --- a/packages/edit-post/src/components/sidebar/post-schedule/style.scss +++ /dev/null @@ -1,34 +0,0 @@ -.edit-post-post-schedule { - width: 100%; - position: relative; - justify-content: flex-start; - align-items: flex-start; - - span { - display: block; - width: 45%; - flex-shrink: 0; - // Match padding on tertiary buttons for alignment. - padding: $grid-unit-15 * 0.5 0; - } -} - -.components-button.edit-post-post-schedule__toggle { - text-align: left; - white-space: normal; - height: auto; - - // This span is added by the Popover in Tooltip when no anchor is - // provided. We set its width to 0 so that it does not cause the button text - // to wrap to a new line when displaying the tooltip. A better fix would be - // to pass anchorRef and avoid the need for a span alltogether, which is - // what this PR allows us to do: - // https://github.com/WordPress/gutenberg/pull/41268. - span { - width: 0; - } -} - -.edit-post-post-schedule__dialog .block-editor-publish-date-time-picker { - margin: $grid-unit-10; -} diff --git a/packages/edit-post/src/components/sidebar/post-status/index.js b/packages/edit-post/src/components/sidebar/post-status/index.js index 59fdc5d189c946..1b24de6082d160 100644 --- a/packages/edit-post/src/components/sidebar/post-status/index.js +++ b/packages/edit-post/src/components/sidebar/post-status/index.js @@ -7,23 +7,26 @@ import { PanelBody, } from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; -import { PostSwitchToDraftButton, PostSyncStatus } from '@wordpress/editor'; +import { + PostAuthorPanel, + PostSchedulePanel, + PostSwitchToDraftButton, + PostSyncStatus, + PostURLPanel, +} from '@wordpress/editor'; /** * Internal dependencies */ import PostVisibility from '../post-visibility'; import PostTrash from '../post-trash'; -import PostSchedule from '../post-schedule'; import PostSticky from '../post-sticky'; -import PostAuthor from '../post-author'; import PostSlug from '../post-slug'; import PostFormat from '../post-format'; import PostPendingStatus from '../post-pending-status'; import PluginPostStatusInfo from '../plugin-post-status-info'; import { store as editPostStore } from '../../../store'; import PostTemplate from '../post-template'; -import PostURL from '../post-url'; /** * Module Constants @@ -58,15 +61,15 @@ export default function PostStatus() { { ( fills ) => ( <> - + - + + - - + { fills } - + - + ); } diff --git a/packages/edit-post/src/components/sidebar/post-template/index.js b/packages/edit-post/src/components/sidebar/post-template/index.js index 88809294f17db8..fa1579fa3a82a2 100644 --- a/packages/edit-post/src/components/sidebar/post-template/index.js +++ b/packages/edit-post/src/components/sidebar/post-template/index.js @@ -2,10 +2,13 @@ * WordPress dependencies */ import { useState, useMemo } from '@wordpress/element'; -import { PanelRow, Dropdown, Button } from '@wordpress/components'; +import { Dropdown, Button } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; -import { store as editorStore } from '@wordpress/editor'; +import { + store as editorStore, + privateApis as editorPrivateApis, +} from '@wordpress/editor'; import { store as coreStore } from '@wordpress/core-data'; /** @@ -13,6 +16,9 @@ import { store as coreStore } from '@wordpress/core-data'; */ import PostTemplateForm from './form'; import { store as editPostStore } from '../../../store'; +import { unlock } from '../../../lock-unlock'; + +const { PostPanelRow } = unlock( editorPrivateApis ); export default function PostTemplate() { // Use internal state instead of a ref to make sure that the component @@ -53,11 +59,9 @@ export default function PostTemplate() { } return ( - - { __( 'Template' ) } + ( @@ -70,7 +74,7 @@ export default function PostTemplate() { ) } /> - + ); } diff --git a/packages/edit-post/src/components/sidebar/post-template/style.scss b/packages/edit-post/src/components/sidebar/post-template/style.scss index caafabbe4227cc..91f82d4d0f9f30 100644 --- a/packages/edit-post/src/components/sidebar/post-template/style.scss +++ b/packages/edit-post/src/components/sidebar/post-template/style.scss @@ -1,19 +1,3 @@ -.edit-post-post-template { - width: 100%; - justify-content: flex-start; - - span { - display: block; - width: 45%; - // Match padding on tertiary buttons for alignment. - padding: $grid-unit-15 * 0.5 0; - } -} - -.edit-post-post-template__dropdown { - max-width: 55%; -} - .components-button.edit-post-post-template__toggle { display: inline-block; width: 100%; diff --git a/packages/edit-post/src/components/sidebar/post-url/style.scss b/packages/edit-post/src/components/sidebar/post-url/style.scss deleted file mode 100644 index 73a93d477df588..00000000000000 --- a/packages/edit-post/src/components/sidebar/post-url/style.scss +++ /dev/null @@ -1,26 +0,0 @@ -.edit-post-post-url { - width: 100%; - justify-content: flex-start; - align-items: flex-start; - - span { - display: block; - width: 45%; - flex-shrink: 0; - // Match padding on tertiary buttons for alignment. - padding: $grid-unit-15 * 0.5 0; - } -} - -.components-button.edit-post-post-url__toggle { - text-align: left; - white-space: normal; - height: auto; - word-break: break-word; -} - -.edit-post-post-url__dialog .editor-post-url { - // sidebar width - popover padding - form margin - min-width: $sidebar-width - $grid-unit-20 - $grid-unit-20; - margin: $grid-unit-10; -} diff --git a/packages/edit-post/src/components/sidebar/post-visibility/index.js b/packages/edit-post/src/components/sidebar/post-visibility/index.js index 080737c431f1e0..9a70d636388af7 100644 --- a/packages/edit-post/src/components/sidebar/post-visibility/index.js +++ b/packages/edit-post/src/components/sidebar/post-visibility/index.js @@ -2,15 +2,23 @@ * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; -import { PanelRow, Dropdown, Button } from '@wordpress/components'; +import { Dropdown, Button } from '@wordpress/components'; import { PostVisibility as PostVisibilityForm, PostVisibilityLabel, PostVisibilityCheck, usePostVisibilityLabel, + privateApis as editorPrivateApis, } from '@wordpress/editor'; import { useMemo, useState } from '@wordpress/element'; +/** + * Internal dependencies + */ +import { unlock } from '../../../lock-unlock'; + +const { PostPanelRow } = unlock( editorPrivateApis ); + export function PostVisibility() { // Use internal state instead of a ref to make sure that the component // re-renders when the popover's anchor updates. @@ -29,11 +37,10 @@ export function PostVisibility() { return ( ( - - { __( 'Visibility' ) } { ! canEdit && ( @@ -55,7 +62,7 @@ export function PostVisibility() { ) } /> ) } - + ) } /> ); diff --git a/packages/edit-post/src/components/sidebar/post-visibility/style.scss b/packages/edit-post/src/components/sidebar/post-visibility/style.scss index d578905a482ac1..0dd9824e5bde76 100644 --- a/packages/edit-post/src/components/sidebar/post-visibility/style.scss +++ b/packages/edit-post/src/components/sidebar/post-visibility/style.scss @@ -1,15 +1,3 @@ -.edit-post-post-visibility { - width: 100%; - justify-content: flex-start; - - span { - display: block; - width: 45%; - // Match padding on tertiary buttons for alignment. - padding: $grid-unit-15 * 0.5 0; - } -} - .edit-post-post-visibility__dialog .editor-post-visibility { // sidebar width - popover padding - form margin min-width: $sidebar-width - $grid-unit-20 - $grid-unit-20; diff --git a/packages/edit-post/src/components/visual-editor/index.js b/packages/edit-post/src/components/visual-editor/index.js index bd236551c7cf14..25dcf941970aca 100644 --- a/packages/edit-post/src/components/visual-editor/index.js +++ b/packages/edit-post/src/components/visual-editor/index.js @@ -411,6 +411,13 @@ export default function VisualEditor( { styles } ) { : `${ blockListLayoutClass } wp-block-post-content` // Ensure root level blocks receive default/flow blockGap styling rules. } layout={ blockListLayout } + dropZoneElement={ + // When iframed, pass in the html element of the iframe to + // ensure the drop zone extends to the edges of the iframe. + isToBeIframed + ? ref.current?.parentNode + : ref.current + } /> diff --git a/packages/edit-post/src/store/actions.js b/packages/edit-post/src/store/actions.js index f7a5e211fd769c..27e45ab0edf9d6 100644 --- a/packages/edit-post/src/store/actions.js +++ b/packages/edit-post/src/store/actions.js @@ -630,7 +630,7 @@ export const toggleDistractionFree = registry.batch( () => { registry .dispatch( preferencesStore ) - .set( 'core/edit-post', 'fixedToolbar', false ); + .set( 'core/edit-post', 'fixedToolbar', true ); dispatch.setIsInserterOpened( false ); dispatch.setIsListViewOpened( false ); dispatch.closeGeneralSidebar(); diff --git a/packages/edit-post/src/store/test/actions.js b/packages/edit-post/src/store/test/actions.js index cfd3eecb39e5b8..76f527935cdf3e 100644 --- a/packages/edit-post/src/store/test/actions.js +++ b/packages/edit-post/src/store/test/actions.js @@ -375,7 +375,7 @@ describe( 'actions', () => { registry .select( preferencesStore ) .get( 'core/edit-post', 'fixedToolbar' ) - ).toBe( false ); + ).toBe( true ); expect( registry.select( editPostStore ).isListViewOpened() ).toBe( false ); diff --git a/packages/edit-post/src/style.scss b/packages/edit-post/src/style.scss index 60544e7fc3a8ab..e015d084afae1b 100644 --- a/packages/edit-post/src/style.scss +++ b/packages/edit-post/src/style.scss @@ -10,12 +10,9 @@ @import "./components/secondary-sidebar/style.scss"; @import "./components/sidebar/style.scss"; @import "./components/sidebar/last-revision/style.scss"; -@import "./components/sidebar/post-author/style.scss"; @import "./components/sidebar/post-format/style.scss"; -@import "./components/sidebar/post-schedule/style.scss"; @import "./components/sidebar/post-slug/style.scss"; @import "./components/sidebar/post-template/style.scss"; -@import "./components/sidebar/post-url/style.scss"; @import "./components/sidebar/post-visibility/style.scss"; @import "./components/sidebar/settings-header/style.scss"; @import "./components/sidebar/template-summary/style.scss"; diff --git a/packages/edit-site/CHANGELOG.md b/packages/edit-site/CHANGELOG.md index ad970d703cb43e..b8814356b2b6b4 100644 --- a/packages/edit-site/CHANGELOG.md +++ b/packages/edit-site/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.23.0 (2023-11-16) + ## 5.22.0 (2023-11-02) ## 5.21.0 (2023-10-18) diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json index 03d1ea851ff9a8..072dc0b1c027a8 100644 --- a/packages/edit-site/package.json +++ b/packages/edit-site/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-site", - "version": "5.22.0", + "version": "5.23.0", "description": "Edit Site Page module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -30,6 +30,7 @@ "@tanstack/react-table": "^8.10.3", "@wordpress/a11y": "file:../a11y", "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", "@wordpress/block-editor": "file:../block-editor", "@wordpress/block-library": "file:../block-library", "@wordpress/blocks": "file:../blocks", @@ -70,7 +71,6 @@ "classnames": "^2.3.1", "colord": "^2.9.2", "deepmerge": "^4.3.0", - "downloadjs": "^1.4.7", "fast-deep-equal": "^3.1.3", "is-plain-object": "^5.0.0", "memize": "^2.1.0", diff --git a/packages/edit-site/src/components/block-editor/block-editor-provider/default-block-editor-provider.js b/packages/edit-site/src/components/block-editor/block-editor-provider/default-block-editor-provider.js deleted file mode 100644 index c91c687ce5284c..00000000000000 --- a/packages/edit-site/src/components/block-editor/block-editor-provider/default-block-editor-provider.js +++ /dev/null @@ -1,75 +0,0 @@ -/** - * WordPress dependencies - */ -import { useEntityBlockEditor } from '@wordpress/core-data'; -import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; -import { useSelect } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import { store as editSiteStore } from '../../../store'; -import { unlock } from '../../../lock-unlock'; -import useSiteEditorSettings from '../use-site-editor-settings'; -import usePageContentBlocks from './use-page-content-blocks'; - -const { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis ); - -const noop = () => {}; - -/** - * The default block editor provider for the site editor. Typically used when - * the post type is `'wp_template_part'` or `'wp_template'` and allows editing - * of the template and its nested entities. - * - * If the page content focus type is `'hideTemplate'`, the provider will provide - * a set of page content blocks wrapped in a container that, together, - * mimic the look and feel of the post editor and - * allow editing of the page content only. - * - * @param {Object} props - * @param {Element} props.children - */ -export default function DefaultBlockEditorProvider( { children } ) { - const settings = useSiteEditorSettings(); - - const { templateType, isTemplateHidden } = useSelect( ( select ) => { - const { getEditedPostType } = select( editSiteStore ); - const { getPageContentFocusType, getCanvasMode } = unlock( - select( editSiteStore ) - ); - return { - templateType: getEditedPostType(), - isTemplateHidden: - getCanvasMode() === 'edit' && - getPageContentFocusType() === 'hideTemplate', - canvasMode: unlock( select( editSiteStore ) ).getCanvasMode(), - }; - }, [] ); - - const [ blocks, onInput, onChange ] = useEntityBlockEditor( - 'postType', - templateType - ); - const pageContentBlocks = usePageContentBlocks( { - blocks, - isPageContentFocused: isTemplateHidden, - wrapPageContent: true, - } ); - - return ( - - { children } - - ); -} diff --git a/packages/edit-site/src/components/block-editor/block-editor-provider/index.js b/packages/edit-site/src/components/block-editor/block-editor-provider/index.js deleted file mode 100644 index ab07a3fefc5d31..00000000000000 --- a/packages/edit-site/src/components/block-editor/block-editor-provider/index.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * WordPress dependencies - */ -import { useSelect } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import { store as editSiteStore } from '../../../store'; -import DefaultBlockEditorProvider from './default-block-editor-provider'; -import NavigationBlockEditorProvider from './navigation-block-editor-provider'; -import { NAVIGATION_POST_TYPE } from '../../../utils/constants'; - -export default function BlockEditorProvider( { children } ) { - const entityType = useSelect( - ( select ) => select( editSiteStore ).getEditedPostType(), - [] - ); - if ( entityType === NAVIGATION_POST_TYPE ) { - return ( - - { children } - - ); - } - return ( - { children } - ); -} diff --git a/packages/edit-site/src/components/block-editor/block-editor-provider/navigation-block-editor-provider.js b/packages/edit-site/src/components/block-editor/block-editor-provider/navigation-block-editor-provider.js deleted file mode 100644 index 9927cbb040c7bc..00000000000000 --- a/packages/edit-site/src/components/block-editor/block-editor-provider/navigation-block-editor-provider.js +++ /dev/null @@ -1,114 +0,0 @@ -/** - * WordPress dependencies - */ -import { useSelect, useDispatch } from '@wordpress/data'; -import { useMemo, useEffect } from '@wordpress/element'; -import { useEntityId } from '@wordpress/core-data'; -import { - store as blockEditorStore, - privateApis as blockEditorPrivateApis, -} from '@wordpress/block-editor'; -import { createBlock } from '@wordpress/blocks'; - -/** - * Internal dependencies - */ -import { unlock } from '../../../lock-unlock'; -import useSiteEditorSettings from '../use-site-editor-settings'; -import { store as editSiteStore } from '../../../store'; -import { NAVIGATION_POST_TYPE } from '../../../utils/constants'; - -const { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis ); - -const noop = () => {}; - -/** - * Block editor component for editing navigation menus. - * - * Note: Navigation entities require a wrapping Navigation block to provide - * them with some basic layout and styling. Therefore we create a "ghost" block - * and provide it will a reference to the navigation entity ID being edited. - * - * In this scenario it is the **block** that handles syncing the entity content - * whereas for other entities this is handled by entity block editor. - * - * @param {number} navigationMenuId the navigation menu ID - * @return {[WPBlock[], Function, Function]} The block array and setters. - */ -export default function NavigationBlockEditorProvider( { children } ) { - const defaultSettings = useSiteEditorSettings(); - - const navigationMenuId = useEntityId( 'postType', NAVIGATION_POST_TYPE ); - - const blocks = useMemo( () => { - return [ - createBlock( 'core/navigation', { - ref: navigationMenuId, - // As the parent editor is locked with `templateLock`, the template locking - // must be explicitly "unset" on the block itself to allow the user to modify - // the block's content. - templateLock: false, - } ), - ]; - }, [ navigationMenuId ] ); - - const { isEditMode } = useSelect( ( select ) => { - const { getCanvasMode } = unlock( select( editSiteStore ) ); - - return { - isEditMode: getCanvasMode() === 'edit', - }; - }, [] ); - - const { selectBlock, setBlockEditingMode, unsetBlockEditingMode } = - useDispatch( blockEditorStore ); - - const navigationBlockClientId = blocks && blocks[ 0 ]?.clientId; - - const settings = useMemo( () => { - return { - ...defaultSettings, - // Lock the editor to allow the root ("ghost") Navigation block only. - templateLock: 'insert', - template: [ [ 'core/navigation', {}, [] ] ], - }; - }, [ defaultSettings ] ); - - // Auto-select the Navigation block when entering Navigation focus mode. - useEffect( () => { - if ( navigationBlockClientId && isEditMode ) { - selectBlock( navigationBlockClientId ); - } - }, [ navigationBlockClientId, isEditMode, selectBlock ] ); - - // Set block editing mode to contentOnly when entering Navigation focus mode. - // This ensures that non-content controls on the block will be hidden and thus - // the user can focus on editing the Navigation Menu content only. - useEffect( () => { - if ( ! navigationBlockClientId ) { - return; - } - - setBlockEditingMode( navigationBlockClientId, 'contentOnly' ); - - return () => { - unsetBlockEditingMode( navigationBlockClientId ); - }; - }, [ - navigationBlockClientId, - unsetBlockEditingMode, - setBlockEditingMode, - ] ); - - return ( - - { children } - - ); -} diff --git a/packages/edit-site/src/components/block-editor/block-editor-provider/test/use-page-content-blocks.js b/packages/edit-site/src/components/block-editor/block-editor-provider/test/use-page-content-blocks.js deleted file mode 100644 index 775aea0bdb391e..00000000000000 --- a/packages/edit-site/src/components/block-editor/block-editor-provider/test/use-page-content-blocks.js +++ /dev/null @@ -1,116 +0,0 @@ -/** - * External dependencies - */ -import { renderHook } from '@testing-library/react'; -/** - * WordPress dependencies - */ -import { createBlock } from '@wordpress/blocks'; - -/** - * Internal dependencies - */ -import usePageContentBlocks from '../use-page-content-blocks'; - -jest.mock( '@wordpress/blocks', () => { - return { - __esModule: true, - ...jest.requireActual( '@wordpress/blocks' ), - createBlock( name, attributes = {}, innerBlocks = [] ) { - return { - name, - attributes, - innerBlocks, - }; - }, - }; -} ); - -describe( 'usePageContentBlocks', () => { - const blocksList = [ - createBlock( 'core/group', {}, [ - createBlock( 'core/post-title' ), - createBlock( 'core/post-featured-image' ), - createBlock( 'core/query', {}, [ - createBlock( 'core/post-title' ), - createBlock( 'core/post-featured-image' ), - createBlock( 'core/post-content' ), - ] ), - createBlock( 'core/post-content' ), - ] ), - createBlock( 'core/query' ), - createBlock( 'core/paragraph' ), - createBlock( 'core/post-content' ), - ]; - it( 'should return empty array if `isPageContentFocused` is `false`', () => { - const { result } = renderHook( () => - usePageContentBlocks( { - blocks: blocksList, - isPageContentFocused: false, - } ) - ); - expect( result.current ).toEqual( [] ); - } ); - it( 'should return empty array if `blocks` is undefined', () => { - const { result } = renderHook( () => - usePageContentBlocks( { - blocks: undefined, - isPageContentFocused: true, - } ) - ); - expect( result.current ).toEqual( [] ); - } ); - it( 'should return empty array if `blocks` is an empty array', () => { - const { result } = renderHook( () => - usePageContentBlocks( { - blocks: [], - isPageContentFocused: true, - } ) - ); - expect( result.current ).toEqual( [] ); - } ); - it( 'should return new block list', () => { - const { result } = renderHook( () => - usePageContentBlocks( { - blocks: blocksList, - isPageContentFocused: true, - } ) - ); - expect( result.current ).toEqual( [ - createBlock( 'core/post-title' ), - createBlock( 'core/post-featured-image' ), - createBlock( 'core/post-content' ), - createBlock( 'core/post-content' ), - ] ); - } ); - it( 'should return new block list wrapped in a Group block', () => { - const { result } = renderHook( () => - usePageContentBlocks( { - blocks: blocksList, - isPageContentFocused: true, - wrapPageContent: true, - } ) - ); - expect( result.current ).toEqual( [ - { - name: 'core/group', - attributes: { - layout: { type: 'constrained' }, - style: { - spacing: { - margin: { - top: '4em', // Mimics the post editor. - }, - }, - }, - }, - innerBlocks: [ - createBlock( 'core/post-title' ), - createBlock( 'core/post-featured-image' ), - createBlock( 'core/post-content' ), - createBlock( 'core/post-content' ), - ], - }, - ] ); - } ); -} ); diff --git a/packages/edit-site/src/components/block-editor/block-editor-provider/use-page-content-blocks.js b/packages/edit-site/src/components/block-editor/block-editor-provider/use-page-content-blocks.js deleted file mode 100644 index dd05f90a56bbbb..00000000000000 --- a/packages/edit-site/src/components/block-editor/block-editor-provider/use-page-content-blocks.js +++ /dev/null @@ -1,90 +0,0 @@ -/** - * WordPress dependencies - */ -import { useMemo } from '@wordpress/element'; -import { createBlock } from '@wordpress/blocks'; - -/** - * Internal dependencies - */ -import { PAGE_CONTENT_BLOCK_TYPES } from '../../../utils/constants'; - -/** - * Helper method to iterate through all blocks, recursing into allowed inner blocks. - * Returns a flattened object of transformed blocks. - * - * @param {Array} blocks Blocks to flatten. - * @param {Function} transform Transforming function to be applied to each block. If transform returns `undefined`, the block is skipped. - * - * @return {Array} Flattened object. - */ -function flattenBlocks( blocks, transform ) { - const result = []; - for ( let i = 0; i < blocks.length; i++ ) { - // Since the Query Block could contain PAGE_CONTENT_BLOCK_TYPES block types, - // we skip it because we only want to render stand-alone page content blocks in the block list. - if ( [ 'core/query' ].includes( blocks[ i ].name ) ) { - continue; - } - const transformedBlock = transform( blocks[ i ] ); - if ( transformedBlock ) { - result.push( transformedBlock ); - } - result.push( ...flattenBlocks( blocks[ i ].innerBlocks, transform ) ); - } - - return result; -} - -/** - * Returns a memoized array of blocks that contain only page content blocks, - * surrounded by an optional group block to mimic the post editor. - * - * @param {Object} props The argument for the function. - * @param {Array} props.blocks Block list. - * @param {boolean} props.isPageContentFocused Whether the page content has focus (and the surrounding template is inert). If `true` return page content blocks. Default `false`. - * @param {boolean} props.wrapPageContent Whether to wrap the page content blocks in a group block to mimic the post editor. Default `false`. - * @return {Array} Page content blocks. - */ -export default function usePageContentBlocks( { - blocks = [], - isPageContentFocused = false, - wrapPageContent = false, -} ) { - return useMemo( () => { - if ( ! isPageContentFocused || ! blocks?.length ) { - return []; - } - - const innerBlocks = flattenBlocks( blocks, ( block ) => - PAGE_CONTENT_BLOCK_TYPES[ block.name ] - ? createBlock( block.name ) - : undefined - ); - - if ( ! innerBlocks.length ) { - return []; - } - - if ( ! wrapPageContent ) { - return innerBlocks; - } - - return [ - createBlock( - 'core/group', - { - layout: { type: 'constrained' }, - style: { - spacing: { - margin: { - top: '4em', // Mimics the post editor. - }, - }, - }, - }, - innerBlocks - ), - ]; - }, [ blocks, isPageContentFocused ] ); -} diff --git a/packages/edit-site/src/components/block-editor/index.js b/packages/edit-site/src/components/block-editor/index.js deleted file mode 100644 index 2c635ff860a5bd..00000000000000 --- a/packages/edit-site/src/components/block-editor/index.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * WordPress dependencies - */ -import { BlockInspector } from '@wordpress/block-editor'; -import { privateApis as editPatternsPrivateApis } from '@wordpress/patterns'; - -/** - * Internal dependencies - */ -import TemplatePartConverter from '../template-part-converter'; -import { SidebarInspectorFill } from '../sidebar-edit-mode'; -import SiteEditorCanvas from './site-editor-canvas'; -import BlockEditorProvider from './block-editor-provider'; - -import { unlock } from '../../lock-unlock'; -const { PatternsMenuItems } = unlock( editPatternsPrivateApis ); -export default function BlockEditor() { - return ( - - - - - - - - - ); -} diff --git a/packages/edit-site/src/components/block-editor/site-editor-canvas.js b/packages/edit-site/src/components/block-editor/site-editor-canvas.js index e715587891c0a5..643fa84559f60e 100644 --- a/packages/edit-site/src/components/block-editor/site-editor-canvas.js +++ b/packages/edit-site/src/components/block-editor/site-editor-canvas.js @@ -134,6 +134,12 @@ export default function SiteEditorCanvas() { isTemplateTypeNavigation, } ) } + dropZoneElement={ + // Pass in the html element of the iframe to ensure that + // the drop zone extends to the very edges of the iframe, + // even if the template is shorter than the viewport. + contentRef.current?.parentNode + } layout={ LAYOUT } renderAppender={ showBlockAppender } /> diff --git a/packages/edit-site/src/components/block-editor/use-site-editor-settings.js b/packages/edit-site/src/components/block-editor/use-site-editor-settings.js index f597abee0726d6..cb3fb3f1cb3336 100644 --- a/packages/edit-site/src/components/block-editor/use-site-editor-settings.js +++ b/packages/edit-site/src/components/block-editor/use-site-editor-settings.js @@ -87,7 +87,7 @@ function useArchiveLabel( templateSlug ) { ); } -export default function useSiteEditorSettings() { +export function useSpecificEditorSettings() { const { setIsInserterOpened } = useDispatch( editSiteStore ); const { templateSlug, @@ -97,8 +97,6 @@ export default function useSiteEditorSettings() { keepCaretInsideBlock, canvasMode, settings, - postType, - postId, } = useSelect( ( select ) => { const { getEditedPostType, @@ -132,8 +130,6 @@ export default function useSiteEditorSettings() { ), canvasMode: getCanvasMode(), settings: getSettings(), - postType: usedPostType, - postId: usedPostId, }; }, [] ); const archiveLabels = useArchiveLabel( templateSlug ); @@ -164,5 +160,21 @@ export default function useSiteEditorSettings() { archiveLabels.archiveNameLabel, ] ); + return defaultEditorSettings; +} + +export default function useSiteEditorSettings() { + const defaultEditorSettings = useSpecificEditorSettings(); + const { postType, postId } = useSelect( ( select ) => { + const { getEditedPostType, getEditedPostId } = unlock( + select( editSiteStore ) + ); + const usedPostType = getEditedPostType(); + const usedPostId = getEditedPostId(); + return { + postType: usedPostType, + postId: usedPostId, + }; + }, [] ); return useBlockEditorSettings( defaultEditorSettings, postType, postId ); } diff --git a/packages/edit-site/src/components/dataviews/README.md b/packages/edit-site/src/components/dataviews/README.md index 5502165f0cfdc5..9c6725a39ead26 100644 --- a/packages/edit-site/src/components/dataviews/README.md +++ b/packages/edit-site/src/components/dataviews/README.md @@ -5,6 +5,7 @@ This file documents the DataViews UI component, which provides an API to render ```js item.id } isLoading={ isLoadingPages } view={ view } onChangeView={ onChangeView } @@ -44,10 +45,9 @@ Example: }, search: '', filters: [ - { field: 'author', operator: 'in', value: 2 }, - { field: 'status', operator: 'in', value: 'publish,draft' } + { field: 'author', operator: OPERATOR_IN, value: 2 }, + { field: 'status', operator: OPERATOR_IN, value: 'publish,draft' } ], - visibleFilters: [ 'author', 'status' ], hiddenFields: [ 'date', 'featured-image' ], layout: {}, } @@ -62,8 +62,7 @@ Example: - `filters`: the filters applied to the dataset. Each item describes: - `field`: which field this filter is bound to. - `operator`: which type of filter it is. Only `in` available at the moment. - - `vaule`: the actual value selected by the user. -- `visibleFilters`: the `id` of the filters that are visible in the UI. + - `value`: the actual value selected by the user. - `hiddenFields`: the `id` of the fields that are hidden in the UI. - `layout`: ... @@ -85,10 +84,9 @@ function MyCustomPageList() { }, search: '', filters: [ - { field: 'author', operator: 'in', value: 2 }, - { field: 'status', operator: 'in', value: 'publish,draft' } + { field: 'author', operator: OPERATOR_IN, value: 2 }, + { field: 'status', operator: OPERATOR_IN, value: 'publish,draft' } ], - visibleFilters: [ 'author', 'status' ], hiddenFields: [ 'date', 'featured-image' ], layout: {}, } ); @@ -96,10 +94,10 @@ function MyCustomPageList() { const queryArgs = useMemo( () => { const filters = {}; view.filters.forEach( ( filter ) => { - if ( filter.field === 'status' && filter.operator === 'in' ) { + if ( filter.field === 'status' && filter.operator === OPERATOR_IN ) { filters.status = filter.value; } - if ( filter.field === 'author' && filter.operator === 'in' ) { + if ( filter.field === 'author' && filter.operator === OPERATOR_IN ) { filters.author = filter.value; } } ); @@ -157,11 +155,11 @@ Example: { item.author } ); }, + type: ENUMERATION_TYPE, elements: [ { value: 1, label: 'Admin' } { value: 2, label: 'User' } ] - filters: [ 'in' ], enableSorting: false } ] @@ -172,7 +170,7 @@ Example: - `getValue`: function that returns the value of the field. - `render`: function that renders the field. - `elements`: the set of valid values for the field's value. -- `filters`: what filter operators are available for the user to use over this field. Only `in` available at the moment. +- `type`: the type of the field. Used to generate the proper filters. Only `enumeration` available at the moment. - `enableSorting`: whether the data can be sorted by the given field. True by default. - `enableHiding`: whether the field can be hidden. True by default. diff --git a/packages/edit-site/src/components/dataviews/add-filter.js b/packages/edit-site/src/components/dataviews/add-filter.js new file mode 100644 index 00000000000000..7192a507b2afe8 --- /dev/null +++ b/packages/edit-site/src/components/dataviews/add-filter.js @@ -0,0 +1,108 @@ +/** + * WordPress dependencies + */ +import { + privateApis as componentsPrivateApis, + Button, + Icon, +} from '@wordpress/components'; +import { chevronRightSmall, plus } from '@wordpress/icons'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { unlock } from '../../lock-unlock'; +import { ENUMERATION_TYPE, OPERATOR_IN } from './constants'; + +const { + DropdownMenuV2, + DropdownSubMenuV2, + DropdownSubMenuTriggerV2, + DropdownMenuItemV2, +} = unlock( componentsPrivateApis ); + +export default function AddFilter( { fields, view, onChangeView } ) { + const filters = []; + fields.forEach( ( field ) => { + if ( ! field.type ) { + return; + } + + switch ( field.type ) { + case ENUMERATION_TYPE: + filters.push( { + field: field.id, + name: field.header, + elements: field.elements || [], + isVisible: view.filters.some( + ( f ) => + f.field === field.id && f.operator === OPERATOR_IN + ), + } ); + } + } ); + + if ( filters.length === 0 ) { + return null; + } + + return ( + + + { __( 'Add filter' ) } + + } + > + { filters.map( ( filter ) => { + if ( filter.isVisible ) { + return null; + } + + return ( + } + > + { filter.name } + + } + > + { filter.elements.map( ( element ) => ( + { + onChangeView( ( currentView ) => ( { + ...currentView, + page: 1, + filters: [ + ...currentView.filters, + { + field: filter.field, + operator: OPERATOR_IN, + value: element.value, + }, + ], + } ) ); + } } + role="menuitemcheckbox" + > + { element.label } + + ) ) } + + ); + } ) } + + ); +} diff --git a/packages/edit-site/src/components/dataviews/constants.js b/packages/edit-site/src/components/dataviews/constants.js new file mode 100644 index 00000000000000..2af12b04c559d7 --- /dev/null +++ b/packages/edit-site/src/components/dataviews/constants.js @@ -0,0 +1,5 @@ +// Field types. +export const ENUMERATION_TYPE = 'enumeration'; + +// Filter operators. +export const OPERATOR_IN = 'in'; diff --git a/packages/edit-site/src/components/dataviews/dataviews.js b/packages/edit-site/src/components/dataviews/dataviews.js index 40bc87bed200c4..682060203de7c3 100644 --- a/packages/edit-site/src/components/dataviews/dataviews.js +++ b/packages/edit-site/src/components/dataviews/dataviews.js @@ -41,6 +41,7 @@ export default function DataViews( { searchLabel = undefined, actions, data, + getItemId, isLoading = false, paginationInfo, supportedLayouts, @@ -70,7 +71,7 @@ export default function DataViews( { onChangeView={ onChangeView } /> - + { - if ( ! field.filters ) { + if ( ! field.type ) { return; } - field.filters.forEach( ( filter ) => { - if ( VALID_OPERATORS.some( ( operator ) => operator === filter ) ) { - filtersRegistered.push( { + switch ( field.type ) { + case ENUMERATION_TYPE: + filters.push( { field: field.id, name: field.header, - operator: filter, - elements: [ - { - value: '', - label: __( 'All' ), - }, - ...( field.elements || [] ), - ], + elements: field.elements || [], + isVisible: view.filters.some( + ( f ) => + f.field === field.id && f.operator === OPERATOR_IN + ), } ); - } - } ); + } } ); - const visibleFilters = view.visibleFilters - ?.map( ( fieldName ) => { - const visibleFiltersForField = filtersRegistered.filter( - ( f ) => f.field === fieldName - ); + const filterComponents = filters.map( ( filter ) => { + if ( ! filter.isVisible ) { + return null; + } - if ( visibleFiltersForField.length === 0 ) { - return null; - } + return ( + + ); + } ); - return visibleFiltersForField.map( ( filter ) => { - if ( OPERATOR_IN === filter.operator ) { - return ( - - ); - } - return null; - } ); - } ) - .filter( Boolean ); + filterComponents.push( + + ); - if ( visibleFilters?.length > 0 ) { - visibleFilters.push( + if ( filterComponents.length > 1 ) { + filterComponents.push( { - const valueFound = view.filters.find( - ( f ) => f.field === filter.field && f.operator === OPERATOR_IN + const filterInView = view.filters.find( ( f ) => f.field === filter.field ); + const activeElement = filter.elements.find( + ( element ) => element.value === filterInView?.value ); - const activeValue = - ! valueFound || ! valueFound.hasOwnProperty( 'value' ) - ? '' - : valueFound.value; - - const id = `dataviews__filters-in-${ filter.field }`; - return ( - - { sprintf( - /* translators: filter name. */ - __( '%s:' ), - filter.name - ) } - + + { activeElement !== undefined + ? sprintf( + /* translators: 1: Filter name. 2: filter value. e.g.: "Author is Admin". */ + __( '%1$s is %2$s' ), + filter.name, + activeElement.label + ) + : filter.name } + + } - options={ filter.elements } - onChange={ ( value ) => { - const filters = view.filters.filter( - ( f ) => - f.field !== filter.field || f.operator !== OPERATOR_IN + > + { filter.elements.map( ( element ) => { + return ( + + onChangeView( ( currentView ) => ( { + ...currentView, + page: 1, + filters: [ + ...view.filters.filter( + ( f ) => f.field !== filter.field + ), + { + field: filter.field, + operator: OPERATOR_IN, + value: + activeElement?.value === + element.value + ? undefined + : element.value, + }, + ], + } ) ) + } + > + { element.label } + ); - if ( value !== '' ) { - filters.push( { - field: filter.field, - operator: OPERATOR_IN, - value, - } ); - } - - onChangeView( ( currentView ) => ( { - ...currentView, - page: 1, - filters, - } ) ); - } } - /> + } ) } + ); }; diff --git a/packages/edit-site/src/components/dataviews/reset-filters.js b/packages/edit-site/src/components/dataviews/reset-filters.js index 68b5c17590c977..d78c06624087a7 100644 --- a/packages/edit-site/src/components/dataviews/reset-filters.js +++ b/packages/edit-site/src/components/dataviews/reset-filters.js @@ -1,26 +1,26 @@ /** * WordPress dependencies */ -import { BaseControl, Button } from '@wordpress/components'; +import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; export default ( { view, onChangeView } ) => { return ( - - - + ); }; diff --git a/packages/edit-site/src/components/dataviews/search.js b/packages/edit-site/src/components/dataviews/search.js index 3ade147922ac99..17a882637a7183 100644 --- a/packages/edit-site/src/components/dataviews/search.js +++ b/packages/edit-site/src/components/dataviews/search.js @@ -31,6 +31,7 @@ export default function Search( { label, view, onChangeView } ) { const searchLabel = label || __( 'Filter list' ); return ( + } > diff --git a/packages/edit-site/src/components/dataviews/view-grid.js b/packages/edit-site/src/components/dataviews/view-grid.js index d319a25579f1a8..b9d4bd78d96d68 100644 --- a/packages/edit-site/src/components/dataviews/view-grid.js +++ b/packages/edit-site/src/components/dataviews/view-grid.js @@ -14,7 +14,7 @@ import { */ import ItemActions from './item-actions'; -export function ViewGrid( { data, fields, view, actions } ) { +export function ViewGrid( { data, fields, view, actions, getItemId } ) { const mediaField = fields.find( ( field ) => field.id === view.layout.mediaField ); @@ -27,7 +27,7 @@ export function ViewGrid( { data, fields, view, actions } ) { { data.map( ( item, index ) => { return ( - +
{ mediaField?.render( { item, view } ) || ( 0 && - header.column.columnDef.filters.some( - ( f ) => 'string' === typeof f && f === 'in' - ) - ) { + if ( header.column.columnDef.type === ENUMERATION_TYPE ) { filter = { field: header.column.columnDef.id, - elements: [ - { - value: '', - label: __( 'All' ), - }, - ...( header.column.columnDef.elements || [] ), - ], + elements: header.column.columnDef.elements || [], }; } const isFilterable = !! filter; @@ -169,11 +160,6 @@ function HeaderMenu( { dataView, header } ) { )[ 0 ] === filter.field ); - // Set the empty item as active if the filter is not set. - if ( ! columnFilter && element.value === '' ) { - isActive = true; - } - if ( columnFilter ) { const value = Object.values( columnFilter )[ 0 ]; @@ -202,24 +188,21 @@ function HeaderMenu( { dataView, header } ) { return ( field !== filter.field || - operator !== 'in' + operator !== + OPERATOR_IN ); } ); - if ( element.value === '' ) { - dataView.setColumnFilters( - otherFilters - ); - } else { - dataView.setColumnFilters( [ - ...otherFilters, - { - [ filter.field + - ':in' ]: element.value, - }, - ] ); - } + dataView.setColumnFilters( [ + ...otherFilters, + { + [ filter.field + ':in' ]: + isActive + ? undefined + : element.value, + }, + ] ); } } > { element.label } @@ -251,6 +234,7 @@ function ViewList( { fields, actions, data, + getItemId, isLoading = false, paginationInfo, } ) { @@ -372,6 +356,7 @@ function ViewList( { }, columnVisibility: columnVisibility ?? EMPTY_OBJECT, }, + getRowId: getItemId, onSortingChange: ( sortingUpdater ) => { onChangeView( ( currentView ) => { const sort = @@ -490,7 +475,7 @@ function ViewList( {
{ row.getVisibleCells().map( ( cell ) => (
{ const { getEditedPostContext, @@ -100,14 +113,24 @@ export default function Editor( { listViewToggleElement, isLoading } ) { isInserterOpened, isListViewOpened, hasPageContentFocus: _hasPageContentFocus, + getPageContentFocusType, } = unlock( select( editSiteStore ) ); const { __unstableGetEditorMode } = select( blockEditorStore ); const { getActiveComplementaryArea } = select( interfaceStore ); + const { getEntityRecord } = select( coreDataStore ); + const _context = getEditedPostContext(); // The currently selected entity to display. // Typically template or template part in the site editor. return { - context: getEditedPostContext(), + context: _context, + contextPost: _context?.postId + ? getEntityRecord( + 'postType', + _context.postType, + _context.postId + ) + : undefined, editorMode: getEditorMode(), canvasMode: getCanvasMode(), blockEditorMode: __unstableGetEditorMode(), @@ -125,6 +148,7 @@ export default function Editor( { listViewToggleElement, isLoading } ) { 'showBlockBreadcrumbs' ), hasPageContentFocus: _hasPageContentFocus(), + pageContentFocusType: getPageContentFocusType(), }; }, [] ); @@ -141,18 +165,7 @@ export default function Editor( { listViewToggleElement, isLoading } ) { const secondarySidebarLabel = isListViewOpen ? __( 'List View' ) : __( 'Block Library' ); - const blockContext = useMemo( () => { - const { postType, postId, ...nonPostFields } = context ?? {}; - - return { - ...( hasPageContentFocus ? context : nonPostFields ), - // Ideally this context should be removed. However, it is currently used by the Query Loop block. - templateSlug: - editedPostType === TEMPLATE_POST_TYPE - ? editedPost.slug - : undefined, - }; - }, [ editedPost.slug, editedPostType, hasPageContentFocus, context ] ); + const postWithTemplate = context?.postId; let title; if ( hasLoadedPost ) { @@ -174,110 +187,131 @@ export default function Editor( { listViewToggleElement, isLoading } ) { 'edit-site-editor__loading-progress' ); - const contentProps = isLoading - ? { - 'aria-busy': 'true', - 'aria-describedby': loadingProgressId, - } - : undefined; + const settings = useSpecificEditorSettings(); + const isReady = + ! isLoading && + ( ( postWithTemplate && !! contextPost && !! editedPost ) || + ( ! postWithTemplate && !! editedPost ) ); + const mode = useMemo( () => { + if ( isViewMode ) { + return postWithTemplate ? 'template-locked' : 'all'; + } + + if ( isEditMode && pageContentFocusType === 'hideTemplate' ) { + return 'post-only'; + } + + if ( postWithTemplate && hasPageContentFocus ) { + return 'template-locked'; + } + + if ( postWithTemplate && ! hasPageContentFocus ) { + return 'template-only'; + } + + return 'all'; + }, [ + isViewMode, + isEditMode, + postWithTemplate, + pageContentFocusType, + hasPageContentFocus, + ] ); return ( <> - { isLoading ? : null } + { ! isReady ? : null } { isEditMode && } - - + { __( + "You attempted to edit an item that doesn't exist. Perhaps it was deleted?" + ) } + + ) } + { isReady && ( + - - - { isEditMode && } - } - content={ - <> - - { isEditMode && } - { showVisualEditor && editedPost && ( - <> - - - - - ) } - { editorMode === 'text' && - editedPost && - isEditMode && } - { hasLoadedPost && ! editedPost && ( - - { __( - "You attempted to edit an item that doesn't exist. Perhaps it was deleted?" - ) } - - ) } - { isEditMode && ( - - ) } - - } - contentProps={ contentProps } - secondarySidebar={ - isEditMode && - ( ( shouldShowInserter && ( - - ) ) || - ( shouldShowListView && ( - - ) ) ) + + { isEditMode && } + } + content={ + <> + + { isEditMode && } + { showVisualEditor && ( <> - - + + + + + + + - ) - } - footer={ - shouldShowBlockBreadcrumbs && ( - + ) } + { isEditMode && } + + } + secondarySidebar={ + isEditMode && + ( ( shouldShowInserter && ) || + ( shouldShowListView && ( + - ) - } - labels={ { - ...interfaceLabels, - secondarySidebar: secondarySidebarLabel, - } } - /> - - - + ) ) ) + } + sidebar={ + isEditMode && + isRightSidebarOpen && ( + <> + + + + ) + } + footer={ + shouldShowBlockBreadcrumbs && ( + + ) + } + labels={ { + ...interfaceLabels, + secondarySidebar: secondarySidebarLabel, + } } + /> + + ) } ); } diff --git a/packages/edit-site/src/components/global-styles/screen-block-list.js b/packages/edit-site/src/components/global-styles/screen-block-list.js index 1db81377ff9a3b..b4adcd64e41361 100644 --- a/packages/edit-site/src/components/global-styles/screen-block-list.js +++ b/packages/edit-site/src/components/global-styles/screen-block-list.js @@ -9,7 +9,13 @@ import { __experimentalHStack as HStack, } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; -import { useState, useMemo, useEffect, useRef } from '@wordpress/element'; +import { + useState, + useEffect, + useRef, + useDeferredValue, + memo, +} from '@wordpress/element'; import { BlockIcon, privateApis as blockEditorPrivateApis, @@ -99,22 +105,16 @@ function BlockMenuItem( { block } ) { ); } -function ScreenBlockList() { +function BlockList( { filterValue } ) { const sortedBlockTypes = useSortedBlockTypes(); - const [ filterValue, setFilterValue ] = useState( '' ); const debouncedSpeak = useDebounce( speak, 500 ); - const isMatchingSearchTerm = useSelect( - ( select ) => select( blocksStore ).isMatchingSearchTerm, - [] - ); - const filteredBlockTypes = useMemo( () => { - if ( ! filterValue ) { - return sortedBlockTypes; - } - return sortedBlockTypes.filter( ( blockType ) => - isMatchingSearchTerm( blockType, filterValue ) - ); - }, [ filterValue, sortedBlockTypes, isMatchingSearchTerm ] ); + const { isMatchingSearchTerm } = useSelect( blocksStore ); + + const filteredBlockTypes = ! filterValue + ? sortedBlockTypes + : sortedBlockTypes.filter( ( blockType ) => + isMatchingSearchTerm( blockType, filterValue ) + ); const blockTypesListRef = useRef(); @@ -140,6 +140,27 @@ function ScreenBlockList() { debouncedSpeak( resultsFoundMessage, count ); }, [ filterValue, debouncedSpeak ] ); + return ( +
+ { filteredBlockTypes.map( ( block ) => ( + + ) ) } +
+ ); +} + +const MemoizedBlockList = memo( BlockList ); + +function ScreenBlockList() { + const [ filterValue, setFilterValue ] = useState( '' ); + const deferredFilterValue = useDeferredValue( filterValue ); + return ( <> -
- { filteredBlockTypes.map( ( block ) => ( - - ) ) } -
+ ); } diff --git a/packages/edit-site/src/components/global-styles/screen-revisions/index.js b/packages/edit-site/src/components/global-styles/screen-revisions/index.js index ca580edd12a2fa..d5c884f9d20cfe 100644 --- a/packages/edit-site/src/components/global-styles/screen-revisions/index.js +++ b/packages/edit-site/src/components/global-styles/screen-revisions/index.js @@ -121,7 +121,7 @@ function ScreenRevisions() { { isLoading && ( diff --git a/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js b/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js index feec0f25ac8823..2786bf6d791212 100644 --- a/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js +++ b/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js @@ -12,20 +12,28 @@ import { dateI18n, getDate, humanTimeDiff, getSettings } from '@wordpress/date'; import { store as coreStore } from '@wordpress/core-data'; import { useSelect } from '@wordpress/data'; +const DAY_IN_MILLISECONDS = 60 * 60 * 1000 * 24; + /** * Returns a button label for the revision. * - * @param {Object} revision A revision object. + * @param {string|number} id A revision object. + * @param {boolean} isLatest Whether the revision is the most current. + * @param {string} authorDisplayName Author name. + * @param {string} formattedModifiedDate Revision modified date formatted. * @return {string} Translated label. */ -function getRevisionLabel( revision ) { - const authorDisplayName = revision?.author?.name || __( 'User' ); - - if ( 'parent' === revision?.id ) { +function getRevisionLabel( + id, + isLatest, + authorDisplayName, + formattedModifiedDate +) { + if ( 'parent' === id ) { return __( 'Reset the styles to the theme defaults' ); } - if ( 'unsaved' === revision?.id ) { + if ( 'unsaved' === id ) { return sprintf( /* translators: %s author display name */ __( 'Unsaved changes by %s' ), @@ -33,23 +41,18 @@ function getRevisionLabel( revision ) { ); } - const formattedDate = dateI18n( - getSettings().formats.datetimeAbbreviated, - getDate( revision?.modified ) - ); - - return revision?.isLatest + return isLatest ? sprintf( /* translators: %1$s author display name, %2$s: revision creation date */ __( 'Changes saved by %1$s on %2$s (current)' ), authorDisplayName, - formattedDate + formattedModifiedDate ) : sprintf( /* translators: %1$s author display name, %2$s: revision creation date */ __( 'Changes saved by %1$s on %2$s' ), authorDisplayName, - formattedDate + formattedModifiedDate ); } @@ -65,10 +68,18 @@ function getRevisionLabel( revision ) { * @return {JSX.Element} The modal component. */ function RevisionsButtons( { userRevisions, selectedRevisionId, onChange } ) { - const currentTheme = useSelect( - ( select ) => select( coreStore ).getCurrentTheme(), - [] - ); + const { currentThemeName, currentUser } = useSelect( ( select ) => { + const { getCurrentTheme, getCurrentUser } = select( coreStore ); + const currentTheme = getCurrentTheme(); + return { + currentThemeName: + currentTheme?.name?.rendered || currentTheme?.stylesheet, + currentUser: getCurrentUser(), + }; + }, [] ); + const dateNowInMs = getDate().getTime(); + const { date: dateFormat, datetimeAbbreviated } = getSettings().formats; + return (
    { userRevisions.map( ( revision, index ) => { - const { id, author, modified } = revision; - const authorDisplayName = author?.name || __( 'User' ); - const authorAvatar = author?.avatar_urls?.[ '48' ]; - const isUnsaved = 'unsaved' === revision?.id; + const { id, isLatest, author, modified } = revision; + const isUnsaved = 'unsaved' === id; + // Unsaved changes are created by the current user. + const revisionAuthor = isUnsaved ? currentUser : author; + const authorDisplayName = revisionAuthor?.name || __( 'User' ); + const authorAvatar = revisionAuthor?.avatar_urls?.[ '48' ]; const isSelected = selectedRevisionId - ? selectedRevisionId === revision?.id + ? selectedRevisionId === id : index === 0; - const isReset = 'parent' === revision?.id; + const isReset = 'parent' === id; + const modifiedDate = getDate( modified ); + const displayDate = + modified && + dateNowInMs - modifiedDate.getTime() > DAY_IN_MILLISECONDS + ? dateI18n( dateFormat, modifiedDate ) + : humanTimeDiff( modified ); + const revisionLabel = getRevisionLabel( + id, + isLatest, + authorDisplayName, + dateI18n( datetimeAbbreviated, modifiedDate ) + ); return (
  1. { onChange( revision ); } } - label={ getRevisionLabel( revision ) } + label={ revisionLabel } > { isReset ? ( { __( 'Default styles' ) } - { currentTheme?.name?.rendered || - currentTheme?.stylesheet } + { currentThemeName } ) : ( - + { isUnsaved ? ( + + { __( '(Unsaved)' ) } + + ) : ( + + ) } - { isUnsaved - ? sprintf( - /* translators: %s author display name */ - __( - 'Unsaved changes by %s' - ), - authorDisplayName - ) - : sprintf( - /* translators: %s author display name */ - __( 'Changes saved by %s' ), - authorDisplayName - ) } - { + { authorDisplayName } ) } diff --git a/packages/edit-site/src/components/global-styles/screen-revisions/style.scss b/packages/edit-site/src/components/global-styles/screen-revisions/style.scss index 238f3f7d116e19..6598fcb5ce1c74 100644 --- a/packages/edit-site/src/components/global-styles/screen-revisions/style.scss +++ b/packages/edit-site/src/components/global-styles/screen-revisions/style.scss @@ -8,62 +8,80 @@ margin: 0; li { margin-bottom: 0; - border-left: 1px solid $gray-300; } } .edit-site-global-styles-screen-revisions__revision-item { position: relative; - padding: $grid-unit-10 0 $grid-unit-10 $grid-unit-15; + padding-left: $grid-unit-20; + overflow: hidden; + cursor: pointer; - &:first-child { - padding-top: 0; + &:hover { + background: rgba(var(--wp-admin-theme-color--rgb), 0.04); + .edit-site-global-styles-screen-revisions__date { + color: var(--wp-admin-theme-color); + } } - &:last-child { - padding-bottom: 0; + &::before, + &::after { + position: absolute; + content: "\a"; + display: block; } &::before { background: $gray-300; border-radius: 50%; - content: "\a"; - display: inline-block; height: $grid-unit-10; width: $grid-unit-10; - position: absolute; - top: 50%; - left: 0; + top: $grid-unit-20 + 2; + left: $grid-unit-20 + 1; // So the circle is centered on the line. transform: translate(-50%, -50%); + z-index: 1; } &.is-selected::before { background: var(--wp-components-color-accent, var(--wp-admin-theme-color, #007cba)); } -} -.edit-site-global-styles-screen-revisions__revision-button { - width: 100%; - height: auto; - display: block; - padding: $grid-unit-10 $grid-unit-15; + &::after { + height: 100%; + left: $grid-unit-20; + top: 0; + width: 0; + border: 0.5px solid $gray-300; + } - &:hover { - background: rgba(var(--wp-admin-theme-color--rgb), 0.04); + &:first-child::after { + top: $grid-unit-20 + 2; + } - .edit-site-global-styles-screen-revisions__date { - color: var(--wp-admin-theme-color); + &:last-child::after { + height: $grid-unit-20 + 2; + } + + // Nested to override specificity of .components-button. + .edit-site-global-styles-screen-revisions__revision-button { + width: 100%; + height: auto; + display: block; + padding: $grid-unit-15 $grid-unit-15 $grid-unit-15 $grid-unit-30; + &:focus, + &:active { + outline: 0; + box-shadow: none; } } } .is-selected { + color: var(--wp-components-color-accent, var(--wp-admin-theme-color, #007cba)); + background: rgba(var(--wp-admin-theme-color--rgb), 0.04); .edit-site-global-styles-screen-revisions__revision-button { - color: var(--wp-components-color-accent, var(--wp-admin-theme-color, #007cba)); opacity: 1; - background: rgba(var(--wp-admin-theme-color--rgb), 0.04); } - - .edit-site-global-styles-screen-revisions__meta { + .edit-site-global-styles-screen-revisions__date { color: var(--wp-admin-theme-color); } } @@ -78,20 +96,26 @@ flex-direction: column; align-items: flex-start; gap: $grid-unit-10; + .edit-site-global-styles-screen-revisions__date { + text-transform: uppercase; + font-weight: 600; + font-size: 12px; + } } .edit-site-global-styles-screen-revisions__meta { - color: $gray-700; + color: $gray-600; display: flex; - justify-content: space-between; + justify-content: start; width: 100%; align-items: center; - text-align: left; + font-size: 12px; img { width: $grid-unit-20; height: $grid-unit-20; border-radius: 100%; + margin-right: $grid-unit-10; } } diff --git a/packages/edit-site/src/components/global-styles/screen-revisions/use-global-styles-revisions.js b/packages/edit-site/src/components/global-styles/screen-revisions/use-global-styles-revisions.js index 6e3573061a4214..0c293e417cf8ef 100644 --- a/packages/edit-site/src/components/global-styles/screen-revisions/use-global-styles-revisions.js +++ b/packages/edit-site/src/components/global-styles/screen-revisions/use-global-styles-revisions.js @@ -32,14 +32,22 @@ export default function useGlobalStylesRevisions() { __experimentalGetDirtyEntityRecords, getCurrentUser, getUsers, - getCurrentThemeGlobalStylesRevisions, + getRevisions, + __experimentalGetCurrentGlobalStylesId, isResolving, } = select( coreStore ); const dirtyEntityRecords = __experimentalGetDirtyEntityRecords(); const _currentUser = getCurrentUser(); const _isDirty = dirtyEntityRecords.length > 0; const globalStylesRevisions = - getCurrentThemeGlobalStylesRevisions() || EMPTY_ARRAY; + getRevisions( + 'root', + 'globalStyles', + __experimentalGetCurrentGlobalStylesId(), + { + per_page: 100, + } + ) || EMPTY_ARRAY; const _authors = getUsers( SITE_EDITOR_AUTHORS_QUERY ) || EMPTY_ARRAY; return { diff --git a/packages/edit-site/src/components/header-edit-mode/more-menu/index.js b/packages/edit-site/src/components/header-edit-mode/more-menu/index.js index 4b9d6c40fc6eec..2185258ad338a6 100644 --- a/packages/edit-site/src/components/header-edit-mode/more-menu/index.js +++ b/packages/edit-site/src/components/header-edit-mode/more-menu/index.js @@ -52,7 +52,7 @@ export default function MoreMenu( { showIconLabels } ) { const toggleDistractionFree = () => { registry.batch( () => { - setPreference( 'core/edit-site', 'fixedToolbar', false ); + setPreference( 'core/edit-site', 'fixedToolbar', true ); setIsInserterOpened( false ); setIsListViewOpened( false ); closeGeneralSidebar(); diff --git a/packages/edit-site/src/components/header-edit-mode/more-menu/site-export.js b/packages/edit-site/src/components/header-edit-mode/more-menu/site-export.js index ec9492081a42b3..85ec4f0dd7335e 100644 --- a/packages/edit-site/src/components/header-edit-mode/more-menu/site-export.js +++ b/packages/edit-site/src/components/header-edit-mode/more-menu/site-export.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import downloadjs from 'downloadjs'; - /** * WordPress dependencies */ @@ -11,6 +6,7 @@ import { MenuItem } from '@wordpress/components'; import apiFetch from '@wordpress/api-fetch'; import { download } from '@wordpress/icons'; import { useDispatch } from '@wordpress/data'; +import { downloadBlob } from '@wordpress/blob'; import { store as noticesStore } from '@wordpress/notices'; export default function SiteExport() { @@ -35,7 +31,7 @@ export default function SiteExport() { ? contentDispositionMatches[ 1 ] : 'edit-site-export'; - downloadjs( blob, fileName + '.zip', 'application/zip' ); + downloadBlob( fileName + '.zip', blob, 'application/zip' ); } catch ( errorResponse ) { let error = {}; try { diff --git a/packages/edit-site/src/components/list/added-by.js b/packages/edit-site/src/components/list/added-by.js index da6111e0b29bd5..e9c8df0fa7f263 100644 --- a/packages/edit-site/src/components/list/added-by.js +++ b/packages/edit-site/src/components/list/added-by.js @@ -152,7 +152,7 @@ export function useAddedBy( postType, postId ) { * @param {Object} props * @param {string} props.imageUrl */ -function AvatarImage( { imageUrl } ) { +export function AvatarImage( { imageUrl } ) { const [ isImageLoaded, setIsImageLoaded ] = useState( false ); return ( diff --git a/packages/edit-site/src/components/page-content-focus-manager/back-to-page-notification.js b/packages/edit-site/src/components/page-content-focus-manager/back-to-page-notification.js index a2990c56a673cf..9bf9ac33b1d198 100644 --- a/packages/edit-site/src/components/page-content-focus-manager/back-to-page-notification.js +++ b/packages/edit-site/src/components/page-content-focus-manager/back-to-page-notification.js @@ -25,27 +25,19 @@ export default function BackToPageNotification() { * switches from focusing on editing page content to editing a template. */ export function useBackToPageNotification() { - const { isPage, hasPageContentFocus } = useSelect( - ( select ) => ( { - isPage: select( editSiteStore ).isPage(), - hasPageContentFocus: select( editSiteStore ).hasPageContentFocus(), - } ), + const hasPageContentFocus = useSelect( + ( select ) => select( editSiteStore ).hasPageContentFocus(), [] ); + const { isPage } = useSelect( editSiteStore ); const alreadySeen = useRef( false ); - const prevHasPageContentFocus = useRef( false ); const { createInfoNotice } = useDispatch( noticesStore ); const { setHasPageContentFocus } = useDispatch( editSiteStore ); useEffect( () => { - if ( - ! alreadySeen.current && - isPage && - prevHasPageContentFocus.current && - ! hasPageContentFocus - ) { + if ( isPage() && ! alreadySeen.current && ! hasPageContentFocus ) { createInfoNotice( __( 'You are editing a template.' ), { isDismissible: true, type: 'snackbar', @@ -58,11 +50,8 @@ export function useBackToPageNotification() { } ); alreadySeen.current = true; } - prevHasPageContentFocus.current = hasPageContentFocus; }, [ - alreadySeen, isPage, - prevHasPageContentFocus, hasPageContentFocus, createInfoNotice, setHasPageContentFocus, diff --git a/packages/edit-site/src/components/page-content-focus-manager/disable-non-page-content-blocks.js b/packages/edit-site/src/components/page-content-focus-manager/disable-non-page-content-blocks.js index f3e021ba885244..2f81f80d0ce63d 100644 --- a/packages/edit-site/src/components/page-content-focus-manager/disable-non-page-content-blocks.js +++ b/packages/edit-site/src/components/page-content-focus-manager/disable-non-page-content-blocks.js @@ -1,9 +1,11 @@ /** * WordPress dependencies */ -import { createHigherOrderComponent } from '@wordpress/compose'; -import { addFilter, removeFilter } from '@wordpress/hooks'; -import { useBlockEditingMode } from '@wordpress/block-editor'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { + useBlockEditingMode, + store as blockEditorStore, +} from '@wordpress/block-editor'; import { useEffect } from '@wordpress/element'; /** @@ -11,42 +13,45 @@ import { useEffect } from '@wordpress/element'; */ import { PAGE_CONTENT_BLOCK_TYPES } from '../../utils/constants'; +function DisableBlock( { clientId } ) { + const isDescendentOfQueryLoop = useSelect( + ( select ) => { + const { getBlockParentsByBlockName } = select( blockEditorStore ); + return ( + getBlockParentsByBlockName( clientId, 'core/query' ).length !== + 0 + ); + }, + [ clientId ] + ); + const mode = isDescendentOfQueryLoop ? undefined : 'contentOnly'; + const { setBlockEditingMode, unsetBlockEditingMode } = + useDispatch( blockEditorStore ); + useEffect( () => { + if ( mode ) { + setBlockEditingMode( clientId, mode ); + return () => { + unsetBlockEditingMode( clientId ); + }; + } + }, [ clientId, mode, setBlockEditingMode, unsetBlockEditingMode ] ); +} + /** * Component that when rendered, makes it so that the site editor allows only * page content to be edited. */ export default function DisableNonPageContentBlocks() { - useDisableNonPageContentBlocks(); - return null; -} - -/** - * Disables non-content blocks using the `useBlockEditingMode` hook. - */ -export function useDisableNonPageContentBlocks() { useBlockEditingMode( 'disabled' ); - useEffect( () => { - addFilter( - 'editor.BlockEdit', - 'core/edit-site/disable-non-content-blocks', - withDisableNonPageContentBlocks + const clientIds = useSelect( ( select ) => { + const { __experimentalGetGlobalBlocksByName } = + select( blockEditorStore ); + return __experimentalGetGlobalBlocksByName( + Object.keys( PAGE_CONTENT_BLOCK_TYPES ) ); - return () => - removeFilter( - 'editor.BlockEdit', - 'core/edit-site/disable-non-content-blocks' - ); }, [] ); -} -const withDisableNonPageContentBlocks = createHigherOrderComponent( - ( BlockEdit ) => ( props ) => { - const isDescendentOfQueryLoop = props.context.queryId !== undefined; - const isPageContent = - PAGE_CONTENT_BLOCK_TYPES[ props.name ] && ! isDescendentOfQueryLoop; - const mode = isPageContent ? 'contentOnly' : undefined; - useBlockEditingMode( mode ); - return ; - }, - 'withDisableNonPageContentBlocks' -); + return clientIds.map( ( clientId ) => { + return ; + } ); +} diff --git a/packages/edit-site/src/components/page-pages/index.js b/packages/edit-site/src/components/page-pages/index.js index c2461adc34f8d0..b933f1d82500e4 100644 --- a/packages/edit-site/src/components/page-pages/index.js +++ b/packages/edit-site/src/components/page-pages/index.js @@ -31,6 +31,7 @@ import { import SideEditor from './side-editor'; import Media from '../media'; import { unlock } from '../../lock-unlock'; +import { ENUMERATION_TYPE, OPERATOR_IN } from '../dataviews/constants'; const { useLocation } = unlock( routerPrivateApis ); const EMPTY_ARRAY = []; @@ -120,10 +121,16 @@ export default function PagePages() { const queryArgs = useMemo( () => { const filters = {}; view.filters.forEach( ( filter ) => { - if ( filter.field === 'status' && filter.operator === 'in' ) { + if ( + filter.field === 'status' && + filter.operator === OPERATOR_IN + ) { filters.status = filter.value; } - if ( filter.field === 'author' && filter.operator === 'in' ) { + if ( + filter.field === 'author' && + filter.operator === OPERATOR_IN + ) { filters.author = filter.value; } } ); @@ -227,7 +234,7 @@ export default function PagePages() { ); }, - filters: [ 'in' ], + type: ENUMERATION_TYPE, elements: authors?.map( ( { id, name } ) => ( { value: id, @@ -240,7 +247,7 @@ export default function PagePages() { getValue: ( { item } ) => STATUSES.find( ( { value } ) => value === item.status ) ?.label ?? item.status, - filters: [ 'in' ], + type: ENUMERATION_TYPE, elements: STATUSES, enableSorting: false, }, @@ -309,6 +316,7 @@ export default function PagePages() { fields={ fields } actions={ actions } data={ pages || EMPTY_ARRAY } + getItemId={ ( item ) => item.id } isLoading={ isLoadingPages || isLoadingAuthors } view={ view } onChangeView={ onChangeView } diff --git a/packages/edit-site/src/components/page-patterns/grid-item.js b/packages/edit-site/src/components/page-patterns/grid-item.js index 64b70fb87de62d..b394ef8eb6e76c 100644 --- a/packages/edit-site/src/components/page-patterns/grid-item.js +++ b/packages/edit-site/src/components/page-patterns/grid-item.js @@ -36,6 +36,7 @@ import { } from '@wordpress/icons'; import { store as noticesStore } from '@wordpress/notices'; import { store as reusableBlocksStore } from '@wordpress/reusable-blocks'; +import { downloadBlob } from '@wordpress/blob'; /** * Internal dependencies @@ -51,25 +52,6 @@ import { store as editSiteStore } from '../../store'; import { useLink } from '../routes/link'; import { unlock } from '../../lock-unlock'; -/** - * Downloads a file. - * Also used in packages/list-reusable-blocks/src/utils/file.js. - * - * @param {string} fileName File Name. - * @param {string} content File Content. - * @param {string} contentType File mime type. - */ -function download( fileName, content, contentType ) { - const file = new window.Blob( [ content ], { type: contentType } ); - const a = document.createElement( 'a' ); - a.href = URL.createObjectURL( file ); - a.download = fileName; - a.style.display = 'none'; - document.body.appendChild( a ); - a.click(); - document.body.removeChild( a ); -} - const { useGlobalStyle } = unlock( blockEditorPrivateApis ); const templatePartIcons = { header, footer, uncategorized }; @@ -136,7 +118,7 @@ function GridItem( { categoryId, item, ...props } ) { syncStatus: item.patternBlock.wp_pattern_sync_status, }; - return download( + return downloadBlob( `${ kebabCase( item.title || item.name ) }.json`, JSON.stringify( json, null, 2 ), 'application/json' diff --git a/packages/edit-site/src/components/page-patterns/use-patterns.js b/packages/edit-site/src/components/page-patterns/use-patterns.js index 9cb6c8b998c412..be5992bd9b4efe 100644 --- a/packages/edit-site/src/components/page-patterns/use-patterns.js +++ b/packages/edit-site/src/components/page-patterns/use-patterns.js @@ -159,7 +159,7 @@ const selectPatterns = createSelector( // User patterns can have their sync statuses checked directly // Non-user patterns are all unsynced for the time being. patterns = patterns.filter( ( pattern ) => { - return pattern.id + return pattern.type === PATTERN_TYPES.user ? pattern.syncStatus === syncStatus : syncStatus === PATTERN_SYNC_TYPES.unsynced; } ); diff --git a/packages/edit-site/src/components/page-templates/dataviews-templates.js b/packages/edit-site/src/components/page-templates/dataviews-templates.js index 8f8a052b79b948..39a9d1b60abad6 100644 --- a/packages/edit-site/src/components/page-templates/dataviews-templates.js +++ b/packages/edit-site/src/components/page-templates/dataviews-templates.js @@ -7,11 +7,13 @@ import removeAccents from 'remove-accents'; * WordPress dependencies */ import { + Icon, __experimentalHeading as Heading, __experimentalText as Text, + __experimentalHStack as HStack, __experimentalVStack as VStack, } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; +import { __, _x } from '@wordpress/i18n'; import { useState, useMemo, useCallback } from '@wordpress/element'; import { useEntityRecords } from '@wordpress/core-data'; import { decodeEntities } from '@wordpress/html-entities'; @@ -21,7 +23,7 @@ import { decodeEntities } from '@wordpress/html-entities'; */ import Page from '../page'; import Link from '../routes/link'; -import AddedBy from '../list/added-by'; +import { useAddedBy, AvatarImage } from '../list/added-by'; import { TEMPLATE_POST_TYPE } from '../../utils/constants'; import { DataViews } from '../dataviews'; import { @@ -47,6 +49,51 @@ function normalizeSearchInput( input = '' ) { return removeAccents( input.trim().toLowerCase() ); } +// TODO: these are going to be reused in the template part list. +// That's the reason for leaving the template parts code for now. +function TemplateTitle( { item } ) { + const { isCustomized } = useAddedBy( item.type, item.id ); + return ( + + + + { decodeEntities( item.title?.rendered || item.slug ) || + __( '(no title)' ) } + + + { isCustomized && ( + + { item.type === TEMPLATE_POST_TYPE + ? _x( 'Customized', 'template' ) + : _x( 'Customized', 'template part' ) } + + ) } + + ); +} + +function AuthorField( { item } ) { + const { text, icon, imageUrl } = useAddedBy( item.type, item.id ); + return ( + + { imageUrl ? ( + + ) : ( +
    + +
    + ) } + { text } +
    + ); +} + export default function DataviewsTemplates() { const [ view, setView ] = useState( DEFAULT_VIEW ); const { records: allTemplates, isResolving: isLoadingData } = @@ -110,25 +157,7 @@ export default function DataviewsTemplates() { header: __( 'Template' ), id: 'title', getValue: ( { item } ) => item.title?.rendered || item.slug, - render: ( { item } ) => { - return ( - - - - { decodeEntities( - item.title?.rendered || item.slug - ) || __( '(no title)' ) } - - - - ); - }, + render: ( { item } ) => , maxWidth: 400, enableHiding: false, }, @@ -145,17 +174,14 @@ export default function DataviewsTemplates() { ) ); }, + maxWidth: 200, enableSorting: false, }, { header: __( 'Author' ), id: 'author', getValue: () => {}, - render: ( { item } ) => { - return ( - - ); - }, + render: ( { item } ) => , enableHiding: false, enableSorting: false, }, @@ -188,6 +214,7 @@ export default function DataviewsTemplates() { fields={ fields } actions={ actions } data={ shownTemplates } + getItemId={ ( item ) => item.id } isLoading={ isLoadingData } view={ view } onChangeView={ onChangeView } diff --git a/packages/edit-site/src/components/preferences-modal/index.js b/packages/edit-site/src/components/preferences-modal/index.js index c8a8d103250f5b..8439661a7910b2 100644 --- a/packages/edit-site/src/components/preferences-modal/index.js +++ b/packages/edit-site/src/components/preferences-modal/index.js @@ -34,7 +34,7 @@ export default function EditSitePreferencesModal() { const { set: setPreference } = useDispatch( preferencesStore ); const toggleDistractionFree = () => { registry.batch( () => { - setPreference( 'core/edit-site', 'fixedToolbar', false ); + setPreference( 'core/edit-site', 'fixedToolbar', true ); setIsInserterOpened( false ); setIsListViewOpened( false ); closeGeneralSidebar(); diff --git a/packages/edit-site/src/components/save-button/index.js b/packages/edit-site/src/components/save-button/index.js index 455269f073437f..7be87e8956e7db 100644 --- a/packages/edit-site/src/components/save-button/index.js +++ b/packages/edit-site/src/components/save-button/index.js @@ -3,7 +3,7 @@ */ import { useSelect, useDispatch } from '@wordpress/data'; import { Button } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { store as coreStore } from '@wordpress/core-data'; import { displayShortcut } from '@wordpress/keycodes'; @@ -11,7 +11,10 @@ import { displayShortcut } from '@wordpress/keycodes'; * Internal dependencies */ import { store as editSiteStore } from '../../store'; -import { isPreviewingTheme } from '../../utils/is-previewing-theme'; +import { + currentlyPreviewingTheme, + isPreviewingTheme, +} from '../../utils/is-previewing-theme'; export default function SaveButton( { className = 'edit-site-save-button__button', @@ -21,24 +24,34 @@ export default function SaveButton( { icon, __next40pxDefaultSize = false, } ) { - const { isDirty, isSaving, isSaveViewOpen } = useSelect( ( select ) => { - const { - __experimentalGetDirtyEntityRecords, - isSavingEntityRecord, - isResolving, - } = select( coreStore ); - const dirtyEntityRecords = __experimentalGetDirtyEntityRecords(); - const { isSaveViewOpened } = select( editSiteStore ); - const isActivatingTheme = isResolving( 'activateTheme' ); - return { - isDirty: dirtyEntityRecords.length > 0, - isSaving: - dirtyEntityRecords.some( ( record ) => - isSavingEntityRecord( record.kind, record.name, record.key ) - ) || isActivatingTheme, - isSaveViewOpen: isSaveViewOpened(), - }; - }, [] ); + const { isDirty, isSaving, isSaveViewOpen, previewingThemeName } = + useSelect( ( select ) => { + const { + __experimentalGetDirtyEntityRecords, + isSavingEntityRecord, + isResolving, + } = select( coreStore ); + const dirtyEntityRecords = __experimentalGetDirtyEntityRecords(); + const { isSaveViewOpened } = select( editSiteStore ); + const isActivatingTheme = isResolving( 'activateTheme' ); + const previewingTheme = select( coreStore ).getTheme( + currentlyPreviewingTheme() + ); + + return { + isDirty: dirtyEntityRecords.length > 0, + isSaving: + dirtyEntityRecords.some( ( record ) => + isSavingEntityRecord( + record.kind, + record.name, + record.key + ) + ) || isActivatingTheme, + isSaveViewOpen: isSaveViewOpened(), + previewingThemeName: previewingTheme?.name?.rendered, + }; + }, [] ); const { setIsSaveViewOpened } = useDispatch( editSiteStore ); const activateSaveEnabled = isPreviewingTheme() || isDirty; @@ -47,13 +60,13 @@ export default function SaveButton( { const getLabel = () => { if ( isPreviewingTheme() ) { if ( isSaving ) { - return __( 'Activating' ); + return sprintf( 'Activating %s', previewingThemeName ); } else if ( disabled ) { return __( 'Saved' ); } else if ( isDirty ) { - return __( 'Activate & Save' ); + return sprintf( 'Activate %s & Save', previewingThemeName ); } - return __( 'Activate' ); + return sprintf( 'Activate %s', previewingThemeName ); } if ( isSaving ) { diff --git a/packages/edit-site/src/components/sidebar-dataviews/custom-dataviews-list.js b/packages/edit-site/src/components/sidebar-dataviews/custom-dataviews-list.js index f11c63653205fa..12659a36bbf9b8 100644 --- a/packages/edit-site/src/components/sidebar-dataviews/custom-dataviews-list.js +++ b/packages/edit-site/src/components/sidebar-dataviews/custom-dataviews-list.js @@ -9,8 +9,13 @@ import { DropdownMenu, MenuGroup, MenuItem, + TextControl, + __experimentalHStack as HStack, + __experimentalVStack as VStack, + Button, + Modal, } from '@wordpress/components'; -import { useMemo } from '@wordpress/element'; +import { useMemo, useState } from '@wordpress/element'; import { moreVertical } from '@wordpress/icons'; import { __ } from '@wordpress/i18n'; import { privateApis as routerPrivateApis } from '@wordpress/router'; @@ -26,6 +31,55 @@ const { useHistory, useLocation } = unlock( routerPrivateApis ); const EMPTY_ARRAY = []; +function RenameItemModalContent( { dataviewId, currentTitle, setIsRenaming } ) { + const { editEntityRecord } = useDispatch( coreStore ); + const [ title, setTitle ] = useState( currentTitle ); + return ( +
    { + event.preventDefault(); + await editEntityRecord( + 'postType', + 'wp_dataviews', + dataviewId, + { + title, + } + ); + setIsRenaming( false ); + } } + > + + + + + + + +
    + ); +} + function CustomDataViewItem( { dataviewId, isActive } ) { const { params: { path }, @@ -49,51 +103,76 @@ function CustomDataViewItem( { dataviewId, isActive } ) { const viewContent = JSON.parse( dataview.content ); return viewContent.type; }, [ dataview.content ] ); + const [ isRenaming, setIsRenaming ] = useState( false ); return ( - + + { ( { onClose } ) => ( + + { + setIsRenaming( true ); + onClose(); + } } + > + { __( 'Rename' ) } + + { + await deleteEntityRecord( + 'postType', + 'wp_dataviews', + dataview.id, + { + force: true, + } + ); + if ( isActive ) { + history.replace( { + path, + } ); + } + onClose(); + } } + isDestructive + > + { __( 'Delete' ) } + + + ) } + + } + /> + { isRenaming && ( + { + setIsRenaming( false ); } } > - { ( { onClose } ) => ( - - { - await deleteEntityRecord( - 'postType', - 'wp_dataviews', - dataview.id, - { - force: true, - } - ); - if ( isActive ) { - history.replace( { - path, - } ); - } - onClose(); - } } - isDestructive - > - { __( 'Delete' ) } - - - ) } - - } - /> + + + ) } + ); } diff --git a/packages/edit-site/src/components/sidebar-dataviews/dataview-item.js b/packages/edit-site/src/components/sidebar-dataviews/dataview-item.js index fdd3272fbc24a6..c6d7bbe4a231ba 100644 --- a/packages/edit-site/src/components/sidebar-dataviews/dataview-item.js +++ b/packages/edit-site/src/components/sidebar-dataviews/dataview-item.js @@ -1,8 +1,14 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ import { page, columns, pullRight } from '@wordpress/icons'; import { privateApis as routerPrivateApis } from '@wordpress/router'; +import { __experimentalHStack as HStack } from '@wordpress/components'; /** * Internal dependencies @@ -39,13 +45,23 @@ export default function DataViewItem( { isCustom, } ); return ( - - { title } - + + { title } + + { suffix } + ); } diff --git a/packages/edit-site/src/components/sidebar-dataviews/default-views.js b/packages/edit-site/src/components/sidebar-dataviews/default-views.js index a1f5375635d377..d853b6cde112f0 100644 --- a/packages/edit-site/src/components/sidebar-dataviews/default-views.js +++ b/packages/edit-site/src/components/sidebar-dataviews/default-views.js @@ -4,6 +4,11 @@ import { __ } from '@wordpress/i18n'; import { trash } from '@wordpress/icons'; +/** + * Internal dependencies + */ +import { OPERATOR_IN } from '../dataviews/constants'; + const DEFAULT_PAGE_BASE = { type: 'list', search: '', @@ -14,7 +19,6 @@ const DEFAULT_PAGE_BASE = { field: 'date', direction: 'desc', }, - visibleFilters: [ 'author', 'status' ], // All fields are visible by default, so it's // better to keep track of the hidden ones. hiddenFields: [ 'date', 'featured-image' ], @@ -34,7 +38,7 @@ const DEFAULT_VIEWS = { view: { ...DEFAULT_PAGE_BASE, filters: [ - { field: 'status', operator: 'in', value: 'draft' }, + { field: 'status', operator: OPERATOR_IN, value: 'draft' }, ], }, }, @@ -45,7 +49,7 @@ const DEFAULT_VIEWS = { view: { ...DEFAULT_PAGE_BASE, filters: [ - { field: 'status', operator: 'in', value: 'trash' }, + { field: 'status', operator: OPERATOR_IN, value: 'trash' }, ], }, }, diff --git a/packages/edit-site/src/components/sidebar-dataviews/style.scss b/packages/edit-site/src/components/sidebar-dataviews/style.scss index 3e8812267b076d..6d8561dc8edca7 100644 --- a/packages/edit-site/src/components/sidebar-dataviews/style.scss +++ b/packages/edit-site/src/components/sidebar-dataviews/style.scss @@ -6,3 +6,19 @@ text-transform: uppercase; } } + +.edit-site-sidebar-dataviews-dataview-item { + border-radius: $radius-block-ui; + + &:hover, + &:focus, + &[aria-current] { + color: $gray-200; + background: $gray-800; + } + + &.is-selected { + background: var(--wp-admin-theme-color); + color: $white; + } +} diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/edit-template.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/edit-template.js index 8067453a518217..4bc81b8c7a2921 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/edit-template.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/edit-template.js @@ -3,16 +3,12 @@ */ import { useSelect, useDispatch } from '@wordpress/data'; import { decodeEntities } from '@wordpress/html-entities'; -import { - DropdownMenu, - MenuGroup, - MenuItem, - __experimentalHStack as HStack, - __experimentalText as Text, -} from '@wordpress/components'; +import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { store as coreStore, useEntityBlockEditor } from '@wordpress/core-data'; +import { store as coreStore } from '@wordpress/core-data'; import { check } from '@wordpress/icons'; +import { store as blockEditorStore } from '@wordpress/block-editor'; +import { privateApis as editorPrivateApis } from '@wordpress/editor'; /** * Internal dependencies @@ -21,7 +17,9 @@ import { store as editSiteStore } from '../../../store'; import SwapTemplateButton from './swap-template-button'; import ResetDefaultTemplate from './reset-default-template'; import { unlock } from '../../../lock-unlock'; -import usePageContentBlocks from '../../block-editor/block-editor-provider/use-page-content-blocks'; +import { PAGE_CONTENT_BLOCK_TYPES } from '../../../utils/constants'; + +const { PostPanelRow } = unlock( editorPrivateApis ); const POPOVER_PROPS = { className: 'edit-site-page-panels-edit-template__dropdown', @@ -29,8 +27,8 @@ const POPOVER_PROPS = { }; export default function EditTemplate() { - const { hasResolved, template, isTemplateHidden, postType } = useSelect( - ( select ) => { + const { hasPostContentBlocks, hasResolved, template, isTemplateHidden } = + useSelect( ( select ) => { const { getEditedPostContext, getEditedPostType, getEditedPostId } = select( editSiteStore ); const { getCanvasMode, getPageContentFocusType } = unlock( @@ -38,15 +36,15 @@ export default function EditTemplate() { ); const { getEditedEntityRecord, hasFinishedResolution } = select( coreStore ); + const { __experimentalGetGlobalBlocksByName } = + select( blockEditorStore ); const _context = getEditedPostContext(); const _postType = getEditedPostType(); - const queryArgs = [ - 'postType', - getEditedPostType(), - getEditedPostId(), - ]; - + const queryArgs = [ 'postType', _postType, getEditedPostId() ]; return { + hasPostContentBlocks: !! __experimentalGetGlobalBlocksByName( + Object.keys( PAGE_CONTENT_BLOCK_TYPES ) + ).length, context: _context, hasResolved: hasFinishedResolution( 'getEditedEntityRecord', @@ -58,31 +56,19 @@ export default function EditTemplate() { getPageContentFocusType() === 'hideTemplate', postType: _postType, }; - }, - [] - ); - - const [ blocks ] = useEntityBlockEditor( 'postType', postType ); + }, [] ); const { setHasPageContentFocus } = useDispatch( editSiteStore ); // Disable reason: `useDispatch` can't be called conditionally. // eslint-disable-next-line @wordpress/no-unused-vars-before-return const { setPageContentFocusType } = unlock( useDispatch( editSiteStore ) ); - // Check if there are any post content block types in the blocks tree. - const pageContentBlocks = usePageContentBlocks( { - blocks, - isPageContentFocused: true, - } ); if ( ! hasResolved ) { return null; } return ( - - - { __( 'Template' ) } - + - { !! pageContentBlocks?.length && ( + { hasPostContentBlocks && ( { setPageContentFocusType( isTemplateHidden @@ -129,6 +116,6 @@ export default function EditTemplate() { ) } - + ); } diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-slug.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-slug.js deleted file mode 100644 index d6ffa1991333ec..00000000000000 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-slug.js +++ /dev/null @@ -1,161 +0,0 @@ -/** - * WordPress dependencies - */ -import { useSelect, useDispatch } from '@wordpress/data'; -import { - safeDecodeURIComponent, - filterURLForDisplay, - cleanForSlug, -} from '@wordpress/url'; -import { useState, useMemo } from '@wordpress/element'; -import { __experimentalInspectorPopoverHeader as InspectorPopoverHeader } from '@wordpress/block-editor'; -import { __ } from '@wordpress/i18n'; -import { - __experimentalInputControl as InputControl, - __experimentalHStack as HStack, - __experimentalVStack as VStack, - __experimentalText as Text, - Dropdown, - Button, -} from '@wordpress/components'; -import { store as coreStore } from '@wordpress/core-data'; - -export const PERMALINK_POSTNAME_REGEX = /%(?:postname|pagename)%/; - -function getPostPermalink( record, isEditable ) { - if ( ! record?.permalink_template ) { - return; - } - const slug = record?.slug || record?.generated_slug; - const [ prefix, suffix ] = record.permalink_template.split( - PERMALINK_POSTNAME_REGEX - ); - const permalink = isEditable ? prefix + slug + suffix : record.link; - return filterURLForDisplay( safeDecodeURIComponent( permalink ) ); -} - -export default function PageSlug( { postType, postId } ) { - const { editEntityRecord } = useDispatch( coreStore ); - const { record, savedSlug } = useSelect( - ( select ) => { - const { getEntityRecord, getEditedEntityRecord } = - select( coreStore ); - const savedRecord = getEntityRecord( 'postType', postType, postId ); - return { - record: getEditedEntityRecord( 'postType', postType, postId ), - savedSlug: savedRecord?.slug || savedRecord?.generated_slug, - }; - }, - [ postType, postId ] - ); - const [ popoverAnchor, setPopoverAnchor ] = useState( null ); - const [ forceEmptyField, setForceEmptyField ] = useState( false ); - const isEditable = - PERMALINK_POSTNAME_REGEX.test( record?.permalink_template ) && - record?._links?.[ 'wp:action-publish' ]; - const popoverProps = useMemo( - () => ( { - // Anchor the popover to the middle of the entire row so that it doesn't - // move around when the label changes. - anchor: popoverAnchor, - 'aria-label': __( 'Change slug' ), - placement: 'bottom-end', - } ), - [ popoverAnchor ] - ); - if ( ! record || ! isEditable ) { - return null; - } - const recordSlug = safeDecodeURIComponent( - record?.slug || record?.generated_slug - ); - const permaLink = getPostPermalink( record, isEditable ); - const onSlugChange = ( newValue ) => { - editEntityRecord( 'postType', postType, postId, { - slug: newValue, - } ); - }; - return ( - - - { __( 'URL' ) } - - { - if ( forceEmptyField ) { - onSlugChange( cleanForSlug( savedSlug ) ); - setForceEmptyField( false ); - } - } } - renderToggle={ ( { onToggle } ) => ( - - ) } - renderContent={ ( { onClose } ) => { - return ( - <> - - -
    - { - onSlugChange( newValue ); - // When we delete the field the permalink gets - // reverted to the original value. - // The forceEmptyField logic allows the user to have - // the field temporarily empty while typing. - if ( ! newValue ) { - if ( ! forceEmptyField ) { - setForceEmptyField( true ); - } - return; - } - if ( forceEmptyField ) { - setForceEmptyField( false ); - } - } } - onBlur={ ( event ) => { - onSlugChange( - cleanForSlug( - event.target.value || - savedSlug - ) - ); - if ( forceEmptyField ) { - setForceEmptyField( false ); - } - } } - /> - -
    - - ); - } } - /> -
    - ); -} diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-status.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-status.js index f80ed4d3fe8204..1d74b09db773e6 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-status.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-status.js @@ -6,7 +6,6 @@ import { ToggleControl, Dropdown, __experimentalText as Text, - __experimentalHStack as HStack, __experimentalVStack as VStack, TextControl, RadioControl, @@ -19,11 +18,15 @@ import { store as coreStore } from '@wordpress/core-data'; import { store as noticesStore } from '@wordpress/notices'; import { __experimentalInspectorPopoverHeader as InspectorPopoverHeader } from '@wordpress/block-editor'; import { useInstanceId } from '@wordpress/compose'; +import { privateApis as editorPrivateApis } from '@wordpress/editor'; /** * Internal dependencies */ import StatusLabel from '../../sidebar-navigation-screen-page/status-label'; +import { unlock } from '../../../lock-unlock'; + +const { PostPanelRow } = unlock( editorPrivateApis ); const STATUS_OPTIONS = [ { @@ -159,10 +162,7 @@ export default function PageStatus( { }; return ( - - - { __( 'Status' ) } - + ) } /> - + ); } diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-summary.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-summary.js index 25b69985bcbd6e..0219b568e57c50 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-summary.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-summary.js @@ -2,13 +2,17 @@ * WordPress dependencies */ import { __experimentalVStack as VStack } from '@wordpress/components'; +import { + PostAuthorPanel, + PostURLPanel, + PostSchedulePanel, +} from '@wordpress/editor'; + /** * Internal dependencies */ import PageStatus from './page-status'; -import PublishDate from './publish-date'; import EditTemplate from './edit-template'; -import PageSlug from './page-slug'; export default function PageSummary( { status, @@ -18,7 +22,7 @@ export default function PageSummary( { postType, } ) { return ( - + - + - + + ); } diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/publish-date.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/publish-date.js deleted file mode 100644 index d000394f6816ba..00000000000000 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/publish-date.js +++ /dev/null @@ -1,94 +0,0 @@ -/** - * WordPress dependencies - */ -import { - Button, - Dropdown, - __experimentalText as Text, - __experimentalHStack as HStack, -} from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { useDispatch } from '@wordpress/data'; -import { useState, useMemo } from '@wordpress/element'; -import { store as coreStore } from '@wordpress/core-data'; -import { store as noticesStore } from '@wordpress/notices'; -import { __experimentalPublishDateTimePicker as PublishDateTimePicker } from '@wordpress/block-editor'; -import { humanTimeDiff } from '@wordpress/date'; - -export default function ChangeStatus( { postType, postId, status, date } ) { - const { editEntityRecord } = useDispatch( coreStore ); - const { createErrorNotice } = useDispatch( noticesStore ); - - const [ popoverAnchor, setPopoverAnchor ] = useState( null ); - // Memoize popoverProps to avoid returning a new object every time. - const popoverProps = useMemo( - () => ( { - // Anchor the popover to the middle of the entire row so that it doesn't - // move around when the label changes. - anchor: popoverAnchor, - 'aria-label': __( 'Change publish date' ), - placement: 'bottom-end', - } ), - [ popoverAnchor ] - ); - - const saveDate = async ( newDate ) => { - try { - let newStatus = status; - if ( status === 'future' && new Date( newDate ) < new Date() ) { - newStatus = 'publish'; - } else if ( - status === 'publish' && - new Date( newDate ) > new Date() - ) { - newStatus = 'future'; - } - await editEntityRecord( 'postType', postType, postId, { - status: newStatus, - date: newDate, - } ); - } catch ( error ) { - const errorMessage = - error.message && error.code !== 'unknown_error' - ? error.message - : __( 'An error occurred while updating the status' ); - - createErrorNotice( errorMessage, { - type: 'snackbar', - } ); - } - }; - - const relateToNow = date ? humanTimeDiff( date ) : __( 'Immediately' ); - - return ( - - - { __( 'Publish' ) } - - ( - - ) } - renderContent={ ( { onClose } ) => ( - - ) } - /> - - ); -} diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/style.scss b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/style.scss index e1a8e4acb72273..f3da54c244bd1b 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/style.scss +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/style.scss @@ -59,36 +59,24 @@ } } -.edit-site-summary-field { - .components-dropdown { - width: 70%; - } - - .edit-site-summary-field__trigger { - max-width: 100%; - - // Truncate - display: block; - text-align: left; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .edit-site-summary-field__label { - width: 30%; - } +.edit-site-summary-field__trigger { + max-width: 100%; + + // Truncate + display: block; + text-align: left; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .edit-site-page-panels-edit-template__dropdown { .components-popover__content { min-width: 240px; } -} - -.edit-site-page-panels-edit-slug__dropdown { - .components-popover__content { - min-width: 320px; - padding: $grid-unit-20; + .components-button.is-pressed, + .components-button.is-pressed:hover { + background: inherit; + color: inherit; } } diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-page/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-page/index.js index 7effca4510ede7..110bc920fb0a9f 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-page/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-page/index.js @@ -15,6 +15,8 @@ import { pencil } from '@wordpress/icons'; import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; import { escapeAttribute } from '@wordpress/escape-html'; import { safeDecodeURIComponent, filterURLForDisplay } from '@wordpress/url'; +import { useEffect } from '@wordpress/element'; +import { privateApis as routerPrivateApis } from '@wordpress/router'; /** * Internal dependencies @@ -27,13 +29,20 @@ import PageDetails from './page-details'; import PageActions from '../page-actions'; import SidebarNavigationScreenDetailsFooter from '../sidebar-navigation-screen-details-footer'; +const { useHistory } = unlock( routerPrivateApis ); + export default function SidebarNavigationScreenPage() { - const navigator = useNavigator(); const { setCanvasMode } = unlock( useDispatch( editSiteStore ) ); + const history = useHistory(); const { params: { postId }, + goTo, } = useNavigator(); - const { record } = useEntityRecord( 'postType', 'page', postId ); + const { record, hasResolved } = useEntityRecord( + 'postType', + 'page', + postId + ); const { featuredMediaAltText, featuredMediaSourceUrl } = useSelect( ( select ) => { @@ -61,6 +70,18 @@ export default function SidebarNavigationScreenPage() { [ record ] ); + // Redirect to the main pages navigation screen if the page is not found or has been deleted. + useEffect( () => { + if ( hasResolved && ! record ) { + history.push( { + path: '/page', + postId: undefined, + postType: undefined, + canvas: 'view', + } ); + } + }, [ hasResolved, history ] ); + const featureImageAltText = featuredMediaAltText ? decodeEntities( featuredMediaAltText ) : decodeEntities( record?.title?.rendered || __( 'Featured image' ) ); @@ -76,7 +97,7 @@ export default function SidebarNavigationScreenPage() { postId={ postId } toggleProps={ { as: SidebarButton } } onRemove={ () => { - navigator.goTo( '/page' ); + goTo( '/page' ); } } /> } /> diff --git a/packages/edit-site/src/hooks/template-part-edit.js b/packages/edit-site/src/hooks/template-part-edit.js index 66f54967c55e22..0b14bbbbd77121 100644 --- a/packages/edit-site/src/hooks/template-part-edit.js +++ b/packages/edit-site/src/hooks/template-part-edit.js @@ -24,11 +24,13 @@ function EditTemplatePartMenuItem( { attributes } ) { const { params } = useLocation(); const templatePart = useSelect( ( select ) => { - return select( coreStore ).getEntityRecord( + const { getCurrentTheme, getEntityRecord } = select( coreStore ); + + return getEntityRecord( 'postType', TEMPLATE_PART_POST_TYPE, // Ideally this should be an official public API. - `${ theme }//${ slug }` + `${ theme || getCurrentTheme()?.stylesheet }//${ slug }` ); }, [ theme, slug ] diff --git a/packages/edit-site/src/store/actions.js b/packages/edit-site/src/store/actions.js index 4de1f4ac2a61fd..000a5f71bbbf0e 100644 --- a/packages/edit-site/src/store/actions.js +++ b/packages/edit-site/src/store/actions.js @@ -599,7 +599,7 @@ export const toggleDistractionFree = registry.batch( () => { registry .dispatch( preferencesStore ) - .set( 'core/edit-site', 'fixedToolbar', false ); + .set( 'core/edit-site', 'fixedToolbar', true ); dispatch.setIsInserterOpened( false ); dispatch.setIsListViewOpened( false ); dispatch.closeGeneralSidebar(); diff --git a/packages/edit-site/src/store/test/actions.js b/packages/edit-site/src/store/test/actions.js index 787809acda0890..3eb1855e68e9d7 100644 --- a/packages/edit-site/src/store/test/actions.js +++ b/packages/edit-site/src/store/test/actions.js @@ -158,7 +158,7 @@ describe( 'actions', () => { registry .select( preferencesStore ) .get( 'core/edit-site', 'fixedToolbar' ) - ).toBe( false ); + ).toBe( true ); expect( registry.select( editSiteStore ).isListViewOpened() ).toBe( false ); diff --git a/packages/edit-widgets/CHANGELOG.md b/packages/edit-widgets/CHANGELOG.md index e1d1c915a2ad21..65b83144691eb1 100644 --- a/packages/edit-widgets/CHANGELOG.md +++ b/packages/edit-widgets/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.23.0 (2023-11-16) + ## 5.22.0 (2023-11-02) ## 5.21.0 (2023-10-18) diff --git a/packages/edit-widgets/package.json b/packages/edit-widgets/package.json index 754e83f54a5f20..33c7f9d75dd619 100644 --- a/packages/edit-widgets/package.json +++ b/packages/edit-widgets/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-widgets", - "version": "5.22.0", + "version": "5.23.0", "description": "Widgets Page module for WordPress..", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index 0e99e83f464181..1012c6163ec292 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 13.23.0 (2023-11-16) + ## 13.22.0 (2023-11-02) ## 13.21.0 (2023-10-18) diff --git a/packages/editor/package.json b/packages/editor/package.json index b45a28c982ee4c..5a4bf3ba7bf216 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/editor", - "version": "13.22.0", + "version": "13.23.0", "description": "Enhanced block editor for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/editor/src/components/index.js b/packages/editor/src/components/index.js index 39b562806c109a..a3dfc991523225 100644 --- a/packages/editor/src/components/index.js +++ b/packages/editor/src/components/index.js @@ -26,6 +26,7 @@ export { default as PageAttributesParent } from './page-attributes/parent'; export { default as PageTemplate } from './post-template'; export { default as PostAuthor } from './post-author'; export { default as PostAuthorCheck } from './post-author/check'; +export { default as PostAuthorPanel } from './post-author/panel'; export { default as PostComments } from './post-comments'; export { default as PostExcerpt } from './post-excerpt'; export { default as PostExcerptCheck } from './post-excerpt/check'; @@ -50,6 +51,7 @@ export { default as PostScheduleLabel, usePostScheduleLabel, } from './post-schedule/label'; +export { default as PostSchedulePanel } from './post-schedule/panel'; export { default as PostSlug } from './post-slug'; export { default as PostSlugCheck } from './post-slug/check'; export { default as PostSticky } from './post-sticky'; @@ -71,6 +73,7 @@ export { default as PostTypeSupportCheck } from './post-type-support-check'; export { default as PostURL } from './post-url'; export { default as PostURLCheck } from './post-url/check'; export { default as PostURLLabel, usePostURLLabel } from './post-url/label'; +export { default as PostURLPanel } from './post-url/panel'; export { default as PostVisibility } from './post-visibility'; export { default as PostVisibilityLabel, diff --git a/packages/editor/src/components/page-attributes/order.js b/packages/editor/src/components/page-attributes/order.js index 416636d14bdbf8..4a751c0b151aba 100644 --- a/packages/editor/src/components/page-attributes/order.js +++ b/packages/editor/src/components/page-attributes/order.js @@ -39,6 +39,7 @@ function PageAttributesOrder() { + + + + + ); +} + +export default PostAuthor; diff --git a/packages/editor/src/components/post-author/style.scss b/packages/editor/src/components/post-author/style.scss new file mode 100644 index 00000000000000..349ad712334c8d --- /dev/null +++ b/packages/editor/src/components/post-author/style.scss @@ -0,0 +1,7 @@ +.editor-post-author__panel { + padding-top: $grid-unit-10; +} + +.editor-post-author__panel .editor-post-panel__row-control > div { + width: 100%; +} diff --git a/packages/editor/src/components/post-featured-image/index.js b/packages/editor/src/components/post-featured-image/index.js index 9304115b0b7f11..96eb164696228f 100644 --- a/packages/editor/src/components/post-featured-image/index.js +++ b/packages/editor/src/components/post-featured-image/index.js @@ -115,7 +115,9 @@ function PostFeaturedImage( { setIsLoading( true ); return; } - onUpdateImage( image ); + if ( image ) { + onUpdateImage( image ); + } setIsLoading( false ); }, onError( message ) { diff --git a/packages/editor/src/components/post-panel-row/index.js b/packages/editor/src/components/post-panel-row/index.js new file mode 100644 index 00000000000000..f6f0c658cd7240 --- /dev/null +++ b/packages/editor/src/components/post-panel-row/index.js @@ -0,0 +1,26 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { __experimentalHStack as HStack } from '@wordpress/components'; +import { forwardRef } from '@wordpress/element'; + +const PostPanelRow = forwardRef( ( { className, label, children }, ref ) => { + return ( + + { label && ( +
    { label }
    + ) } +
    { children }
    +
    + ); +} ); + +export default PostPanelRow; diff --git a/packages/editor/src/components/post-panel-row/style.scss b/packages/editor/src/components/post-panel-row/style.scss new file mode 100644 index 00000000000000..bc1c7fbd000c63 --- /dev/null +++ b/packages/editor/src/components/post-panel-row/style.scss @@ -0,0 +1,21 @@ +.editor-post-panel__row { + width: 100%; + min-height: $button-size; + justify-content: flex-start !important; + align-items: flex-start !important; +} + +.editor-post-panel__row-label { + width: 30%; + flex-shrink: 0; + min-height: $button-size; + display: flex; + align-items: center; +} + +.editor-post-panel__row-control { + flex-grow: 1; + min-height: $button-size; + display: flex; + align-items: center; +} diff --git a/packages/editor/src/components/post-schedule/panel.js b/packages/editor/src/components/post-schedule/panel.js new file mode 100644 index 00000000000000..2e725a06bc9fd7 --- /dev/null +++ b/packages/editor/src/components/post-schedule/panel.js @@ -0,0 +1,65 @@ +/** + * WordPress dependencies + */ +import { Button, Dropdown } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; +import { useState, useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import PostScheduleCheck from './check'; +import PostScheduleForm from './index'; +import { usePostScheduleLabel } from './label'; +import PostPanelRow from '../post-panel-row'; + +export default function PostSchedulePanel() { + const [ popoverAnchor, setPopoverAnchor ] = useState( null ); + // Memoize popoverProps to avoid returning a new object every time. + const popoverProps = useMemo( + () => ( { + // Anchor the popover to the middle of the entire row so that it doesn't + // move around when the label changes. + anchor: popoverAnchor, + 'aria-label': __( 'Change publish date' ), + placement: 'bottom-end', + } ), + [ popoverAnchor ] + ); + + const label = usePostScheduleLabel(); + const fullLabel = usePostScheduleLabel( { full: true } ); + + return ( + + + ( + + ) } + renderContent={ ( { onClose } ) => ( + + ) } + /> + + + ); +} diff --git a/packages/editor/src/components/post-schedule/style.scss b/packages/editor/src/components/post-schedule/style.scss new file mode 100644 index 00000000000000..ca5bc19ae7d5ca --- /dev/null +++ b/packages/editor/src/components/post-schedule/style.scss @@ -0,0 +1,23 @@ +.editor-post-schedule__panel-dropdown { + width: 100%; +} + +.editor-post-schedule__dialog { + .components-popover__content { + min-width: 320px; + padding: $grid-unit-20; + } +} + +.editor-post-schedule__dialog-toggle.components-button { + display: block; + max-width: 100%; + overflow: hidden; + text-align: left; + white-space: unset; + height: auto; + + // The line height + the padding should be the same as the button size. + padding: ( ( $button-size - $grid-unit-20 ) / 2 ) 12px; + line-height: $grid-unit-20; +} diff --git a/packages/editor/src/components/post-sync-status/index.js b/packages/editor/src/components/post-sync-status/index.js index abc45146c36af1..c1a317e8e18129 100644 --- a/packages/editor/src/components/post-sync-status/index.js +++ b/packages/editor/src/components/post-sync-status/index.js @@ -4,7 +4,6 @@ import { useSelect, useDispatch } from '@wordpress/data'; import { __, _x } from '@wordpress/i18n'; import { - PanelRow, Modal, Button, __experimentalHStack as HStack, @@ -17,6 +16,7 @@ import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; /** * Internal dependencies */ +import PostPanelRow from '../post-panel-row'; import { store as editorStore } from '../../store'; import { unlock } from '../../lock-unlock'; @@ -44,14 +44,13 @@ export default function PostSyncStatus() { } return ( - - { __( 'Sync status' ) } -
    + +
    { syncStatus === 'unsynced' ? __( 'Not synced' ) : __( 'Fully synced' ) }
    - +
    ); } diff --git a/packages/editor/src/components/post-sync-status/style.scss b/packages/editor/src/components/post-sync-status/style.scss index 90a75c86bf466d..d5ee21cad8ee46 100644 --- a/packages/editor/src/components/post-sync-status/style.scss +++ b/packages/editor/src/components/post-sync-status/style.scss @@ -1,19 +1,4 @@ -.edit-post-sync-status { - width: 100%; - position: relative; - justify-content: flex-start; - align-items: flex-start; - - > span { - display: block; - width: 45%; - flex-shrink: 0; - padding: $grid-unit-15 * 0.5 0; - word-break: break-word; - } - - > div { - // Match padding on tertiary buttons for alignment. - padding: $grid-unit-15 * 0.5 0 $grid-unit-15 * 0.5 $grid-unit-15; - } +.editor-post-sync-status__value { + // Match padding on tertiary buttons for alignment. + padding: $grid-unit-15 * 0.5 0 $grid-unit-15 * 0.5 $grid-unit-15; } diff --git a/packages/edit-post/src/components/sidebar/post-url/index.js b/packages/editor/src/components/post-url/panel.js similarity index 66% rename from packages/edit-post/src/components/sidebar/post-url/index.js rename to packages/editor/src/components/post-url/panel.js index 1dc1b8d804cd77..4c4fc38d3e2df3 100644 --- a/packages/edit-post/src/components/sidebar/post-url/index.js +++ b/packages/editor/src/components/post-url/panel.js @@ -2,15 +2,18 @@ * WordPress dependencies */ import { useMemo, useState } from '@wordpress/element'; -import { PanelRow, Dropdown, Button } from '@wordpress/components'; +import { Dropdown, Button } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; -import { - PostURLCheck, - PostURL as PostURLForm, - usePostURLLabel, -} from '@wordpress/editor'; -export default function PostURL() { +/** + * Internal dependencies + */ +import PostURLCheck from './check'; +import PostURL from './index'; +import { usePostURLLabel } from './label'; +import PostPanelRow from '../post-panel-row'; + +export default function PostURLPanel() { // Use internal state instead of a ref to make sure that the component // re-renders when the popover's anchor updates. const [ popoverAnchor, setPopoverAnchor ] = useState( null ); @@ -22,21 +25,20 @@ export default function PostURL() { return ( - - { __( 'URL' ) } + ( ) } renderContent={ ( { onClose } ) => ( - + ) } /> - + ); } @@ -45,7 +47,7 @@ function PostURLToggle( { isOpen, onClick } ) { const label = usePostURLLabel(); return (
    \n", - "originalContent": "\n
    \n\n\n\n\n\n\n\n\n\n\n\n\n\n
    \n\n
    \n" + "originalContent": "\n
    \n\n\n\n\n\n\n\n\n\n\n\n\n\n
    \n\n
    \n" }, "innerBlocks": [ { @@ -14,7 +14,7 @@ "attributes": { "originalName": "core/form-input", "originalUndelimitedContent": "", - "originalContent": "\n\n" + "originalContent": "\n\n" }, "innerBlocks": [] }, @@ -24,7 +24,7 @@ "attributes": { "originalName": "core/form-input", "originalUndelimitedContent": "", - "originalContent": "\n\n" + "originalContent": "\n\n" }, "innerBlocks": [] }, @@ -34,7 +34,7 @@ "attributes": { "originalName": "core/form-input", "originalUndelimitedContent": "", - "originalContent": "\n\n" + "originalContent": "\n\n" }, "innerBlocks": [] }, @@ -44,7 +44,7 @@ "attributes": { "originalName": "core/form-input", "originalUndelimitedContent": "", - "originalContent": "\n\n" + "originalContent": "\n\n" }, "innerBlocks": [] }, diff --git a/test/integration/fixtures/blocks/core__form.serialized.html b/test/integration/fixtures/blocks/core__form.serialized.html index 58a2a49967eb56..585a50868b85e0 100644 --- a/test/integration/fixtures/blocks/core__form.serialized.html +++ b/test/integration/fixtures/blocks/core__form.serialized.html @@ -1,16 +1,16 @@
    - + - + - + - +
    diff --git a/test/integration/fixtures/blocks/core__image__center-caption.json b/test/integration/fixtures/blocks/core__image__center-caption.json index a369e433b4028e..034047c07089b9 100644 --- a/test/integration/fixtures/blocks/core__image__center-caption.json +++ b/test/integration/fixtures/blocks/core__image__center-caption.json @@ -3,10 +3,10 @@ "name": "core/image", "isValid": true, "attributes": { - "align": "center", "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", "alt": "", - "caption": "Give it a try. Press the \"really wide\" button on the image toolbar." + "caption": "Give it a try. Press the \"really wide\" button on the image toolbar.", + "align": "center" }, "innerBlocks": [] } diff --git a/test/integration/fixtures/blocks/core__image__deprecated-v2-add-is-resized-class.serialized.html b/test/integration/fixtures/blocks/core__image__deprecated-v2-add-is-resized-class.serialized.html index 9a66da6c018989..c03189e9b456c0 100644 --- a/test/integration/fixtures/blocks/core__image__deprecated-v2-add-is-resized-class.serialized.html +++ b/test/integration/fixtures/blocks/core__image__deprecated-v2-add-is-resized-class.serialized.html @@ -1,3 +1,3 @@ - +
    diff --git a/test/integration/fixtures/blocks/core__image__deprecated-v3-add-align-wrapper.serialized.html b/test/integration/fixtures/blocks/core__image__deprecated-v3-add-align-wrapper.serialized.html index 9a66da6c018989..c03189e9b456c0 100644 --- a/test/integration/fixtures/blocks/core__image__deprecated-v3-add-align-wrapper.serialized.html +++ b/test/integration/fixtures/blocks/core__image__deprecated-v3-add-align-wrapper.serialized.html @@ -1,3 +1,3 @@ - +
    diff --git a/test/integration/fixtures/blocks/core__image__deprecated-v4-remove-align-wrapper.serialized.html b/test/integration/fixtures/blocks/core__image__deprecated-v4-remove-align-wrapper.serialized.html index 99da2155bce88f..b9e58cc24fddb4 100644 --- a/test/integration/fixtures/blocks/core__image__deprecated-v4-remove-align-wrapper.serialized.html +++ b/test/integration/fixtures/blocks/core__image__deprecated-v4-remove-align-wrapper.serialized.html @@ -1,3 +1,3 @@ - +
    diff --git a/test/integration/fixtures/blocks/core__image__deprecated-v6-add-style-width-height.serialized.html b/test/integration/fixtures/blocks/core__image__deprecated-v6-add-style-width-height.serialized.html index 807ba3abc9f9ce..57545968847e1b 100644 --- a/test/integration/fixtures/blocks/core__image__deprecated-v6-add-style-width-height.serialized.html +++ b/test/integration/fixtures/blocks/core__image__deprecated-v6-add-style-width-height.serialized.html @@ -1,3 +1,3 @@ - +
    diff --git a/test/integration/fixtures/documents/wordpress-out.html b/test/integration/fixtures/documents/wordpress-out.html index c394fa232081d1..a0ac4b89ec5520 100644 --- a/test/integration/fixtures/documents/wordpress-out.html +++ b/test/integration/fixtures/documents/wordpress-out.html @@ -28,7 +28,7 @@

    Shortcode

    - +