mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
parent
746ba803af
commit
6a848cbc3a
5 changed files with 99 additions and 59 deletions
|
@ -49,7 +49,7 @@ tool on your machine to be able to use these formats.
|
||||||
|
|
||||||
Hugo passes reasonable default arguments to these external helpers by default:
|
Hugo passes reasonable default arguments to these external helpers by default:
|
||||||
|
|
||||||
- `asciidoctor`: `--no-header-footer --trace -`
|
- `asciidoctor`: `--no-header-footer -`
|
||||||
- `rst2html`: `--leave-comments --initial-header-level=2`
|
- `rst2html`: `--leave-comments --initial-header-level=2`
|
||||||
- `pandoc`: `--mathjax`
|
- `pandoc`: `--mathjax`
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ noheaderorfooter | true | Output an embeddable document, which excludes the head
|
||||||
safemode | `unsafe` | Safe mode level `unsafe`, `safe`, `server` or `secure`. Don't change this unless you know what you are doing.
|
safemode | `unsafe` | Safe mode level `unsafe`, `safe`, `server` or `secure`. Don't change this unless you know what you are doing.
|
||||||
sectionnumbers | `false` | Auto-number section titles.
|
sectionnumbers | `false` | Auto-number section titles.
|
||||||
verbose | `false` | Verbosely print processing information and configuration file checks to stderr.
|
verbose | `false` | Verbosely print processing information and configuration file checks to stderr.
|
||||||
trace | `true` | Include backtrace information on errors.
|
trace | `false` | Include backtrace information on errors.
|
||||||
failurelevel | `fatal` | The minimum logging level that triggers a non-zero exit code (failure).
|
failurelevel | `fatal` | The minimum logging level that triggers a non-zero exit code (failure).
|
||||||
workingfoldercurrent | `false` | Set the working folder to the rendered `adoc` file, so [include](https://asciidoctor.org/docs/asciidoc-syntax-quick-reference/#include-files) will work with relative paths. This setting uses the `asciidoctor` cli parameter `--base-dir` and attribute `outdir=`. For rendering [asciidoctor-diagram](https://asciidoctor.org/docs/asciidoctor-diagram/) `workingfoldercurrent` must be set to `true`.
|
workingfoldercurrent | `false` | Set the working folder to the rendered `adoc` file, so [include](https://asciidoctor.org/docs/asciidoc-syntax-quick-reference/#include-files) will work with relative paths. This setting uses the `asciidoctor` cli parameter `--base-dir` and attribute `outdir=`. For rendering [asciidoctor-diagram](https://asciidoctor.org/docs/asciidoctor-diagram/) `workingfoldercurrent` must be set to `true`.
|
||||||
|
|
||||||
|
|
|
@ -92,11 +92,11 @@ The following is a [partial template][partials] that adds slightly more logic fo
|
||||||
With the preceding example, even pages with > 400 words *and* `toc` not set to `false` will not render a table of contents if there are no headings in the page for the `{{.TableOfContents}}` variable to pull from.
|
With the preceding example, even pages with > 400 words *and* `toc` not set to `false` will not render a table of contents if there are no headings in the page for the `{{.TableOfContents}}` variable to pull from.
|
||||||
{{% /note %}}
|
{{% /note %}}
|
||||||
|
|
||||||
## Usage with asciidoc
|
## Usage with AsciiDoc
|
||||||
|
|
||||||
Hugo supports table of contents with Asciidoc content format.
|
Hugo supports table of contents with AsciiDoc content format.
|
||||||
|
|
||||||
In the header of your content file, specify the Asciidoc TOC directives, by using the macro style:
|
In the header of your content file, specify the AsciiDoc TOC directives, by using the macro or auto style:
|
||||||
|
|
||||||
```asciidoc
|
```asciidoc
|
||||||
// <!-- Your front matter up here -->
|
// <!-- Your front matter up here -->
|
||||||
|
@ -117,7 +117,7 @@ He lay on his armour-like back, and if he lifted his head a little he could see
|
||||||
|
|
||||||
A collection of textile samples lay spread out on the table - Samsa was a travelling salesman - and above it there hung a picture that he had recently cut out of an illustrated magazine and housed in a nice, gilded frame. It showed a lady fitted out with a fur hat and fur boa who sat upright, raising a heavy fur muff that covered the whole of her lower arm towards the viewer. Gregor then turned to look out the window at the dull weather. Drops
|
A collection of textile samples lay spread out on the table - Samsa was a travelling salesman - and above it there hung a picture that he had recently cut out of an illustrated magazine and housed in a nice, gilded frame. It showed a lady fitted out with a fur hat and fur boa who sat upright, raising a heavy fur muff that covered the whole of her lower arm towards the viewer. Gregor then turned to look out the window at the dull weather. Drops
|
||||||
```
|
```
|
||||||
Hugo will take this Asciddoc and create a table of contents store it in the page variable `.TableOfContents`, in the same as described for Markdown.
|
Hugo will take this AsciiDoc and create a table of contents store it in the page variable `.TableOfContents`, in the same as described for Markdown.
|
||||||
|
|
||||||
[conditionals]: /templates/introduction/#conditionals
|
[conditionals]: /templates/introduction/#conditionals
|
||||||
[front matter]: /content-management/front-matter/
|
[front matter]: /content-management/front-matter/
|
||||||
|
|
|
@ -11,13 +11,14 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
// Package asciidocext converts Asciidoc to HTML using Asciidoc or Asciidoctor
|
// Package asciidocext converts AsciiDoc to HTML using Asciidoctor
|
||||||
// external binaries. The `asciidoc` module is reserved for a future golang
|
// external binary. The `asciidoc` module is reserved for a future golang
|
||||||
// implementation.
|
// implementation.
|
||||||
package asciidocext
|
package asciidocext
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"io"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
@ -77,12 +78,12 @@ func (a *asciidocConverter) Supports(_ identity.Identity) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// getAsciidocContent calls asciidoctor or asciidoc as an external helper
|
// getAsciidocContent calls asciidoctor as an external helper
|
||||||
// to convert AsciiDoc content to HTML.
|
// to convert AsciiDoc content to HTML.
|
||||||
func (a *asciidocConverter) getAsciidocContent(src []byte, ctx converter.DocumentContext) []byte {
|
func (a *asciidocConverter) getAsciidocContent(src []byte, ctx converter.DocumentContext) []byte {
|
||||||
path := getAsciidoctorExecPath()
|
path := getAsciidoctorExecPath()
|
||||||
if path == "" {
|
if path == "" {
|
||||||
a.cfg.Logger.ERROR.Println("asciidoctor / asciidoc not found in $PATH: Please install.\n",
|
a.cfg.Logger.ERROR.Println("asciidoctor not found in $PATH: Please install.\n",
|
||||||
" Leaving AsciiDoc content unrendered.")
|
" Leaving AsciiDoc content unrendered.")
|
||||||
return src
|
return src
|
||||||
}
|
}
|
||||||
|
@ -216,30 +217,21 @@ func extractTOC(src []byte) ([]byte, tableofcontents.Root, error) {
|
||||||
toVisit []*html.Node
|
toVisit []*html.Node
|
||||||
)
|
)
|
||||||
f = func(n *html.Node) bool {
|
f = func(n *html.Node) bool {
|
||||||
if n.Type == html.ElementNode && n.Data == "div" {
|
if n.Type == html.ElementNode && n.Data == "div" && attr(n, "id") == "toc" {
|
||||||
for _, a := range n.Attr {
|
toc = parseTOC(n)
|
||||||
if a.Key == "id" && a.Val == "toc" {
|
n.Parent.RemoveChild(n)
|
||||||
toc, err = parseTOC(n)
|
return true
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
n.Parent.RemoveChild(n)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if n.FirstChild != nil {
|
if n.FirstChild != nil {
|
||||||
toVisit = append(toVisit, n.FirstChild)
|
toVisit = append(toVisit, n.FirstChild)
|
||||||
}
|
}
|
||||||
if n.NextSibling != nil {
|
if n.NextSibling != nil && f(n.NextSibling) {
|
||||||
if ok := f(n.NextSibling); ok {
|
return true
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for len(toVisit) > 0 {
|
for len(toVisit) > 0 {
|
||||||
nv := toVisit[0]
|
nv := toVisit[0]
|
||||||
toVisit = toVisit[1:]
|
toVisit = toVisit[1:]
|
||||||
if ok := f(nv); ok {
|
if f(nv) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -261,50 +253,58 @@ func extractTOC(src []byte) ([]byte, tableofcontents.Root, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseTOC returns a TOC root from the given toc Node
|
// parseTOC returns a TOC root from the given toc Node
|
||||||
func parseTOC(doc *html.Node) (tableofcontents.Root, error) {
|
func parseTOC(doc *html.Node) tableofcontents.Root {
|
||||||
var (
|
var (
|
||||||
toc tableofcontents.Root
|
toc tableofcontents.Root
|
||||||
f func(*html.Node, int, int)
|
f func(*html.Node, int, int)
|
||||||
)
|
)
|
||||||
f = func(n *html.Node, parent, level int) {
|
f = func(n *html.Node, row, level int) {
|
||||||
if n.Type == html.ElementNode {
|
if n.Type == html.ElementNode {
|
||||||
switch n.Data {
|
switch n.Data {
|
||||||
case "ul":
|
case "ul":
|
||||||
if level == 0 {
|
if level == 0 {
|
||||||
parent += 1
|
row++
|
||||||
}
|
}
|
||||||
level += 1
|
level++
|
||||||
f(n.FirstChild, parent, level)
|
f(n.FirstChild, row, level)
|
||||||
case "li":
|
case "li":
|
||||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||||
if c.Type != html.ElementNode || c.Data != "a" {
|
if c.Type != html.ElementNode || c.Data != "a" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var href string
|
href := attr(c, "href")[1:]
|
||||||
for _, a := range c.Attr {
|
toc.AddAt(tableofcontents.Header{
|
||||||
if a.Key == "href" {
|
Text: nodeContent(c),
|
||||||
href = a.Val[1:]
|
ID: href,
|
||||||
break
|
}, row, level)
|
||||||
}
|
|
||||||
}
|
|
||||||
for d := c.FirstChild; d != nil; d = d.NextSibling {
|
|
||||||
if d.Type == html.TextNode {
|
|
||||||
toc.AddAt(tableofcontents.Header{
|
|
||||||
Text: d.Data,
|
|
||||||
ID: href,
|
|
||||||
}, parent, level)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
f(n.FirstChild, parent, level)
|
f(n.FirstChild, row, level)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if n.NextSibling != nil {
|
if n.NextSibling != nil {
|
||||||
f(n.NextSibling, parent, level)
|
f(n.NextSibling, row, level)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
f(doc.FirstChild, 0, 0)
|
f(doc.FirstChild, 0, 0)
|
||||||
return toc, nil
|
return toc
|
||||||
|
}
|
||||||
|
|
||||||
|
func attr(node *html.Node, key string) string {
|
||||||
|
for _, a := range node.Attr {
|
||||||
|
if a.Key == key {
|
||||||
|
return a.Val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func nodeContent(node *html.Node) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
w := io.Writer(&buf)
|
||||||
|
for c := node.FirstChild; c != nil; c = c.NextSibling {
|
||||||
|
html.Render(w, c)
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Supports returns whether Asciidoctor is installed on this computer.
|
// Supports returns whether Asciidoctor is installed on this computer.
|
||||||
|
|
|
@ -11,8 +11,8 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
// Package asciidocext converts Asciidoc to HTML using Asciidoc or Asciidoctor
|
// Package asciidocext converts AsciiDoc to HTML using Asciidoctor
|
||||||
// external binaries. The `asciidoc` module is reserved for a future golang
|
// external binary. The `asciidoc` module is reserved for a future golang
|
||||||
// implementation.
|
// implementation.
|
||||||
|
|
||||||
package asciidocext
|
package asciidocext
|
||||||
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/gohugoio/hugo/common/loggers"
|
"github.com/gohugoio/hugo/common/loggers"
|
||||||
"github.com/gohugoio/hugo/markup/converter"
|
"github.com/gohugoio/hugo/markup/converter"
|
||||||
"github.com/gohugoio/hugo/markup/markup_config"
|
"github.com/gohugoio/hugo/markup/markup_config"
|
||||||
|
"github.com/gohugoio/hugo/markup/tableofcontents"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
qt "github.com/frankban/quicktest"
|
||||||
|
@ -250,7 +251,7 @@ func TestAsciidoctorAttributes(t *testing.T) {
|
||||||
|
|
||||||
func TestConvert(t *testing.T) {
|
func TestConvert(t *testing.T) {
|
||||||
if !Supports() {
|
if !Supports() {
|
||||||
t.Skip("asciidoc/asciidoctor not installed")
|
t.Skip("asciidoctor not installed")
|
||||||
}
|
}
|
||||||
c := qt.New(t)
|
c := qt.New(t)
|
||||||
|
|
||||||
|
@ -273,7 +274,7 @@ func TestConvert(t *testing.T) {
|
||||||
|
|
||||||
func TestTableOfContents(t *testing.T) {
|
func TestTableOfContents(t *testing.T) {
|
||||||
if !Supports() {
|
if !Supports() {
|
||||||
t.Skip("asciidoc/asciidoctor not installed")
|
t.Skip("asciidoctor not installed")
|
||||||
}
|
}
|
||||||
c := qt.New(t)
|
c := qt.New(t)
|
||||||
p, err := Provider.New(converter.ProviderConfig{Logger: loggers.NewErrorLogger()})
|
p, err := Provider.New(converter.ProviderConfig{Logger: loggers.NewErrorLogger()})
|
||||||
|
@ -305,3 +306,42 @@ testContent
|
||||||
c.Assert(root.ToHTML(2, 4, false), qt.Equals, "<nav id=\"TableOfContents\">\n <ul>\n <li><a href=\"#_introduction\">Introduction</a></li>\n <li><a href=\"#_section_1\">Section 1</a>\n <ul>\n <li><a href=\"#_section_1_1\">Section 1.1</a>\n <ul>\n <li><a href=\"#_section_1_1_1\">Section 1.1.1</a></li>\n </ul>\n </li>\n <li><a href=\"#_section_1_2\">Section 1.2</a></li>\n </ul>\n </li>\n <li><a href=\"#_section_2\">Section 2</a></li>\n </ul>\n</nav>")
|
c.Assert(root.ToHTML(2, 4, false), qt.Equals, "<nav id=\"TableOfContents\">\n <ul>\n <li><a href=\"#_introduction\">Introduction</a></li>\n <li><a href=\"#_section_1\">Section 1</a>\n <ul>\n <li><a href=\"#_section_1_1\">Section 1.1</a>\n <ul>\n <li><a href=\"#_section_1_1_1\">Section 1.1.1</a></li>\n </ul>\n </li>\n <li><a href=\"#_section_1_2\">Section 1.2</a></li>\n </ul>\n </li>\n <li><a href=\"#_section_2\">Section 2</a></li>\n </ul>\n</nav>")
|
||||||
c.Assert(root.ToHTML(2, 3, false), qt.Equals, "<nav id=\"TableOfContents\">\n <ul>\n <li><a href=\"#_introduction\">Introduction</a></li>\n <li><a href=\"#_section_1\">Section 1</a>\n <ul>\n <li><a href=\"#_section_1_1\">Section 1.1</a></li>\n <li><a href=\"#_section_1_2\">Section 1.2</a></li>\n </ul>\n </li>\n <li><a href=\"#_section_2\">Section 2</a></li>\n </ul>\n</nav>")
|
c.Assert(root.ToHTML(2, 3, false), qt.Equals, "<nav id=\"TableOfContents\">\n <ul>\n <li><a href=\"#_introduction\">Introduction</a></li>\n <li><a href=\"#_section_1\">Section 1</a>\n <ul>\n <li><a href=\"#_section_1_1\">Section 1.1</a></li>\n <li><a href=\"#_section_1_2\">Section 1.2</a></li>\n </ul>\n </li>\n <li><a href=\"#_section_2\">Section 2</a></li>\n </ul>\n</nav>")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTableOfContentsWithCode(t *testing.T) {
|
||||||
|
if !Supports() {
|
||||||
|
t.Skip("asciidoctor not installed")
|
||||||
|
}
|
||||||
|
c := qt.New(t)
|
||||||
|
mconf := markup_config.Default
|
||||||
|
p, err := Provider.New(
|
||||||
|
converter.ProviderConfig{
|
||||||
|
MarkupConfig: mconf,
|
||||||
|
Logger: loggers.NewErrorLogger(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
c.Assert(err, qt.IsNil)
|
||||||
|
conv, err := p.New(converter.DocumentContext{})
|
||||||
|
c.Assert(err, qt.IsNil)
|
||||||
|
b, err := conv.Convert(converter.RenderContext{Src: []byte(`:toc: auto
|
||||||
|
|
||||||
|
== Some ` + "`code`" + ` in the title
|
||||||
|
`)})
|
||||||
|
c.Assert(err, qt.IsNil)
|
||||||
|
toc, ok := b.(converter.TableOfContentsProvider)
|
||||||
|
c.Assert(ok, qt.Equals, true)
|
||||||
|
expected := tableofcontents.Headers{
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
ID: "",
|
||||||
|
Text: "",
|
||||||
|
Headers: tableofcontents.Headers{
|
||||||
|
{
|
||||||
|
ID: "_some_code_in_the_title",
|
||||||
|
Text: "Some <code>code</code> in the title",
|
||||||
|
Headers: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c.Assert(toc.TableOfContents().Headers, qt.DeepEquals, expected)
|
||||||
|
}
|
||||||
|
|
|
@ -40,19 +40,19 @@ type Root struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddAt adds the header into the given location.
|
// AddAt adds the header into the given location.
|
||||||
func (toc *Root) AddAt(h Header, y, x int) {
|
func (toc *Root) AddAt(h Header, row, level int) {
|
||||||
for i := len(toc.Headers); i <= y; i++ {
|
for i := len(toc.Headers); i <= row; i++ {
|
||||||
toc.Headers = append(toc.Headers, Header{})
|
toc.Headers = append(toc.Headers, Header{})
|
||||||
}
|
}
|
||||||
|
|
||||||
if x == 0 {
|
if level == 0 {
|
||||||
toc.Headers[y] = h
|
toc.Headers[row] = h
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
header := &toc.Headers[y]
|
header := &toc.Headers[row]
|
||||||
|
|
||||||
for i := 1; i < x; i++ {
|
for i := 1; i < level; i++ {
|
||||||
if len(header.Headers) == 0 {
|
if len(header.Headers) == 0 {
|
||||||
header.Headers = append(header.Headers, Header{})
|
header.Headers = append(header.Headers, Header{})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue