Add Table Of Contents feature
This commit is contained in:
parent
9a615ea55a
commit
b037a43e2d
@ -18,7 +18,8 @@
|
||||
"league/commonmark": "^0.13",
|
||||
"symfony/console": "~3.0",
|
||||
"symfony/finder": "~3.0",
|
||||
"webuni/commonmark-table-extension": "0.4.*"
|
||||
"webuni/commonmark-table-extension": "0.4.*",
|
||||
"myclabs/deep-copy": "^1.5"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
46
composer.lock
generated
46
composer.lock
generated
@ -4,8 +4,8 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"hash": "2587ec4642e574482832632b73b74202",
|
||||
"content-hash": "598abc0b07c38c31f8cc44bbf293dae3",
|
||||
"hash": "5d99c57e9efe49df55a765026a15f586",
|
||||
"content-hash": "88197b6eaf8fc4b266eb8c72b115580a",
|
||||
"packages": [
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
@ -286,6 +286,48 @@
|
||||
],
|
||||
"time": "2015-07-09 02:14:40"
|
||||
},
|
||||
{
|
||||
"name": "myclabs/deep-copy",
|
||||
"version": "1.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/myclabs/DeepCopy.git",
|
||||
"reference": "e3abefcd7f106677fd352cd7c187d6c969aa9ddc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/e3abefcd7f106677fd352cd7c187d6c969aa9ddc",
|
||||
"reference": "e3abefcd7f106677fd352cd7c187d6c969aa9ddc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/collections": "1.*",
|
||||
"phpunit/phpunit": "~4.1"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"DeepCopy\\": "src/DeepCopy/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Create deep copies (clones) of your objects",
|
||||
"homepage": "https://github.com/myclabs/DeepCopy",
|
||||
"keywords": [
|
||||
"clone",
|
||||
"copy",
|
||||
"duplicate",
|
||||
"object",
|
||||
"object graph"
|
||||
],
|
||||
"time": "2015-11-07 22:20:37"
|
||||
},
|
||||
{
|
||||
"name": "react/promise",
|
||||
"version": "v2.4.0",
|
||||
|
@ -1,5 +1,7 @@
|
||||
**Daux.io** is an documentation generator that uses a simple folder structure and Markdown files to create custom documentation on the fly. It helps you create great looking documentation in a developer friendly way.
|
||||
|
||||
[TOC]
|
||||
|
||||
## Features
|
||||
|
||||
### For Authors
|
||||
@ -12,6 +14,7 @@
|
||||
* [Multiple Languages Support](!Features/Multilanguage)
|
||||
* [No Build Step](!Features/Live_mode)
|
||||
* [Static Output Generation](!Features/Static_Site_Generation)
|
||||
* [Table of Contents](!Features/Table_of_contents)
|
||||
|
||||
### For Developers
|
||||
|
||||
|
19
docs/01_Features/Table_of_contents.md
Normal file
19
docs/01_Features/Table_of_contents.md
Normal file
@ -0,0 +1,19 @@
|
||||
Adding a table of contents becomes very easy with Daux.io
|
||||
|
||||
## Automatic
|
||||
|
||||
A table of contents can be added automatically to all pages.
|
||||
|
||||
If `[TOC]` isn't present it will add it at the beginning of the page.
|
||||
|
||||
You can enable this feature in your configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"auto_toc": true
|
||||
}
|
||||
```
|
||||
|
||||
## Manual
|
||||
|
||||
Add `[TOC]` anywhere in your document and it will be replaced by a table of contents
|
@ -18,6 +18,7 @@
|
||||
* [Multiple Languages Support](!Features/Multilanguage)
|
||||
* [No Build Step](!Features/Live_mode)
|
||||
* [Static Output Generation](!Features/Static_Site_Generation)
|
||||
* [Table of Contents](!Features/Table_of_contents)
|
||||
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
|
@ -18,6 +18,8 @@
|
||||
|
||||
"timezone": "America/Los_Angeles",
|
||||
|
||||
"auto_toc": false,
|
||||
|
||||
"live": {
|
||||
"inherit_index": false,
|
||||
"clean_urls": false
|
||||
|
@ -3,6 +3,8 @@
|
||||
use League\CommonMark\DocParser;
|
||||
use League\CommonMark\Environment;
|
||||
use League\CommonMark\HtmlRenderer;
|
||||
use Todaymade\Daux\ContentTypes\Markdown\TOC\Parser;
|
||||
use Todaymade\Daux\ContentTypes\Markdown\TOC\TOCProcessor;
|
||||
use Webuni\CommonMark\TableExtension\TableExtension;
|
||||
|
||||
class CommonMarkConverter extends \League\CommonMark\CommonMarkConverter
|
||||
@ -18,6 +20,10 @@ class CommonMarkConverter extends \League\CommonMark\CommonMarkConverter
|
||||
$environment->mergeConfig($config);
|
||||
$environment->addExtension(new TableExtension());
|
||||
|
||||
// Table of Contents
|
||||
$environment->addBlockParser(new Parser());
|
||||
$environment->addDocumentProcessor(new TOCProcessor($config['daux']));
|
||||
|
||||
$this->extendEnvironment($environment);
|
||||
|
||||
if (array_key_exists('processor_instance', $config['daux'])) {
|
||||
|
50
libs/ContentTypes/Markdown/TOC/Element.php
Normal file
50
libs/ContentTypes/Markdown/TOC/Element.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php namespace Todaymade\Daux\ContentTypes\Markdown\TOC;
|
||||
|
||||
use League\CommonMark\Block\Element\AbstractBlock;
|
||||
use League\CommonMark\Cursor;
|
||||
|
||||
class Element extends AbstractBlock
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns true if this block can contain the given block as a child node
|
||||
*
|
||||
* @param AbstractBlock $block
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canContain(AbstractBlock $block)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if block type can accept lines of text
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function acceptsLines()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this is a code block
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isCode()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Cursor $cursor
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function matchesNextLine(Cursor $cursor)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
83
libs/ContentTypes/Markdown/TOC/Entry.php
Normal file
83
libs/ContentTypes/Markdown/TOC/Entry.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?php namespace Todaymade\Daux\ContentTypes\Markdown\TOC;
|
||||
|
||||
use League\CommonMark\Block\Element\Heading;
|
||||
|
||||
class Entry
|
||||
{
|
||||
protected $content;
|
||||
protected $level;
|
||||
protected $parent = null;
|
||||
protected $children = [];
|
||||
|
||||
public function __construct(Heading $content)
|
||||
{
|
||||
$this->content = $content;
|
||||
$this->level = $content->getLevel();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->content->data['attributes']['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getLevel()
|
||||
{
|
||||
return $this->level;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Entry
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Heading
|
||||
*/
|
||||
public function getContent()
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Entry[]
|
||||
*/
|
||||
public function getChildren()
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Entry $parent
|
||||
* @param bool $addChild
|
||||
*/
|
||||
public function setParent(Entry $parent, $addChild = true)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
if ($addChild) {
|
||||
$parent->addChild($this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Entry $child
|
||||
*/
|
||||
public function addChild(Entry $child)
|
||||
{
|
||||
$child->setParent($this, false);
|
||||
$this->children[] = $child;
|
||||
}
|
||||
|
||||
public function toString()
|
||||
{
|
||||
return $this->getLevel() . " - " . $this->getId();
|
||||
}
|
||||
}
|
44
libs/ContentTypes/Markdown/TOC/Parser.php
Normal file
44
libs/ContentTypes/Markdown/TOC/Parser.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by IntelliJ IDEA.
|
||||
* User: onigoetz
|
||||
* Date: 09/04/16
|
||||
* Time: 23:03
|
||||
*/
|
||||
|
||||
namespace Todaymade\Daux\ContentTypes\Markdown\TOC;
|
||||
|
||||
|
||||
use League\CommonMark\Block\Parser\AbstractBlockParser;
|
||||
use League\CommonMark\ContextInterface;
|
||||
use League\CommonMark\Cursor;
|
||||
|
||||
class Parser extends AbstractBlockParser
|
||||
{
|
||||
|
||||
/**
|
||||
* @param ContextInterface $context
|
||||
* @param Cursor $cursor
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function parse(ContextInterface $context, Cursor $cursor)
|
||||
{
|
||||
if ($cursor->isIndented()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$previousState = $cursor->saveState();
|
||||
$cursor->advanceToFirstNonSpace();
|
||||
$fence = $cursor->match('/^\[TOC\]/');
|
||||
if (is_null($fence)) {
|
||||
$cursor->restoreState($previousState);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$context->addBlock(new Element());
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
18
libs/ContentTypes/Markdown/TOC/RootEntry.php
Normal file
18
libs/ContentTypes/Markdown/TOC/RootEntry.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php namespace Todaymade\Daux\ContentTypes\Markdown\TOC;
|
||||
|
||||
class RootEntry extends Entry
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->content = null;
|
||||
$this->level = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Entry
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
throw new \RuntimeException("No Parent Exception");
|
||||
}
|
||||
}
|
203
libs/ContentTypes/Markdown/TOC/TOCProcessor.php
Normal file
203
libs/ContentTypes/Markdown/TOC/TOCProcessor.php
Normal file
@ -0,0 +1,203 @@
|
||||
<?php namespace Todaymade\Daux\ContentTypes\Markdown\TOC;
|
||||
|
||||
use DeepCopy\DeepCopy;
|
||||
use League\CommonMark\Block\Element\Document;
|
||||
use League\CommonMark\Block\Element\Heading;
|
||||
use League\CommonMark\Block\Element\ListBlock;
|
||||
use League\CommonMark\Block\Element\ListData;
|
||||
use League\CommonMark\Block\Element\ListItem;
|
||||
use League\CommonMark\Block\Element\Paragraph;
|
||||
use League\CommonMark\DocumentProcessorInterface;
|
||||
use League\CommonMark\Inline\Element\Link;
|
||||
use League\CommonMark\Inline\Element\Text;
|
||||
use League\CommonMark\Node\Node;
|
||||
use ReflectionMethod;
|
||||
use Todaymade\Daux\Config;
|
||||
use Todaymade\Daux\DauxHelper;
|
||||
|
||||
class TOCProcessor implements DocumentProcessorInterface
|
||||
{
|
||||
protected $config;
|
||||
|
||||
public function __construct(Config $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function hasAutoTOC()
|
||||
{
|
||||
return array_key_exists('auto_toc', $this->config) && $this->config['auto_toc'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $document
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function processDocument(Document $document)
|
||||
{
|
||||
/** @var Element[] $tocs */
|
||||
$tocs = [];
|
||||
|
||||
$headings = [];
|
||||
|
||||
$walker = $document->walker();
|
||||
while ($event = $walker->next()) {
|
||||
$node = $event->getNode();
|
||||
|
||||
if ($node instanceof Element && !$event->isEntering()) {
|
||||
$tocs[] = $node;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!($node instanceof Heading) || !$event->isEntering()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$id = $this->addId($node);
|
||||
|
||||
$headings[] = new Entry($node, $id);
|
||||
}
|
||||
|
||||
if (count($headings) && (count($tocs) || $this->hasAutoTOC())) {
|
||||
$generated = $this->generate($headings);
|
||||
|
||||
if (count($tocs)) {
|
||||
foreach ($tocs as $toc) {
|
||||
$toc->replaceWith($this->render($generated->getChildren()));
|
||||
}
|
||||
} else {
|
||||
$document->prependChild($this->render($generated->getChildren()));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected function addId(Heading $node)
|
||||
{
|
||||
// If the node has an ID, no need to generate it
|
||||
$attributes = $node->getData('attributes', []);
|
||||
if (array_key_exists('id', $attributes) && !empty($attributes['id'])) {
|
||||
// TODO :: check for uniqueness
|
||||
|
||||
return $attributes['id'];
|
||||
}
|
||||
|
||||
// Well, seems we have to generate an ID
|
||||
|
||||
$walker = $node->walker();
|
||||
$inside = [];
|
||||
while ($event = $walker->next()) {
|
||||
$insideNode = $event->getNode();
|
||||
|
||||
if ($insideNode instanceof Heading) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$inside[] = $insideNode;
|
||||
}
|
||||
|
||||
$text = '';
|
||||
foreach ($inside as $other) {
|
||||
if ($other instanceof Text) {
|
||||
$text .= ' ' . $other->getContent();
|
||||
}
|
||||
}
|
||||
|
||||
$text = 'page_' . DauxHelper::slug(trim($text));
|
||||
|
||||
// TODO :: check for uniqueness
|
||||
$node->data['attributes']['id'] = $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Entry[] $headings
|
||||
* @return RootEntry
|
||||
*/
|
||||
public function generate($headings)
|
||||
{
|
||||
/** @var Entry $previous */
|
||||
$root = $previous = new RootEntry();
|
||||
foreach ($headings as $heading) {
|
||||
if ($heading->getLevel() < $previous->getLevel()) {
|
||||
$parent = $previous;
|
||||
do {
|
||||
$parent = $parent->getParent();
|
||||
} while ($heading->getLevel() <= $parent->getLevel() || $parent->getLevel() != 0);
|
||||
|
||||
$parent->addChild($heading);
|
||||
$previous = $heading;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if ($heading->getLevel() > $previous->getLevel()) {
|
||||
$previous->addChild($heading);
|
||||
$previous = $heading;
|
||||
continue;
|
||||
}
|
||||
|
||||
//if ($heading->getLevel() == $previous->getLevel()) {
|
||||
$previous->getParent()->addChild($heading);
|
||||
$previous = $heading;
|
||||
continue;
|
||||
//}
|
||||
}
|
||||
|
||||
return $root;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Entry[] $entries
|
||||
* @return ListBlock
|
||||
*/
|
||||
protected function render(array $entries)
|
||||
{
|
||||
$data = new ListData();
|
||||
$data->type = ListBlock::TYPE_UNORDERED;
|
||||
|
||||
$list = new ListBlock($data);
|
||||
$list->data['attributes']['class'] = 'TableOfContents';
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
$item = new ListItem($data);
|
||||
|
||||
$a = new Link('#' . $entry->getId());
|
||||
|
||||
foreach ($this->cloneChildren($entry->getContent()) as $node) {
|
||||
$a->appendChild($node);
|
||||
}
|
||||
|
||||
$p = new Paragraph();
|
||||
$p->appendChild($a);
|
||||
|
||||
$item->appendChild($p);
|
||||
|
||||
if (!empty($entry->getChildren())) {
|
||||
$item->appendChild($this->render($entry->getChildren()));
|
||||
}
|
||||
|
||||
$list->appendChild($item);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Heading $node
|
||||
* @return Node[]
|
||||
*/
|
||||
protected function cloneChildren(Heading $node)
|
||||
{
|
||||
$deepCopy = new DeepCopy();
|
||||
|
||||
$firstClone = clone $node;
|
||||
|
||||
// We have no choice but to hack into the system to reset the parent, to avoid cloning the complete tree
|
||||
$method = new ReflectionMethod(get_class($firstClone), 'setParent');
|
||||
$method->setAccessible(true);
|
||||
$method->invoke($firstClone, null);
|
||||
|
||||
return $deepCopy->copy($firstClone)->children();
|
||||
}
|
||||
}
|
2
themes/daux/css/theme-blue.min.css
vendored
2
themes/daux/css/theme-blue.min.css
vendored
File diff suppressed because one or more lines are too long
2
themes/daux/css/theme-green.min.css
vendored
2
themes/daux/css/theme-green.min.css
vendored
File diff suppressed because one or more lines are too long
2
themes/daux/css/theme-navy.min.css
vendored
2
themes/daux/css/theme-navy.min.css
vendored
File diff suppressed because one or more lines are too long
2
themes/daux/css/theme-red.min.css
vendored
2
themes/daux/css/theme-red.min.css
vendored
File diff suppressed because one or more lines are too long
@ -375,3 +375,20 @@ table {
|
||||
top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.TableOfContents {
|
||||
|
||||
font-size:16px;
|
||||
padding-left:30px;
|
||||
|
||||
border-left:6px solid #efefef;
|
||||
|
||||
p {
|
||||
margin-bottom:0;
|
||||
}
|
||||
|
||||
.TableOfContents {
|
||||
border-left-width:0;
|
||||
padding-left:20px;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user