Skip to content

Commit

Permalink
Footnotes: try with post meta (WordPress#51201)
Browse files Browse the repository at this point in the history
* wip

* wip

* wip

* higher order reducer

* Make links interactive

* min diff

* Add style

* wip

* Lock apis

* Fix failing tests

* Add fn block fixture

* Clean up hor

* Fix unit test

* Don't save ce=false

* Fix selection issues

* Remove editor style in bock json

* Footnotes: try with post meta

* Clean up

* Add server site render callback

* Add fn block if not present

* Fix scroll into view

* Fix tests

* Fix PHP linting

* Guard for no meta

* clean up

* Fix unit tests

* Fix package lock

* Temporarily use __unstable

* Don't do anything if fn is not supported

* Add tests and comments

* Fix php lint

* Fix removal and test

* Writing flow: ignore ce false on mouse leave

* Insert footnote after selection instead of replacing it

* Somewhat fix copy/cut/paste

* Keep old footnotes so they can be restored on paste

* Address feedback: flatMap

* Prefer BFS search over flattenBlocks

This should make a difference in performance for large enough posts.

* Share RichText.Content with native

* Try private api

* Try removing private api from native

* Try unlocking just in time

* Skip JSON.parse for empty string

* Fix perf issue

---------

Co-authored-by: Miguel Fonseca <[email protected]>
  • Loading branch information
2 people authored and sethrubenstein committed Jul 13, 2023
1 parent e7864e5 commit 1e8e111
Show file tree
Hide file tree
Showing 36 changed files with 851 additions and 97 deletions.
9 changes: 9 additions & 0 deletions docs/reference-guides/core-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,15 @@ Add a link to a downloadable file. ([Source](https://github.com/WordPress/gutenb
- **Supports:** align, anchor, color (background, gradients, link, ~~text~~)
- **Attributes:** displayPreview, downloadButtonText, fileId, fileName, href, id, previewHeight, showDownloadButton, textLinkHref, textLinkTarget

## Footnotes

([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/footnotes))

- **Name:** core/footnotes
- **Category:** text
- **Supports:** ~~html~~, ~~inserter~~, ~~multiple~~, ~~reusable~~
- **Attributes:**

## Classic

Use the classic WordPress editor. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/freeform))
Expand Down
2 changes: 2 additions & 0 deletions lib/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ function gutenberg_reregister_core_block_types() {
'comments',
'details',
'group',
'footnotes',
'html',
'list',
'list-item',
Expand Down Expand Up @@ -65,6 +66,7 @@ function gutenberg_reregister_core_block_types() {
'comments-pagination-previous.php' => 'core/comments-pagination-previous',
'comments-title.php' => 'core/comments-title',
'comments.php' => 'core/comments',
'footnotes.php' => 'core/footnotes',
'file.php' => 'core/file',
'home-link.php' => 'core/home-link',
'image.php' => 'core/image',
Expand Down
5 changes: 4 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions packages/block-editor/src/components/copy-handler/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ export function useClipboardHandler() {

return useRefEffect( ( node ) => {
function handler( event ) {
if ( event.defaultPrevented ) {
// This was likely already handled in rich-text/use-paste-handler.js.
return;
}

const selectedBlockClientIds = getSelectedBlockClientIds();

if ( selectedBlockClientIds.length === 0 ) {
Expand Down Expand Up @@ -127,7 +132,6 @@ export function useClipboardHandler() {
return;
}

const eventDefaultPrevented = event.defaultPrevented;
event.preventDefault();

const isSelectionMergeable = __unstableIsSelectionMergeable();
Expand Down Expand Up @@ -197,10 +201,6 @@ export function useClipboardHandler() {
__unstableDeleteSelection();
}
} else if ( event.type === 'paste' ) {
if ( eventDefaultPrevented ) {
// This was likely already handled in rich-text/use-paste-handler.js.
return;
}
const {
__experimentalCanUserUseUnfilteredHTML:
canUserUseUnfilteredHTML,
Expand Down
85 changes: 85 additions & 0 deletions packages/block-editor/src/components/rich-text/content.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* WordPress dependencies
*/
import { RawHTML } from '@wordpress/element';
import {
children as childrenSource,
getSaveElement,
__unstableGetBlockProps as getBlockProps,
} from '@wordpress/blocks';
import deprecated from '@wordpress/deprecated';

/**
* Internal dependencies
*/
import { getMultilineTag } from './utils';

export const Content = ( { value, tagName: Tag, multiline, ...props } ) => {
// Handle deprecated `children` and `node` sources.
if ( Array.isArray( value ) ) {
deprecated( 'wp.blockEditor.RichText value prop as children type', {
since: '6.1',
version: '6.3',
alternative: 'value prop as string',
link: 'https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields/',
} );

value = childrenSource.toHTML( value );
}

const MultilineTag = getMultilineTag( multiline );

if ( ! value && MultilineTag ) {
value = `<${ MultilineTag }></${ MultilineTag }>`;
}

const content = <RawHTML>{ value }</RawHTML>;

if ( Tag ) {
const { format, ...restProps } = props;
return <Tag { ...restProps }>{ content }</Tag>;
}

return content;
};

Content.__unstableIsRichTextContent = {};

function findContent( blocks, richTextValues = [] ) {
if ( ! Array.isArray( blocks ) ) {
blocks = [ blocks ];
}

for ( const block of blocks ) {
if (
block?.type?.__unstableIsRichTextContent ===
Content.__unstableIsRichTextContent
) {
richTextValues.push( block.props.value );
continue;
}

if ( block?.props?.children ) {
findContent( block.props.children, richTextValues );
}
}

return richTextValues;
}

function _getSaveElement( { name, attributes, innerBlocks } ) {
return getSaveElement(
name,
attributes,
innerBlocks.map( _getSaveElement )
);
}

export function getRichTextValues( blocks = [] ) {
getBlockProps.skipFilters = true;
const values = findContent(
( Array.isArray( blocks ) ? blocks : [ blocks ] ).map( _getSaveElement )
);
getBlockProps.skipFilters = false;
return values;
}
37 changes: 2 additions & 35 deletions packages/block-editor/src/components/rich-text/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import classnames from 'classnames';
* WordPress dependencies
*/
import {
RawHTML,
useRef,
useCallback,
forwardRef,
Expand Down Expand Up @@ -46,6 +45,7 @@ import { useInsertReplacementText } from './use-insert-replacement-text';
import { useFirefoxCompat } from './use-firefox-compat';
import FormatEdit from './format-edit';
import { getMultilineTag, getAllowedFormats } from './utils';
import { Content } from './content';

export const keyboardShortcutContext = createContext();
export const inputEventContext = createContext();
Expand Down Expand Up @@ -419,40 +419,7 @@ function RichTextWrapper(

const ForwardedRichTextContainer = forwardRef( RichTextWrapper );

ForwardedRichTextContainer.Content = ( {
value,
tagName: Tag,
multiline,
...props
} ) => {
// Handle deprecated `children` and `node` sources.
if ( Array.isArray( value ) ) {
deprecated( 'wp.blockEditor.RichText value prop as children type', {
since: '6.1',
version: '6.3',
alternative: 'value prop as string',
link: 'https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields/',
} );

value = childrenSource.toHTML( value );
}

const MultilineTag = getMultilineTag( multiline );

if ( ! value && MultilineTag ) {
value = `<${ MultilineTag }></${ MultilineTag }>`;
}

const content = <RawHTML>{ value }</RawHTML>;

if ( Tag ) {
const { format, ...restProps } = props;
return <Tag { ...restProps }>{ content }</Tag>;
}

return content;
};

ForwardedRichTextContainer.Content = Content;
ForwardedRichTextContainer.isEmpty = ( value ) => {
return ! value || value.length === 0;
};
Expand Down
36 changes: 3 additions & 33 deletions packages/block-editor/src/components/rich-text/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,7 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
import {
RawHTML,
Platform,
useRef,
useCallback,
forwardRef,
} from '@wordpress/element';
import { Platform, useRef, useCallback, forwardRef } from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';
import {
pasteHandler,
Expand Down Expand Up @@ -55,6 +49,7 @@ import {
createLinkInParagraph,
} from './utils';
import EmbedHandlerPicker from './embed-handler-picker';
import { Content } from './content';

const classes = 'block-editor-rich-text__editable';

Expand Down Expand Up @@ -707,32 +702,7 @@ function RichTextWrapper(

const ForwardedRichTextContainer = forwardRef( RichTextWrapper );

ForwardedRichTextContainer.Content = ( {
value,
tagName: Tag,
multiline,
...props
} ) => {
// Handle deprecated `children` and `node` sources.
if ( Array.isArray( value ) ) {
value = childrenSource.toHTML( value );
}

const MultilineTag = getMultilineTag( multiline );

if ( ! value && MultilineTag ) {
value = `<${ MultilineTag }></${ MultilineTag }>`;
}

const content = <RawHTML>{ value }</RawHTML>;

if ( Tag ) {
const { format, ...restProps } = props;
return <Tag { ...restProps }>{ content }</Tag>;
}

return content;
};
ForwardedRichTextContainer.Content = Content;

ForwardedRichTextContainer.isEmpty = ( value ) => {
return ! value || value.length === 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export default function useDragSelection() {
// child elements of the content editable wrapper are editable
// and return true for this property. We only want to start
// multi selecting when the mouse leaves the wrapper.
if ( ! target.getAttribute( 'contenteditable' ) ) {
if ( target.getAttribute( 'contenteditable' ) !== 'true' ) {
return;
}

Expand Down
2 changes: 2 additions & 0 deletions packages/block-editor/src/private-apis.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import * as globalStyles from './components/global-styles';
import { ExperimentalBlockEditorProvider } from './components/provider';
import { lock } from './lock-unlock';
import { getRichTextValues } from './components/rich-text/content';
import ResizableBoxPopover from './components/resizable-box-popover';
import { ComposedPrivateInserter as PrivateInserter } from './components/inserter';
import { PrivateListView } from './components/list-view';
Expand All @@ -23,6 +24,7 @@ export const privateApis = {};
lock( privateApis, {
...globalStyles,
ExperimentalBlockEditorProvider,
getRichTextValues,
PrivateInserter,
PrivateListView,
ResizableBoxPopover,
Expand Down
3 changes: 2 additions & 1 deletion packages/block-library/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@
"memize": "^2.1.0",
"micromodal": "^0.4.10",
"preact": "^10.13.2",
"remove-accents": "^0.4.2"
"remove-accents": "^0.4.2",
"uuid": "^8.3.0"
},
"peerDependencies": {
"react": "^18.0.0",
Expand Down
18 changes: 18 additions & 0 deletions packages/block-library/src/footnotes/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "core/footnotes",
"title": "Footnotes",
"category": "text",
"description": "",
"keywords": [ "references" ],
"textdomain": "default",
"usesContext": [ "postId", "postType" ],
"supports": {
"html": false,
"multiple": false,
"inserter": false,
"reusable": false
},
"style": "wp-block-footnotes"
}
52 changes: 52 additions & 0 deletions packages/block-library/src/footnotes/edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* WordPress dependencies
*/
import { RichText, useBlockProps } from '@wordpress/block-editor';
import { useEntityProp } from '@wordpress/core-data';

export default function FootnotesEdit( { context: { postType, postId } } ) {
const [ meta, updateMeta ] = useEntityProp(
'postType',
postType,
'meta',
postId
);
const footnotes = meta?.footnotes ? JSON.parse( meta.footnotes ) : [];
return (
<ol { ...useBlockProps() }>
{ footnotes.map( ( { id, content } ) => (
<li key={ id }>
<RichText
id={ id }
tagName="span"
value={ content }
identifier={ id }
// To do: figure out why the browser is not scrolling
// into view when it receives focus.
onFocus={ ( event ) => {
if ( ! event.target.textContent.trim() ) {
event.target.scrollIntoView();
}
} }
onChange={ ( nextFootnote ) => {
updateMeta( {
...meta,
footnotes: JSON.stringify(
footnotes.map( ( footnote ) => {
return footnote.id === id
? {
content: nextFootnote,
id,
}
: footnote;
} )
),
} );
} }
/>{ ' ' }
<a href={ `#${ id }-link` }>↩︎</a>
</li>
) ) }
</ol>
);
}
Loading

0 comments on commit 1e8e111

Please sign in to comment.