From d7eeb320f6b342c2f86afbe882d565589b670701 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Mon, 18 Dec 2023 21:28:38 +0900 Subject: [PATCH 01/25] Image Block: Get lightbox trigger button ref via data-wp-init (#57089) --- packages/block-library/src/image/index.php | 1 + packages/block-library/src/image/view.js | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/image/index.php b/packages/block-library/src/image/index.php index 48e6e0585b96ec..85cf3de57b1275 100644 --- a/packages/block-library/src/image/index.php +++ b/packages/block-library/src/image/index.php @@ -239,6 +239,7 @@ class="lightbox-trigger" type="button" aria-haspopup="dialog" aria-label="' . esc_attr( $aria_label ) . '" + data-wp-init="callbacks.initTriggerButton" data-wp-on--click="actions.showLightbox" data-wp-style--right="context.imageButtonRight" data-wp-style--top="context.imageButtonTop" diff --git a/packages/block-library/src/image/view.js b/packages/block-library/src/image/view.js index 315ed995f26cfc..2d5268e4836cb7 100644 --- a/packages/block-library/src/image/view.js +++ b/packages/block-library/src/image/view.js @@ -230,13 +230,16 @@ const { state, actions, callbacks } = store( 'core/image', { const ctx = getContext(); const { ref } = getElement(); ctx.imageRef = ref; - ctx.lightboxTriggerRef = - ref.parentElement.querySelector( '.lightbox-trigger' ); if ( ref.complete ) { ctx.imageLoaded = true; ctx.imageCurrentSrc = ref.currentSrc; } }, + initTriggerButton() { + const ctx = getContext(); + const { ref } = getElement(); + ctx.lightboxTriggerRef = ref; + }, initLightbox() { const ctx = getContext(); const { ref } = getElement(); From bea46cbe967bb481b12b9f20a6024d3ede609daf Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 18 Dec 2023 13:29:34 +0100 Subject: [PATCH 02/25] Site Editor: Add the Discussion panel (#57150) --- .../sidebar/settings-sidebar/index.js | 4 ++-- .../sidebar-edit-mode/page-panels/index.js | 2 ++ .../sidebar-edit-mode/template-panel/index.js | 2 ++ packages/editor/src/components/index.js | 1 + .../src/components/post-discussion/panel.js} | 17 ++++++++--------- 5 files changed, 15 insertions(+), 11 deletions(-) rename packages/{edit-post/src/components/sidebar/discussion-panel/index.js => editor/src/components/post-discussion/panel.js} (79%) diff --git a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js index 76d1f1b63ad636..8f71b3908d584d 100644 --- a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js @@ -13,6 +13,7 @@ import { store as interfaceStore } from '@wordpress/interface'; import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; import { store as editorStore, + PostDiscussionPanel, PostExcerptPanel, PostFeaturedImagePanel, PostLastRevisionPanel, @@ -24,7 +25,6 @@ import { */ import SettingsHeader from '../settings-header'; import PostStatus from '../post-status'; -import DiscussionPanel from '../discussion-panel'; import PageAttributes from '../page-attributes'; import MetaBoxes from '../../meta-boxes'; import PluginDocumentSettingPanel from '../plugin-document-setting-panel'; @@ -85,7 +85,7 @@ const SidebarContent = ( { - + diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/index.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/index.js index e350225a1212aa..87be48220ec95e 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/index.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/index.js @@ -13,6 +13,7 @@ import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; import { decodeEntities } from '@wordpress/html-entities'; import { + PostDiscussionPanel, PostExcerptPanel, PostFeaturedImagePanel, PostLastRevisionPanel, @@ -104,6 +105,7 @@ export default function PagePanels() { + ); } diff --git a/packages/edit-site/src/components/sidebar-edit-mode/template-panel/index.js b/packages/edit-site/src/components/sidebar-edit-mode/template-panel/index.js index 157a56b2461712..21903f0066767f 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/template-panel/index.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/template-panel/index.js @@ -4,6 +4,7 @@ import { useSelect } from '@wordpress/data'; import { PanelBody } from '@wordpress/components'; import { + PostDiscussionPanel, PostExcerptPanel, PostFeaturedImagePanel, PostLastRevisionPanel, @@ -68,6 +69,7 @@ export default function TemplatePanel() { + ); } diff --git a/packages/editor/src/components/index.js b/packages/editor/src/components/index.js index 0ae7ac0824a7fd..d20ba59215b9b1 100644 --- a/packages/editor/src/components/index.js +++ b/packages/editor/src/components/index.js @@ -30,6 +30,7 @@ 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 PostDiscussionPanel } from './post-discussion/panel'; export { default as PostExcerpt } from './post-excerpt'; export { default as PostExcerptCheck } from './post-excerpt/check'; export { default as PostExcerptPanel } from './post-excerpt/panel'; diff --git a/packages/edit-post/src/components/sidebar/discussion-panel/index.js b/packages/editor/src/components/post-discussion/panel.js similarity index 79% rename from packages/edit-post/src/components/sidebar/discussion-panel/index.js rename to packages/editor/src/components/post-discussion/panel.js index 3ed175ca66e1e6..8d9a6a691ac901 100644 --- a/packages/edit-post/src/components/sidebar/discussion-panel/index.js +++ b/packages/editor/src/components/post-discussion/panel.js @@ -3,20 +3,19 @@ */ import { __ } from '@wordpress/i18n'; import { PanelBody, PanelRow } from '@wordpress/components'; -import { - PostComments, - PostPingbacks, - PostTypeSupportCheck, - store as editorStore, -} from '@wordpress/editor'; import { useDispatch, useSelect } from '@wordpress/data'; /** - * Module Constants + * Internal dependencies */ +import { store as editorStore } from '../../store'; +import PostTypeSupportCheck from '../post-type-support-check'; +import PostComments from '../post-comments'; +import PostPingbacks from '../post-pingbacks'; + const PANEL_NAME = 'discussion-panel'; -function DiscussionPanel() { +function PostDiscussionPanel() { const { isEnabled, isOpened } = useSelect( ( select ) => { const { isEditorPanelEnabled, isEditorPanelOpened } = select( editorStore ); @@ -55,4 +54,4 @@ function DiscussionPanel() { ); } -export default DiscussionPanel; +export default PostDiscussionPanel; From 7ef77340cdf88a299fbd637b141768adf11da656 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Mon, 18 Dec 2023 21:44:51 +0900 Subject: [PATCH 03/25] e2e: Try to fix flaky font-library test (#57092) --- test/e2e/specs/site-editor/font-library.spec.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/test/e2e/specs/site-editor/font-library.spec.js b/test/e2e/specs/site-editor/font-library.spec.js index 531398fb495906..8bc7cfb17ea629 100644 --- a/test/e2e/specs/site-editor/font-library.spec.js +++ b/test/e2e/specs/site-editor/font-library.spec.js @@ -10,10 +10,7 @@ test.describe( 'Font Library', () => { } ); test.beforeEach( async ( { admin, editor } ) => { - await admin.visitSiteEditor( { - postId: 'emptytheme//index', - postType: 'wp_template', - } ); + await admin.visitSiteEditor(); await editor.canvas.locator( 'body' ).click(); } ); @@ -35,10 +32,7 @@ test.describe( 'Font Library', () => { } ); test.beforeEach( async ( { admin, editor } ) => { - await admin.visitSiteEditor( { - postId: 'twentytwentythree//index', - postType: 'wp_template', - } ); + await admin.visitSiteEditor(); await editor.canvas.locator( 'body' ).click(); } ); From 8cd528beafc813e192c13d28c2d39289f8e8b433 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 18 Dec 2023 13:57:03 +0100 Subject: [PATCH 04/25] Swap Template: Show the right templates for the right post type (#57149) --- .../editor/src/components/post-template/hooks.js | 12 ++++++------ .../components/post-template/swap-template-button.js | 11 +++++++---- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/editor/src/components/post-template/hooks.js b/packages/editor/src/components/post-template/hooks.js index e676bf66cf2fbd..1529228fe95151 100644 --- a/packages/editor/src/components/post-template/hooks.js +++ b/packages/editor/src/components/post-template/hooks.js @@ -41,21 +41,21 @@ export function useAllowSwitchingTemplates() { ); } -function useTemplates() { +function useTemplates( postType ) { return useSelect( ( select ) => select( coreStore ).getEntityRecords( 'postType', 'wp_template', { per_page: -1, - post_type: 'page', + post_type: postType, } ), - [] + [ postType ] ); } -export function useAvailableTemplates() { +export function useAvailableTemplates( postType ) { const currentTemplateSlug = useCurrentTemplateSlug(); const allowSwitchingTemplate = useAllowSwitchingTemplates(); - const templates = useTemplates(); + const templates = useTemplates( postType ); return useMemo( () => allowSwitchingTemplate && @@ -71,7 +71,7 @@ export function useAvailableTemplates() { export function useCurrentTemplateSlug() { const { postType, postId } = useEditedPostContext(); - const templates = useTemplates(); + const templates = useTemplates( postType ); const entityTemplate = useSelect( ( select ) => { const post = select( coreStore ).getEditedEntityRecord( diff --git a/packages/editor/src/components/post-template/swap-template-button.js b/packages/editor/src/components/post-template/swap-template-button.js index 240dee42214d56..1e9562970f6828 100644 --- a/packages/editor/src/components/post-template/swap-template-button.js +++ b/packages/editor/src/components/post-template/swap-template-button.js @@ -18,11 +18,11 @@ import { useAvailableTemplates, useEditedPostContext } from './hooks'; export default function SwapTemplateButton( { onClick } ) { const [ showModal, setShowModal ] = useState( false ); - const availableTemplates = useAvailableTemplates(); const onClose = useCallback( () => { setShowModal( false ); }, [] ); const { postType, postId } = useEditedPostContext(); + const availableTemplates = useAvailableTemplates( postType ); const { editEntityRecord } = useDispatch( coreStore ); if ( ! availableTemplates?.length ) { return null; @@ -51,7 +51,10 @@ export default function SwapTemplateButton( { onClick } ) { isFullScreen >
- +
) } @@ -59,8 +62,8 @@ export default function SwapTemplateButton( { onClick } ) { ); } -function TemplatesList( { onSelect } ) { - const availableTemplates = useAvailableTemplates(); +function TemplatesList( { postType, onSelect } ) { + const availableTemplates = useAvailableTemplates( postType ); const templatesAsPatterns = useMemo( () => availableTemplates.map( ( template ) => ( { From 1b3f48b17c1dafbb03348a211949e9e9665cc2e7 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Mon, 18 Dec 2023 18:37:23 +0400 Subject: [PATCH 05/25] Block Editor: Try removing extra memoization for individual style panels (#57160) --- packages/block-editor/src/hooks/background.js | 8 +------- packages/block-editor/src/hooks/border.js | 8 +------- packages/block-editor/src/hooks/color.js | 8 +------- packages/block-editor/src/hooks/dimensions.js | 8 +------- packages/block-editor/src/hooks/typography.js | 8 +------- 5 files changed, 5 insertions(+), 35 deletions(-) diff --git a/packages/block-editor/src/hooks/background.js b/packages/block-editor/src/hooks/background.js index b75dc95b75241f..e8518bcbc419f2 100644 --- a/packages/block-editor/src/hooks/background.js +++ b/packages/block-editor/src/hooks/background.js @@ -24,7 +24,6 @@ import { Platform, useCallback, useRef } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; import { getFilename } from '@wordpress/url'; -import { pure } from '@wordpress/compose'; /** * Internal dependencies @@ -302,7 +301,7 @@ function BackgroundImagePanelItem( { clientId, setAttributes } ) { ); } -function BackgroundImagePanelPure( props ) { +export function BackgroundImagePanel( props ) { const [ backgroundImage ] = useSettings( 'background.backgroundImage' ); if ( ! backgroundImage || @@ -317,8 +316,3 @@ function BackgroundImagePanelPure( props ) { ); } - -// We don't want block controls to re-render when typing inside a block. `pure` -// will prevent re-renders unless props change, so only pass the needed props -// and not the whole attributes object. -export const BackgroundImagePanel = pure( BackgroundImagePanelPure ); diff --git a/packages/block-editor/src/hooks/border.js b/packages/block-editor/src/hooks/border.js index a11fdc4b97e48b..e67105969df81c 100644 --- a/packages/block-editor/src/hooks/border.js +++ b/packages/block-editor/src/hooks/border.js @@ -8,7 +8,6 @@ import classnames from 'classnames'; */ import { getBlockSupport } from '@wordpress/blocks'; import { __experimentalHasSplitBorders as hasSplitBorders } from '@wordpress/components'; -import { pure } from '@wordpress/compose'; import { Platform, useCallback, useMemo } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; import { useSelect } from '@wordpress/data'; @@ -133,7 +132,7 @@ function BordersInspectorControl( { children, resetAllFilter } ) { ); } -function BorderPanelPure( { clientId, name, setAttributes, settings } ) { +export function BorderPanel( { clientId, name, setAttributes, settings } ) { const isEnabled = useHasBorderPanel( settings ); function selector( select ) { const { style, borderColor } = @@ -170,11 +169,6 @@ function BorderPanelPure( { clientId, name, setAttributes, settings } ) { ); } -// We don't want block controls to re-render when typing inside a block. `pure` -// will prevent re-renders unless props change, so only pass the needed props -// and not the whole attributes object. -export const BorderPanel = pure( BorderPanelPure ); - /** * Determine whether there is block support for border properties. * diff --git a/packages/block-editor/src/hooks/color.js b/packages/block-editor/src/hooks/color.js index 267bafe1201739..5767db829d1b37 100644 --- a/packages/block-editor/src/hooks/color.js +++ b/packages/block-editor/src/hooks/color.js @@ -9,7 +9,6 @@ import classnames from 'classnames'; import { addFilter } from '@wordpress/hooks'; import { getBlockSupport } from '@wordpress/blocks'; import { useMemo, Platform, useCallback } from '@wordpress/element'; -import { pure } from '@wordpress/compose'; import { useSelect } from '@wordpress/data'; /** @@ -267,7 +266,7 @@ function ColorInspectorControl( { children, resetAllFilter } ) { ); } -function ColorEditPure( { clientId, name, setAttributes, settings } ) { +export function ColorEdit( { clientId, name, setAttributes, settings } ) { const isEnabled = useHasColorPanel( settings ); function selector( select ) { const { style, textColor, backgroundColor, gradient } = @@ -336,11 +335,6 @@ function ColorEditPure( { clientId, name, setAttributes, settings } ) { ); } -// We don't want block controls to re-render when typing inside a block. `pure` -// will prevent re-renders unless props change, so only pass the needed props -// and not the whole attributes object. -export const ColorEdit = pure( ColorEditPure ); - function useBlockProps( { name, backgroundColor, diff --git a/packages/block-editor/src/hooks/dimensions.js b/packages/block-editor/src/hooks/dimensions.js index 4dcba5c4abef68..bbf5b12ca27cf8 100644 --- a/packages/block-editor/src/hooks/dimensions.js +++ b/packages/block-editor/src/hooks/dimensions.js @@ -5,7 +5,6 @@ import { useState, useEffect, useCallback } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; import { getBlockSupport } from '@wordpress/blocks'; import deprecated from '@wordpress/deprecated'; -import { pure } from '@wordpress/compose'; /** * Internal dependencies @@ -66,7 +65,7 @@ function DimensionsInspectorControl( { children, resetAllFilter } ) { ); } -function DimensionsPanelPure( { clientId, name, setAttributes, settings } ) { +export function DimensionsPanel( { clientId, name, setAttributes, settings } ) { const isEnabled = useHasDimensionsPanel( settings ); const value = useSelect( ( select ) => @@ -126,11 +125,6 @@ function DimensionsPanelPure( { clientId, name, setAttributes, settings } ) { ); } -// We don't want block controls to re-render when typing inside a block. `pure` -// will prevent re-renders unless props change, so only pass the needed props -// and not the whole attributes object. -export const DimensionsPanel = pure( DimensionsPanelPure ); - /** * @deprecated */ diff --git a/packages/block-editor/src/hooks/typography.js b/packages/block-editor/src/hooks/typography.js index 7b2fdc9ca28fb2..12d0075527bec5 100644 --- a/packages/block-editor/src/hooks/typography.js +++ b/packages/block-editor/src/hooks/typography.js @@ -4,7 +4,6 @@ import { getBlockSupport, hasBlockSupport } from '@wordpress/blocks'; import { useMemo, useCallback } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; -import { pure } from '@wordpress/compose'; /** * Internal dependencies @@ -109,7 +108,7 @@ function TypographyInspectorControl( { children, resetAllFilter } ) { ); } -function TypographyPanelPure( { clientId, name, setAttributes, settings } ) { +export function TypographyPanel( { clientId, name, setAttributes, settings } ) { function selector( select ) { const { style, fontFamily, fontSize } = select( blockEditorStore ).getBlockAttributes( clientId ) || {}; @@ -147,11 +146,6 @@ function TypographyPanelPure( { clientId, name, setAttributes, settings } ) { ); } -// We don't want block controls to re-render when typing inside a block. `pure` -// will prevent re-renders unless props change, so only pass the needed props -// and not the whole attributes object. -export const TypographyPanel = pure( TypographyPanelPure ); - export const hasTypographySupport = ( blockName ) => { return TYPOGRAPHY_SUPPORT_KEYS.some( ( key ) => hasBlockSupport( blockName, key ) From 44d1e21842b905e8c3e03fc9ee2793ab2fea5c49 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Mon, 18 Dec 2023 15:52:40 +0100 Subject: [PATCH 06/25] [RNMobile] Fix pasting HTML into the post title (#57118) * Update paste handler of post title This logic is a duplicate of the web implementation. * Allow set initial title in integration tests * Add `getEditorTitle` test helper The file has been renamed to a proper name now that provides different getters for the editor content. * Update index of integration test helpers Now it exports modules using wildcard, instead of needing to specify every single item. * Add integration tests of `PostTitle` component --- .../src/components/post-title/index.native.js | 49 ++++++++---- .../test/__snapshots__/index.native.js.snap | 25 ++++++ .../post-title/test/index.native.js | 78 +++++++++++++++++++ ...t-editor-html.js => get-editor-content.js} | 25 ++++-- test/native/integration-test-helpers/index.js | 46 +++++------ .../initialize-editor.js | 3 +- 6 files changed, 180 insertions(+), 46 deletions(-) create mode 100644 packages/editor/src/components/post-title/test/__snapshots__/index.native.js.snap create mode 100644 packages/editor/src/components/post-title/test/index.native.js rename test/native/integration-test-helpers/{get-editor-html.js => get-editor-content.js} (59%) diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index 6d905e743581e9..d82206303314a3 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -7,7 +7,7 @@ import { View } from 'react-native'; * WordPress dependencies */ import { Component } from '@wordpress/element'; -import { create, insert } from '@wordpress/rich-text'; +import { create, toHTMLString, insert } from '@wordpress/rich-text'; import { decodeEntities } from '@wordpress/html-entities'; import { withDispatch, withSelect } from '@wordpress/data'; import { withFocusOutside } from '@wordpress/components'; @@ -16,6 +16,7 @@ import { __, sprintf } from '@wordpress/i18n'; import { pasteHandler } from '@wordpress/blocks'; import { store as blockEditorStore, RichText } from '@wordpress/block-editor'; import { store as editorStore } from '@wordpress/editor'; +import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; /** * Internal dependencies @@ -57,7 +58,7 @@ class PostTitle extends Component { this.props.onSelect(); } - onPaste( { value, onChange, plainText, html } ) { + onPaste( { value, plainText, html } ) { const { title, onInsertBlockAfter, onUpdate } = this.props; const content = pasteHandler( { @@ -65,23 +66,37 @@ class PostTitle extends Component { plainText, } ); - if ( content.length ) { - if ( typeof content === 'string' ) { - const valueToInsert = create( { html: content } ); - onChange( insert( value, valueToInsert ) ); + if ( ! content.length ) { + return; + } + + if ( typeof content !== 'string' ) { + const [ firstBlock ] = content; + + if ( + ! title && + ( firstBlock.name === 'core/heading' || + firstBlock.name === 'core/paragraph' ) + ) { + // Strip HTML to avoid unwanted HTML being added to the title. + // In the majority of cases it is assumed that HTML in the title + // is undesirable. + const contentNoHTML = stripHTML( + firstBlock.attributes.content + ); + onUpdate( contentNoHTML ); + onInsertBlockAfter( content.slice( 1 ) ); } else { - const [ firstBlock ] = content; - if ( - ! title && - ( firstBlock.name === 'core/heading' || - firstBlock.name === 'core/paragraph' ) - ) { - onUpdate( firstBlock.attributes.content ); - onInsertBlockAfter( content.slice( 1 ) ); - } else { - onInsertBlockAfter( content ); - } + onInsertBlockAfter( content ); } + } else { + // Strip HTML to avoid unwanted HTML being added to the title. + // In the majority of cases it is assumed that HTML in the title + // is undesirable. + const contentNoHTML = stripHTML( content ); + + const newValue = insert( value, create( { html: contentNoHTML } ) ); + onUpdate( toHTMLString( { value: newValue } ) ); } } diff --git a/packages/editor/src/components/post-title/test/__snapshots__/index.native.js.snap b/packages/editor/src/components/post-title/test/__snapshots__/index.native.js.snap new file mode 100644 index 00000000000000..d595171b880785 --- /dev/null +++ b/packages/editor/src/components/post-title/test/__snapshots__/index.native.js.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PostTitle does not update title with existing content when pasting HTML 1`] = ` +" +

Howdy

+ + + +

This is a heading.

+ + + +

This is a paragraph.

+" +`; + +exports[`PostTitle populates empty title with first block content when pasting HTML 1`] = ` +" +

This is a heading.

+ + + +

This is a paragraph.

+" +`; diff --git a/packages/editor/src/components/post-title/test/index.native.js b/packages/editor/src/components/post-title/test/index.native.js new file mode 100644 index 00000000000000..1d7cc492f44b8b --- /dev/null +++ b/packages/editor/src/components/post-title/test/index.native.js @@ -0,0 +1,78 @@ +/** + * External dependencies + */ +import { + getEditorHtml, + getEditorTitle, + initializeEditor, + pasteIntoRichText, + selectRangeInRichText, + screen, + setupCoreBlocks, + within, +} from 'test/helpers'; + +setupCoreBlocks(); + +const HTML_MULTIPLE_TAGS = `

Howdy

+

This is a heading.

+

This is a paragraph.

`; + +describe( 'PostTitle', () => { + it( 'populates empty title with first block content when pasting HTML', async () => { + await initializeEditor( { initialTitle: '' } ); + + const postTitle = within( + screen.getByTestId( 'post-title' ) + ).getByPlaceholderText( 'Add title' ); + pasteIntoRichText( postTitle, { html: HTML_MULTIPLE_TAGS } ); + + expect( console ).toHaveLogged(); + expect( getEditorTitle() ).toBe( 'Howdy' ); + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'does not update title with existing content when pasting HTML', async () => { + const initialTitle = 'Hello'; + await initializeEditor( { initialTitle } ); + + const postTitle = within( + screen.getByTestId( 'post-title' ) + ).getByPlaceholderText( 'Add title' ); + selectRangeInRichText( postTitle, 0 ); + pasteIntoRichText( postTitle, { html: HTML_MULTIPLE_TAGS } ); + + expect( console ).toHaveLogged(); + expect( getEditorTitle() ).toBe( initialTitle ); + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'updates title with existing content when pasting text', async () => { + await initializeEditor( { initialTitle: 'World' } ); + + const postTitle = within( + screen.getByTestId( 'post-title' ) + ).getByPlaceholderText( 'Add title' ); + selectRangeInRichText( postTitle, 0 ); + pasteIntoRichText( postTitle, { text: 'Hello' } ); + + expect( console ).toHaveLogged(); + expect( getEditorTitle() ).toBe( 'HelloWorld' ); + expect( getEditorHtml() ).toBe( '' ); + } ); + + it( 'does not add HTML to title when pasting span tag', async () => { + const pasteHTML = `l`; + await initializeEditor( { initialTitle: 'Helo' } ); + + const postTitle = within( + screen.getByTestId( 'post-title' ) + ).getByPlaceholderText( 'Add title' ); + selectRangeInRichText( postTitle, 2 ); + pasteIntoRichText( postTitle, { html: pasteHTML } ); + + expect( console ).toHaveLogged(); + expect( getEditorTitle() ).toBe( 'Hello' ); + expect( getEditorHtml() ).toBe( '' ); + } ); +} ); diff --git a/test/native/integration-test-helpers/get-editor-html.js b/test/native/integration-test-helpers/get-editor-content.js similarity index 59% rename from test/native/integration-test-helpers/get-editor-html.js rename to test/native/integration-test-helpers/get-editor-content.js index eb1a9909895d2c..2594382d8ce686 100644 --- a/test/native/integration-test-helpers/get-editor-html.js +++ b/test/native/integration-test-helpers/get-editor-content.js @@ -8,7 +8,7 @@ import { // Set up the mocks for getting the HTML output of the editor let triggerHtmlSerialization; -let serializedHtml; +let serializedContent = {}; subscribeParentGetHtml.mockImplementation( ( callback ) => { if ( ! triggerHtmlSerialization ) { triggerHtmlSerialization = callback; @@ -19,9 +19,11 @@ subscribeParentGetHtml.mockImplementation( ( callback ) => { }; } } ); -provideToNativeHtml.mockImplementation( ( html ) => { - serializedHtml = html; -} ); +provideToNativeHtml.mockImplementation( + ( html, title, hasChanges, contentInfo ) => { + serializedContent = { html, title, hasChanges, contentInfo }; + } +); /** * Gets the current HTML output of the editor. @@ -33,5 +35,18 @@ export function getEditorHtml() { throw new Error( 'HTML serialization trigger is not defined.' ); } triggerHtmlSerialization(); - return serializedHtml; + return serializedContent.html; +} + +/** + * Gets the current title of the editor. + * + * @return {string} Title + */ +export function getEditorTitle() { + if ( ! triggerHtmlSerialization ) { + throw new Error( 'HTML serialization trigger is not defined.' ); + } + triggerHtmlSerialization(); + return serializedContent.title; } diff --git a/test/native/integration-test-helpers/index.js b/test/native/integration-test-helpers/index.js index 5aca46049715cd..43bbb768081c67 100644 --- a/test/native/integration-test-helpers/index.js +++ b/test/native/integration-test-helpers/index.js @@ -3,26 +3,26 @@ export { advanceAnimationByTime, advanceAnimationByFrames, } from './advance-animation'; -export { dismissModal } from './dismiss-modal'; -export { getBlock } from './get-block'; -export { getBlockTransformOptions } from './get-block-transform-options'; -export { getEditorHtml } from './get-editor-html'; -export { getInnerBlock } from './get-inner-block'; -export { initializeEditor } from './initialize-editor'; -export { openBlockActionsMenu } from './open-block-actions-menu'; -export { openBlockSettings } from './open-block-settings'; -export { selectRangeInRichText } from './rich-text-select-range'; -export { typeInRichText } from './rich-text-type'; -export { pasteIntoRichText } from './rich-text-paste'; -export { setupApiFetch } from './setup-api-fetch'; -export { setupCoreBlocks } from './setup-core-blocks'; -export { setupMediaPicker } from './setup-media-picker'; -export { setupMediaUpload } from './setup-media-upload'; -export { setupPicker } from './setup-picker'; -export { changeTextOfTextInput } from './text-input-change-text'; -export { transformBlock } from './transform-block'; -export { triggerBlockListLayout } from './trigger-block-list-layout'; -export { waitForModalVisible } from './wait-for-modal-visible'; -export { waitForStoreResolvers } from './wait-for-store-resolvers'; -export { withFakeTimers } from './with-fake-timers'; -export { withReanimatedTimer } from './with-reanimated-timer'; +export * from './dismiss-modal'; +export * from './get-block'; +export * from './get-block-transform-options'; +export * from './get-editor-content'; +export * from './get-inner-block'; +export * from './initialize-editor'; +export * from './open-block-actions-menu'; +export * from './open-block-settings'; +export * from './rich-text-select-range'; +export * from './rich-text-type'; +export * from './rich-text-paste'; +export * from './setup-api-fetch'; +export * from './setup-core-blocks'; +export * from './setup-media-picker'; +export * from './setup-media-upload'; +export * from './setup-picker'; +export * from './text-input-change-text'; +export * from './transform-block'; +export * from './trigger-block-list-layout'; +export * from './wait-for-modal-visible'; +export * from './wait-for-store-resolvers'; +export * from './with-fake-timers'; +export * from './with-reanimated-timer'; diff --git a/test/native/integration-test-helpers/initialize-editor.js b/test/native/integration-test-helpers/initialize-editor.js index 54ece9d347a07c..511f0223e11356 100644 --- a/test/native/integration-test-helpers/initialize-editor.js +++ b/test/native/integration-test-helpers/initialize-editor.js @@ -36,6 +36,7 @@ export async function initializeEditor( props, { component } = {} ) { const { screenWidth = 320, withGlobalStyles = false, + initialTitle = 'test', ...rest } = props || {}; const editorElement = component @@ -44,7 +45,7 @@ export async function initializeEditor( props, { component } = {} ) { const screen = render( cloneElement( editorElement, { - initialTitle: 'test', + initialTitle, ...( withGlobalStyles ? getGlobalStyles() : {} ), ...rest, } ) From 34dbbe4226d1d2bcc99c0eb33a48e42a5064edcd Mon Sep 17 00:00:00 2001 From: Nick Diego Date: Mon, 18 Dec 2023 09:11:43 -0600 Subject: [PATCH 07/25] Remove "How to use JavaScript" docs (#57166) --- docs/how-to-guides/javascript/README.md | 20 -- docs/how-to-guides/javascript/esnext-js.md | 123 ------------ .../javascript/extending-the-block-editor.md | 64 ------ .../javascript/js-build-setup.md | 182 ------------------ .../javascript/loading-javascript.md | 49 ----- .../javascript/plugins-background.md | 22 --- .../javascript/scope-your-code.md | 117 ----------- .../javascript/troubleshooting.md | 76 -------- .../javascript/versions-and-building.md | 13 -- docs/manifest.json | 54 ------ docs/toc.json | 20 -- 11 files changed, 740 deletions(-) delete mode 100644 docs/how-to-guides/javascript/README.md delete mode 100644 docs/how-to-guides/javascript/esnext-js.md delete mode 100644 docs/how-to-guides/javascript/extending-the-block-editor.md delete mode 100644 docs/how-to-guides/javascript/js-build-setup.md delete mode 100644 docs/how-to-guides/javascript/loading-javascript.md delete mode 100644 docs/how-to-guides/javascript/plugins-background.md delete mode 100644 docs/how-to-guides/javascript/scope-your-code.md delete mode 100644 docs/how-to-guides/javascript/troubleshooting.md delete mode 100644 docs/how-to-guides/javascript/versions-and-building.md diff --git a/docs/how-to-guides/javascript/README.md b/docs/how-to-guides/javascript/README.md deleted file mode 100644 index 4b2d123c8ab136..00000000000000 --- a/docs/how-to-guides/javascript/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# How to use JavaScript with the Block Editor - -The Block Editor Handbook contains information on the APIs available for working with this new setup. The goal of this tutorial is to get you comfortable using the API reference and snippets of code found within. - -### What is JavaScript - -JavaScript is a programming language which is loaded and executed in your web browser; compared to PHP which is run by a web server with the results sent to the browser, typically as HTML. - -The block editor introduced in WordPress 5.0 is written in JavaScript, with the code run in the browser, and not on the server, this allows for a richer and more dynamic user experience. It also requires you to learn how to use JavaScript to extend and enhance the block editor. - -### Table of Contents - -1. [Plugins Background](/docs/how-to-guides/javascript/plugins-background.md) -2. [Loading JavaScript](/docs/how-to-guides/javascript/loading-javascript.md) -3. [Extending the Block Editor](/docs/how-to-guides/javascript/extending-the-block-editor.md) -4. [Troubleshooting](/docs/how-to-guides/javascript/troubleshooting.md) -5. [JavaScript Versions and Building](/docs/how-to-guides/javascript/versions-and-building.md) -6. [Scope your code](/docs/how-to-guides/javascript/scope-your-code.md) -7. [JavaScript Build Step](/docs/how-to-guides/javascript/js-build-setup.md) -8. [ESNext Syntax](/docs/how-to-guides/javascript/esnext-js.md) diff --git a/docs/how-to-guides/javascript/esnext-js.md b/docs/how-to-guides/javascript/esnext-js.md deleted file mode 100644 index f478d1fd596eaa..00000000000000 --- a/docs/how-to-guides/javascript/esnext-js.md +++ /dev/null @@ -1,123 +0,0 @@ -# ESNext Syntax - -The JavaScript language continues to evolve, the syntax used to write JavaScript code is not fixed but changes over time. [Ecma International](https://en.wikipedia.org/wiki/Ecma_International) is the organization that sets the standard for the language, officially called [ECMAScript](https://en.wikipedia.org/wiki/ECMAScript). A new standard for JavaScript is published each year, the 6th edition published in 2015 is often referred to as ES6. Our usage would more appropriately be **ESNext** referring to the latest standard. The build step is what converts this latest syntax of JavaScript to a version understood by browsers. - -Here are some common ESNext syntax patterns used throughout the Gutenberg project. - -## Destructuring Assignments - -The [destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) syntax allows you to pull apart arrays, or properties from objects into their own variable. - -For the object `const obj = { foo: "bar" }` - -Creating and assigning a new variable `foo` can be done in a single step: `const { foo } = obj;` - -The curly brackets on the left side tells JavaScript to inspect the object `obj` for the property `foo` and assign its value to the new variable of the same name. - -## Arrow Functions - -Arrow functions provide a shorter syntax for defining a function; this is such a common task in JavaScript that having a syntax a bit shorter is quite helpful. - -Before you might define a function like: - -```js -const f = function ( param ) { - console.log( param ); -}; -``` - -Using arrow function, you can define the same using: - -```js -const g = ( param ) => { - console.log( param ); -}; -``` - -Or even shorter, if the function is only a single-line you can omit the -curly braces: - -```js -const g2 = ( param ) => console.log( param ); -``` - -In the examples above, using `console.log` we aren't too concerned about the return values. However, when using arrow functions in this way, the return value is set whatever the line returns. - -For example, our save function could be shortened from: - -```js -save: ( { attributes } ) => { - return
{ attributes.url }
; -}; -``` - -To: - -```js -save: ( { attributes } ) =>
{ attributes.url }
; -``` - -There are even more ways to shorten code, but you don't want to take it too far and make it harder to read what is going on. - -## Imports - -The [import statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) is used to import variables or functions from an exported file. You can use destructuring on imports, for example: - -```js -import { TextControl } from '@wordpress/components'; -``` - -This will look in the `@wordpress/components` package for the exported `TextControl` variable. - -A package or file can also set a `default` export, this is imported without using the curly brackets. For example - -```js -const edit = ( { attributes, setAttributes } ) => { - return ( -
- -
- ); -}; - -export default edit; -``` - -To import, you would use: - -```js -import edit from './edit'; - -registerBlockType( 'mkaz/qrcode-block', { - title: 'QRCode Block', - icon: 'visibility', - category: 'widgets', - attributes: { - url: { - type: 'string', - source: 'text', - selector: '.theurl', - }, - }, - edit, - save: ( { attributes } ) => { - return
...
; - }, -} ); -``` - -Note, you can also shorten `edit: edit` to just `edit` as shown above. JavaScript will automatically assign the property `edit` to the value of `edit`. This is another form of destructuring. - -## Summary - -It helps to become familiar with the ESNext syntax and the common shorter forms. It will give you a greater understanding of reading code examples and what is going on. - -Here are a few more resources that may help - -- [ES5 vs ES6 with example code](https://medium.com/recraftrelic/es5-vs-es6-with-example-code-9901fa0136fc) -- [Top 10 ES6 Features by Example](https://blog.pragmatists.com/top-10-es6-features-by-example-80ac878794bb) -- [ES6 Syntax and Feature Overview](https://www.taniarascia.com/es6-syntax-and-feature-overview/) diff --git a/docs/how-to-guides/javascript/extending-the-block-editor.md b/docs/how-to-guides/javascript/extending-the-block-editor.md deleted file mode 100644 index 5944b9208407f5..00000000000000 --- a/docs/how-to-guides/javascript/extending-the-block-editor.md +++ /dev/null @@ -1,64 +0,0 @@ -# Extending the Block Editor - -Let's look at using the [Block Style example](/docs/reference-guides/block-api/block-styles.md) to extend the editor. This example allows you to add your own custom CSS class name to any core block type. - -Replace the existing `console.log()` code in your `myguten.js` file with: - -```js -wp.blocks.registerBlockStyle( 'core/quote', { - name: 'fancy-quote', - label: 'Fancy Quote', -} ); -``` - -**Important:** Notice that you are using a function from `wp.blocks` package. This means you must specify it as a dependency when you enqueue the script. Update the `myguten-plugin.php` file to: - -```php -
Hola, mundo!
, - save: () =>
Hola, mundo!
, -} ); -``` - -To configure npm to run a script, you use the scripts section in `package.json` webpack: - -```json - "scripts": { - "build": "wp-scripts build" - }, -``` - -You can then run the build using: `npm run build`. - -After the build finishes, you will see the built file created at `build/index.js`. Enqueue this file in the admin screen as you would any JavaScript in WordPress, see [loading JavaScript step in this tutorial](/docs/how-to-guides/javascript/loading-javascript.md), and the block will load in the editor. - -## Development Mode - -The **build** command in `@wordpress/scripts` runs in "production" mode. This shrinks the code down so it downloads faster, but makes it difficult to read in the process. You can use the **start** command which runs in development mode that does not shrink the code, and additionally continues a running process to watch the source file for more changes and rebuilds as you develop. - -The start command can be added to the same scripts section of `package.json`: - -```json - "scripts": { - "start": "wp-scripts start", - "build": "wp-scripts build" - }, -``` - -Now, when you run `npm start` a watcher will run in the terminal. You can then edit away in your text editor; after each save, it will automatically build. You can then use the familiar edit/save/reload development process. - -**Note:** keep an eye on your terminal for any errors. If you make a typo or syntax error, the build will fail and the error will be in the terminal. - -## Source Control - -Because a typical `node_modules` folder will contain thousands of files that change with every software update, you should exclude `node_modules/` from your source control. If you ever start from a fresh clone, simply run `npm install` in the same folder your `package.json` is located to pull your required packages. - -Likewise, you do not need to include `node_modules` or any of the above configuration files in your plugin because they will be bundled inside the file that webpack builds. **Be sure to enqueue the `build/index.js` file** in your plugin PHP. This is the main JavaScript file needed for your block to run. - -## Dependency Management - -Using `wp-scripts` ver 5.0.0+ build step will also produce an `index.asset.php` file that contains an array of dependencies and a version number for your block. For our simple example above, it is something like: -`array('dependencies' => array('react', 'wp-polyfill'), 'version' => 'fc93c4a9675c108725227db345898bcc');` - -Here is how to use this asset file to automatically set the dependency list for enqueuing the script. This prevents having to manually update the dependencies, it will be created based on the package imports used within your block. - -```php -$asset_file = include( plugin_dir_path( __FILE__ ) . 'build/index.asset.php'); - -wp_register_script( - 'myguten-block', - plugins_url( 'build/index.js', __FILE__ ), - $asset_file['dependencies'], - $asset_file['version'] -); -``` - -See [blocks in the block-development-examples repo](https://github.com/WordPress/block-development-examples) for full examples. - -## Summary - -Yes, the initial setup is a bit more involved, but the additional features and benefits are usually worth the trade off in setup time. - -With a setup in place, the standard workflow is: - -1. Install dependencies: `npm install` -2. Start development builds: `npm start` -3. Develop. Test. Repeat. -4. Create production build: `npm run build` diff --git a/docs/how-to-guides/javascript/loading-javascript.md b/docs/how-to-guides/javascript/loading-javascript.md deleted file mode 100644 index 80150f14445a1d..00000000000000 --- a/docs/how-to-guides/javascript/loading-javascript.md +++ /dev/null @@ -1,49 +0,0 @@ -# Loading JavaScript - -With the plugin in place, you can add the code that loads the JavaScript. This methodology follows the standard WordPress procedure of enqueuing scripts, see [enqueuing section of the Plugin Handbook](https://developer.wordpress.org/plugins/javascript/enqueuing/). - -Add the following code to your `myguten-plugin.php` file: - -```php -function myguten_enqueue() { - wp_enqueue_script( - 'myguten-script', - plugins_url( 'myguten.js', __FILE__ ) - ); -} -add_action( 'enqueue_block_editor_assets', 'myguten_enqueue' ); -``` - -The `enqueue_block_editor_assets` hook is used, which is called when the block editor loads, and will enqueue the JavaScript file `myguten.js`. - -Create a file called `myguten.js` and add: - -```js -console.log( "I'm loaded!" ); -``` - -Next, create a new post in the block editor. - -We'll check the JavaScript console in your browser's Developer Tools, to see if the message is displayed. If you're not sure what developer tools are, Mozilla's ["What are browser developer tools?"](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_are_browser_developer_tools) documentation provides more information, including more background on the [JavaScript console](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_are_browser_developer_tools#The_JavaScript_console). - -If your code is registered and enqueued correctly, you should see a message in your console: - -![Console Log Message Success](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/assets/js-tutorial-console-log-success.png) - -**Note for Theme Developers:** The above method of enqueuing is used for plugins. If you are extending the block editor for your theme there is a minor difference, you will use the `get_template_directory_uri()` function instead of `plugins_url()`. So for a theme, the enqueue example is: - -```php -function myguten_enqueue() { - wp_enqueue_script( - 'myguten-script', - get_template_directory_uri() . '/myguten.js' - ); -} -add_action( 'enqueue_block_editor_assets', 'myguten_enqueue' ); -``` - -### Recap - -At this point, you have a plugin in the directory `wp-content/plugins/myguten-plugin` with two files: the PHP server-side code in `myguten-plugin.php`, and the JavaScript which runs in the browser in `myguten.js`. - -This puts all the initial pieces in place for you to start extending the block editor. diff --git a/docs/how-to-guides/javascript/plugins-background.md b/docs/how-to-guides/javascript/plugins-background.md deleted file mode 100644 index 617cbe64e05ede..00000000000000 --- a/docs/how-to-guides/javascript/plugins-background.md +++ /dev/null @@ -1,22 +0,0 @@ -# Plugins Background - -The primary means of extending WordPress is the plugin. The WordPress [Plugin Basics](https://developer.wordpress.org/plugins/plugin-basics/) documentation provides details on building a plugin. - -The quickest way to start is to create a new directory in `wp-content/plugins/` to contain your plugin code. For this example, call it `myguten-plugin`. - -Inside this new directory, create a file called `myguten-plugin.php`. This is the server-side code that runs when your plugin is active. - -For now, add the following code in the file: - -```php - Historically, JavaScript files loaded in a web page share the same scope. - -Notice the _historically_. - -JavaScript has evolved quite a bit since its creation. As of 2015, the language supports modules, also known as _ES6 modules_, that introduce separate scope per file: a global variable in `first.js` wouldn't be exposed to `second.js`. This feature is already [supported by modern browsers](https://caniuse.com/#feat=es6-module), but not all of them do. If your code needs to run in browsers that don't support modules, your last resort is using IIFEs. diff --git a/docs/how-to-guides/javascript/troubleshooting.md b/docs/how-to-guides/javascript/troubleshooting.md deleted file mode 100644 index 99484054d7b012..00000000000000 --- a/docs/how-to-guides/javascript/troubleshooting.md +++ /dev/null @@ -1,76 +0,0 @@ -# Troubleshooting - -If you're having trouble getting your JavaScript code to work, here are a few tips on how to find errors to help you troubleshoot. - -## Console Log - -The console log is a JavaScript developer's best friend. It is a good practice to work with it open, as it displays errors and notices in one place. See Mozilla's [JavaScript console](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_are_browser_developer_tools#The_JavaScript_console) documentation for more. - -To open the JavaScript console, find the correct key combination for your browser and OS: - -| Browser | Windows | Linux | Mac | -| ------- | ------------ | ------------ | --------- | -| Firefox | Ctrl+Shift+K | Ctrl+Shift+K | Cmd+Opt+K | -| Chrome | Ctrl+Shift+J | Ctrl+Shift+J | Cmd+Opt+J | -| Edge | Ctrl+Shift+J | Ctrl+Shift+J | Cmd+Opt+J | -| Safari | | | Cmd+Opt+C | - -### First Step - -Your first step in debugging should be to check the JavaScript console for any errors. Here is an example, which shows a syntax error on line 6: - -![console error](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/assets/js-tutorial-console-log-error.png) - -### Display your message in console log - -You can also write directly to the console from your JavaScript code for debugging and checking variable values. Use the `console.log` function like so: - -```js -console.log( 'My message' ); -``` - -Or if you want to include a message and variable, in this case display the contents of settings variable: - -```js -console.log( 'Settings value:', settings ); -``` - -### Using console log - -You can also write JavaScript directly in the console if you want to test a short command. The commands you run apply to the open browser window. Try this example with the [wp.data package](/packages/data/README.md) to count how many blocks are in the editor. Play with it and also try to use the console to browse available functions. - -```js -wp.data.select( 'core/block-editor' ).getBlockCount(); -``` - -![JavaScript example command](https://developer.wordpress.org/files/2020/07/js-console-cmd.gif) - -### Using the `debugger` statement - -If you would like to pause code execution at a certain line of code, you can write `debugger;` anywhere in your code. Once the browser sees the statement `debugger;`, it will pause execution of your code. This allows you to inspect all variables around the `debugger` statement, which is very useful. [See this MDN page for more information](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger). - -## Confirm JavaScript is loading - -If you are not seeing your changes, and no errors, check that your JavaScript file is being enqueued. Open the page source in your browser's web inspector (some browsers may allow you to view the page source by right clicking on the page and selecting "View Page Source"), and look for the `