mirror of
https://github.com/thomiceli/opengist.git
synced 2024-12-22 20:42:40 +00:00
Add SVG parser (#346)
This commit is contained in:
parent
3068588111
commit
df226cbd99
11 changed files with 267 additions and 76 deletions
2
go.mod
2
go.mod
|
@ -97,7 +97,7 @@ require (
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||||
go.etcd.io/bbolt v1.3.10 // indirect
|
go.etcd.io/bbolt v1.3.10 // indirect
|
||||||
golang.org/x/net v0.25.0 // indirect
|
golang.org/x/net v0.26.0 // indirect
|
||||||
golang.org/x/oauth2 v0.20.0 // indirect
|
golang.org/x/oauth2 v0.20.0 // indirect
|
||||||
golang.org/x/sync v0.8.0 // indirect
|
golang.org/x/sync v0.8.0 // indirect
|
||||||
golang.org/x/sys v0.23.0 // indirect
|
golang.org/x/sys v0.23.0 // indirect
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -232,8 +232,8 @@ golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||||
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
|
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
|
||||||
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
|
|
|
@ -3,6 +3,7 @@ package render
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/alecthomas/chroma/v2"
|
"github.com/alecthomas/chroma/v2"
|
||||||
"github.com/alecthomas/chroma/v2/formatters/html"
|
"github.com/alecthomas/chroma/v2/formatters/html"
|
||||||
|
@ -11,6 +12,7 @@ import (
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/thomiceli/opengist/internal/db"
|
"github.com/thomiceli/opengist/internal/db"
|
||||||
"github.com/thomiceli/opengist/internal/git"
|
"github.com/thomiceli/opengist/internal/git"
|
||||||
|
"path"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -28,18 +30,22 @@ type RenderedGist struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func HighlightFile(file *git.File) (RenderedFile, error) {
|
func HighlightFile(file *git.File) (RenderedFile, error) {
|
||||||
rendered := RenderedFile{
|
|
||||||
File: file,
|
|
||||||
}
|
|
||||||
|
|
||||||
style := newStyle()
|
style := newStyle()
|
||||||
lexer := newLexer(file.Filename)
|
lexer := newLexer(file.Filename)
|
||||||
|
|
||||||
if lexer.Config().Name == "markdown" {
|
if lexer.Config().Name == "markdown" {
|
||||||
return MarkdownFile(file)
|
return MarkdownFile(file)
|
||||||
}
|
}
|
||||||
|
if lexer.Config().Name == "XML" && path.Ext(file.Filename) == ".svg" {
|
||||||
|
return RenderSvgFile(file), nil
|
||||||
|
}
|
||||||
|
|
||||||
formatter := html.New(html.WithClasses(true), html.PreventSurroundingPre(true))
|
formatter := html.New(html.WithClasses(true), html.PreventSurroundingPre(true))
|
||||||
|
|
||||||
|
rendered := RenderedFile{
|
||||||
|
File: file,
|
||||||
|
}
|
||||||
|
|
||||||
iterator, err := lexer.Tokenise(nil, file.Content+"\n")
|
iterator, err := lexer.Tokenise(nil, file.Content+"\n")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return rendered, err
|
return rendered, err
|
||||||
|
@ -140,6 +146,20 @@ func HighlightGistPreview(gist *db.Gist) (RenderedGist, error) {
|
||||||
return rendered, err
|
return rendered, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RenderSvgFile(file *git.File) RenderedFile {
|
||||||
|
rendered := RenderedFile{
|
||||||
|
File: file,
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded := base64.StdEncoding.EncodeToString([]byte(file.Content))
|
||||||
|
content := `<img src="data:image/svg+xml;base64,` + encoded + `" />`
|
||||||
|
|
||||||
|
rendered.HTML = content
|
||||||
|
rendered.Type = "SVG"
|
||||||
|
|
||||||
|
return rendered
|
||||||
|
}
|
||||||
|
|
||||||
func parseFileTypeName(config chroma.Config) string {
|
func parseFileTypeName(config chroma.Config) string {
|
||||||
fileType := config.Name
|
fileType := config.Name
|
||||||
if fileType == "fallback" || fileType == "plaintext" {
|
if fileType == "fallback" || fileType == "plaintext" {
|
||||||
|
|
|
@ -1,24 +1,17 @@
|
||||||
package render
|
package render
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"github.com/Kunde21/markdownfmt/v3"
|
|
||||||
"github.com/alecthomas/chroma/v2/formatters/html"
|
"github.com/alecthomas/chroma/v2/formatters/html"
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"github.com/thomiceli/opengist/internal/db"
|
"github.com/thomiceli/opengist/internal/db"
|
||||||
"github.com/thomiceli/opengist/internal/git"
|
"github.com/thomiceli/opengist/internal/git"
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
emoji "github.com/yuin/goldmark-emoji"
|
emoji "github.com/yuin/goldmark-emoji"
|
||||||
highlighting "github.com/yuin/goldmark-highlighting/v2"
|
highlighting "github.com/yuin/goldmark-highlighting/v2"
|
||||||
"github.com/yuin/goldmark/ast"
|
|
||||||
"github.com/yuin/goldmark/extension"
|
"github.com/yuin/goldmark/extension"
|
||||||
astex "github.com/yuin/goldmark/extension/ast"
|
|
||||||
"github.com/yuin/goldmark/parser"
|
"github.com/yuin/goldmark/parser"
|
||||||
"github.com/yuin/goldmark/text"
|
|
||||||
"github.com/yuin/goldmark/util"
|
"github.com/yuin/goldmark/util"
|
||||||
"go.abhg.dev/goldmark/mermaid"
|
"go.abhg.dev/goldmark/mermaid"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func MarkdownGistPreview(gist *db.Gist) (RenderedGist, error) {
|
func MarkdownGistPreview(gist *db.Gist) (RenderedGist, error) {
|
||||||
|
@ -33,7 +26,7 @@ func MarkdownGistPreview(gist *db.Gist) (RenderedGist, error) {
|
||||||
|
|
||||||
func MarkdownFile(file *git.File) (RenderedFile, error) {
|
func MarkdownFile(file *git.File) (RenderedFile, error) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err := newMarkdown().Convert([]byte(file.Content), &buf)
|
err := newMarkdownWithSvgExtension().Convert([]byte(file.Content), &buf)
|
||||||
|
|
||||||
return RenderedFile{
|
return RenderedFile{
|
||||||
File: file,
|
File: file,
|
||||||
|
@ -43,77 +36,34 @@ func MarkdownFile(file *git.File) (RenderedFile, error) {
|
||||||
}
|
}
|
||||||
func MarkdownString(content string) (string, error) {
|
func MarkdownString(content string) (string, error) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err := newMarkdown().Convert([]byte(content), &buf)
|
err := newMarkdownWithSvgExtension().Convert([]byte(content), &buf)
|
||||||
|
|
||||||
return buf.String(), err
|
return buf.String(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMarkdown() goldmark.Markdown {
|
func newMarkdown(extraExtensions ...goldmark.Extender) goldmark.Markdown {
|
||||||
return goldmark.New(
|
extensions := []goldmark.Extender{
|
||||||
goldmark.WithExtensions(
|
extension.GFM,
|
||||||
extension.GFM,
|
highlighting.NewHighlighting(
|
||||||
highlighting.NewHighlighting(
|
highlighting.WithStyle("catppuccin-latte"),
|
||||||
highlighting.WithStyle("catppuccin-latte"),
|
highlighting.WithFormatOptions(html.WithClasses(true)),
|
||||||
highlighting.WithFormatOptions(html.WithClasses(true))),
|
|
||||||
emoji.Emoji,
|
|
||||||
&mermaid.Extender{},
|
|
||||||
),
|
),
|
||||||
|
emoji.Emoji,
|
||||||
|
&mermaid.Extender{},
|
||||||
|
}
|
||||||
|
|
||||||
|
extensions = append(extensions, extraExtensions...)
|
||||||
|
|
||||||
|
return goldmark.New(
|
||||||
|
goldmark.WithExtensions(extensions...),
|
||||||
goldmark.WithParserOptions(
|
goldmark.WithParserOptions(
|
||||||
parser.WithASTTransformers(
|
parser.WithASTTransformers(
|
||||||
util.Prioritized(&CheckboxTransformer{}, 10000),
|
util.Prioritized(&checkboxTransformer{}, 10000),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheckboxTransformer struct{}
|
func newMarkdownWithSvgExtension() goldmark.Markdown {
|
||||||
|
return newMarkdown(&svgToImgBase64{})
|
||||||
func (t *CheckboxTransformer) Transform(node *ast.Document, _ text.Reader, _ parser.Context) {
|
|
||||||
i := 1
|
|
||||||
err := ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
|
|
||||||
if entering {
|
|
||||||
if _, ok := n.(*astex.TaskCheckBox); ok {
|
|
||||||
listitem := n.Parent().Parent()
|
|
||||||
listitem.SetAttribute([]byte("data-checkbox-nb"), []byte(strconv.Itoa(i)))
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ast.WalkContinue, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Err(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Checkbox(content string, checkboxNb int) (string, error) {
|
|
||||||
buf := bytes.Buffer{}
|
|
||||||
w := bufio.NewWriter(&buf)
|
|
||||||
|
|
||||||
source := []byte(content)
|
|
||||||
markdown := markdownfmt.NewGoldmark()
|
|
||||||
reader := text.NewReader(source)
|
|
||||||
document := markdown.Parser().Parse(reader)
|
|
||||||
|
|
||||||
i := 1
|
|
||||||
err := ast.Walk(document, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
|
|
||||||
if entering {
|
|
||||||
if listItem, ok := n.(*astex.TaskCheckBox); ok {
|
|
||||||
if i == checkboxNb {
|
|
||||||
listItem.IsChecked = !listItem.IsChecked
|
|
||||||
}
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ast.WalkContinue, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = markdown.Renderer().Render(w, source, document); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
_ = w.Flush()
|
|
||||||
|
|
||||||
return buf.String(), nil
|
|
||||||
}
|
}
|
||||||
|
|
65
internal/render/markdown_checkbox.go
Normal file
65
internal/render/markdown_checkbox.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"github.com/Kunde21/markdownfmt/v3"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/yuin/goldmark/ast"
|
||||||
|
astex "github.com/yuin/goldmark/extension/ast"
|
||||||
|
"github.com/yuin/goldmark/parser"
|
||||||
|
"github.com/yuin/goldmark/text"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type checkboxTransformer struct{}
|
||||||
|
|
||||||
|
func (t *checkboxTransformer) Transform(node *ast.Document, _ text.Reader, _ parser.Context) {
|
||||||
|
i := 1
|
||||||
|
err := ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
|
if entering {
|
||||||
|
if _, ok := n.(*astex.TaskCheckBox); ok {
|
||||||
|
listitem := n.Parent().Parent()
|
||||||
|
listitem.SetAttribute([]byte("data-checkbox-nb"), []byte(strconv.Itoa(i)))
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ast.WalkContinue, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Checkbox(content string, checkboxNb int) (string, error) {
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
w := bufio.NewWriter(&buf)
|
||||||
|
|
||||||
|
source := []byte(content)
|
||||||
|
markdown := markdownfmt.NewGoldmark()
|
||||||
|
reader := text.NewReader(source)
|
||||||
|
document := markdown.Parser().Parse(reader)
|
||||||
|
|
||||||
|
i := 1
|
||||||
|
err := ast.Walk(document, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
|
if entering {
|
||||||
|
if listItem, ok := n.(*astex.TaskCheckBox); ok {
|
||||||
|
if i == checkboxNb {
|
||||||
|
listItem.IsChecked = !listItem.IsChecked
|
||||||
|
}
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ast.WalkContinue, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = markdown.Renderer().Render(w, source, document); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
_ = w.Flush()
|
||||||
|
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
141
internal/render/markdown_svg.go
Normal file
141
internal/render/markdown_svg.go
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"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"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var svgRegex = regexp.MustCompile(`(?i)^[ ]{0,3}<(svg)(?:\s.*|>.*|/>.*|)(?:\r\n|\n)?$`)
|
||||||
|
|
||||||
|
type svgToImgBase64 struct{}
|
||||||
|
|
||||||
|
func (e *svgToImgBase64) Extend(m goldmark.Markdown) {
|
||||||
|
m.Parser().AddOptions(parser.WithBlockParsers(
|
||||||
|
util.Prioritized(newSvgParser(), 1),
|
||||||
|
))
|
||||||
|
m.Renderer().AddOptions(renderer.WithNodeRenderers(
|
||||||
|
util.Prioritized(newSvgRenderer(), 1),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- SVG Block -- //
|
||||||
|
|
||||||
|
type svgBlock struct {
|
||||||
|
ast.BaseBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *svgBlock) IsRaw() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *svgBlock) Dump(source []byte, level int) {
|
||||||
|
ast.DumpHelper(n, source, level, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var svgBlockKind = ast.NewNodeKind("SVG")
|
||||||
|
|
||||||
|
func (n *svgBlock) Kind() ast.NodeKind {
|
||||||
|
return svgBlockKind
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSvgBlock() *svgBlock {
|
||||||
|
return &svgBlock{
|
||||||
|
BaseBlock: ast.BaseBlock{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- SVG Parser -- //
|
||||||
|
|
||||||
|
type svgParser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultSvgParser = &svgParser{}
|
||||||
|
|
||||||
|
func newSvgParser() parser.BlockParser {
|
||||||
|
return defaultSvgParser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *svgParser) Trigger() []byte {
|
||||||
|
return []byte{'<'}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *svgParser) Open(parent ast.Node, reader text.Reader, _ parser.Context) (ast.Node, parser.State) {
|
||||||
|
var node *svgBlock
|
||||||
|
line, segment := reader.PeekLine()
|
||||||
|
|
||||||
|
if !bytes.HasPrefix(line, []byte("<svg")) {
|
||||||
|
return nil, parser.None
|
||||||
|
}
|
||||||
|
|
||||||
|
if svgRegex.Match(line) {
|
||||||
|
node = newSvgBlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if node != nil {
|
||||||
|
reader.Advance(segment.Len() - util.TrimRightSpaceLength(line))
|
||||||
|
node.Lines().Append(segment)
|
||||||
|
return node, parser.NoChildren
|
||||||
|
}
|
||||||
|
return nil, parser.None
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *svgParser) Continue(node ast.Node, reader text.Reader, _ parser.Context) parser.State {
|
||||||
|
line, segment := reader.PeekLine()
|
||||||
|
if util.IsBlank(line) {
|
||||||
|
return parser.Close
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.HasSuffix(util.TrimRightSpace(line), []byte("</svg>")) {
|
||||||
|
node.Lines().Append(segment)
|
||||||
|
return parser.Continue | parser.NoChildren
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Lines().Append(segment)
|
||||||
|
reader.Advance(segment.Len())
|
||||||
|
return parser.Close
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *svgParser) Close(_ ast.Node, _ text.Reader, _ parser.Context) {}
|
||||||
|
|
||||||
|
func (b *svgParser) CanInterruptParagraph() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *svgParser) CanAcceptIndentedLine() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- SVG Renderer -- //
|
||||||
|
|
||||||
|
type svgRenderer struct{}
|
||||||
|
|
||||||
|
func newSvgRenderer() renderer.NodeRenderer {
|
||||||
|
return &svgRenderer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *svgRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||||
|
reg.Register(svgBlockKind, r.renderSVG)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *svgRenderer) renderSVG(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
|
if !entering {
|
||||||
|
return ast.WalkContinue, nil
|
||||||
|
}
|
||||||
|
rawHTML := node.(*svgBlock)
|
||||||
|
var svgContent []byte
|
||||||
|
for i := 0; i < rawHTML.Lines().Len(); i++ {
|
||||||
|
segment := rawHTML.Lines().At(i)
|
||||||
|
svgContent = append(svgContent, segment.Value(source)...)
|
||||||
|
}
|
||||||
|
encoded := base64.StdEncoding.EncodeToString(svgContent)
|
||||||
|
imgTag := `<img src="data:image/svg+xml;base64,` + encoded + `" />`
|
||||||
|
_, _ = w.Write([]byte(imgTag))
|
||||||
|
return ast.WalkContinue, nil
|
||||||
|
}
|
|
@ -65,6 +65,9 @@ var (
|
||||||
"isCsv": func(i string) bool {
|
"isCsv": func(i string) bool {
|
||||||
return strings.ToLower(filepath.Ext(i)) == ".csv"
|
return strings.ToLower(filepath.Ext(i)) == ".csv"
|
||||||
},
|
},
|
||||||
|
"isSvg": func(i string) bool {
|
||||||
|
return strings.ToLower(filepath.Ext(i)) == ".svg"
|
||||||
|
},
|
||||||
"csvFile": func(file *git.File) *git.CsvFile {
|
"csvFile": func(file *git.File) *git.CsvFile {
|
||||||
if strings.ToLower(filepath.Ext(file.Filename)) != ".csv" {
|
if strings.ToLower(filepath.Ext(file.Filename)) != ".csv" {
|
||||||
return nil
|
return nil
|
||||||
|
|
4
public/embed.scss
vendored
4
public/embed.scss
vendored
|
@ -107,6 +107,10 @@ dl.dl-config dd {
|
||||||
@apply overflow-auto whitespace-pre;
|
@apply overflow-auto whitespace-pre;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.markdown-body img {
|
||||||
|
@apply bg-transparent dark:bg-transparent;
|
||||||
|
}
|
||||||
|
|
||||||
.chroma.preview.markdown pre code {
|
.chroma.preview.markdown pre code {
|
||||||
@apply p-4;
|
@apply p-4;
|
||||||
}
|
}
|
||||||
|
|
4
public/style.css
vendored
4
public/style.css
vendored
|
@ -167,6 +167,10 @@ dl.dl-config dd {
|
||||||
@apply overflow-auto whitespace-pre !important;
|
@apply overflow-auto whitespace-pre !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.markdown-body img {
|
||||||
|
@apply bg-transparent dark:bg-transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
.chroma.preview.markdown pre code {
|
.chroma.preview.markdown pre code {
|
||||||
@apply p-4 !important;
|
@apply p-4 !important;
|
||||||
}
|
}
|
||||||
|
|
2
templates/pages/gist.html
vendored
2
templates/pages/gist.html
vendored
|
@ -68,6 +68,8 @@
|
||||||
</table>
|
</table>
|
||||||
{{ else if isMarkdown $file.Filename }}
|
{{ else if isMarkdown $file.Filename }}
|
||||||
<div class="chroma markdown markdown-body p-8">{{ $file.HTML | safe }}</div>
|
<div class="chroma markdown markdown-body p-8">{{ $file.HTML | safe }}</div>
|
||||||
|
{{ else if isSvg $file.Filename }}
|
||||||
|
<div class="p-8 flex justify-center">{{ $file.HTML | safe }}</div>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<div class="code">
|
<div class="code">
|
||||||
{{ $fileslug := slug $file.Filename }}
|
{{ $fileslug := slug $file.Filename }}
|
||||||
|
|
2
templates/pages/gist_embed.html
vendored
2
templates/pages/gist_embed.html
vendored
|
@ -32,6 +32,8 @@
|
||||||
</table>
|
</table>
|
||||||
{{ else if isMarkdown $file.Filename }}
|
{{ else if isMarkdown $file.Filename }}
|
||||||
<div class="chroma markdown markdown-body p-8">{{ $file.HTML | safe }}</div>
|
<div class="chroma markdown markdown-body p-8">{{ $file.HTML | safe }}</div>
|
||||||
|
{{ else if isSvg $file.Filename }}
|
||||||
|
<div class="p-8 flex justify-center">{{ $file.HTML | safe }}</div>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<div class="code dark:bg-gray-900">
|
<div class="code dark:bg-gray-900">
|
||||||
{{ $fileslug := slug $file.Filename }}
|
{{ $fileslug := slug $file.Filename }}
|
||||||
|
|
Loading…
Reference in a new issue