Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(gnoweb): titles missing id and toc anchors #3538

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 9 additions & 49 deletions gno.land/pkg/gnoweb/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,9 @@
"path"
"strings"

markdown "github.com/yuin/goldmark-highlighting/v2"

"github.com/alecthomas/chroma/v2"
chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
"github.com/alecthomas/chroma/v2/styles"
"github.com/gnolang/gno/gno.land/pkg/gnoweb/components"
"github.com/gnolang/gno/tm2/pkg/bft/rpc/client"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/extension"
mdhtml "github.com/yuin/goldmark/renderer/html"
)

Expand Down Expand Up @@ -55,16 +49,6 @@
}
}

var chromaDefaultStyle = mustGetStyle("friendly")

func mustGetStyle(name string) *chroma.Style {
s := styles.Get(name)
if s == nil {
panic("unable to get chroma style")
}
return s
}

// NewRouter initializes the gnoweb router with the specified logger and configuration.
func NewRouter(logger *slog.Logger, cfg *AppConfig) (http.Handler, error) {
// Initialize RPC Client
Expand All @@ -73,42 +57,18 @@
return nil, fmt.Errorf("unable to create HTTP client: %w", err)
}

// Configure Chroma highlighter
chromaOptions := []chromahtml.Option{
chromahtml.WithLineNumbers(true),
chromahtml.WithLinkableLineNumbers(true, "L"),
chromahtml.WithClasses(true),
chromahtml.ClassPrefix("chroma-"),
}
chroma := chromahtml.New(chromaOptions...)

// Configure Goldmark markdown parser
mdopts := []goldmark.Option{
goldmark.WithExtensions(
markdown.NewHighlighting(
markdown.WithFormatOptions(chromaOptions...),
),
extension.Table,
),
}
// Setup web client HTML
webcfg := NewDefaultHTMLWebClientConfig(client)
webcfg.Domain = cfg.Domain
if cfg.UnsafeHTML {
mdopts = append(mdopts, goldmark.WithRendererOptions(mdhtml.WithXHTML(), mdhtml.WithUnsafe()))
}
md := goldmark.New(mdopts...)

// Configure WebClient
webcfg := HTMLWebClientConfig{
Markdown: md,
Highlighter: NewChromaSourceHighlighter(chroma, chromaDefaultStyle),
Domain: cfg.Domain,
UnsafeHTML: cfg.UnsafeHTML,
RPCClient: client,
webcfg.GoldmarkOptions = append(webcfg.GoldmarkOptions, goldmark.WithRendererOptions(
mdhtml.WithXHTML(), mdhtml.WithUnsafe(),
))

Check warning on line 66 in gno.land/pkg/gnoweb/app.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/gnoweb/app.go#L64-L66

Added lines #L64 - L66 were not covered by tests
}

webcli := NewHTMLClient(logger, &webcfg)
chromaStylePath := path.Join(cfg.AssetsPath, "_chroma", "style.css")
webcli := NewHTMLClient(logger, webcfg)

