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

Titles for footnotes #390

Open
wants to merge 5 commits into
base: lib
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
88 changes: 86 additions & 2 deletions Michelf/MarkdownExtra.php
Original file line number Diff line number Diff line change
Expand Up @@ -1653,6 +1653,88 @@ protected function appendFootnotes($text) {
return $text;
}

/**
* Footnote title - get plaintext representation of a footnote for use in tooltip.
* @param string $footnote
* @return string
*/
protected function get_footnote_title( $footnote ) {
// Remove newlines from the source code so they don't become visible in the tooltip
// Replace with spaces so there's a suitable gap between adjacent elements
$footnote = str_replace("\n", " ", $footnote);

// Turn the HTML of the footnote into a DOM
$doc = new \DOMDocument();
$doc->loadHTML( mb_convert_encoding( $footnote, 'HTML', 'UTF-8' ), LIBXML_NOERROR);
$xp = new \DOMXPath( $doc );

// Replace each element with the text inside it
foreach ( $xp->query('//*/text()') as $node ) {
$p_text = $doc->createTextNode( $node->wholeText );
$node->parentNode->replaceChild( $p_text, $node );
}

// Replace each <br> with a newline
foreach ( $xp->query('//br') as $node ) {
$br2nl = $doc->createTextNode( "\n" );
$node->parentNode->replaceChild( $br2nl, $node );
}

// Replace each <img> with its alt text
foreach ( $xp->query('//img') as $node ) {
$alt_text = $doc->createTextNode( $node->getAttribute('alt') . " " );
$node->parentNode->replaceChild( $alt_text, $node );
}

// Replace each <area> with its alt text
foreach ( $xp->query('//area') as $node ) {
$alt_text = $doc->createTextNode( $node->getAttribute('alt') . " " );
$node->parentNode->replaceChild( $alt_text, $node );
}

// Get a plaintext representation
$title_text = trim( html_entity_decode( strip_tags( $doc->saveHTML() ) ));

// Split by space
$parts = explode( " ", $title_text );

// Remove empty elements - strlen is needed to prevent text which evaluates to false from being removed
$parts = array_filter($parts, "strlen");

// Add each part to a new string until it is 200 characters long
$title_length = 200;
$title = "";

foreach ( $parts as $part) {
// Always add the first part
if ( mb_strlen( $title ) == 0 ) {
$title .= $part . " ";
// If the first part is a very long string, reduce it to the specified length
if ( mb_strlen( $title ) > $title_length ) {
$title = mb_substr( $title, 0, $title_length );
$title .= "…";
break;
}
} else if ( ( mb_strlen( $title ) + mb_strlen( $part ) ) < $title_length ) {
// Add the next whole word which doesn't take the total length over the specified length
$part = str_replace("\n ", "\n", $part);

if ( $part == "\n" ) {
// Don't add spaces if it ends with a newline to prevent indenting.
$title .= $part;
} else {
$title .= $part . " ";
}
} else {
// If it has been truncated, add an ellipsis
$title = trim( $title );
$title .= "…";
break;
}
}

return trim($title);
}

