-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Footnotes (again): MVP #47682
Footnotes (again): MVP #47682
Conversation
Size Change: +2.87 kB (0%) Total Size: 1.35 MB
ℹ️ View Unchanged
|
Flaky tests detected in 284a071b4493abd3b525b33a6d4e9c7a4d6b0d16. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/4607359876
|
Thanks for getting the discussion started here! I like the idea of an inline approach whose intrinsic markup intuitively makes sense to the reader even in the absence of server-side or client-side processing. https://jsfiddle.net/kd63cqrx/
|
This is a really exciting exploration, thank you @ellatrix!
+1 👍 Regarding rendering the footnotes and where to place them: |
I don't think a footnotes block is a good idea.
In my opinion it's better to automatically accumulate the footnotes. A block has additional benefits. The block would also just be a marker of where to insert the notes, it wouldn't actually contain the notes. |
e5d9b93
to
070e182
Compare
c0fb205
to
c639871
Compare
$count = $notes[ $id ]['count']; | ||
|
||
return ( | ||
'<sup><a class="note-link" href="#' . $id . '" id="' . $id . '-link-' . $count . '">[' . $index . ']</a></sup>' |
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.
Musing a bit here to explore ways this can be done with the HTML API. This is me playing with the idea and seeing how this need might be taken care of and lead the HTML API design…
This first one uses the Tag Processor in a way that's tightly coupled to internal details. It's not the way we want people to use the Tag Processor, but it's technically possible with what's in Core.
class FootNoteProcessor extends WP_HTML_Tag_Processor {
public function replace_footnotes() {
$notes = array();
while ( $this->next_tag( 'data' ) ) {
if ( 'core/footnote' !== $this->get_attribute( 'value' ) ) {
continue;
}
// This bookmark stuff is internal. Possible to use it here now
// like this, but not something intended to be used in practice
// outside of the Processor classes.
$this->set_bookmark( 'start' );
$start = $this->bookmarks['start']->start;
// Move to the very next tag token. If it isn't closing
// the DATA tag then bail; it's not what we expect.
if ( ! (
$this->next_tag( array( 'tag_closers' => 'visit' ) ) &&
'DATA' === $this->get_tag() &&
$this->is_tag_closer()
) ) {
return false;
}
$this->set_bookmark( 'end' );
// Grab the existing inner content.
$note = substr(
$this->html,
$this->bookmarks['start']->end,
$this->bookmarks['end']->start
);
$id = md5( $note );
if ( isset( $notes[ $id ] ) ) {
$notes[ $id ]['count'] += 1;
} else {
$notes[ $id ] = array(
'note' => $note,
'count' => 1,
);
}
// List starts at 1. If the note already exists, use the existing index.
$index = 1 + array_search( $id, array_keys( $notes ), true );
$count = $notes[ $id ]['count'];
$footnote = new WP_HTML_Tag_Processor( "<sup><a>[{$index}]</a></sup>" );
$footnote->next_tag( 'a' );
$footnote->add_class( 'note-link' );
$footnote->set_attribute( 'href', "#{$id}" );
$footnote->set_attribute( 'id', "{$id}-link-{$count}" );
// Replace the outer content with the new structure.
$this->lexical_updates[] = new WP_HTML_Text_Replacement(
$this->bookmarks['start']->start,
$this->bookmarks['end']->end + 1,
$footnote->get_updated_html()
);
$this->bookmarks['start']->start = $start;
$this->bookmarks['start']->end = $start;
$this->seek( 'start' );
$this->next_tag();
}
if ( empty( $notes ) ) {
return true;
}
…
}
}
$footnote_processor = new FootnoteProcessor( $block_html );
$footnote_processor->replace_footnotes();
$with_footnotes = $footnote_processor->get_updated_html();
The HTML processor should end up making this far less noisy and remove the internal coupling, but isn't available yet. I think that the code above will work today.
function replace_footnotes( $html ) {
$p = new WP_HTML_Processor( $html );g
$notes = array();
while ( $p->select( 'data[value = "core/footnote"]' ) ) {
$note = $p->get_inner_html();
…
$p->set_outer_html( $footnote->get_updated_html() );
}
if ( empty( $notes ) ) {
return $p->get_updated_html();
}
…
}
And yesterday while exploring some unrelated bits I thought of an idea to use today's Tag Processor to generate the template in easier way, utilizing the funky HTML placeholder syntax I realized is possible through abuse of the "tag closer with invalid tag name is a comment" rule.
$footnote = wp_html_template(
<<<HTML
<sup>
<a class="note-link" href="#</%id>" id="</%id>-link-</%count>">[</%index>]</a>
</sup>
HTML,
array(
'id' => $id,
'index' => $index,
'count' => $count
)
);
284a071
to
b7d5a41
Compare
I order to preserve some earlier work, I created a new PR (#49797). |
What?
Builds on the dynamic data API. In this case, we do want a fallback the footnote content can be HTML, we store the footnote in between brackets as the fallback.
Old description:
This PR attempts to add footnotes again. For storing footnotes with use the
[# ...]
shortcode syntax.shortcode
property.Even older description:
There's no standard way to do footnotes in HTML. There's a lot of different ways to display them: some people like them numbered at the bottom of the page, some people like them in a popover/tooltip style. It's even possible to place them on the side of the text (I really like this, but may be challenging to generalise with multiple notes on the same line). Some people like them to slide in inline. Some people even want multiple footnote variants. All of these ways requires different markup, and have accessibility challenges. I would love to have a system for footnotes that is not too opinionated where themes and plugins can decide to render them in unique ways.
Let's first think about the best way to store them. The least complex way seems to be inline, but it has a few disadvantages as well: while you can store HTML/rich text in them, you can't store blocks in them. So this doesn't work well if you want to have footnotes larger than a paragraph. It's a small use case, but I've seen many books that have longer (foot)notes.
Storing them somewhere else means that we need to save IDs (not just generate IDs on render time in case it's needed for a specific style). Saving IDs means they could go out of sync and out of order. And maybe external editors don't even know how to edit these. And where do we store them? Storing them at the bottom of the post content means we need to do some preprocessing before sending the content to the editor.
Let's now think about the best default way to render them. Ideally we have something that works whether you view the post on the front-end, RSS, email, JSON API etc., which means a way to render them without requiring JS and CSS. This may already be partially fixed by some ways of saving them. For example, if we save them as
[# Some footnote here.]
, this raw content already conveys that it's a note without any PHP/JS/CSS processing.But for the front-end, that's not nice enough. At first I thought a nice and easy way to render them by default is positioning the note fixed at the bottom of the page when the link is clicked. But with fixed positioning you can't inherit the width of content, so it's only possible to have them full-width. And when notes are larger, they start to take up a lot of screen space. This is only a good way to render them if you do it at the theme level.
So for the front-end, it seems that the easiest way to render them is at the bottom as a numbered list. This barely needs any additional CSS (whether we do the numbering in CSS or not is another question...). Themes/plugins can override the rendering in PHP or adjust it with JS.
How we do this in the editor is another issue. In the FSE, a good way to approach this is hook into the post content block, but for the post editor (which doesn't use the post content block yet), we'd need to hardcode the list at the bottom or add a temporary slot. That is, if we want it do be displayed at the bottom at all in the editor. It would be good to match the front-end (at the bottom), but we could also decide to let them be edited in popovers.
It will generally be complex to match the front-end: if a theme/plugin changes the way the footnotes are rendered in PHP, JS, or CSS, they will also need to find a way to do the same thing in the editor.
Other explorations
I quite like the idea of storing them in a shortcode-like way as
[# ...]
. We've anyway had discussions in the past about bringing back the shortcode syntax (in a limited way) for rich text tokens. It would be nice if we can autocomplete registered tokens when the user inputs[
. I also like that[#]
matches the default rendering of footnote links as[1]
...[]
has great character level semantics: it's immediately obvious that the text it contains is a note.Another thing that makes this so great is that it's easy to edit the rich text footnote in any other editor even if it has no footnote support. So you could edit the content through the JSON API and it will be fine with any visual editor.
Note that all of the following explorations are for inline storing and doesn't solve the problem of long footnotes.
HTML comments
This has also been a candidate for rich text tokens, so we could also consider it for footnotes. Like
[# ... ]
, this needs to be replaced in PHP. The difference is that the footnote is invisible by default.Span
Similar to
[# ... ]
in that the note is displayed inline, but there's no way of knowing it's a footnote.Small
"The side comment element". Similar to span, but you can see it's different from the rest of the text. Looks strange though.
Ruby
Basically the way to do annotations in HTML. Seems like a good idea at first, but the default styling is not great. Also altering it in CSS is a pain, but positioning footnotes in general is CSS without some JS is a pain. I also think it's a bit verbose. See also: <ruby> HTML footnotes
Details
Also seemed like a great idea at first. I wasn't the first. The problem is that while you can style this element as inline displayed, you can't put them in paragraphs because it's always parsed as a block level element. Would have been great to have a native fallback for show/hiding the note.
Template
Like an HTML comment, it's a great way to store the note at a location in an invisible way, especially if it will be processed by JS later on.
Other links
Accessible footnotes with CSS
Some inspiration: https://openai.com/research/image-gpt
Why?
How?
Testing Instructions
Testing Instructions for Keyboard
Screenshots or screencast