Skip to content
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

[WIP] Add footnotes support to Gutenberg #6549

Closed
wants to merge 36 commits into from
Closed
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
791305a
Add footnotes control button to paragraph block
Aljullu May 2, 2018
533aa51
Store footnote ids in the block object
Aljullu May 2, 2018
95237fc
Create footnotes block on editor setup and update footnotes on block …
Aljullu May 4, 2018
213150e
Create Footnotes block
Aljullu May 5, 2018
2e6de5e
Use auto-incrementing numbers instead of asterisks for footnotes
Aljullu May 5, 2018
40dc686
Update footnotes block when inserting or removing blocks
Aljullu May 5, 2018
b5cee89
Make footnotes block to render nothing if there are no footnotes
Aljullu May 5, 2018
3941d18
Rename variables like 'newState' to 'nextState' to keep consistency w…
Aljullu May 6, 2018
55889c2
Use a template literal for the footnotes markup
Aljullu May 6, 2018
202dfd9
Save block footnotes in the block attributes instead of a block prope…
Aljullu May 6, 2018
e8597a0
Use withSelect HOC in Footnotes block so it's automatically updated w…
Aljullu May 6, 2018
ec802ab
Clean up parser code no longer being used
Aljullu May 6, 2018
d2309af
Simplify the code which rendersthe footnotes block
Aljullu May 6, 2018
e7dc2bc
Create insertFootnotesBlock action to create the footnotes block when…
Aljullu May 8, 2018
23a3037
Remove footnotes block when the last footnote is deleted
Aljullu May 8, 2018
f519cac
Import parseFootnotesFromContent from @wordpress/editor instead of @w…
Aljullu May 8, 2018
e7715cd
Update paragraph snapshots with blockFootnotes attribute
Aljullu May 8, 2018
a9ddeee
Flatten the blocks before searching for footnotes
Aljullu May 9, 2018
db6e61d
Clean up unnecessary properties from footnotes tests
Aljullu May 9, 2018
991a67a
Allow only one instance of footnotes block
Aljullu May 10, 2018
bed9b39
Remove check if core/editor selector is defined in footnotes block
Aljullu May 15, 2018
33d0045
Add/remove the footnotes blocks every time the first footnote is adde…
Aljullu May 16, 2018
d06aff6
Rename footnote SUP element attribute from data-footnote-id to data-w…
Aljullu May 20, 2018
75fc957
Clean up footnotes block attributes
Aljullu May 20, 2018
089dd9d
Optimize checking if footnotes block must be added/removed
Aljullu May 20, 2018
c33beac
Update footnotes block fixtures
Aljullu May 21, 2018
8c2a1a2
Raise paragraph key in pullquote test so it doesn't collide with rece…
Aljullu May 21, 2018
b3c303d
Extract blockFootnotes paragraph attribute from the markup
Aljullu May 21, 2018
14b9b2d
Refactor getFootnotes selector
Aljullu May 21, 2018
0646693
Several minor fixes and typos corrected
Aljullu May 21, 2018
ac2e5af
Don't trigger updateFootnotes when spliting a paragraph block
Aljullu May 21, 2018
72277b5
Update footnotes attribute when the order is changed
Aljullu May 22, 2018
11794cc
Order footnotes when saving footnotes block
Aljullu May 27, 2018
c958cca
Rename FootnotesEditor to FootnotesEdit to keep consistency with othe…
Aljullu Jun 4, 2018
578d71a
Replace spaces with tabs in footnotes style sheets
Aljullu Jun 4, 2018
400140f
Make the footnote number non-editable
Aljullu Jun 10, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions blocks/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export {
export {
default as parse,
getBlockAttributes,
parseFootnotesFromContent,
parseWithAttributeSchema,
} from './parser';
export { default as rawHandler, getPhrasingContentSchema } from './raw-handling';
Expand Down
24 changes: 24 additions & 0 deletions blocks/api/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,30 @@ export function getAttributesAndInnerBlocksFromDeprecatedVersion( blockType, inn
}
}

