mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-27 16:58:53 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c8232e3bd |
@@ -4,7 +4,7 @@ parameters:
|
||||
defaults: &defaults
|
||||
resource_class: large
|
||||
docker:
|
||||
- image: bepsays/ci-hugoreleaser:1.22200.20201
|
||||
- image: bepsays/ci-hugoreleaser:1.22200.20200
|
||||
environment: &buildenv
|
||||
GOMODCACHE: /root/project/gomodcache
|
||||
version: 2
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
environment:
|
||||
<<: [*buildenv]
|
||||
docker:
|
||||
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22200.20201
|
||||
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22200.20200
|
||||
steps:
|
||||
- *restore-cache
|
||||
- &attach-workspace
|
||||
|
||||
@@ -123,20 +123,6 @@ func InSlicEqualFold(arr []string, el string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ToString converts the given value to a string.
|
||||
// Note that this is a more strict version compared to cast.ToString,
|
||||
// as it will not try to convert numeric values to strings,
|
||||
// but only accept strings or fmt.Stringer.
|
||||
func ToString(v any) (string, bool) {
|
||||
switch vv := v.(type) {
|
||||
case string:
|
||||
return vv, true
|
||||
case fmt.Stringer:
|
||||
return vv.String(), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
type Tuple struct {
|
||||
First string
|
||||
Second string
|
||||
|
||||
@@ -18,6 +18,6 @@ package hugo
|
||||
var CurrentVersion = Version{
|
||||
Major: 0,
|
||||
Minor: 125,
|
||||
PatchLevel: 1,
|
||||
Suffix: "",
|
||||
PatchLevel: 0,
|
||||
Suffix: "-DEV",
|
||||
}
|
||||
|
||||
@@ -4017,11 +4017,6 @@ tpl:
|
||||
- s
|
||||
Description: CountWords returns the approximate word count in s.
|
||||
Examples: []
|
||||
Diff:
|
||||
Aliases: null
|
||||
Args: null
|
||||
Description: ""
|
||||
Examples: null
|
||||
FindRE:
|
||||
Aliases:
|
||||
- findRE
|
||||
|
||||
@@ -55,7 +55,7 @@ require (
|
||||
github.com/niklasfasching/go-org v1.7.0
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58
|
||||
github.com/pelletier/go-toml/v2 v2.2.1
|
||||
github.com/pelletier/go-toml/v2 v2.2.0
|
||||
github.com/rogpeppe/go-internal v1.12.0
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
|
||||
github.com/sanity-io/litter v1.5.5
|
||||
@@ -152,7 +152,7 @@ require (
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
|
||||
google.golang.org/grpc v1.59.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
software.sslmate.com/src/go-pkcs12 v0.2.0 // indirect
|
||||
|
||||
@@ -376,8 +376,8 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
|
||||
github.com/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg=
|
||||
github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo=
|
||||
github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
||||
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
@@ -797,8 +797,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
|
||||
+14
-16
@@ -36,6 +36,11 @@ import (
|
||||
"github.com/gohugoio/hugo/config"
|
||||
)
|
||||
|
||||
var (
|
||||
openingPTag = []byte("<p>")
|
||||
closingPTag = []byte("</p>")
|
||||
)
|
||||
|
||||
// ContentSpec provides functionality to render markdown content.
|
||||
type ContentSpec struct {
|
||||
Converters markup.ConverterProvider
|
||||
@@ -237,26 +242,19 @@ func (c *ContentSpec) TruncateWordsToWholeSentence(s string) (string, bool) {
|
||||
return strings.TrimSpace(s[:endIndex]), endIndex < len(s)
|
||||
}
|
||||
|
||||
// TrimShortHTML removes the outer tags from HTML input where (a) the opening
|
||||
// tag is present only once with the input, and (b) the opening and closing
|
||||
// tags wrap the input after white space removal.
|
||||
func (c *ContentSpec) TrimShortHTML(input []byte, markup string) []byte {
|
||||
openingTag := []byte("<p>")
|
||||
closingTag := []byte("</p>")
|
||||
|
||||
if markup == "asciidocext" {
|
||||
openingTag = []byte("<div class=\"paragraph\">\n<p>")
|
||||
closingTag = []byte("</p>\n</div>")
|
||||
}
|
||||
|
||||
if bytes.Count(input, openingTag) == 1 {
|
||||
// TrimShortHTML removes the <p>/</p> tags from HTML input in the situation
|
||||
// where said tags are the only <p> tags in the input and enclose the content
|
||||
// of the input (whitespace excluded).
|
||||
func (c *ContentSpec) TrimShortHTML(input []byte) []byte {
|
||||
if bytes.Count(input, openingPTag) == 1 {
|
||||
input = bytes.TrimSpace(input)
|
||||
if bytes.HasPrefix(input, openingTag) && bytes.HasSuffix(input, closingTag) {
|
||||
input = bytes.TrimPrefix(input, openingTag)
|
||||
input = bytes.TrimSuffix(input, closingTag)
|
||||
if bytes.HasPrefix(input, openingPTag) && bytes.HasSuffix(input, closingPTag) {
|
||||
input = bytes.TrimPrefix(input, openingPTag)
|
||||
input = bytes.TrimSuffix(input, closingPTag)
|
||||
input = bytes.TrimSpace(input)
|
||||
}
|
||||
}
|
||||
|
||||
return input
|
||||
}
|
||||
|
||||
|
||||
+14
-17
@@ -26,27 +26,24 @@ import (
|
||||
|
||||
func TestTrimShortHTML(t *testing.T) {
|
||||
tests := []struct {
|
||||
markup string
|
||||
input []byte
|
||||
output []byte
|
||||
input, output []byte
|
||||
}{
|
||||
{"markdown", []byte(""), []byte("")},
|
||||
{"markdown", []byte("Plain text"), []byte("Plain text")},
|
||||
{"markdown", []byte("<p>Simple paragraph</p>"), []byte("Simple paragraph")},
|
||||
{"markdown", []byte("\n \n \t <p> \t Whitespace\nHTML \n\t </p>\n\t"), []byte("Whitespace\nHTML")},
|
||||
{"markdown", []byte("<p>Multiple</p><p>paragraphs</p>"), []byte("<p>Multiple</p><p>paragraphs</p>")},
|
||||
{"markdown", []byte("<p>Nested<p>paragraphs</p></p>"), []byte("<p>Nested<p>paragraphs</p></p>")},
|
||||
{"markdown", []byte("<p>Hello</p>\n<ul>\n<li>list1</li>\n<li>list2</li>\n</ul>"), []byte("<p>Hello</p>\n<ul>\n<li>list1</li>\n<li>list2</li>\n</ul>")},
|
||||
// Issue 11698
|
||||
{"markdown", []byte("<h2 id=`a`>b</h2>\n\n<p>c</p>"), []byte("<h2 id=`a`>b</h2>\n\n<p>c</p>")},
|
||||
// Issue 12369
|
||||
{"markdown", []byte("<div class=\"paragraph\">\n<p>foo</p>\n</div>"), []byte("<div class=\"paragraph\">\n<p>foo</p>\n</div>")},
|
||||
{"asciidocext", []byte("<div class=\"paragraph\">\n<p>foo</p>\n</div>"), []byte("foo")},
|
||||
{[]byte(""), []byte("")},
|
||||
{[]byte("Plain text"), []byte("Plain text")},
|
||||
// This seems wrong. Why touch it if it doesn't have p tag?
|
||||
// {[]byte(" \t\n Whitespace text\n\n"), []byte("Whitespace text")},
|
||||
{[]byte("<p>Simple paragraph</p>"), []byte("Simple paragraph")},
|
||||
{[]byte("\n \n \t <p> \t Whitespace\nHTML \n\t </p>\n\t"), []byte("Whitespace\nHTML")},
|
||||
{[]byte("<p>Multiple</p><p>paragraphs</p>"), []byte("<p>Multiple</p><p>paragraphs</p>")},
|
||||
{[]byte("<p>Nested<p>paragraphs</p></p>"), []byte("<p>Nested<p>paragraphs</p></p>")},
|
||||
{[]byte("<p>Hello</p>\n<ul>\n<li>list1</li>\n<li>list2</li>\n</ul>"), []byte("<p>Hello</p>\n<ul>\n<li>list1</li>\n<li>list2</li>\n</ul>")},
|
||||
// Issue #11698
|
||||
{[]byte("<h2 id=`a`>b</h2>\n\n<p>c</p>"), []byte("<h2 id=`a`>b</h2>\n\n<p>c</p>")},
|
||||
}
|
||||
|
||||
c := newTestContentSpec(nil)
|
||||
for i, test := range tests {
|
||||
output := c.TrimShortHTML(test.input, test.markup)
|
||||
output := c.TrimShortHTML(test.input)
|
||||
if !bytes.Equal(test.output, output) {
|
||||
t.Errorf("Test %d failed. Expected %q got %q", i, test.output, output)
|
||||
}
|
||||
@@ -57,7 +54,7 @@ func BenchmarkTrimShortHTML(b *testing.B) {
|
||||
c := newTestContentSpec(nil)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
c.TrimShortHTML([]byte("<p>Simple paragraph</p>"), "markdown")
|
||||
c.TrimShortHTML([]byte("<p>Simple paragraph</p>"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1603,7 +1603,7 @@ func (sa *sitePagesAssembler) assembleResources() error {
|
||||
targetPaths := ps.targetPaths()
|
||||
baseTarget := targetPaths.SubResourceBaseTarget
|
||||
duplicateResourceFiles := true
|
||||
if ps.m.pageConfig.IsGoldmark {
|
||||
if ps.s.ContentSpec.Converters.IsGoldmark(ps.m.pageConfig.Markup) {
|
||||
duplicateResourceFiles = ps.s.ContentSpec.Converters.GetMarkupConfig().Goldmark.DuplicateResourceFiles
|
||||
}
|
||||
|
||||
|
||||
@@ -610,7 +610,7 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
|
||||
|
||||
// For a list of events for the different OSes, see the test output in https://github.com/bep/fsnotifyeventlister/.
|
||||
events = h.fileEventsFilter(events)
|
||||
events = h.fileEventsTrim(events)
|
||||
events = h.fileEventsTranslate(events)
|
||||
eventInfos := h.fileEventsApplyInfo(events)
|
||||
|
||||
logger := h.Log
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -686,17 +685,8 @@ func (s *IntegrationTestBuilder) build(cfg BuildCfg) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We simulate the fsnotify events.
|
||||
// See the test output in https://github.com/bep/fsnotifyeventlister for what events gets produced
|
||||
// by the different OSes.
|
||||
func (s *IntegrationTestBuilder) changeEvents() []fsnotify.Event {
|
||||
var (
|
||||
events []fsnotify.Event
|
||||
isLinux = runtime.GOOS == "linux"
|
||||
isMacOs = runtime.GOOS == "darwin"
|
||||
isWindows = runtime.GOOS == "windows"
|
||||
)
|
||||
|
||||
var events []fsnotify.Event
|
||||
for _, v := range s.removedFiles {
|
||||
events = append(events, fsnotify.Event{
|
||||
Name: v,
|
||||
@@ -723,32 +713,12 @@ func (s *IntegrationTestBuilder) changeEvents() []fsnotify.Event {
|
||||
Name: v,
|
||||
Op: fsnotify.Write,
|
||||
})
|
||||
if isLinux || isWindows {
|
||||
// Duplicate write events, for some reason.
|
||||
events = append(events, fsnotify.Event{
|
||||
Name: v,
|
||||
Op: fsnotify.Write,
|
||||
})
|
||||
}
|
||||
if isMacOs {
|
||||
events = append(events, fsnotify.Event{
|
||||
Name: v,
|
||||
Op: fsnotify.Chmod,
|
||||
})
|
||||
}
|
||||
}
|
||||
for _, v := range s.createdFiles {
|
||||
events = append(events, fsnotify.Event{
|
||||
Name: v,
|
||||
Op: fsnotify.Create,
|
||||
})
|
||||
if isLinux || isWindows {
|
||||
events = append(events, fsnotify.Event{
|
||||
Name: v,
|
||||
Op: fsnotify.Write,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Shuffle events.
|
||||
|
||||
@@ -522,7 +522,6 @@ func (c *cachedContent) contentRendered(ctx context.Context, cp *pageContentOutp
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return nil, errors.New("invalid state: astDoc is set but RenderContent returned false")
|
||||
}
|
||||
@@ -627,10 +626,8 @@ func (c *cachedContent) contentToC(ctx context.Context, cp *pageContentOutput) (
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Callback called from below (e.g. in .RenderString)
|
||||
// Callback called from above (e.g. in .RenderString)
|
||||
ctxCallback := func(cp2 *pageContentOutput, ct2 contentTableOfContents) {
|
||||
cp.otherOutputs[cp2.po.p.pid] = cp2
|
||||
|
||||
// Merge content placeholders
|
||||
for k, v := range ct2.contentPlaceholders {
|
||||
ct.contentPlaceholders[k] = v
|
||||
@@ -781,7 +778,7 @@ func (c *cachedContent) contentPlain(ctx context.Context, cp *pageContentOutput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
html := cp.po.p.s.ContentSpec.TrimShortHTML(b.Bytes(), cp.po.p.m.pageConfig.Markup)
|
||||
html := cp.po.p.s.ContentSpec.TrimShortHTML(b.Bytes())
|
||||
result.summary = helpers.BytesToHTML(html)
|
||||
} else {
|
||||
var summary string
|
||||
|
||||
+4
-20
@@ -737,8 +737,6 @@ func (p *pageMeta) applyDefaultValues() error {
|
||||
}
|
||||
}
|
||||
|
||||
p.pageConfig.IsGoldmark = p.s.ContentSpec.Converters.IsGoldmark(p.pageConfig.Markup)
|
||||
|
||||
if p.pageConfig.Title == "" && p.f == nil {
|
||||
switch p.Kind() {
|
||||
case kinds.KindHome:
|
||||
@@ -796,26 +794,12 @@ func (p *pageMeta) newContentConverter(ps *pageState, markup string) (converter.
|
||||
path = p.Path()
|
||||
}
|
||||
|
||||
doc := newPageForRenderHook(ps)
|
||||
|
||||
documentLookup := func(id uint64) any {
|
||||
if id == ps.pid {
|
||||
// This prevents infinite recursion in some cases.
|
||||
return doc
|
||||
}
|
||||
if v, ok := ps.pageOutput.pco.otherOutputs[id]; ok {
|
||||
return v.po.p
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
cpp, err := cp.New(
|
||||
converter.DocumentContext{
|
||||
Document: doc,
|
||||
DocumentLookup: documentLookup,
|
||||
DocumentID: id,
|
||||
DocumentName: path,
|
||||
Filename: filename,
|
||||
Document: newPageForRenderHook(ps),
|
||||
DocumentID: id,
|
||||
DocumentName: path,
|
||||
Filename: filename,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
@@ -30,7 +30,6 @@ import (
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/gohugoio/hugo/markup/converter/hooks"
|
||||
"github.com/gohugoio/hugo/markup/goldmark/hugocontext"
|
||||
"github.com/gohugoio/hugo/markup/highlight/chromalexers"
|
||||
"github.com/gohugoio/hugo/markup/tableofcontents"
|
||||
|
||||
@@ -69,9 +68,8 @@ var (
|
||||
|
||||
func newPageContentOutput(po *pageOutput) (*pageContentOutput, error) {
|
||||
cp := &pageContentOutput{
|
||||
po: po,
|
||||
renderHooks: &renderHooks{},
|
||||
otherOutputs: make(map[uint64]*pageContentOutput),
|
||||
po: po,
|
||||
renderHooks: &renderHooks{},
|
||||
}
|
||||
return cp, nil
|
||||
}
|
||||
@@ -85,10 +83,6 @@ type renderHooks struct {
|
||||
type pageContentOutput struct {
|
||||
po *pageOutput
|
||||
|
||||
// Other pages involved in rendering of this page,
|
||||
// typically included with .RenderShortcodes.
|
||||
otherOutputs map[uint64]*pageContentOutput
|
||||
|
||||
contentRenderedVersion int // Incremented on reset.
|
||||
contentRendered bool // Set on content render.
|
||||
|
||||
@@ -171,13 +165,6 @@ func (pco *pageContentOutput) RenderShortcodes(ctx context.Context) (template.HT
|
||||
cb(pco, ct)
|
||||
}
|
||||
|
||||
if tpl.Context.IsInGoldmark.Get(ctx) {
|
||||
// This content will be parsed and rendered by Goldmark.
|
||||
// Wrap it in a special Hugo markup to assign the correct Page from
|
||||
// the stack.
|
||||
c = hugocontext.Wrap(c, pco.po.p.pid)
|
||||
}
|
||||
|
||||
return helpers.BytesToHTML(c), nil
|
||||
}
|
||||
|
||||
@@ -376,11 +363,9 @@ func (pco *pageContentOutput) RenderString(ctx context.Context, args ...any) (te
|
||||
}
|
||||
|
||||
if opts.Display == "inline" {
|
||||
markup := pco.po.p.m.pageConfig.Markup
|
||||
if opts.Markup != "" {
|
||||
markup = pco.po.p.s.ContentSpec.ResolveMarkup(opts.Markup)
|
||||
}
|
||||
rendered = pco.po.p.s.ContentSpec.TrimShortHTML(rendered, markup)
|
||||
// We may have to rethink this in the future when we get other
|
||||
// renderers.
|
||||
rendered = pco.po.p.s.ContentSpec.TrimShortHTML(rendered)
|
||||
}
|
||||
|
||||
return template.HTML(string(rendered)), nil
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/htesting"
|
||||
"github.com/gohugoio/hugo/markup/asciidocext"
|
||||
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/dartsass"
|
||||
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/scss"
|
||||
)
|
||||
@@ -1515,65 +1514,3 @@ MyTemplate: {{ partial "MyTemplate.html" . }}|
|
||||
|
||||
b.AssertFileContent("public/index.html", "MyTemplate: MyTemplate Edited")
|
||||
}
|
||||
|
||||
func TestRebuildEditAsciidocContentFile(t *testing.T) {
|
||||
if !asciidocext.Supports() {
|
||||
t.Skip("skip asciidoc")
|
||||
}
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableLiveReload = true
|
||||
disableKinds = ["taxonomy", "term", "sitemap", "robotsTXT", "404", "rss", "home", "section"]
|
||||
[security]
|
||||
[security.exec]
|
||||
allow = ['^python$', '^rst2html.*', '^asciidoctor$']
|
||||
-- content/posts/p1.adoc --
|
||||
---
|
||||
title: "P1"
|
||||
---
|
||||
P1 Content.
|
||||
-- content/posts/p2.adoc --
|
||||
---
|
||||
title: "P2"
|
||||
---
|
||||
P2 Content.
|
||||
-- layouts/_default/single.html --
|
||||
Single: {{ .Title }}|{{ .Content }}|
|
||||
`
|
||||
b := TestRunning(t, files)
|
||||
b.AssertFileContent("public/posts/p1/index.html",
|
||||
"Single: P1|<div class=\"paragraph\">\n<p>P1 Content.</p>\n</div>\n|")
|
||||
b.AssertRenderCountPage(2)
|
||||
b.AssertRenderCountContent(2)
|
||||
|
||||
b.EditFileReplaceAll("content/posts/p1.adoc", "P1 Content.", "P1 Content Edited.").Build()
|
||||
|
||||
b.AssertFileContent("public/posts/p1/index.html", "Single: P1|<div class=\"paragraph\">\n<p>P1 Content Edited.</p>\n</div>\n|")
|
||||
b.AssertRenderCountPage(1)
|
||||
b.AssertRenderCountContent(1)
|
||||
}
|
||||
|
||||
func TestRebuildEditSingleListChangeUbuntuIssue12362(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['rss','section','sitemap','taxonomy','term']
|
||||
disableLiveReload = true
|
||||
-- layouts/_default/list.html --
|
||||
{{ range .Pages }}{{ .Title }}|{{ end }}
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Title }}
|
||||
-- content/p1.md --
|
||||
---
|
||||
title: p1
|
||||
---
|
||||
`
|
||||
|
||||
b := TestRunning(t, files)
|
||||
b.AssertFileContent("public/index.html", "p1|")
|
||||
|
||||
b.AddFiles("content/p2.md", "---\ntitle: p2\n---").Build()
|
||||
b.AssertFileContent("public/index.html", "p1|p2|") // this test passes, which doesn't match reality
|
||||
}
|
||||
|
||||
@@ -200,99 +200,3 @@ Myshort Original.
|
||||
b.Build()
|
||||
b.AssertFileContent("public/p1/index.html", "Edited")
|
||||
}
|
||||
|
||||
func TestRenderShortcodesNestedPageContextIssue12356(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ["taxonomy", "term", "rss", "sitemap", "robotsTXT", "404"]
|
||||
-- layouts/_default/_markup/render-image.html --
|
||||
{{- with .PageInner.Resources.Get .Destination -}}Image: {{ .RelPermalink }}|{{- end -}}
|
||||
-- layouts/_default/_markup/render-link.html --
|
||||
{{- with .PageInner.GetPage .Destination -}}Link: {{ .RelPermalink }}|{{- end -}}
|
||||
-- layouts/_default/_markup/render-heading.html --
|
||||
Heading: {{ .PageInner.Title }}: {{ .PlainText }}|
|
||||
-- layouts/_default/_markup/render-codeblock.html --
|
||||
CodeBlock: {{ .PageInner.Title }}: {{ .Type }}|
|
||||
-- layouts/_default/list.html --
|
||||
Content:{{ .Content }}|
|
||||
Fragments: {{ with .Fragments }}{{.Identifiers }}{{ end }}|
|
||||
-- layouts/_default/single.html --
|
||||
Content:{{ .Content }}|
|
||||
-- layouts/shortcodes/include.html --
|
||||
{{ with site.GetPage (.Get 0) }}
|
||||
{{ .RenderShortcodes }}
|
||||
{{ end }}
|
||||
-- content/markdown/_index.md --
|
||||
---
|
||||
title: "Markdown"
|
||||
---
|
||||
# H1
|
||||
|{{% include "/posts/p1" %}}|
|
||||

|
||||
|
||||
§§§go
|
||||
fmt.Println("Hello")
|
||||
§§§
|
||||
|
||||
-- content/markdown2/_index.md --
|
||||
---
|
||||
title: "Markdown 2"
|
||||
---
|
||||
|{{< include "/posts/p1" >}}|
|
||||
-- content/html/_index.html --
|
||||
---
|
||||
title: "HTML"
|
||||
---
|
||||
|{{% include "/posts/p1" %}}|
|
||||
|
||||
-- content/posts/p1/index.md --
|
||||
---
|
||||
title: "p1"
|
||||
---
|
||||
## H2-p1
|
||||

|
||||

|
||||
[p2](p2)
|
||||
|
||||
§§§bash
|
||||
echo "Hello"
|
||||
§§§
|
||||
|
||||
-- content/posts/p2/index.md --
|
||||
---
|
||||
title: "p2"
|
||||
---
|
||||
-- content/posts/p1/pixel1.png --
|
||||
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
|
||||
-- content/posts/p1/pixel2.png --
|
||||
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
|
||||
-- content/markdown/pixel3.png --
|
||||
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
|
||||
-- content/html/pixel4.png --
|
||||
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
|
||||
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/markdown/index.html",
|
||||
// Images.
|
||||
"Image: /posts/p1/pixel1.png|\nImage: /posts/p1/pixel2.png|\n|\nImage: /markdown/pixel3.png|</p>\n|",
|
||||
// Links.
|
||||
"Link: /posts/p2/|",
|
||||
// Code blocks
|
||||
"CodeBlock: p1: bash|", "CodeBlock: Markdown: go|",
|
||||
// Headings.
|
||||
"Heading: Markdown: H1|", "Heading: p1: H2-p1|",
|
||||
// Fragments.
|
||||
"Fragments: [h1 h2-p1]|",
|
||||
// Check that the special context markup is not rendered.
|
||||
"! hugo_ctx",
|
||||
)
|
||||
|
||||
b.AssertFileContent("public/markdown2/index.html", "! hugo_ctx", "Content:<p>|\n \n\n|</p>\n|")
|
||||
|
||||
b.AssertFileContent("public/html/index.html", "! hugo_ctx")
|
||||
}
|
||||
|
||||
@@ -321,16 +321,10 @@ func prepareShortcode(
|
||||
|
||||
// Allow the caller to delay the rendering of the shortcode if needed.
|
||||
var fn shortcodeRenderFunc = func(ctx context.Context) ([]byte, bool, error) {
|
||||
if p.m.pageConfig.IsGoldmark && sc.doMarkup {
|
||||
// Signal downwards that the content rendered will be
|
||||
// parsed and rendered by Goldmark.
|
||||
ctx = tpl.Context.IsInGoldmark.Set(ctx, true)
|
||||
}
|
||||
r, err := doRenderShortcode(ctx, level, s, tplVariants, sc, parent, p, isRenderString)
|
||||
if err != nil {
|
||||
return nil, false, toParseErr(err)
|
||||
}
|
||||
|
||||
b, hasVariants, err := r.renderShortcode(ctx)
|
||||
if err != nil {
|
||||
return nil, false, toParseErr(err)
|
||||
|
||||
+31
-35
@@ -424,35 +424,7 @@ func (h *HugoSites) fileEventsFilter(events []fsnotify.Event) []fsnotify.Event {
|
||||
events[n] = ev
|
||||
n++
|
||||
}
|
||||
events = events[:n]
|
||||
|
||||
eventOrdinal := func(e fsnotify.Event) int {
|
||||
// Pull the structural changes to the top.
|
||||
if e.Op.Has(fsnotify.Create) {
|
||||
return 1
|
||||
}
|
||||
if e.Op.Has(fsnotify.Remove) {
|
||||
return 2
|
||||
}
|
||||
if e.Op.Has(fsnotify.Rename) {
|
||||
return 3
|
||||
}
|
||||
if e.Op.Has(fsnotify.Write) {
|
||||
return 4
|
||||
}
|
||||
return 5
|
||||
}
|
||||
|
||||
sort.Slice(events, func(i, j int) bool {
|
||||
// First sort by event type.
|
||||
if eventOrdinal(events[i]) != eventOrdinal(events[j]) {
|
||||
return eventOrdinal(events[i]) < eventOrdinal(events[j])
|
||||
}
|
||||
// Then sort by name.
|
||||
return events[i].Name < events[j].Name
|
||||
})
|
||||
|
||||
return events
|
||||
return events[:n]
|
||||
}
|
||||
|
||||
type fileEventInfo struct {
|
||||
@@ -522,17 +494,41 @@ func (h *HugoSites) fileEventsApplyInfo(events []fsnotify.Event) []fileEventInfo
|
||||
return infos
|
||||
}
|
||||
|
||||
func (h *HugoSites) fileEventsTrim(events []fsnotify.Event) []fsnotify.Event {
|
||||
seen := make(map[string]bool)
|
||||
func (h *HugoSites) fileEventsTranslate(events []fsnotify.Event) []fsnotify.Event {
|
||||
eventMap := make(map[string][]fsnotify.Event)
|
||||
|
||||
// We often get a Remove etc. followed by a Create, a Create followed by a Write.
|
||||
// Remove the superfluous events to make the update logic simpler.
|
||||
for _, ev := range events {
|
||||
eventMap[ev.Name] = append(eventMap[ev.Name], ev)
|
||||
}
|
||||
|
||||
n := 0
|
||||
for _, ev := range events {
|
||||
if seen[ev.Name] {
|
||||
continue
|
||||
mapped := eventMap[ev.Name]
|
||||
|
||||
// Keep one
|
||||
found := false
|
||||
var kept fsnotify.Event
|
||||
for i, ev2 := range mapped {
|
||||
if i == 0 {
|
||||
kept = ev2
|
||||
}
|
||||
|
||||
if ev2.Op&fsnotify.Write == fsnotify.Write {
|
||||
kept = ev2
|
||||
found = true
|
||||
}
|
||||
|
||||
if !found && ev2.Op&fsnotify.Create == fsnotify.Create {
|
||||
kept = ev2
|
||||
}
|
||||
}
|
||||
seen[ev.Name] = true
|
||||
events[n] = ev
|
||||
|
||||
events[n] = kept
|
||||
n++
|
||||
}
|
||||
|
||||
return events
|
||||
}
|
||||
|
||||
|
||||
+2
-3
@@ -1,8 +1,7 @@
|
||||
# Release env.
|
||||
# These will be replaced by script before release.
|
||||
HUGORELEASER_TAG=v0.125.0
|
||||
HUGORELEASER_COMMITISH=a32400b5f4e704daf7de19f44584baf77a4501ab
|
||||
|
||||
HUGORELEASER_TAG=v0.124.1
|
||||
HUGORELEASER_COMMITISH=db083b05f16c945fec04f745f0ca8640560cf1ec
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -135,11 +135,10 @@ func (b Bytes) Bytes() []byte {
|
||||
|
||||
// DocumentContext holds contextual information about the document to convert.
|
||||
type DocumentContext struct {
|
||||
Document any // May be nil. Usually a page.Page
|
||||
DocumentLookup func(uint64) any // May be nil.
|
||||
DocumentID string
|
||||
DocumentName string
|
||||
Filename string
|
||||
Document any // May be nil. Usually a page.Page
|
||||
DocumentID string
|
||||
DocumentName string
|
||||
Filename string
|
||||
}
|
||||
|
||||
// RenderContext holds contextual information about the content to render.
|
||||
|
||||
@@ -32,7 +32,8 @@ type AttributesProvider interface {
|
||||
|
||||
// LinkContext is the context passed to a link render hook.
|
||||
type LinkContext interface {
|
||||
PageProvider
|
||||
// The Page being rendered.
|
||||
Page() any
|
||||
|
||||
// The link URL.
|
||||
Destination() string
|
||||
@@ -63,7 +64,6 @@ type ImageLinkContext interface {
|
||||
type CodeblockContext interface {
|
||||
AttributesProvider
|
||||
text.Positioner
|
||||
PageProvider
|
||||
|
||||
// Chroma highlighting processing options. This will only be filled if Type is a known Chroma Lexer.
|
||||
Options() map[string]any
|
||||
@@ -76,6 +76,9 @@ type CodeblockContext interface {
|
||||
|
||||
// Zero-based ordinal for all code blocks in the current document.
|
||||
Ordinal() int
|
||||
|
||||
// The owning Page.
|
||||
Page() any
|
||||
}
|
||||
|
||||
type AttributesOptionsSliceProvider interface {
|
||||
@@ -98,7 +101,8 @@ type IsDefaultCodeBlockRendererProvider interface {
|
||||
// HeadingContext contains accessors to all attributes that a HeadingRenderer
|
||||
// can use to render a heading.
|
||||
type HeadingContext interface {
|
||||
PageProvider
|
||||
// Page is the page containing the heading.
|
||||
Page() any
|
||||
// Level is the level of the header (i.e. 1 for top-level, 2 for sub-level, etc.).
|
||||
Level() int
|
||||
// Anchor is the HTML id assigned to the heading.
|
||||
@@ -112,16 +116,6 @@ type HeadingContext interface {
|
||||
AttributesProvider
|
||||
}
|
||||
|
||||
type PageProvider interface {
|
||||
// Page is the page being rendered.
|
||||
Page() any
|
||||
|
||||
// PageInner may be different than Page when .RenderShortcodes is in play.
|
||||
// The main use case for this is to include other pages' markdown into the current page
|
||||
// but resolve resources and pages relative to the original.
|
||||
PageInner() any
|
||||
}
|
||||
|
||||
// HeadingRenderer describes a uniquely identifiable rendering hook.
|
||||
type HeadingRenderer interface {
|
||||
// RenderHeading writes the rendered content to w using the data in w.
|
||||
|
||||
@@ -108,7 +108,6 @@ func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.No
|
||||
}
|
||||
cbctx := &codeBlockContext{
|
||||
page: ctx.DocumentContext().Document,
|
||||
pageInner: r.getPageInner(ctx),
|
||||
lang: lang,
|
||||
code: s,
|
||||
ordinal: ordinal,
|
||||
@@ -133,6 +132,7 @@ func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.No
|
||||
w,
|
||||
cbctx,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return ast.WalkContinue, herrors.NewFileErrorFromPos(err, cbctx.createPos())
|
||||
}
|
||||
@@ -140,24 +140,11 @@ func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.No
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
func (r *htmlRenderer) getPageInner(rctx *render.Context) any {
|
||||
pid := rctx.PeekPid()
|
||||
if pid > 0 {
|
||||
if lookup := rctx.DocumentContext().DocumentLookup; lookup != nil {
|
||||
if v := rctx.DocumentContext().DocumentLookup(pid); v != nil {
|
||||
return v
|
||||
}
|
||||
}
|
||||
}
|
||||
return rctx.DocumentContext().Document
|
||||
}
|
||||
|
||||
type codeBlockContext struct {
|
||||
page any
|
||||
pageInner any
|
||||
lang string
|
||||
code string
|
||||
ordinal int
|
||||
page any
|
||||
lang string
|
||||
code string
|
||||
ordinal int
|
||||
|
||||
// This is only used in error situations and is expensive to create,
|
||||
// to delay creation until needed.
|
||||
@@ -172,10 +159,6 @@ func (c *codeBlockContext) Page() any {
|
||||
return c.page
|
||||
}
|
||||
|
||||
func (c *codeBlockContext) PageInner() any {
|
||||
return c.pageInner
|
||||
}
|
||||
|
||||
func (c *codeBlockContext) Type() string {
|
||||
return c.lang
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"bytes"
|
||||
|
||||
"github.com/gohugoio/hugo-goldmark-extensions/passthrough"
|
||||
"github.com/gohugoio/hugo/markup/goldmark/hugocontext"
|
||||
"github.com/yuin/goldmark/util"
|
||||
|
||||
"github.com/gohugoio/hugo/markup/goldmark/codeblocks"
|
||||
@@ -104,7 +103,6 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
|
||||
renderer.WithNodeRenderers(util.Prioritized(emoji.NewHTMLRenderer(), 200)))
|
||||
var (
|
||||
extensions = []goldmark.Extender{
|
||||
hugocontext.New(),
|
||||
newLinks(cfg),
|
||||
newTocExtension(tocRendererOptions),
|
||||
}
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package hugocontext
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/gohugoio/hugo/bufferpool"
|
||||
"github.com/gohugoio/hugo/markup/goldmark/internal/render"
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/ast"
|
||||
"github.com/yuin/goldmark/parser"
|
||||
"github.com/yuin/goldmark/renderer"
|
||||
"github.com/yuin/goldmark/text"
|
||||
"github.com/yuin/goldmark/util"
|
||||
)
|
||||
|
||||
func New() goldmark.Extender {
|
||||
return &hugoContextExtension{}
|
||||
}
|
||||
|
||||
// Wrap wraps the given byte slice in a Hugo context that used to determine the correct Page
|
||||
// in .RenderShortcodes.
|
||||
func Wrap(b []byte, pid uint64) []byte {
|
||||
buf := bufferpool.GetBuffer()
|
||||
defer bufferpool.PutBuffer(buf)
|
||||
buf.Write(prefix)
|
||||
buf.WriteString(" pid=")
|
||||
buf.WriteString(strconv.FormatUint(pid, 10))
|
||||
buf.Write(endDelim)
|
||||
buf.WriteByte('\n')
|
||||
buf.Write(b)
|
||||
buf.Write(prefix)
|
||||
buf.Write(closingDelimAndNewline)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
var kindHugoContext = ast.NewNodeKind("HugoContext")
|
||||
|
||||
// HugoContext is a node that represents a Hugo context.
|
||||
type HugoContext struct {
|
||||
ast.BaseInline
|
||||
|
||||
Closing bool
|
||||
|
||||
// Internal page ID. Not persisted.
|
||||
Pid uint64
|
||||
}
|
||||
|
||||
// Dump implements Node.Dump.
|
||||
func (n *HugoContext) Dump(source []byte, level int) {
|
||||
m := map[string]string{}
|
||||
m["Pid"] = fmt.Sprintf("%v", n.Pid)
|
||||
ast.DumpHelper(n, source, level, m, nil)
|
||||
}
|
||||
|
||||
func (n *HugoContext) parseAttrs(attrBytes []byte) {
|
||||
keyPairs := bytes.Split(attrBytes, []byte(" "))
|
||||
for _, keyPair := range keyPairs {
|
||||
kv := bytes.Split(keyPair, []byte("="))
|
||||
if len(kv) != 2 {
|
||||
continue
|
||||
}
|
||||
key := string(kv[0])
|
||||
val := string(kv[1])
|
||||
switch key {
|
||||
case "pid":
|
||||
pid, _ := strconv.ParseUint(val, 10, 64)
|
||||
n.Pid = pid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HugoContext) Kind() ast.NodeKind {
|
||||
return kindHugoContext
|
||||
}
|
||||
|
||||
var (
|
||||
prefix = []byte("{{__hugo_ctx")
|
||||
endDelim = []byte("}}")
|
||||
closingDelimAndNewline = []byte("/}}\n")
|
||||
)
|
||||
|
||||
var _ parser.InlineParser = (*hugoContextParser)(nil)
|
||||
|
||||
type hugoContextParser struct{}
|
||||
|
||||
func (s *hugoContextParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
|
||||
line, _ := block.PeekLine()
|
||||
if !bytes.HasPrefix(line, prefix) {
|
||||
return nil
|
||||
}
|
||||
end := bytes.Index(line, endDelim)
|
||||
if end == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
block.Advance(end + len(endDelim) + 1) // +1 for the newline
|
||||
|
||||
if line[end-1] == '/' {
|
||||
return &HugoContext{Closing: true}
|
||||
}
|
||||
|
||||
attrBytes := line[len(prefix)+1 : end]
|
||||
h := &HugoContext{}
|
||||
h.parseAttrs(attrBytes)
|
||||
return h
|
||||
}
|
||||
|
||||
func (a *hugoContextParser) Trigger() []byte {
|
||||
return []byte{'{'}
|
||||
}
|
||||
|
||||
type hugoContextRenderer struct{}
|
||||
|
||||
func (r *hugoContextRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||
reg.Register(kindHugoContext, r.handleHugoContext)
|
||||
}
|
||||
|
||||
func (r *hugoContextRenderer) handleHugoContext(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||
if !entering {
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
hctx := node.(*HugoContext)
|
||||
ctx, ok := w.(*render.Context)
|
||||
if !ok {
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
if hctx.Closing {
|
||||
_ = ctx.PopPid()
|
||||
} else {
|
||||
ctx.PushPid(hctx.Pid)
|
||||
}
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
type hugoContextExtension struct{}
|
||||
|
||||
func (a *hugoContextExtension) Extend(m goldmark.Markdown) {
|
||||
m.Parser().AddOptions(
|
||||
parser.WithInlineParsers(
|
||||
util.Prioritized(&hugoContextParser{}, 50),
|
||||
),
|
||||
)
|
||||
|
||||
m.Renderer().AddOptions(
|
||||
renderer.WithNodeRenderers(
|
||||
util.Prioritized(&hugoContextRenderer{}, 50),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package hugocontext
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestWrap(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
b := []byte("test")
|
||||
|
||||
c.Assert(string(Wrap(b, 42)), qt.Equals, "{{__hugo_ctx pid=42}}\ntest{{__hugo_ctx/}}\n")
|
||||
}
|
||||
|
||||
func BenchmarkWrap(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Wrap([]byte("test"), 42)
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,6 @@ func (b *BufWriter) Flush() error {
|
||||
type Context struct {
|
||||
*BufWriter
|
||||
positions []int
|
||||
pids []uint64
|
||||
ContextData
|
||||
}
|
||||
|
||||
@@ -56,30 +55,6 @@ func (ctx *Context) PopPos() int {
|
||||
return p
|
||||
}
|
||||
|
||||
// PushPid pushes a new page ID to the stack.
|
||||
func (ctx *Context) PushPid(pid uint64) {
|
||||
ctx.pids = append(ctx.pids, pid)
|
||||
}
|
||||
|
||||
// PeekPid returns the current page ID without removing it from the stack.
|
||||
func (ctx *Context) PeekPid() uint64 {
|
||||
if len(ctx.pids) == 0 {
|
||||
return 0
|
||||
}
|
||||
return ctx.pids[len(ctx.pids)-1]
|
||||
}
|
||||
|
||||
// PopPid pops the last page ID from the stack.
|
||||
func (ctx *Context) PopPid() uint64 {
|
||||
if len(ctx.pids) == 0 {
|
||||
return 0
|
||||
}
|
||||
i := len(ctx.pids) - 1
|
||||
p := ctx.pids[i]
|
||||
ctx.pids = ctx.pids[:i]
|
||||
return p
|
||||
}
|
||||
|
||||
type ContextData interface {
|
||||
RenderContext() converter.RenderContext
|
||||
DocumentContext() converter.DocumentContext
|
||||
|
||||
@@ -49,7 +49,6 @@ func newLinks(cfg goldmark_config.Config) goldmark.Extender {
|
||||
|
||||
type linkContext struct {
|
||||
page any
|
||||
pageInner any
|
||||
destination string
|
||||
title string
|
||||
text hstring.RenderedString
|
||||
@@ -65,10 +64,6 @@ func (ctx linkContext) Page() any {
|
||||
return ctx.page
|
||||
}
|
||||
|
||||
func (ctx linkContext) PageInner() any {
|
||||
return ctx.pageInner
|
||||
}
|
||||
|
||||
func (ctx linkContext) Text() hstring.RenderedString {
|
||||
return ctx.text
|
||||
}
|
||||
@@ -97,7 +92,6 @@ func (ctx imageLinkContext) Ordinal() int {
|
||||
|
||||
type headingContext struct {
|
||||
page any
|
||||
pageInner any
|
||||
level int
|
||||
anchor string
|
||||
text hstring.RenderedString
|
||||
@@ -109,10 +103,6 @@ func (ctx headingContext) Page() any {
|
||||
return ctx.page
|
||||
}
|
||||
|
||||
func (ctx headingContext) PageInner() any {
|
||||
return ctx.pageInner
|
||||
}
|
||||
|
||||
func (ctx headingContext) Level() int {
|
||||
return ctx.level
|
||||
}
|
||||
@@ -196,7 +186,6 @@ func (r *hookedRenderer) renderImage(w util.BufWriter, source []byte, node ast.N
|
||||
imageLinkContext{
|
||||
linkContext: linkContext{
|
||||
page: ctx.DocumentContext().Document,
|
||||
pageInner: r.getPageInner(ctx),
|
||||
destination: string(n.Destination),
|
||||
title: string(n.Title),
|
||||
text: hstring.RenderedString(text),
|
||||
@@ -211,18 +200,6 @@ func (r *hookedRenderer) renderImage(w util.BufWriter, source []byte, node ast.N
|
||||
return ast.WalkContinue, err
|
||||
}
|
||||
|
||||
func (r *hookedRenderer) getPageInner(rctx *render.Context) any {
|
||||
pid := rctx.PeekPid()
|
||||
if pid > 0 {
|
||||
if lookup := rctx.DocumentContext().DocumentLookup; lookup != nil {
|
||||
if v := rctx.DocumentContext().DocumentLookup(pid); v != nil {
|
||||
return v
|
||||
}
|
||||
}
|
||||
}
|
||||
return rctx.DocumentContext().Document
|
||||
}
|
||||
|
||||
func (r *hookedRenderer) filterInternalAttributes(attrs []ast.Attribute) []ast.Attribute {
|
||||
n := 0
|
||||
for _, x := range attrs {
|
||||
@@ -297,7 +274,6 @@ func (r *hookedRenderer) renderLink(w util.BufWriter, source []byte, node ast.No
|
||||
w,
|
||||
linkContext{
|
||||
page: ctx.DocumentContext().Document,
|
||||
pageInner: r.getPageInner(ctx),
|
||||
destination: string(n.Destination),
|
||||
title: string(n.Title),
|
||||
text: hstring.RenderedString(text),
|
||||
@@ -363,7 +339,6 @@ func (r *hookedRenderer) renderAutoLink(w util.BufWriter, source []byte, node as
|
||||
w,
|
||||
linkContext{
|
||||
page: ctx.DocumentContext().Document,
|
||||
pageInner: r.getPageInner(ctx),
|
||||
destination: url,
|
||||
text: hstring.RenderedString(label),
|
||||
plainText: label,
|
||||
@@ -448,7 +423,6 @@ func (r *hookedRenderer) renderHeading(w util.BufWriter, source []byte, node ast
|
||||
w,
|
||||
headingContext{
|
||||
page: ctx.DocumentContext().Document,
|
||||
pageInner: r.getPageInner(ctx),
|
||||
level: n.Level,
|
||||
anchor: string(anchor),
|
||||
text: hstring.RenderedString(text),
|
||||
|
||||
@@ -128,7 +128,7 @@ func (e *errorResource) Exif() *exif.ExifInfo {
|
||||
panic(e.ResourceError)
|
||||
}
|
||||
|
||||
func (e *errorResource) Colors() ([]images.Color, error) {
|
||||
func (e *errorResource) Colors() ([]string, error) {
|
||||
panic(e.ResourceError)
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -67,7 +67,7 @@ type imageResource struct {
|
||||
meta *imageMeta
|
||||
|
||||
dominantColorInit sync.Once
|
||||
dominantColors []images.Color
|
||||
dominantColors []string
|
||||
|
||||
baseResource
|
||||
}
|
||||
@@ -143,7 +143,7 @@ func (i *imageResource) getExif() *exif.ExifInfo {
|
||||
|
||||
// Colors returns a slice of the most dominant colors in an image
|
||||
// using a simple histogram method.
|
||||
func (i *imageResource) Colors() ([]images.Color, error) {
|
||||
func (i *imageResource) Colors() ([]string, error) {
|
||||
var err error
|
||||
i.dominantColorInit.Do(func() {
|
||||
var img image.Image
|
||||
@@ -153,7 +153,7 @@ func (i *imageResource) Colors() ([]images.Color, error) {
|
||||
}
|
||||
colors := color_extractor.ExtractColors(img)
|
||||
for _, c := range colors {
|
||||
i.dominantColors = append(i.dominantColors, images.ColorGoToColor(c))
|
||||
i.dominantColors = append(i.dominantColors, images.ColorToHexString(c))
|
||||
}
|
||||
})
|
||||
return i.dominantColors, nil
|
||||
|
||||
+2
-27
@@ -85,16 +85,9 @@ func TestImageTransformBasic(t *testing.T) {
|
||||
assertWidthHeight(c, img, w, h)
|
||||
}
|
||||
|
||||
gotColors, err := image.Colors()
|
||||
colors, err := image.Colors()
|
||||
c.Assert(err, qt.IsNil)
|
||||
expectedColors := images.HexStringsToColors("#2d2f33", "#a49e93", "#d39e59", "#a76936", "#737a84", "#7c838b")
|
||||
c.Assert(len(gotColors), qt.Equals, len(expectedColors))
|
||||
for i := range gotColors {
|
||||
c1, c2 := gotColors[i], expectedColors[i]
|
||||
c.Assert(c1.ColorHex(), qt.Equals, c2.ColorHex())
|
||||
c.Assert(c1.ColorGo(), qt.DeepEquals, c2.ColorGo())
|
||||
c.Assert(c1.Luminance(), qt.Equals, c2.Luminance())
|
||||
}
|
||||
c.Assert(colors, qt.DeepEquals, []string{"#2d2f33", "#a49e93", "#d39e59", "#a76936", "#737a84", "#7c838b"})
|
||||
|
||||
c.Assert(image.RelPermalink(), qt.Equals, "/a/sunset.jpg")
|
||||
c.Assert(image.ResourceType(), qt.Equals, "image")
|
||||
@@ -452,24 +445,6 @@ func TestImageExif(t *testing.T) {
|
||||
getAndCheckExif(c, image)
|
||||
}
|
||||
|
||||
func TestImageColorsLuminance(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
_, image := fetchSunset(c)
|
||||
c.Assert(image, qt.Not(qt.IsNil))
|
||||
colors, err := image.Colors()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(len(colors), qt.Equals, 6)
|
||||
var prevLuminance float64
|
||||
for i, color := range colors {
|
||||
luminance := color.Luminance()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(luminance > 0, qt.IsTrue)
|
||||
c.Assert(luminance, qt.Not(qt.Equals), prevLuminance, qt.Commentf("i=%d", i))
|
||||
prevLuminance = luminance
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkImageExif(b *testing.B) {
|
||||
getImages := func(c *qt.C, b *testing.B, fs afero.Fs) []images.ImageResource {
|
||||
spec := newTestResourceSpec(specDescriptor{fs: fs, c: c})
|
||||
|
||||
+4
-116
@@ -16,76 +16,10 @@ package images
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"image/color"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hstrings"
|
||||
)
|
||||
|
||||
type colorGoProvider interface {
|
||||
ColorGo() color.Color
|
||||
}
|
||||
|
||||
type Color struct {
|
||||
// The color.
|
||||
color color.Color
|
||||
|
||||
// The color prefixed with a #.
|
||||
hex string
|
||||
|
||||
// The relative luminance of the color.
|
||||
luminance float64
|
||||
}
|
||||
|
||||
// Luminance as defined by w3.org.
|
||||
// See https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
|
||||
func (c Color) Luminance() float64 {
|
||||
return c.luminance
|
||||
}
|
||||
|
||||
// ColorGo returns the color as a color.Color.
|
||||
// For internal use only.
|
||||
func (c Color) ColorGo() color.Color {
|
||||
return c.color
|
||||
}
|
||||
|
||||
// ColorHex returns the color as a hex string prefixed with a #.
|
||||
func (c Color) ColorHex() string {
|
||||
return c.hex
|
||||
}
|
||||
|
||||
// String returns the color as a hex string prefixed with a #.
|
||||
func (c Color) String() string {
|
||||
return c.hex
|
||||
}
|
||||
|
||||
// For hashstructure. This struct is used in template func options
|
||||
// that needs to be able to hash a Color.
|
||||
// For internal use only.
|
||||
func (c Color) Hash() (uint64, error) {
|
||||
h := fnv.New64a()
|
||||
h.Write([]byte(c.hex))
|
||||
return h.Sum64(), nil
|
||||
}
|
||||
|
||||
func (c *Color) init() error {
|
||||
c.hex = ColorGoToHexString(c.color)
|
||||
r, g, b, _ := c.color.RGBA()
|
||||
c.luminance = 0.2126*c.toSRGB(uint8(r)) + 0.7152*c.toSRGB(uint8(g)) + 0.0722*c.toSRGB(uint8(b))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Color) toSRGB(i uint8) float64 {
|
||||
v := float64(i) / 255
|
||||
if v <= 0.04045 {
|
||||
return v / 12.92
|
||||
} else {
|
||||
return math.Pow((v+0.055)/1.055, 2.4)
|
||||
}
|
||||
}
|
||||
|
||||
// AddColorToPalette adds c as the first color in p if not already there.
|
||||
// Note that it does no additional checks, so callers must make sure
|
||||
// that the palette is valid for the relevant format.
|
||||
@@ -111,60 +45,14 @@ func ReplaceColorInPalette(c color.Color, p color.Palette) {
|
||||
p[p.Index(c)] = c
|
||||
}
|
||||
|
||||
// ColorGoToHexString converts a color.Color to a hex string.
|
||||
func ColorGoToHexString(c color.Color) string {
|
||||
// ColorToHexString converts a color to a hex string.
|
||||
func ColorToHexString(c color.Color) string {
|
||||
r, g, b, a := c.RGBA()
|
||||
rgba := color.RGBA{uint8(r), uint8(g), uint8(b), uint8(a)}
|
||||
if rgba.A == 0xff {
|
||||
return fmt.Sprintf("#%.2x%.2x%.2x", rgba.R, rgba.G, rgba.B)
|
||||
}
|
||||
return fmt.Sprintf("#%.2x%.2x%.2x%.2x", rgba.R, rgba.G, rgba.B, rgba.A)
|
||||
return fmt.Sprintf("#%.2x%.2x%.2x", rgba.R, rgba.G, rgba.B)
|
||||
}
|
||||
|
||||
// ColorGoToColor converts a color.Color to a Color.
|
||||
func ColorGoToColor(c color.Color) Color {
|
||||
cc := Color{color: c}
|
||||
if err := cc.init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return cc
|
||||
}
|
||||
|
||||
func hexStringToColor(s string) Color {
|
||||
c, err := hexStringToColorGo(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return ColorGoToColor(c)
|
||||
}
|
||||
|
||||
// HexStringsToColors converts a slice of hex strings to a slice of Colors.
|
||||
func HexStringsToColors(s ...string) []Color {
|
||||
var colors []Color
|
||||
for _, v := range s {
|
||||
colors = append(colors, hexStringToColor(v))
|
||||
}
|
||||
return colors
|
||||
}
|
||||
|
||||
func toColorGo(v any) (color.Color, bool, error) {
|
||||
switch vv := v.(type) {
|
||||
case colorGoProvider:
|
||||
return vv.ColorGo(), true, nil
|
||||
default:
|
||||
s, ok := hstrings.ToString(v)
|
||||
if !ok {
|
||||
return nil, false, nil
|
||||
}
|
||||
c, err := hexStringToColorGo(s)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return c, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
func hexStringToColorGo(s string) (color.Color, error) {
|
||||
func hexStringToColor(s string) (color.Color, error) {
|
||||
s = strings.TrimPrefix(s, "#")
|
||||
|
||||
if len(s) != 3 && len(s) != 4 && len(s) != 6 && len(s) != 8 {
|
||||
|
||||
@@ -46,7 +46,7 @@ func TestHexStringToColor(t *testing.T) {
|
||||
c.Run(test.arg, func(c *qt.C) {
|
||||
c.Parallel()
|
||||
|
||||
result, err := hexStringToColorGo(test.arg)
|
||||
result, err := hexStringToColor(test.arg)
|
||||
|
||||
if b, ok := test.expect.(bool); ok && !b {
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
@@ -70,18 +70,13 @@ func TestColorToHexString(t *testing.T) {
|
||||
{color.White, "#ffffff"},
|
||||
{color.Black, "#000000"},
|
||||
{color.RGBA{R: 0x42, G: 0x87, B: 0xf5, A: 0xff}, "#4287f5"},
|
||||
|
||||
// 50% opacity.
|
||||
// Note that the .Colors (dominant colors) received from the Image resource
|
||||
// will always have an alpha value of 0xff.
|
||||
{color.RGBA{R: 0x42, G: 0x87, B: 0xf5, A: 0x80}, "#4287f580"},
|
||||
} {
|
||||
|
||||
test := test
|
||||
c.Run(test.expect, func(c *qt.C) {
|
||||
c.Parallel()
|
||||
|
||||
result := ColorGoToHexString(test.arg)
|
||||
result := ColorToHexString(test.arg)
|
||||
|
||||
c.Assert(result, qt.Equals, test.expect)
|
||||
})
|
||||
@@ -96,9 +91,9 @@ func TestAddColorToPalette(t *testing.T) {
|
||||
|
||||
c.Assert(AddColorToPalette(color.White, palette), qt.HasLen, 2)
|
||||
|
||||
blue1, _ := hexStringToColorGo("34c3eb")
|
||||
blue2, _ := hexStringToColorGo("34c3eb")
|
||||
white, _ := hexStringToColorGo("fff")
|
||||
blue1, _ := hexStringToColor("34c3eb")
|
||||
blue2, _ := hexStringToColor("34c3eb")
|
||||
white, _ := hexStringToColor("fff")
|
||||
|
||||
c.Assert(AddColorToPalette(white, palette), qt.HasLen, 2)
|
||||
c.Assert(AddColorToPalette(blue1, palette), qt.HasLen, 3)
|
||||
@@ -109,18 +104,10 @@ func TestReplaceColorInPalette(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
palette := color.Palette{color.White, color.Black}
|
||||
offWhite, _ := hexStringToColorGo("fcfcfc")
|
||||
offWhite, _ := hexStringToColor("fcfcfc")
|
||||
|
||||
ReplaceColorInPalette(offWhite, palette)
|
||||
|
||||
c.Assert(palette, qt.HasLen, 2)
|
||||
c.Assert(palette[0], qt.Equals, offWhite)
|
||||
}
|
||||
|
||||
func TestColorLuminance(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
c.Assert(hexStringToColor("#000000").Luminance(), qt.Equals, 0.0)
|
||||
c.Assert(hexStringToColor("#768a9a").Luminance(), qt.Equals, 0.24361603589088263)
|
||||
c.Assert(hexStringToColor("#d5bc9f").Luminance(), qt.Equals, 0.5261577672685374)
|
||||
c.Assert(hexStringToColor("#ffffff").Luminance(), qt.Equals, 1.0)
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ func DecodeConfig(in map[string]any) (*config.ConfigNamespace[ImagingConfig, Ima
|
||||
return i, nil, err
|
||||
}
|
||||
|
||||
i.BgColor, err = hexStringToColorGo(i.Imaging.BgColor)
|
||||
i.BgColor, err = hexStringToColor(i.Imaging.BgColor)
|
||||
if err != nil {
|
||||
return i, nil, err
|
||||
}
|
||||
@@ -230,7 +230,7 @@ func DecodeImageConfig(action string, options []string, defaults *config.ConfigN
|
||||
c.Hint = hint
|
||||
} else if part[0] == '#' {
|
||||
c.BgColorStr = part[1:]
|
||||
c.BgColor, err = hexStringToColorGo(c.BgColorStr)
|
||||
c.BgColor, err = hexStringToColor(c.BgColorStr)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
@@ -424,7 +424,7 @@ type ImagingConfigInternal struct {
|
||||
|
||||
func (i *ImagingConfigInternal) Compile(externalCfg *ImagingConfig) error {
|
||||
var err error
|
||||
i.BgColor, err = hexStringToColorGo(externalCfg.BgColor)
|
||||
i.BgColor, err = hexStringToColor(externalCfg.BgColor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ func newImageConfig(action string, width, height, quality, rotate int, filter, a
|
||||
c.qualitySetForImage = quality != 75
|
||||
c.Rotate = rotate
|
||||
c.BgColorStr = bgColor
|
||||
c.BgColor, _ = hexStringToColorGo(bgColor)
|
||||
c.BgColor, _ = hexStringToColor(bgColor)
|
||||
|
||||
if filter != "" {
|
||||
filter = strings.ToLower(filter)
|
||||
|
||||
+10
-14
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2019 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -65,7 +65,7 @@ func (*Filters) Opacity(opacity any) gift.Filter {
|
||||
func (*Filters) Text(text string, options ...any) gift.Filter {
|
||||
tf := textFilter{
|
||||
text: text,
|
||||
color: color.White,
|
||||
color: "#ffffff",
|
||||
size: 20,
|
||||
x: 10,
|
||||
y: 10,
|
||||
@@ -78,9 +78,7 @@ func (*Filters) Text(text string, options ...any) gift.Filter {
|
||||
for option, v := range opt {
|
||||
switch option {
|
||||
case "color":
|
||||
if color, ok, _ := toColorGo(v); ok {
|
||||
tf.color = color
|
||||
}
|
||||
tf.color = cast.ToString(v)
|
||||
case "size":
|
||||
tf.size = cast.ToFloat64(v)
|
||||
case "x":
|
||||
@@ -130,14 +128,15 @@ func (*Filters) Padding(args ...any) gift.Filter {
|
||||
|
||||
var top, right, bottom, left int
|
||||
var ccolor color.Color = color.White // canvas color
|
||||
var err error
|
||||
|
||||
_args := args // preserve original args for most stable hash
|
||||
|
||||
if vcs, ok, err := toColorGo(args[len(args)-1]); ok || err != nil {
|
||||
if vcs, ok := (args[len(args)-1]).(string); ok {
|
||||
ccolor, err = hexStringToColor(vcs)
|
||||
if err != nil {
|
||||
panic("invalid canvas color: specify RGB or RGBA using hex notation")
|
||||
}
|
||||
ccolor = vcs
|
||||
args = args[:len(args)-1]
|
||||
if len(args) == 0 {
|
||||
panic("not enough arguments: provide one or more padding values using the CSS shorthand property syntax")
|
||||
@@ -181,11 +180,12 @@ func (*Filters) Padding(args ...any) gift.Filter {
|
||||
// Dither creates a filter that dithers an image.
|
||||
func (*Filters) Dither(options ...any) gift.Filter {
|
||||
ditherOptions := struct {
|
||||
Colors []any
|
||||
Colors []string
|
||||
Method string
|
||||
Serpentine bool
|
||||
Strength float32
|
||||
}{
|
||||
Colors: []string{"000000ff", "ffffffff"},
|
||||
Method: "floydsteinberg",
|
||||
Serpentine: true,
|
||||
Strength: 1.0,
|
||||
@@ -198,18 +198,14 @@ func (*Filters) Dither(options ...any) gift.Filter {
|
||||
}
|
||||
}
|
||||
|
||||
if len(ditherOptions.Colors) == 0 {
|
||||
ditherOptions.Colors = []any{"000000ff", "ffffffff"}
|
||||
}
|
||||
|
||||
if len(ditherOptions.Colors) < 2 {
|
||||
panic("palette must have at least two colors")
|
||||
}
|
||||
|
||||
var palette []color.Color
|
||||
for _, c := range ditherOptions.Colors {
|
||||
cc, ok, err := toColorGo(c)
|
||||
if !ok || err != nil {
|
||||
cc, err := hexStringToColor(c)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("%q is an invalid color: specify RGB or RGBA using hexadecimal notation", c))
|
||||
}
|
||||
palette = append(palette, cc)
|
||||
|
||||
@@ -63,7 +63,7 @@ type ImageResourceOps interface {
|
||||
|
||||
// Colors returns a slice of the most dominant colors in an image
|
||||
// using a simple histogram method.
|
||||
Colors() ([]Color, error)
|
||||
Colors() ([]string, error)
|
||||
|
||||
// For internal use.
|
||||
DecodeImage() (image.Image, error)
|
||||
|
||||
@@ -15,7 +15,6 @@ package images
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"io"
|
||||
"strings"
|
||||
@@ -32,8 +31,7 @@ import (
|
||||
var _ gift.Filter = (*textFilter)(nil)
|
||||
|
||||
type textFilter struct {
|
||||
text string
|
||||
color color.Color
|
||||
text, color string
|
||||
x, y int
|
||||
size float64
|
||||
linespacing int
|
||||
@@ -41,6 +39,11 @@ type textFilter struct {
|
||||
}
|
||||
|
||||
func (f textFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) {
|
||||
color, err := hexStringToColor(f.color)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Load and parse font
|
||||
ttf := goregular.TTF
|
||||
if f.fontSource != nil {
|
||||
@@ -71,7 +74,7 @@ func (f textFilter) Draw(dst draw.Image, src image.Image, options *gift.Options)
|
||||
|
||||
d := font.Drawer{
|
||||
Dst: dst,
|
||||
Src: image.NewUniform(f.color),
|
||||
Src: image.NewUniform(color),
|
||||
Face: face,
|
||||
}
|
||||
|
||||
|
||||
@@ -88,9 +88,6 @@ type PageConfig struct {
|
||||
|
||||
// User defined params.
|
||||
Params maps.Params
|
||||
|
||||
// Compiled values.
|
||||
IsGoldmark bool `json:"-"`
|
||||
}
|
||||
|
||||
// FrontMatterHandler maps front matter into Page fields and .Params.
|
||||
|
||||
@@ -263,7 +263,7 @@ func (r *resourceAdapter) Exif() *exif.ExifInfo {
|
||||
return r.getImageOps().Exif()
|
||||
}
|
||||
|
||||
func (r *resourceAdapter) Colors() ([]images.Color, error) {
|
||||
func (r *resourceAdapter) Colors() ([]string, error) {
|
||||
return r.getImageOps().Colors()
|
||||
}
|
||||
|
||||
|
||||
@@ -165,12 +165,10 @@ var Context = struct {
|
||||
SetDependencyManagerInCurrentScope func(context.Context, identity.Manager) context.Context
|
||||
DependencyScope hcontext.ContextDispatcher[int]
|
||||
Page hcontext.ContextDispatcher[page]
|
||||
IsInGoldmark hcontext.ContextDispatcher[bool]
|
||||
}{
|
||||
DependencyManagerScopedProvider: hcontext.NewContextDispatcher[identity.DependencyManagerScopedProvider](contextKey("DependencyManagerScopedProvider")),
|
||||
DependencyScope: hcontext.NewContextDispatcher[int](contextKey("DependencyScope")),
|
||||
Page: hcontext.NewContextDispatcher[page](contextKey("Page")),
|
||||
IsInGoldmark: hcontext.NewContextDispatcher[bool](contextKey("IsInGoldmark")),
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{{- $u := urls.Parse .Destination -}}
|
||||
{{- $src := $u.String -}}
|
||||
{{- if not $u.IsAbs -}}
|
||||
{{- with or (.PageInner.Resources.Get $u.Path) (resources.Get $u.Path) -}}
|
||||
{{- with or (.Page.Resources.Get $u.Path) (resources.Get $u.Path) -}}
|
||||
{{- $src = .RelPermalink -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{{- $u := urls.Parse .Destination -}}
|
||||
{{- $href := $u.String -}}
|
||||
{{- if strings.HasPrefix $u.String "#" }}
|
||||
{{- $href = printf "%s#%s" .PageInner.RelPermalink $u.Fragment }}
|
||||
{{- $href = printf "%s#%s" .Page.RelPermalink $u.Fragment }}
|
||||
{{- else if not $u.IsAbs -}}
|
||||
{{- with or
|
||||
($.PageInner.GetPage $u.Path)
|
||||
($.PageInner.Resources.Get $u.Path)
|
||||
($.Page.GetPage $u.Path)
|
||||
($.Page.Resources.Get $u.Path)
|
||||
(resources.Get $u.Path)
|
||||
-}}
|
||||
{{- $href = .RelPermalink -}}
|
||||
|
||||
@@ -22,7 +22,6 @@ Renders an embedded YouTube video.
|
||||
*/}}
|
||||
|
||||
{{- $pc := .Page.Site.Config.Privacy.YouTube }}
|
||||
{{- $remoteErrID := "err-youtube-remote" }}
|
||||
{{- if not $pc.Disable }}
|
||||
{{- with $id := or (.Get "id") (.Get 0) }}
|
||||
|
||||
@@ -32,12 +31,12 @@ Renders an embedded YouTube video.
|
||||
{{- $data := dict }}
|
||||
{{- with resources.GetRemote $url }}
|
||||
{{- with .Err }}
|
||||
{{- erroridf $remoteErrID "The %q shortcode was unable to get remote resource %q. %s. See %s" $.Name $url . $.Position }}
|
||||
{{- errorf "The %q shortcode was unable to get remote resource %q. %s. See %s" $.Name $url . $.Position }}
|
||||
{{- else }}
|
||||
{{- $data = .Content | transform.Unmarshal }}
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
{{- erroridf $remoteErrID "The %q shortcode was unable to get remote resource %q. See %s" $.Name $url $.Position }}
|
||||
{{- errorf "The %q shortcode was unable to get remote resource %q. See %s" $.Name $url $.Position }}
|
||||
{{- end }}
|
||||
|
||||
{{/* Set defaults. */}}
|
||||
|
||||
@@ -167,7 +167,7 @@ func (ns *Namespace) Markdownify(ctx context.Context, s any) (template.HTML, err
|
||||
}
|
||||
|
||||
// Strip if this is a short inline type of text.
|
||||
bb := ns.deps.ContentSpec.TrimShortHTML([]byte(ss), "markdown")
|
||||
bb := ns.deps.ContentSpec.TrimShortHTML([]byte(ss))
|
||||
|
||||
return helpers.BytesToHTML(bb), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user