Skip to content

Commit

Permalink
Footnotes: try with post meta (#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 <miguelcsf@gmail.com>
  • Loading branch information
ellatrix and mcsf authored Jun 23, 2023
1 parent 0c69e9c commit 1cdf6df
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

1 comment on commit 1cdf6df

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky tests detected in 1cdf6df.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/5352904177
📝 Reported issues:

Please sign in to comment.