mirror of
				https://github.com/gohugoio/hugo.git
				synced 2024-05-11 05:54:58 +00:00 
			
		
		
		
	Add site-wide/per-page [blackfriday] extensions option
				
					
				
			This commit is contained in:
		| @@ -136,7 +136,7 @@ func InitializeConfig() { | ||||
| 	viper.SetDefault("FootnoteAnchorPrefix", "") | ||||
| 	viper.SetDefault("FootnoteReturnLinkContents", "") | ||||
| 	viper.SetDefault("NewContentEditor", "") | ||||
| 	viper.SetDefault("Blackfriday", map[string]bool{"angledQuotes": false, "fractions": true, "plainIdAnchors": false}) | ||||
| 	viper.SetDefault("Blackfriday", new(helpers.Blackfriday)) | ||||
|  | ||||
| 	if hugoCmdV.PersistentFlags().Lookup("buildDrafts").Changed { | ||||
| 		viper.Set("BuildDrafts", Draft) | ||||
|   | ||||
| @@ -71,7 +71,7 @@ Here is a yaml configuration file which sets a few more options | ||||
|  | ||||
| [Blackfriday](https://github.com/russross/blackfriday) is the [Markdown](http://daringfireball.net/projects/markdown/) rendering engine used in Hugo. The Blackfriday configuration in Hugo is mostly a set of sane defaults that should fit most use cases. | ||||
|  | ||||
| But Hugo does expose some options---as listed in the table below, matched with the corresponding flag in the [Blackfriday source](https://github.com/russross/blackfriday/blob/master/html.go): | ||||
| But Hugo does expose some options---as listed in the table below, matched with the corresponding flag in the Blackfriday source ([html.go](https://github.com/russross/blackfriday/blob/master/html.go) and [markdown.go](https://github.com/russross/blackfriday/blob/master/markdown.go)): | ||||
|  | ||||
| <table class="table table-bordered"> | ||||
| <thead> | ||||
| @@ -115,6 +115,16 @@ but only these three.</small></td> | ||||
| <td class="purpose-title">Purpose:</td> | ||||
| <td class="purpose-description" colspan="2">If <code>true</code>, then header and footnote IDs are generated without the document ID <small>(e.g. <code>#my-header</code> instead of <code>#my-header:bec3ed8ba720b9073ab75abcf3ba5d97</code>)</small></td> | ||||
| </tr> | ||||
|  | ||||
| <tr> | ||||
| <td><code>extensions</code></td> | ||||
| <td><code>[]</code></td> | ||||
| <td><code>EXTENSION_*</code></td> | ||||
| </tr> | ||||
| <tr> | ||||
| <td class="purpose-title">Purpose:</td> | ||||
| <td class="purpose-description" colspan="2">Use non-default additional extensions <small>(e.g. Add <code>"hardLineBreak"</code> to use <code>EXTENSION_HARD_LINE_BREAK</code>)</small></td> | ||||
| </tr> | ||||
| </tbody> | ||||
| </table> | ||||
|  | ||||
| @@ -130,11 +140,14 @@ but only these three.</small></td> | ||||
|   angledQuotes = true | ||||
|   fractions = false | ||||
|   plainIdAnchors = true | ||||
|   extensions = ["hardLineBreak"] | ||||
| </code></pre></td> | ||||
| <td><pre><code>blackfriday: | ||||
|   angledQuotes: true | ||||
|   fractions: false | ||||
|   plainIdAnchors: true | ||||
|   extensions: | ||||
|     - hardLineBreak | ||||
| </code></pre></td> | ||||
| </tr> | ||||
| </table> | ||||
|   | ||||
| @@ -28,6 +28,7 @@ import ( | ||||
| 	jww "github.com/spf13/jwalterweatherman" | ||||
|  | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| ) | ||||
|  | ||||
| // Length of the summary that Hugo extracts from a content. | ||||
| @@ -36,6 +37,30 @@ var SummaryLength = 70 | ||||
| // Custom divider <!--more--> let's user define where summarization ends. | ||||
| var SummaryDivider = []byte("<!--more-->") | ||||
|  | ||||
| type Blackfriday struct { | ||||
| 	AngledQuotes   bool | ||||
| 	Fractions      bool | ||||
| 	PlainIdAnchors bool | ||||
| 	Extensions     []string | ||||
| } | ||||
|  | ||||
| var blackfridayExtensionMap = map[string]int{ | ||||
| 	"noIntraEmphasis":        blackfriday.EXTENSION_NO_INTRA_EMPHASIS, | ||||
| 	"tables":                 blackfriday.EXTENSION_TABLES, | ||||
| 	"fencedCode":             blackfriday.EXTENSION_FENCED_CODE, | ||||
| 	"autolink":               blackfriday.EXTENSION_AUTOLINK, | ||||
| 	"strikethrough":          blackfriday.EXTENSION_STRIKETHROUGH, | ||||
| 	"laxHtmlBlocks":          blackfriday.EXTENSION_LAX_HTML_BLOCKS, | ||||
| 	"spaceHeaders":           blackfriday.EXTENSION_SPACE_HEADERS, | ||||
| 	"hardLineBreak":          blackfriday.EXTENSION_HARD_LINE_BREAK, | ||||
| 	"tabSizeEight":           blackfriday.EXTENSION_TAB_SIZE_EIGHT, | ||||
| 	"footnotes":              blackfriday.EXTENSION_FOOTNOTES, | ||||
| 	"noEmptyLineBeforeBlock": blackfriday.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK, | ||||
| 	"headerIds":              blackfriday.EXTENSION_HEADER_IDS, | ||||
| 	"titleblock":             blackfriday.EXTENSION_TITLEBLOCK, | ||||
| 	"autoHeaderIds":          blackfriday.EXTENSION_AUTO_HEADER_IDS, | ||||
| } | ||||
|  | ||||
| // StripHTML accepts a string, strips out all HTML tags and returns it. | ||||
| func StripHTML(s string) string { | ||||
| 	output := "" | ||||
| @@ -87,7 +112,7 @@ func GetHtmlRenderer(defaultFlags int, ctx RenderingContext) blackfriday.Rendere | ||||
|  | ||||
| 	b := len(ctx.DocumentId) != 0 | ||||
|  | ||||
| 	if m, ok := ctx.ConfigFlags["plainIdAnchors"]; b && ((ok && !m) || !ok) { | ||||
| 	if b && !ctx.getConfig().PlainIdAnchors { | ||||
| 		renderParameters.FootnoteAnchorPrefix = ctx.DocumentId + ":" + renderParameters.FootnoteAnchorPrefix | ||||
| 		renderParameters.HeaderIDSuffix = ":" + ctx.DocumentId | ||||
| 	} | ||||
| @@ -99,40 +124,40 @@ func GetHtmlRenderer(defaultFlags int, ctx RenderingContext) blackfriday.Rendere | ||||
| 	htmlFlags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES | ||||
| 	htmlFlags |= blackfriday.HTML_FOOTNOTE_RETURN_LINKS | ||||
|  | ||||
| 	var angledQuotes bool | ||||
|  | ||||
| 	if m, ok := ctx.ConfigFlags["angledQuotes"]; ok { | ||||
| 		angledQuotes = m | ||||
| 	} | ||||
|  | ||||
| 	if angledQuotes { | ||||
| 	if ctx.getConfig().AngledQuotes { | ||||
| 		htmlFlags |= blackfriday.HTML_SMARTYPANTS_ANGLED_QUOTES | ||||
| 	} | ||||
|  | ||||
| 	if m, ok := ctx.ConfigFlags["fractions"]; ok && !m { | ||||
| 	if !ctx.getConfig().Fractions { | ||||
| 		htmlFlags &^= blackfriday.HTML_SMARTYPANTS_FRACTIONS | ||||
| 	} | ||||
|  | ||||
| 	return blackfriday.HtmlRendererWithParameters(htmlFlags, "", "", renderParameters) | ||||
| } | ||||
|  | ||||
| func GetMarkdownExtensions() int { | ||||
| 	return 0 | blackfriday.EXTENSION_NO_INTRA_EMPHASIS | | ||||
| func GetMarkdownExtensions(ctx RenderingContext) int { | ||||
| 	flags := 0 | blackfriday.EXTENSION_NO_INTRA_EMPHASIS | | ||||
| 		blackfriday.EXTENSION_TABLES | blackfriday.EXTENSION_FENCED_CODE | | ||||
| 		blackfriday.EXTENSION_AUTOLINK | blackfriday.EXTENSION_STRIKETHROUGH | | ||||
| 		blackfriday.EXTENSION_SPACE_HEADERS | blackfriday.EXTENSION_FOOTNOTES | | ||||
| 		blackfriday.EXTENSION_HEADER_IDS | blackfriday.EXTENSION_AUTO_HEADER_IDS | ||||
| 	for _, extension := range ctx.getConfig().Extensions { | ||||
| 		if flag, ok := blackfridayExtensionMap[extension]; ok { | ||||
| 			flags |= flag | ||||
| 		} | ||||
| 	} | ||||
| 	return flags | ||||
| } | ||||
|  | ||||
| func MarkdownRender(ctx RenderingContext) []byte { | ||||
| 	return blackfriday.Markdown(ctx.Content, GetHtmlRenderer(0, ctx), | ||||
| 		GetMarkdownExtensions()) | ||||
| 		GetMarkdownExtensions(ctx)) | ||||
| } | ||||
|  | ||||
| func MarkdownRenderWithTOC(ctx RenderingContext) []byte { | ||||
| 	return blackfriday.Markdown(ctx.Content, | ||||
| 		GetHtmlRenderer(blackfriday.HTML_TOC, ctx), | ||||
| 		GetMarkdownExtensions()) | ||||
| 		GetMarkdownExtensions(ctx)) | ||||
| } | ||||
|  | ||||
| // ExtractTOC extracts Table of Contents from content. | ||||
| @@ -175,7 +200,17 @@ type RenderingContext struct { | ||||
| 	Content    []byte | ||||
| 	PageFmt    string | ||||
| 	DocumentId string | ||||
| 	ConfigFlags map[string]bool | ||||
| 	Config     *Blackfriday | ||||
| 	configInit sync.Once | ||||
| } | ||||
|  | ||||
| func (c *RenderingContext) getConfig() *Blackfriday { | ||||
| 	c.configInit.Do(func() { | ||||
| 		if c.Config == nil { | ||||
| 			c.Config = new(Blackfriday) | ||||
| 		} | ||||
| 	}) | ||||
| 	return c.Config | ||||
| } | ||||
|  | ||||
| func RenderBytesWithTOC(ctx RenderingContext) []byte { | ||||
|   | ||||
| @@ -17,16 +17,12 @@ import ( | ||||
| 	"bytes" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"github.com/spf13/hugo/helpers" | ||||
| 	"github.com/spf13/hugo/parser" | ||||
| 	"reflect" | ||||
|  | ||||
| 	"github.com/spf13/cast" | ||||
| 	"github.com/spf13/hugo/hugofs" | ||||
| 	"github.com/spf13/hugo/source" | ||||
| 	"github.com/spf13/hugo/tpl" | ||||
| 	jww "github.com/spf13/jwalterweatherman" | ||||
| 	"github.com/spf13/viper" | ||||
| 	"github.com/mitchellh/mapstructure" | ||||
| 	"github.com/spf13/hugo/helpers" | ||||
| 	"github.com/spf13/hugo/parser" | ||||
|  | ||||
| 	"html/template" | ||||
| 	"io" | ||||
| 	"net/url" | ||||
| @@ -35,6 +31,13 @@ import ( | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/spf13/cast" | ||||
| 	"github.com/spf13/hugo/hugofs" | ||||
| 	"github.com/spf13/hugo/source" | ||||
| 	"github.com/spf13/hugo/tpl" | ||||
| 	jww "github.com/spf13/jwalterweatherman" | ||||
| 	"github.com/spf13/viper" | ||||
| ) | ||||
|  | ||||
| type Page struct { | ||||
| @@ -61,8 +64,8 @@ type Page struct { | ||||
| 	rawContent          []byte | ||||
| 	contentShortCodes   map[string]string | ||||
| 	plain               string // TODO should be []byte | ||||
| 	renderingConfigFlags     map[string]bool | ||||
| 	renderingConfigFlagsInit sync.Once | ||||
| 	renderingConfig     *helpers.Blackfriday | ||||
| 	renderingConfigInit sync.Once | ||||
| 	PageMeta | ||||
| 	Source | ||||
| 	Position | ||||
| @@ -182,37 +185,33 @@ func (p *Page) setSummary() { | ||||
| func (p *Page) renderBytes(content []byte) []byte { | ||||
| 	return helpers.RenderBytes( | ||||
| 		helpers.RenderingContext{Content: content, PageFmt: p.guessMarkupType(), | ||||
| 			DocumentId: p.UniqueId(), ConfigFlags: p.getRenderingConfigFlags()}) | ||||
| 			DocumentId: p.UniqueId(), Config: p.getRenderingConfig()}) | ||||
| } | ||||
|  | ||||
| func (p *Page) renderContent(content []byte) []byte { | ||||
| 	return helpers.RenderBytesWithTOC(helpers.RenderingContext{Content: content, PageFmt: p.guessMarkupType(), | ||||
| 		DocumentId: p.UniqueId(), ConfigFlags: p.getRenderingConfigFlags()}) | ||||
| 		DocumentId: p.UniqueId(), Config: p.getRenderingConfig()}) | ||||
| } | ||||
|  | ||||
| func (p *Page) getRenderingConfigFlags() map[string]bool { | ||||
|  | ||||
| 	p.renderingConfigFlagsInit.Do(func() { | ||||
| 		p.renderingConfigFlags = make(map[string]bool) | ||||
| func (p *Page) getRenderingConfig() *helpers.Blackfriday { | ||||
|  | ||||
| 	p.renderingConfigInit.Do(func() { | ||||
| 		pageParam := p.GetParam("blackfriday") | ||||
| 		siteParam := viper.GetStringMap("blackfriday") | ||||
|  | ||||
| 		p.renderingConfigFlags = cast.ToStringMapBool(siteParam) | ||||
|  | ||||
| 		if pageParam != nil { | ||||
| 			pageFlags := cast.ToStringMapBool(pageParam) | ||||
| 			for key, value := range pageFlags { | ||||
| 				p.renderingConfigFlags[key] = value | ||||
| 			pageConfig := cast.ToStringMap(pageParam) | ||||
| 			for key, value := range pageConfig { | ||||
| 				siteParam[key] = value | ||||
| 			} | ||||
| 		} | ||||
| 		p.renderingConfig = new(helpers.Blackfriday) | ||||
| 		if err := mapstructure.Decode(siteParam, p.renderingConfig); err != nil { | ||||
| 			jww.FATAL.Printf("Failed to get rendering config for %s:\n%s", p.BaseFileName(), err.Error()) | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	return p.renderingConfigFlags | ||||
| } | ||||
|  | ||||
| func (p *Page) isRenderingFlagEnabled(flag string) bool { | ||||
| 	return p.getRenderingConfigFlags()[flag] | ||||
| 	return p.renderingConfig | ||||
| } | ||||
|  | ||||
| func newPage(filename string) *Page { | ||||
|   | ||||
| @@ -212,6 +212,16 @@ the cylinder and strike me down. ## BB | ||||
| ### BBB | ||||
|  | ||||
| "You're a great Granser," he cried delightedly, "always making believe them little marks mean something." | ||||
| ` | ||||
|  | ||||
| 	SIMPLE_PAGE_WITH_ADDITIONAL_EXTENSION = `+++ | ||||
| [blackfriday] | ||||
|   extensions = ["hardLineBreak"] | ||||
| +++ | ||||
| first line. | ||||
| second line. | ||||
|  | ||||
| fourth line. | ||||
| ` | ||||
| ) | ||||
|  | ||||
| @@ -366,6 +376,16 @@ func TestPageWithEmbeddedScriptTag(t *testing.T) { | ||||
| 	checkPageContent(t, p, "<script type='text/javascript'>alert('the script tags are still there, right?');</script>\n") | ||||
| } | ||||
|  | ||||
| func TestPageWithAdditionalExtension(t *testing.T) { | ||||
| 	p, _ := NewPage("simple.md") | ||||
| 	err := p.ReadFrom(strings.NewReader(SIMPLE_PAGE_WITH_ADDITIONAL_EXTENSION)) | ||||
| 	p.Convert() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to create a page with frontmatter and body content: %s", err) | ||||
| 	} | ||||
| 	checkPageContent(t, p, "<p>first line.<br />\nsecond line.</p>\n\n<p>fourth line.</p>\n") | ||||
| } | ||||
|  | ||||
| func TestTableOfContents(t *testing.T) { | ||||
| 	p, _ := NewPage("tocpage.md") | ||||
| 	err := p.ReadFrom(strings.NewReader(PAGE_WITH_TOC)) | ||||
|   | ||||
| @@ -205,7 +205,7 @@ func renderShortcode(sc shortcode, p *Page, t tpl.Template) string { | ||||
| 		if sc.doMarkup { | ||||
| 			newInner := helpers.RenderBytes(helpers.RenderingContext{ | ||||
| 				Content: []byte(inner), PageFmt: p.guessMarkupType(), | ||||
| 				DocumentId: p.UniqueId(), ConfigFlags: p.getRenderingConfigFlags()}) | ||||
| 				DocumentId: p.UniqueId(), Config: p.getRenderingConfig()}) | ||||
|  | ||||
| 			// If the type is “unknown” or “markdown”, we assume the markdown | ||||
| 			// generation has been performed. Given the input: `a line`, markdown | ||||
|   | ||||
| @@ -187,9 +187,9 @@ func (s *SiteInfo) refLink(ref string, page *Page, relative bool) (string, error | ||||
| 	if refUrl.Fragment != "" { | ||||
| 		link = link + "#" + refUrl.Fragment | ||||
|  | ||||
| 		if refUrl.Path != "" && target != nil && !target.isRenderingFlagEnabled("plainIdAnchors") { | ||||
| 		if refUrl.Path != "" && target != nil && !target.getRenderingConfig().PlainIdAnchors { | ||||
| 			link = link + ":" + target.UniqueId() | ||||
| 		} else if page != nil && !page.isRenderingFlagEnabled("plainIdAnchors") { | ||||
| 		} else if page != nil && !page.getRenderingConfig().PlainIdAnchors { | ||||
| 			link = link + ":" + page.UniqueId() | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user