// Setup StaticMetadata
chromaStylePath := path.Join(cfg.AssetsPath, "_chroma", "style.css")
staticMeta := StaticMetadata{
Domain: cfg.Domain,
AssetsPath: cfg.AssetsPath,
Expand Down Expand Up @@ -146,7 +106,7 @@
// XXX: probably move this elsewhere
mux.Handle(chromaStylePath, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/css")
if err := chroma.WriteCSS(w, chromaDefaultStyle); err != nil {
if err := webcli.WriteFormatterCSS(w); err != nil {
logger.Error("unable to write CSS", "err", err)
http.NotFound(w, r)
}
Expand Down
2 changes: 2 additions & 0 deletions gno.land/pkg/gnoweb/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ func TestRoutes(t *testing.T) {
{"/public/js/index.js", ok, ""},
{"/public/_chroma/style.css", ok, ""},
{"/public/imgs/gnoland.svg", ok, ""},
// Test Toc
{"/", ok, `href="#learn-about-gnoland"`},
}

rootdir := gnoenv.RootDir()
Expand Down
69 changes: 0 additions & 69 deletions gno.land/pkg/gnoweb/format.go

This file was deleted.

100 changes: 83 additions & 17 deletions gno.land/pkg/gnoweb/webclient_html.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,52 +8,83 @@
"path/filepath"
"strings"

"github.com/alecthomas/chroma/v2"
chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
"github.com/alecthomas/chroma/v2/lexers"
"github.com/alecthomas/chroma/v2/styles"
md "github.com/gnolang/gno/gno.land/pkg/gnoweb/markdown"
"github.com/gnolang/gno/gno.land/pkg/sdk/vm" // for error types
"github.com/gnolang/gno/tm2/pkg/amino"
"github.com/gnolang/gno/tm2/pkg/bft/rpc/client"
"github.com/yuin/goldmark"
markdown "github.com/yuin/goldmark-highlighting/v2"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/text"
)

var chromaDefaultStyle = styles.Get("friendly")

type HTMLWebClientConfig struct {
Domain string
UnsafeHTML bool
RPCClient *client.RPCClient
Highlighter FormatSource
Markdown goldmark.Markdown
Domain string
RPCClient *client.RPCClient
ChromaStyle *chroma.Style
ChromaHTMLOptions []chromahtml.Option
GoldmarkOptions []goldmark.Option
}

// NewDefaultHTMLWebClientConfig initializes a WebClientConfig with default settings.
// It sets up goldmark Markdown parsing options and default domain and highlighter.
func NewDefaultHTMLWebClientConfig(client *client.RPCClient) *HTMLWebClientConfig {
mdopts := []goldmark.Option{goldmark.WithParserOptions(parser.WithAutoHeadingID())}
chromaOptions := []chromahtml.Option{
chromahtml.WithLineNumbers(true),
chromahtml.WithLinkableLineNumbers(true, "L"),
chromahtml.WithClasses(true),
chromahtml.ClassPrefix("chroma-"),
}

goldmarkOptions := []goldmark.Option{
goldmark.WithParserOptions(parser.WithAutoHeadingID()),
goldmark.WithExtensions(
markdown.NewHighlighting(
markdown.WithFormatOptions(chromaOptions...),
),
extension.Table,
),
}

return &HTMLWebClientConfig{
Domain: "gno.land",
Highlighter: &noopFormat{},
Markdown: goldmark.New(mdopts...),
RPCClient: client,
Domain: "gno.land",
GoldmarkOptions: goldmarkOptions,
ChromaHTMLOptions: chromaOptions,
ChromaStyle: chromaDefaultStyle,
RPCClient: client,
}
}

type HTMLWebClient struct {
Markdown goldmark.Markdown
Formatter *chromahtml.Formatter

domain string
logger *slog.Logger
client *client.RPCClient
md goldmark.Markdown
highlighter FormatSource
chromaStyle *chroma.Style
}

// NewHTMLClient creates a new instance of WebClient.
// It requires a configured logger and WebClientConfig.
func NewHTMLClient(log *slog.Logger, cfg *HTMLWebClientConfig) *HTMLWebClient {
return &HTMLWebClient{
// XXX: Possibly consider exporting this in a single interface logic.
// For now it's easier to manager all this in one place
Markdown: goldmark.New(cfg.GoldmarkOptions...),
Formatter: chromahtml.New(cfg.ChromaHTMLOptions...),

logger: log,
domain: cfg.Domain,
client: cfg.RPCClient,
md: cfg.Markdown,
highlighter: cfg.Highlighter,
chromaStyle: cfg.ChromaStyle,
}
}

Expand Down Expand Up @@ -108,7 +139,7 @@
}

// Use Chroma for syntax highlighting
if err := s.highlighter.Format(w, fileName, source); err != nil {
if err := s.FormatSource(w, fileName, source); err != nil {
return nil, err
}

Expand Down Expand Up @@ -152,8 +183,8 @@
}

// Use Goldmark for Markdown parsing
doc := s.md.Parser().Parse(text.NewReader(rawres))
if err := s.md.Renderer().Render(w, rawres, doc); err != nil {
doc := s.Markdown.Parser().Parse(text.NewReader(rawres))
if err := s.Markdown.Renderer().Render(w, rawres, doc); err != nil {
return nil, fmt.Errorf("unable to render realm %q: %w", data, err)
}

Expand Down Expand Up @@ -188,3 +219,38 @@

return qres.Response.Data, nil
}

func (s *HTMLWebClient) FormatSource(w io.Writer, fileName string, src []byte) error {
var lexer chroma.Lexer

// Determine the lexer to be used based on the file extension.
switch strings.ToLower(filepath.Ext(fileName)) {
case ".gno":
lexer = lexers.Get("go")
case ".md":
lexer = lexers.Get("markdown")
case ".mod":
lexer = lexers.Get("gomod")

Check warning on line 233 in gno.land/pkg/gnoweb/webclient_html.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/gnoweb/webclient_html.go#L230-L233

Added lines #L230 - L233 were not covered by tests
default:
lexer = lexers.Get("txt") // Unsupported file type, default to plain text.
}

if lexer == nil {
return fmt.Errorf("unsupported lexer for file %q", fileName)
}

Check warning on line 240 in gno.land/pkg/gnoweb/webclient_html.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/gnoweb/webclient_html.go#L239-L240

Added lines #L239 - L240 were not covered by tests

iterator, err := lexer.Tokenise(nil, string(src))
if err != nil {
return fmt.Errorf("unable to tokenise %q: %w", fileName, err)
}

Check warning on line 245 in gno.land/pkg/gnoweb/webclient_html.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/gnoweb/webclient_html.go#L244-L245

Added lines #L244 - L245 were not covered by tests

if err := s.Formatter.Format(w, s.chromaStyle, iterator); err != nil {
return fmt.Errorf("unable to format source file %q: %w", fileName, err)
}

Check warning on line 249 in gno.land/pkg/gnoweb/webclient_html.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/gnoweb/webclient_html.go#L248-L249

Added lines #L248 - L249 were not covered by tests

return nil
}

func (s *HTMLWebClient) WriteFormatterCSS(w io.Writer) error {
return s.Formatter.WriteCSS(w, s.chromaStyle)
}
Loading