/**
* Parses the content and extracts the list of footnotes.
*
* @param {?Array} content The content to parse.
*
* @return {Array} Array of footnote ids.
*/
export function parseFootnotesFromContent( content ) {
if ( ! content || ! Array.isArray( content ) ) {
return [];
}

return content.reduce( ( footnotes, element ) => {
if ( element.type === 'sup' && element.props[ 'data-wp-footnote-id' ] ) {
return [
...footnotes,
{ id: element.props[ 'data-wp-footnote-id' ] },
];
}

return footnotes;
}, [] );
}

/**
* Creates a block with fallback to the unknown type handler.
*
Expand Down
20 changes: 20 additions & 0 deletions blocks/api/test/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
getBlockAttribute,
getBlockAttributes,
asType,
parseFootnotesFromContent,
createBlockWithFallback,
getAttributesAndInnerBlocksFromDeprecatedVersion,
default as parse,
Expand Down Expand Up @@ -312,6 +313,25 @@ describe( 'block parser', () => {
} );
} );

describe( 'parseFootnotesFromContent', () => {
it( 'should return empty array if there is no content', () => {
const footnotes = parseFootnotesFromContent();

expect( footnotes ).toEqual( [] );
} );
it( 'should parse content and return footnote ids', () => {
const content = [
'Lorem ipsum',
{ type: 'sup', props: { 'data-wp-footnote-id': '12345' } },
'is a text',
];

const footnotes = parseFootnotesFromContent( content );

expect( footnotes ).toEqual( [ { id: '12345' } ] );
} );
} );

describe( 'createBlockWithFallback', () => {
it( 'should create the requested block if it exists', () => {
registerBlockType( 'core/test-block', defaultBlockSettings );
Expand Down
107 changes: 107 additions & 0 deletions core-blocks/footnotes/editor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
Copy link
Member

Choose a reason for hiding this comment

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

Should this file be edit.js like the other blocks?

* External dependencies
*/
import { get, isEqual } from 'lodash';

/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { withSelect } from '@wordpress/data';
import { RichText } from '@wordpress/blocks';

/**
* Internal dependencies
*/
import './editor.scss';

class FootnotesEditor extends Component {
constructor( props ) {
super( ...arguments );

this.setFootnotesInOrder( props.footnotesOrder );
this.state = {
editable: null,
};
}

setFootnotesInOrder( footnotesOrder ) {
const { attributes, setAttributes } = this.props;

const footnotes = footnotesOrder.map( ( { id } ) => {
return this.getFootnoteById( attributes.footnotes, id );
} );

setAttributes( { footnotes } );
}

getFootnoteById( footnotes, footnoteUid ) {
const filteredFootnotes = footnotes.filter(
( footnote ) => footnote.id === footnoteUid );

return get( filteredFootnotes, [ 0 ], { id: footnoteUid, text: '' } );
}

onChange( footnoteUid ) {
return ( nextValue ) => {
const { attributes, footnotesOrder, setAttributes } = this.props;

const nextFootnotes = footnotesOrder.map( ( { id } ) => {
if ( id === footnoteUid ) {
return {
id,
text: nextValue,
};
}

return this.getFootnoteById( attributes.footnotes, id );
} );

setAttributes( {
footnotes: nextFootnotes,
} );
};
}

onSetActiveEditable( id ) {
return () => {
this.setState( { editable: id } );
};
}

componentWillReceiveProps( nextProps ) {
Copy link
Member

Choose a reason for hiding this comment

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

This lifecycle method will be deprecated soon.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right! Will replace it.

const { footnotesOrder } = this.props;
const nextFootnotesOrder = nextProps.footnotesOrder;

if ( ! isEqual( footnotesOrder, nextFootnotesOrder ) ) {
Copy link
Member

Choose a reason for hiding this comment

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

Do you think we can avoid the deep comparison here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can't think of any way to avoid this comparison for now.

What we could easily do is to store these two arrays as an array of strings instead of an array of objects (from [ { id: 'abcd' }, ... ] to [ 'abcd', ... ]). That will make the comparison a little bit faster.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I refactored the footnotes block so this comparison is no longer necessary (11794cc).

this.setFootnotesInOrder( nextFootnotesOrder );
}
}

render() {
const { attributes, editable, isSelected } = this.props;
const { footnotes } = attributes;

return (
<ol className="blocks-footnotes__footnotes-list">
{ footnotes.map( ( footnote ) => (
<li key={ footnote.id }>
<RichText
tagName="span"
value={ footnote.text }
onChange={ this.onChange( footnote.id ) }
isSelected={ isSelected && editable === footnote.id }
placeholder={ __( 'Write footnote…' ) }
onFocus={ this.onSetActiveEditable( footnote.id ) }
/>
</li>
) ) }
</ol>
);
}
}