/**
* Generates the HTML for footnotes. Called by appendFootnotes, even if
Expand Down Expand Up @@ -1760,8 +1842,10 @@ protected function _appendFootnotes_callback($matches) {
$class = $this->encodeAttribute($class);
$attr .= " class=\"$class\"";
}
if ($this->fn_link_title !== "") {
$title = $this->fn_link_title;
if ($this->fn_link_title == "") {
Copy link
Owner

Choose a reason for hiding this comment

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

This logic doesn't make much sense. If a title was specified by fn_link_title it shouldn't be ignored. There should be an else clause.

// Decode any markdown in the footnote
$title = $this->get_footnote_title( $this->formParagraphs( $this->footnotes[$node_id] ) );
Copy link
Owner

Choose a reason for hiding this comment

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

It's unfortunate we're calling `formParagraph twice for every footnote now. Once for the footnote reference, and once for the actual footnote at the end of the document. More than twice if there is more than one references to the same footnote. I think this requires more strategy.

// Format it to be suitable for a title tool-tip
$title = $this->encodeAttribute($title);
Copy link
Owner

Choose a reason for hiding this comment

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

I'm not sure encodeAttribute will handle correctly your generated footnote title here when the no_entities configuration variable is set to true. I haven't checked, but I think it could double encode & and < into &amp;amp; and &lt;lt;.

$attr .= " title=\"$title\"";
}
Expand Down
50 changes: 48 additions & 2 deletions test/resources/php-markdown-extra.mdtest/Footnotes.text
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ This is the first paragraph.[^first]
Some paragraph with a footnote[^1], and another[^2].

[^1]: Content for fifth footnote.
[^2]: Content for sixth footnote spaning on
[^2]: Content for sixth footnote spanning on
three lines, with some span-level markup like
_emphasis_, a [link][].

Expand Down Expand Up @@ -50,7 +50,7 @@ footnote test[^reference].

[^nested]:
This footnote should appear even though it is referenced
from another footnote. But [^reference] should be litteral
from another footnote. But [^reference] should be literal
since the footnote with that name has already been used.

- - -
Expand All @@ -68,3 +68,49 @@ Footnotes mixed with images[^image-mixed]
[img6]: images/MGR-1800-travel.jpeg "Travel Speeds in 1800"
[^image-mixed]: Footnote Content
[img7]: images/MGR-1830-travel.jpeg "Travel Speeds in 1830"

Alt text in images is exposed in the title[^imgalt].
[^imgalt]: This is snuggle. <img src="https://placekitten.com/100/100" alt="Photo of a cute kitten." />

Very long footnotes are truncated at word boundaries[^long].
[^long]: Sensors indicate no shuttle or other ships in this sector. According to coordinates, we have travelled 7,000 light years and are located near the system J-25. Tractor beam released, sir. Force field maintaining our hull integrity. Damage report? Sections 27, 28 and 29 on decks four, five and six destroyed. Without our shields, at this range it is probable a photon detonation could destroy the Enterprise.

Although some titles may be split-mid emoji if they use ZWJ[^zwj].
[^zwj]: 👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲👨‍🦲

Fully multilingual[^中国文].
[^中国文]: 两位皇子,你们各拥党羽,雄心勃勃地争取国柄和皇座,我们现在代表民众告诉你们:罗马人民已经众口一辞,公举素有忠诚之名的安德洛尼克斯作为统治罗马的君王,因为他曾经为罗马立下许多丰功伟绩,在今日的邦城之内,没有一个比他更高贵的男子,更英勇的战士。他这次奉着元老院的召唤,从征讨野蛮的哥特人的辛苦的战役中回国;凭着他们父子使敌人破胆的声威,已经镇伏了一个强悍善战的民族。自从他为了罗马的光荣开始出征、用武力膺惩我们敌人的骄傲以来,已经费了十年的时间;他曾经五次流着血护送他的战死疆场的英勇的儿子们的灵榇回到罗马来;现在这位善良的安德洛尼克斯,雄名远播的泰特斯,终于满载着光荣的战利品,旌旗招展,奏凯班师了。凭着你们所希望克绳遗武的先皇陛下的名义,凭着你们在表面上尊崇的议会的权力,让我们请求你们各自退下,解散你们的随从,用和平而谦卑的态度,根据你们本身的才德,提出你们合法的要求。

Also supports elements like `<ruby><rp><rt>`[^ruby]
[^ruby]: <ruby>
漢 <rp>(</rp><rt>kan</rt><rp>)</rp>
字 <rp>(</rp><rt>ji</rt><rp>)</rp>
</ruby>

Also Emoji[^🥰] compatible.
[^🥰]: 🥰

All the text present in a table should be in the title tooltip[^table].
[^table]: <table>
<thead>
<tr>
<th colspan="2">The table header</th>
</tr>
</thead>
<tbody>
<colgroup>
<col>
<col span="2" class="Column A">
<col span="2" class="Column B">
</colgroup>
<tr>
<td>The table body</td>
<td>with two columns</td>
</tr>
</tbody>
<tfoot><tr><td>Footer</td></tr></tfoot>
<caption>A demo table</caption>
</table>

Markdown in a footnote is turned into plain text for the title[^mdtitle].
[^mdtitle]: Markdown *emphasis* and a [link](https://example.com)
Loading