-
-
Notifications
You must be signed in to change notification settings - Fork 194
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This extension is based on https://github.com/rezozero/commonmark-ext-footnotes, imported and relicensed with permission from the maintainer: #474 (comment) In addition to importing the functionality, a number of configuration options were added, as well as some other small tweaks.
- Loading branch information
1 parent
f40f619
commit 09e9079
Showing
39 changed files
with
1,470 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
--- | ||
layout: default | ||
title: Footnote Extension | ||
description: The FootnoteExtension adds the ability to create footnotes in Markdown documents. | ||
--- | ||
|
||
# Footnotes | ||
|
||
The `FootnoteExtension` adds the ability to create footnotes in Markdown documents. | ||
|
||
## Footnote Syntax | ||
|
||
Sample Markdown input: | ||
|
||
```md | ||
Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. | ||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi[^note1] leo risus, porta ac consectetur ac. | ||
|
||
[^note1]: Elit Malesuada Ridiculus | ||
``` | ||
|
||
Result: | ||
|
||
```md | ||
<p> | ||
Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. | ||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. | ||
Morbi<sup id="fnref:note1"><a class="footnote-ref" href="#fn:note1" role="doc-noteref">1</a></sup> leo risus, porta ac consectetur ac. | ||
</p> | ||
<div class="footnotes"> | ||
<hr /> | ||
<ol> | ||
<li class="footnote" id="fn:note1"> | ||
<p> | ||
Elit Malesuada Ridiculus <a class="footnote-backref" rev="footnote" href="#fnref:note1">↩</a> | ||
</p> | ||
</li> | ||
</ol> | ||
</div> | ||
``` | ||
|
||
## Usage | ||
|
||
Configure your `Environment` as usual and simply add the `FootnoteExtension`: | ||
|
||
```php | ||
<?php | ||
use League\CommonMark\CommonMarkConverter; | ||
use League\CommonMark\Environment; | ||
use League\CommonMark\Extension\Footnote\FootnoteExtension; | ||
|
||
// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go | ||
$environment = Environment::createCommonMarkEnvironment(); | ||
|
||
// Add the extension | ||
$environment->addExtension(new FootnoteExtension()); | ||
|
||
// Set your configuration | ||
$config = [ | ||
// Extension defaults are shown below | ||
// If you're happy with the defaults, feel free to remove them from this array | ||
'footnote' => [ | ||
'backref_class' => 'footnote-backref', | ||
'container_add_hr' => true, | ||
'container_class' => 'footnotes', | ||
'ref_class' => 'footnote-ref', | ||
'ref_id_prefix' => 'fnref:', | ||
'footnote_class' => 'footnote', | ||
'footnote_id_prefix' => 'fn:', | ||
], | ||
]; | ||
|
||
// Instantiate the converter engine and start converting some Markdown! | ||
$converter = new CommonMarkConverter($config, $environment); | ||
echo $converter->convertToHtml('# Hello World!'); | ||
``` | ||
|
||
## Configuration | ||
|
||
This extension can be configured by providing a `footnote` array with several nested configuration options. The defaults are shown in the code example above. | ||
|
||
### `backref_class` | ||
|
||
This `string` option defines which HTML class should be assigned to rendered footnote backreference elements. | ||
|
||
### `container_add_hr` | ||
|
||
This `boolean` option controls whether an `<hr>` element should be added inside the container. Set this to `false` if you want more control over how the footnote section at the bottom is differentiated from the rest of the document. | ||
|
||
### `container_class` | ||
|
||
This `string` option defines which HTML class should be assigned to the container at the bottom of the page which shows all the footnotes. | ||
|
||
### `ref_class` | ||
|
||
This `string` option defines which HTML class should be assigned to rendered footnote reference elements. | ||
|
||
### `ref_id_prefix` | ||
|
||
This `string` option defines the prefix prepended to footnote references. | ||
|
||
### `footnote_class` | ||
|
||
This `string` option defines which HTML class should be assigned to rendered footnote elements. | ||
|
||
### `footnote_id_prefix` | ||
|
||
This `string` option defines the prefix prepended to footnote elements. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
51 changes: 51 additions & 0 deletions
51
src/Extension/Footnote/Event/AnonymousFootnotesListener.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the league/commonmark package. | ||
* | ||
* (c) Colin O'Dell <[email protected]> | ||
* (c) Rezo Zero / Ambroise Maupate | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
namespace League\CommonMark\Extension\Footnote\Event; | ||
|
||
use League\CommonMark\Block\Element\Paragraph; | ||
use League\CommonMark\Event\DocumentParsedEvent; | ||
use League\CommonMark\Extension\Footnote\Node\Footnote; | ||
use League\CommonMark\Extension\Footnote\Node\FootnoteBackref; | ||
use League\CommonMark\Extension\Footnote\Node\FootnoteRef; | ||
use League\CommonMark\Inline\Element\Text; | ||
use League\CommonMark\Reference\Reference; | ||
|
||
final class AnonymousFootnotesListener | ||
{ | ||
public function onDocumentParsed(DocumentParsedEvent $event): void | ||
{ | ||
$document = $event->getDocument(); | ||
$walker = $document->walker(); | ||
|
||
while ($event = $walker->next()) { | ||
$node = $event->getNode(); | ||
if ($node instanceof FootnoteRef && $event->isEntering() && null !== $text = $node->getContent()) { | ||
// Anonymous footnote needs to create a footnote from its content | ||
$existingReference = $node->getReference(); | ||
$reference = new Reference( | ||
$existingReference->getLabel(), | ||
'#fnref:' . $existingReference->getLabel(), | ||
$existingReference->getTitle() | ||
); | ||
$footnote = new Footnote($reference); | ||
$footnote->addBackref(new FootnoteBackref($reference)); | ||
$paragraph = new Paragraph(); | ||
$paragraph->appendChild(new Text($text)); | ||
$footnote->appendChild($paragraph); | ||
$document->appendChild($footnote); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the league/commonmark package. | ||
* | ||
* (c) Colin O'Dell <[email protected]> | ||
* (c) Rezo Zero / Ambroise Maupate | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
namespace League\CommonMark\Extension\Footnote\Event; | ||
|
||
use League\CommonMark\Block\Element\Document; | ||
use League\CommonMark\Event\DocumentParsedEvent; | ||
use League\CommonMark\Extension\Footnote\Node\Footnote; | ||
use League\CommonMark\Extension\Footnote\Node\FootnoteBackref; | ||
use League\CommonMark\Extension\Footnote\Node\FootnoteContainer; | ||
use League\CommonMark\Reference\Reference; | ||
|
||
final class GatherFootnotesListener | ||
{ | ||
public function onDocumentParsed(DocumentParsedEvent $event): void | ||
{ | ||
$document = $event->getDocument(); | ||
$walker = $document->walker(); | ||
|
||
$footnotes = []; | ||
while ($event = $walker->next()) { | ||
if (!$event->isEntering()) { | ||
continue; | ||
} | ||
|
||
$node = $event->getNode(); | ||
if (!$node instanceof Footnote) { | ||
continue; | ||
} | ||
|
||
// Look for existing reference with footnote label | ||
$ref = $document->getReferenceMap()->getReference($node->getReference()->getLabel()); | ||
if ($ref !== null) { | ||
// Use numeric title to get footnotes order | ||
$footnotes[\intval($ref->getTitle())] = $node; | ||
} else { | ||
// Footnote call is missing, append footnote at the end | ||
$footnotes[INF] = $node; | ||
} | ||
|
||
/* | ||
* Look for all footnote refs pointing to this footnote | ||
* and create each footnote backrefs. | ||
*/ | ||
$backrefs = $document->getData('#fn:' . $node->getReference()->getDestination(), []); | ||
/** @var Reference $backref */ | ||
foreach ($backrefs as $backref) { | ||
$node->addBackref(new FootnoteBackref(new Reference( | ||
$backref->getLabel(), | ||
'#fnref:' . $backref->getLabel(), | ||
$backref->getTitle() | ||
))); | ||
} | ||
} | ||
|
||
// Only add a footnote container if there are any | ||
if (\count($footnotes) === 0) { | ||
return; | ||
} | ||
|
||
$container = $this->getFootnotesContainer($document); | ||
|
||
\ksort($footnotes); | ||
foreach ($footnotes as $footnote) { | ||
$container->appendChild($footnote); | ||
} | ||
} | ||
|
||
private function getFootnotesContainer(Document $document): FootnoteContainer | ||
{ | ||
$footnoteContainer = new FootnoteContainer(); | ||
$document->appendChild($footnoteContainer); | ||
|
||
return $footnoteContainer; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the league/commonmark package. | ||
* | ||
* (c) Colin O'Dell <[email protected]> | ||
* (c) Rezo Zero / Ambroise Maupate | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
namespace League\CommonMark\Extension\Footnote\Event; | ||
|
||
use League\CommonMark\Event\DocumentParsedEvent; | ||
use League\CommonMark\Extension\Footnote\Node\FootnoteRef; | ||
use League\CommonMark\Reference\Reference; | ||
|
||
final class NumberFootnotesListener | ||
{ | ||
public function onDocumentParsed(DocumentParsedEvent $event): void | ||
{ | ||
$document = $event->getDocument(); | ||
$walker = $document->walker(); | ||
$nextCounter = 1; | ||
$usedLabels = []; | ||
$usedCounters = []; | ||
|
||
while ($event = $walker->next()) { | ||
if (!$event->isEntering()) { | ||
continue; | ||
} | ||
|
||
$node = $event->getNode(); | ||
|
||
if (!$node instanceof FootnoteRef) { | ||
continue; | ||
} | ||
|
||
$existingReference = $node->getReference(); | ||
$label = $existingReference->getLabel(); | ||
$counter = $nextCounter; | ||
$canIncrementCounter = true; | ||
|
||
if (\array_key_exists($label, $usedLabels)) { | ||
/* | ||
* Reference is used again, we need to point | ||
* to the same footnote. But with a different ID | ||
*/ | ||
$counter = $usedCounters[$label]; | ||
$label = $label . '__' . ++$usedLabels[$label]; | ||
$canIncrementCounter = false; | ||
} | ||
|
||
// rewrite reference title to use a numeric link | ||
$newReference = new Reference( | ||
$label, | ||
$existingReference->getDestination(), | ||
(string) $counter | ||
); | ||
|
||
// Override reference with numeric link | ||
$node->setReference($newReference); | ||
$document->getReferenceMap()->addReference($newReference); | ||
|
||
/* | ||
* Store created references in document for | ||
* creating FootnoteBackrefs | ||
*/ | ||
if (false === $document->getData($existingReference->getDestination(), false)) { | ||
$document->data[$existingReference->getDestination()] = []; | ||
} | ||
|
||
$document->data[$existingReference->getDestination()][] = $newReference; | ||
|
||
$usedLabels[$label] = 1; | ||
$usedCounters[$label] = $nextCounter; | ||
|
||
if ($canIncrementCounter) { | ||
$nextCounter++; | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.