From 72f688528b60fad0549645741ef309cbc2f6610f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Wed, 30 Sep 2020 15:16:17 +0300 Subject: [PATCH 01/19] Classic block: use hooks (#25737) --- packages/block-library/src/classic/edit.js | 336 ++++++++++----------- 1 file changed, 165 insertions(+), 171 deletions(-) diff --git a/packages/block-library/src/classic/edit.js b/packages/block-library/src/classic/edit.js index 1f8c675c0effe7..70ccedc0d71e32 100644 --- a/packages/block-library/src/classic/edit.js +++ b/packages/block-library/src/classic/edit.js @@ -8,7 +8,7 @@ import { debounce } from 'lodash'; */ import { BlockControls } from '@wordpress/block-editor'; import { ToolbarGroup } from '@wordpress/components'; -import { Component } from '@wordpress/element'; +import { useEffect, useRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { BACKSPACE, DELETE, F10, isKeyboardEvent } from '@wordpress/keycodes'; @@ -35,211 +35,205 @@ function isTmceEmpty( editor ) { return /^\n?$/.test( body.innerText || body.textContent ); } -export default class ClassicEdit extends Component { - constructor( props ) { - super( props ); - this.initialize = this.initialize.bind( this ); - this.onSetup = this.onSetup.bind( this ); - this.focus = this.focus.bind( this ); - } +export default function ClassicEdit( { + clientId, + attributes: { content }, + setAttributes, + onReplace, +} ) { + const didMount = useRef( false ); + + useEffect( () => { + if ( ! didMount.current ) { + return; + } + + const editor = window.tinymce.get( `editor-${ clientId }` ); + const currentContent = editor?.getContent(); + + if ( currentContent !== content ) { + editor.setContent( content || '' ); + } + }, [ content ] ); - componentDidMount() { + useEffect( () => { const { baseURL, suffix } = window.wpEditorL10n.tinymce; + didMount.current = true; + window.tinymce.EditorManager.overrideDefaults( { base_url: baseURL, suffix, } ); - if ( document.readyState === 'complete' ) { - this.initialize(); - } else { - document.addEventListener( 'readystatechange', () => { - if ( document.readyState === 'complete' ) { - this.initialize(); - } - } ); - } - } - - componentWillUnmount() { - window.addEventListener( 'DOMContentLoaded', this.initialize ); - wp.oldEditor.remove( `editor-${ this.props.clientId }` ); - } + function onSetup( editor ) { + let bookmark; - componentDidUpdate( prevProps ) { - const { - clientId, - attributes: { content }, - } = this.props; + if ( content ) { + editor.on( 'loadContent', () => editor.setContent( content ) ); + } - const editor = window.tinymce.get( `editor-${ clientId }` ); - const currentContent = editor?.getContent(); + editor.on( 'blur', () => { + bookmark = editor.selection.getBookmark( 2, true ); + // There is an issue with Chrome and the editor.focus call in core at https://core.trac.wordpress.org/browser/trunk/src/js/_enqueues/lib/link.js#L451. + // This causes a scroll to the top of editor content on return from some content updating dialogs so tracking + // scroll position until this is fixed in core. + const scrollContainer = document.querySelector( + '.interface-interface-skeleton__content' + ); + const scrollPosition = scrollContainer.scrollTop; - if ( - prevProps.attributes.content !== content && - currentContent !== content - ) { - editor.setContent( content || '' ); - } - } + setAttributes( { + content: editor.getContent(), + } ); - initialize() { - const { clientId } = this.props; - const { settings } = window.wpEditorL10n.tinymce; - wp.oldEditor.initialize( `editor-${ clientId }`, { - tinymce: { - ...settings, - inline: true, - content_css: false, - fixed_toolbar_container: `#toolbar-${ clientId }`, - setup: this.onSetup, - }, - } ); - } + editor.once( 'focus', () => { + if ( bookmark ) { + editor.selection.moveToBookmark( bookmark ); + if ( scrollContainer.scrollTop !== scrollPosition ) { + scrollContainer.scrollTop = scrollPosition; + } + } + } ); - onSetup( editor ) { - const { - attributes: { content }, - setAttributes, - } = this.props; - let bookmark; + return false; + } ); - this.editor = editor; + editor.on( 'mousedown touchstart', () => { + bookmark = null; + } ); - if ( content ) { - editor.on( 'loadContent', () => editor.setContent( content ) ); - } + const debouncedOnChange = debounce( () => { + const value = editor.getContent(); - editor.on( 'blur', () => { - bookmark = editor.selection.getBookmark( 2, true ); - // There is an issue with Chrome and the editor.focus call in core at https://core.trac.wordpress.org/browser/trunk/src/js/_enqueues/lib/link.js#L451. - // This causes a scroll to the top of editor content on return from some content updating dialogs so tracking - // scroll position until this is fixed in core. - const scrollContainer = document.querySelector( - '.interface-interface-skeleton__content' - ); - const scrollPosition = scrollContainer.scrollTop; + if ( value !== editor._lastChange ) { + editor._lastChange = value; + setAttributes( { + content: value, + } ); + } + }, 250 ); + editor.on( 'Paste Change input Undo Redo', debouncedOnChange ); + + // We need to cancel the debounce call because when we remove + // the editor (onUnmount) this callback is executed in + // another tick. This results in setting the content to empty. + editor.on( 'remove', debouncedOnChange.cancel ); + + editor.on( 'keydown', ( event ) => { + if ( isKeyboardEvent.primary( event, 'z' ) ) { + // Prevent the gutenberg undo kicking in so TinyMCE undo stack works as expected + event.stopPropagation(); + } - setAttributes( { - content: editor.getContent(), - } ); + if ( + ( event.keyCode === BACKSPACE || + event.keyCode === DELETE ) && + isTmceEmpty( editor ) + ) { + // delete the block + onReplace( [] ); + event.preventDefault(); + event.stopImmediatePropagation(); + } - editor.once( 'focus', () => { - if ( bookmark ) { - editor.selection.moveToBookmark( bookmark ); - if ( scrollContainer.scrollTop !== scrollPosition ) { - scrollContainer.scrollTop = scrollPosition; - } + const { altKey } = event; + /* + * Prevent Mousetrap from kicking in: TinyMCE already uses its own + * `alt+f10` shortcut to focus its toolbar. + */ + if ( altKey && event.keyCode === F10 ) { + event.stopPropagation(); } } ); - return false; - } ); - - editor.on( 'mousedown touchstart', () => { - bookmark = null; - } ); - - const debouncedOnChange = debounce( () => { - const value = editor.getContent(); + editor.on( 'init', () => { + const rootNode = editor.getBody(); - if ( value !== editor._lastChange ) { - editor._lastChange = value; - setAttributes( { - content: value, - } ); - } - }, 250 ); - editor.on( 'Paste Change input Undo Redo', debouncedOnChange ); - - // We need to cancel the debounce call because when we remove - // the editor (onUnmount) this callback is executed in - // another tick. This results in setting the content to empty. - editor.on( 'remove', debouncedOnChange.cancel ); - - editor.on( 'keydown', ( event ) => { - if ( isKeyboardEvent.primary( event, 'z' ) ) { - // Prevent the gutenberg undo kicking in so TinyMCE undo stack works as expected - event.stopPropagation(); - } + // Create the toolbar by refocussing the editor. + if ( rootNode.ownerDocument.activeElement === rootNode ) { + rootNode.blur(); + editor.focus(); + } + } ); + } - if ( - ( event.keyCode === BACKSPACE || event.keyCode === DELETE ) && - isTmceEmpty( editor ) - ) { - // delete the block - this.props.onReplace( [] ); - event.preventDefault(); - event.stopImmediatePropagation(); - } + function initialize() { + const { settings } = window.wpEditorL10n.tinymce; + wp.oldEditor.initialize( `editor-${ clientId }`, { + tinymce: { + ...settings, + inline: true, + content_css: false, + fixed_toolbar_container: `#toolbar-${ clientId }`, + setup: onSetup, + }, + } ); + } - const { altKey } = event; - /* - * Prevent Mousetrap from kicking in: TinyMCE already uses its own - * `alt+f10` shortcut to focus its toolbar. - */ - if ( altKey && event.keyCode === F10 ) { - event.stopPropagation(); + function onReadyStateChange() { + if ( document.readyState === 'complete' ) { + initialize(); } - } ); + } - editor.on( 'init', () => { - const rootNode = this.editor.getBody(); + if ( document.readyState === 'complete' ) { + initialize(); + } else { + document.addEventListener( 'readystatechange', onReadyStateChange ); + } - // Create the toolbar by refocussing the editor. - if ( rootNode.ownerDocument.activeElement === rootNode ) { - rootNode.blur(); - this.editor.focus(); - } - } ); - } + return () => { + document.removeEventListener( + 'readystatechange', + onReadyStateChange + ); + wp.oldEditor.remove( `editor-${ clientId }` ); + }; + }, [] ); - focus() { - if ( this.editor ) { - this.editor.focus(); + function focus() { + const editor = window.tinymce.get( `editor-${ clientId }` ); + if ( editor ) { + editor.focus(); } } - onToolbarKeyDown( event ) { + function onToolbarKeyDown( event ) { // Prevent WritingFlow from kicking in and allow arrows navigation on the toolbar. event.stopPropagation(); // Prevent Mousetrap from moving focus to the top toolbar when pressing `alt+f10` on this block toolbar. event.nativeEvent.stopImmediatePropagation(); } - render() { - const { clientId } = this.props; - - // Disable reasons: - // - // jsx-a11y/no-static-element-interactions - // - the toolbar itself is non-interactive, but must capture events - // from the KeyboardShortcuts component to stop their propagation. - - /* eslint-disable jsx-a11y/no-static-element-interactions */ - return ( - <> - - - - - -
-
- - ); - /* eslint-enable jsx-a11y/no-static-element-interactions */ - } + // Disable reasons: + // + // jsx-a11y/no-static-element-interactions + // - the toolbar itself is non-interactive, but must capture events + // from the KeyboardShortcuts component to stop their propagation. + + /* eslint-disable jsx-a11y/no-static-element-interactions */ + return ( + <> + + + + + +
+
+ + ); + /* eslint-enable jsx-a11y/no-static-element-interactions */ } From 66048316d9fb7ab897d9812ded51434e9bf0a5fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Wed, 30 Sep 2020 15:22:04 +0300 Subject: [PATCH 02/19] Block API: light block edit/save symmetry (#25644) * Block API: light block edit/save symmetry * Pass block context differently * Export through useBlockWrapperProps * Mark getBlockProps unstable --- .../components/block-list/block-wrapper.js | 8 +++++++ .../block-list/block-wrapper.native.js | 7 ++++++ packages/block-library/src/audio/save.js | 7 ++++-- packages/block-library/src/button/save.js | 7 ++++-- packages/block-library/src/buttons/save.js | 7 ++++-- packages/block-library/src/code/save.js | 7 ++++-- packages/block-library/src/column/save.js | 12 ++++++++-- packages/block-library/src/columns/save.js | 7 ++++-- packages/block-library/src/cover/save.js | 3 ++- packages/block-library/src/group/save.js | 7 ++++-- packages/block-library/src/heading/save.js | 15 ++++++------ packages/block-library/src/image/save.js | 13 +++++++--- packages/block-library/src/list/save.js | 18 +++++++------- packages/block-library/src/media-text/save.js | 7 ++++-- packages/block-library/src/paragraph/save.js | 15 ++++++------ .../block-library/src/preformatted/save.js | 11 +++++++-- packages/block-library/src/quote/save.js | 7 ++++-- .../block-library/src/social-links/save.js | 9 ++++--- packages/block-library/src/verse/save.js | 13 +++++----- packages/block-library/src/video/save.js | 7 ++++-- packages/blocks/src/api/index.js | 1 + packages/blocks/src/api/serializer.js | 24 ++++++++++++++++++- 22 files changed, 151 insertions(+), 61 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block-wrapper.js b/packages/block-editor/src/components/block-list/block-wrapper.js index 34e8e0b7eb35f6..f943b50b7c6f1d 100644 --- a/packages/block-editor/src/components/block-list/block-wrapper.js +++ b/packages/block-editor/src/components/block-list/block-wrapper.js @@ -19,6 +19,7 @@ import { ENTER, BACKSPACE, DELETE } from '@wordpress/keycodes'; import { __, sprintf } from '@wordpress/i18n'; import { useSelect, useDispatch } from '@wordpress/data'; import deprecated from '@wordpress/deprecated'; +import { __unstableGetBlockProps as getBlockProps } from '@wordpress/blocks'; /** * Internal dependencies @@ -300,6 +301,13 @@ export function useBlockWrapperProps( props = {}, { __unstableIsHtml } = {} ) { }; } +/** + * Call within a save function to get the props for the block wrapper. + * + * @param {Object} props Optional. Props to pass to the element. + */ +useBlockWrapperProps.save = getBlockProps; + const BlockComponent = forwardRef( ( { children, tagName: TagName = 'div', ...props }, ref ) => { deprecated( 'wp.blockEditor.__experimentalBlock', { diff --git a/packages/block-editor/src/components/block-list/block-wrapper.native.js b/packages/block-editor/src/components/block-list/block-wrapper.native.js index 10aea4bb9edd71..55c3bff51f66bd 100644 --- a/packages/block-editor/src/components/block-list/block-wrapper.native.js +++ b/packages/block-editor/src/components/block-list/block-wrapper.native.js @@ -1,3 +1,8 @@ +/** + * WordPress dependencies + */ +import { __unstableGetBlockProps as getBlockProps } from '@wordpress/blocks'; + /** * Internal dependencies */ @@ -7,6 +12,8 @@ export function useBlockWrapperProps( props = {} ) { return props; } +useBlockWrapperProps.save = getBlockProps; + const ExtendedBlockComponent = ELEMENTS.reduce( ( acc, element ) => { acc[ element ] = element; return acc; diff --git a/packages/block-library/src/audio/save.js b/packages/block-library/src/audio/save.js index f88eb0bba52e0c..15faa71d8536ba 100644 --- a/packages/block-library/src/audio/save.js +++ b/packages/block-library/src/audio/save.js @@ -1,14 +1,17 @@ /** * WordPress dependencies */ -import { RichText } from '@wordpress/block-editor'; +import { + RichText, + __experimentalUseBlockWrapperProps as useBlockWrapperProps, +} from '@wordpress/block-editor'; export default function save( { attributes } ) { const { autoplay, caption, loop, preload, src } = attributes; return ( src && ( -
+