-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RichText: Add (foot)notes #15267
RichText: Add (foot)notes #15267
Changes from all commits
dc101d6
88bc58b
f8dc77e
43d2a62
682079e
98f61c2
658fa8d
7a6480f
cb94240
bcc29e6
af6a633
47dcc1d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"name": "core/footnotes", | ||
"category": "footnotes", | ||
"attributes": { | ||
"footnotes": { | ||
"type": "array" | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import classnames from 'classnames'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { __ } from '@wordpress/i18n'; | ||
import { IconButton, Toolbar, Slot } from '@wordpress/components'; | ||
import { useRef, useState } from '@wordpress/element'; | ||
import { useDispatch } from '@wordpress/data'; | ||
import { withSafeTimeout } from '@wordpress/compose'; | ||
import { RichText, BlockFormatControls } from '@wordpress/block-editor'; | ||
|
||
function Edit( { attributes, setAttributes, className, setTimeout } ) { | ||
const { footnotes } = attributes; | ||
const ref = useRef( null ); | ||
const { clearSelectedBlock } = useDispatch( 'core/block-editor' ); | ||
const [ selected, select ] = useState( false ); | ||
|
||
if ( ! footnotes.length ) { | ||
return null; | ||
} | ||
|
||
const hasSelection = footnotes.some( ( { isSelected } ) => isSelected ); | ||
const viewAll = () => { | ||
ref.current.focus(); | ||
clearSelectedBlock(); | ||
// Wait for footnotes to be recalculated after the DOM change. | ||
setTimeout( () => | ||
// Wait for DOM to update. | ||
setTimeout( () => ref.current.scrollIntoView() ) | ||
); | ||
}; | ||
|
||
const onFocus = () => { | ||
clearSelectedBlock(); | ||
select( true ); | ||
}; | ||
|
||
const onBlur = () => { | ||
select( false ); | ||
}; | ||
|
||
return ( | ||
<div | ||
className={ classnames( 'wp-block block-library-list', className, { | ||
'is-selected': hasSelection, | ||
} ) } | ||
tabIndex="0" | ||
onFocus={ onFocus } | ||
onBlur={ onBlur } | ||
ref={ ref } | ||
> | ||
{ hasSelection && | ||
<> | ||
<Toolbar> | ||
<IconButton icon="editor-ol" onClick={ viewAll }> | ||
{ __( 'View all Footnotes' ) } | ||
</IconButton> | ||
<Slot name="__unstable-footnote-controls" /> | ||
</Toolbar> | ||
{ selected && <BlockFormatControls.Slot /> } | ||
</> | ||
} | ||
<ol> | ||
{ footnotes.map( ( { id, text, isSelected }, index ) => | ||
<li | ||
key={ id } | ||
id={ id } | ||
className={ classnames( { 'is-selected': isSelected } ) } | ||
> | ||
{ /* Needed for alignment. */ } | ||
<div> | ||
<a | ||
href={ `#${ id }-anchor` } | ||
aria-label={ __( 'Back to content' ) } | ||
onClick={ () => { | ||
// This is a hack to get the target to focus. | ||
// The attribute will later be removed when selection is set. | ||
document.getElementById( `${ id }-anchor` ).contentEditable = 'false'; | ||
} } | ||
> | ||
↑ | ||
</a> | ||
{ ' ' } | ||
<RichText.Bare | ||
value={ text } | ||
onChange={ ( value ) => { | ||
setAttributes( { | ||
footnotes: footnotes.map( ( footnote, i ) => { | ||
if ( i !== index ) { | ||
return footnote; | ||
} | ||
|
||
return { | ||
...footnote, | ||
text: value, | ||
}; | ||
} ), | ||
} ); | ||
} } | ||
placeholder={ __( 'Footnote' ) } | ||
/> | ||
</div> | ||
</li> | ||
) } | ||
</ol> | ||
</div> | ||
); | ||
} | ||
|
||
export default withSafeTimeout( Edit ); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
@keyframes slide-in-note { | ||
from { | ||
transform: translateY(100%); | ||
} | ||
|
||
to { | ||
transform: translateY(0); | ||
} | ||
} | ||
|
||
.editor-styles-wrapper .wp-block-footnotes { | ||
margin: 0; | ||
|
||
.components-toolbar { | ||
border: none; | ||
padding: 10px; | ||
} | ||
|
||
&.is-selected { | ||
background: #fff; | ||
position: sticky; | ||
bottom: 0; | ||
z-index: 1000; | ||
animation-duration: 0.2s; | ||
animation-name: slide-in-note; | ||
animation-timing-function: ease; | ||
box-shadow: -3px 0 0 0 #555d66; | ||
margin: 0 1px; | ||
border-top: 1px solid rgba(66, 88, 99, 0.4); | ||
|
||
// Vertically center the single list item. | ||
ol { | ||
margin-top: 0; | ||
margin-bottom: 0; | ||
padding-bottom: 10px; | ||
} | ||
|
||
// We cannot use display as it will affect the numbering. | ||
li { | ||
visibility: hidden; | ||
height: 0; | ||
} | ||
|
||
li.is-selected { | ||
visibility: visible; | ||
height: auto; | ||
} | ||
} | ||
|
||
li > div > a { | ||
float: left; | ||
margin-right: 5px; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { __ } from '@wordpress/i18n'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import metadata from './block.json'; | ||
import save from './save'; | ||
import edit from './edit'; | ||
|
||
const { name } = metadata; | ||
|
||
export { metadata, name }; | ||
|
||
export const settings = { | ||
title: __( 'Footnotes' ), | ||
supports: { | ||
inserter: false, | ||
}, | ||
save, | ||
edit, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { __ } from '@wordpress/i18n'; | ||
|
||
export default function Save( { attributes } ) { | ||
const { footnotes } = attributes; | ||
|
||
if ( ! footnotes.length ) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<ol> | ||
{ footnotes.map( ( { id, text } ) => | ||
<li key={ id }> | ||
<a | ||
id={ id } | ||
href={ `#${ id }-anchor` } | ||
aria-label={ __( 'Back to content' ) } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will be tricky until we have better validation for blocks if a user edits the post with a different locale loaded. Until we get there, how realistically can we a) hardcode a string in English (in Esperanto?) instead of passing it through There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that's annoying... Emoji? Are they read and translated? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was also a reason against saving the footnotes in the post content, so it can be generated in PHP. |
||
> | ||
↑ | ||
</a> | ||
{ ` ${ text }` } | ||
</li> | ||
) } | ||
</ol> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// Ideally, this should be the post wrapper element. So `.post` or similar. | ||
body { | ||
counter-reset: footnotes; | ||
} | ||
|
||
.footnote-anchor { | ||
counter-increment: footnotes; | ||
} | ||
|
||
.footnote-anchor::after { | ||
margin-left: 2px; | ||
content: "[" counter(footnotes) "]"; | ||
vertical-align: super; | ||
font-size: smaller; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<!-- wp:footnotes {"footnotes":[{"id":"0","text":"test"}]} --> | ||
<ol class="wp-block-footnotes"><li><a id="0" href="#0-anchor" aria-label="Back to content">↑</a> test</li></ol> | ||
<!-- /wp:footnotes --> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this something we could use meta / custom sources for (#16402), rather than to build-in awareness of footnotes to the editor module?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have a super deep understanding of custom sources. Would it work if the source is the post content?