export default withSelect( ( select ) => ( {
footnotesOrder: select( 'core/editor' ).getFootnotes(),
Copy link
Member

Choose a reason for hiding this comment

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

I think we can improve this name a bit – it doesn't only include the order, but also the footnotes. How about orderedFootnotes?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I ended up renaming it to oderedFootnoteUids because it only includes the footnote uids (not the footnotes text), so I wanted to distinguish it from footnotes.

} ) )( FootnotesEditor );
3 changes: 3 additions & 0 deletions core-blocks/footnotes/editor.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.edit-post-visual-editor .blocks-footnotes__footnotes-list {
padding-left: 1.3em;
}
56 changes: 56 additions & 0 deletions core-blocks/footnotes/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import FootnotesEditor from './editor.js';
import './style.scss';

export const name = 'core/footnotes';

export const settings = {
title: __( 'Footnotes' ),
description: __( 'List of footnotes from the article' ),
category: 'common',
useOnce: true,
keywords: [ __( 'footnotes' ), __( 'references' ) ],

attributes: {
footnotes: {
type: 'array',
source: 'query',
selector: 'li',
query: {
id: {
source: 'attribute',
attribute: 'id',
},
text: {
source: 'children',
},
},
default: [],
},
},

edit: FootnotesEditor,

save( { attributes } ) {
const { footnotes } = attributes;

return (
<div>
<ol>
{ footnotes.map( ( footnote ) => (
<li id={ footnote.id } key={ footnote.id }>
{ footnote.text }
</li>
) ) }
</ol>
</div>
);
},
};
9 changes: 9 additions & 0 deletions core-blocks/footnotes/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.post,
.edit-post-visual-editor {
counter-reset: footnotes;
Copy link
Member

Choose a reason for hiding this comment

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

There are spaces here instead of tabs.

}

.wp-footnote::before {
counter-increment: footnotes;
content: counter(footnotes);
}
7 changes: 7 additions & 0 deletions core-blocks/footnotes/test/__snapshots__/index.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`core/footnotes block edit matches snapshot 1`] = `
<ol
class="blocks-footnotes__footnotes-list"
/>
`;
14 changes: 14 additions & 0 deletions core-blocks/footnotes/test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Internal dependencies
*/
import { name, settings } from '../';
import { blockEditRender } from '../../test/helpers';
import '@wordpress/editor';

describe( 'core/footnotes', () => {
test( 'block edit matches snapshot', () => {
const wrapper = blockEditRender( name, settings );

expect( wrapper ).toMatchSnapshot();
} );
} );
2 changes: 2 additions & 0 deletions core-blocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import * as columns from './columns';
import * as coverImage from './cover-image';
import * as embed from './embed';
import * as freeform from './freeform';
import * as footnotes from './footnotes';
import * as html from './html';
import * as latestPosts from './latest-posts';
import * as list from './list';
Expand Down Expand Up @@ -65,6 +66,7 @@ export const registerCoreBlocks = () => {
...embed.common,
...embed.others,
freeform,
footnotes,
html,
latestPosts,
more,
Expand Down
Loading