Skip to content

Commit

Permalink
fix(gnoweb): titles missing id and toc anchors (#3538)
Browse files Browse the repository at this point in the history
Co-authored-by: Guilhem Fanton <[email protected]>
  • Loading branch information
alexiscolin and gfanton authored Jan 21, 2025
1 parent c74fb57 commit 1d72733
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 135 deletions.
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 @@ import (
"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 @@ func NewDefaultAppConfig() *AppConfig {
}
}

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 @@ func NewRouter(logger *slog.Logger, cfg *AppConfig) (http.Handler, error) {
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(),
))
}

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 @@ func NewRouter(logger *slog.Logger, cfg *AppConfig) (http.Handler, error) {
// 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 @@ import (
"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 @@ func (s *HTMLWebClient) SourceFile(w io.Writer, path, fileName string) (*FileMet
}

// 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 @@ func (s *HTMLWebClient) RenderRealm(w io.Writer, pkgPath string, args string) (*
}

// 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 @@ func (s *HTMLWebClient) query(qpath string, data []byte) ([]byte, error) {

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")
default:
lexer = lexers.Get("txt") // Unsupported file type, default to plain text.
}

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

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

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

return nil
}

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

0 comments on commit 1d72733

Please sign in to comment.