This commit is contained in:
mathew 2023-03-08 23:55:42 -05:00 committed by GitHub
commit 7e2f06e012
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 193 additions and 20 deletions

View File

@ -17,6 +17,8 @@ import (
"github.com/go-ini/ini"
"github.com/writeas/web-core/log"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/extension"
"golang.org/x/net/idna"
)
@ -168,6 +170,14 @@ type (
// Disable password authentication if use only Oauth
DisablePasswordAuth bool `ini:"disable_password_auth"`
// Which Markdown renderer to use
Renderer string `ini:"markdown_renderer"`
// Options for the Goldmark renderer
RendererOptions string `ini:"markdown_options"`
// Conversion of options ready for the renderer
rendererExtensions []goldmark.Extender
}
// Config holds the complete configuration for running a writefreely instance
@ -245,6 +255,45 @@ func (ac AppCfg) SignupPath() string {
return "/"
}
func (ac AppCfg) MarkdownRenderer() string {
if strings.EqualFold(ac.Renderer, "goldmark") {
return "goldmark"
}
return "saturday"
}
func (ac AppCfg) RendererExtensions() []goldmark.Extender {
if ac.rendererExtensions != nil {
return ac.rendererExtensions
}
var extlist []goldmark.Extender
optlist := strings.FieldsFunc(ac.RendererOptions, func(r rune) bool {
return r == ' ' || r == '\t' || r == ','
})
for _, opt := range optlist {
switch opt {
case "table":
extlist = append(extlist, extension.Table)
case "strikethrough":
extlist = append(extlist, extension.Strikethrough)
case "linkify":
extlist = append(extlist, extension.Linkify)
case "tasklist":
extlist = append(extlist, extension.TaskList)
case "gfm":
extlist = append(extlist, extension.GFM)
case "definitionlist":
extlist = append(extlist, extension.DefinitionList)
case "typographer":
extlist = append(extlist, extension.Typographer)
case "cjk":
extlist = append(extlist, extension.CJK)
}
}
ac.rendererExtensions = extlist
return extlist
}
// Load reads the given configuration file, then parses and returns it as a Config.
func Load(fname string) (*Config, error) {
if fname == "" {

4
go.mod
View File

@ -1,6 +1,8 @@
module github.com/writefreely/writefreely
require (
git.mills.io/prologic/go-gopher v0.0.0-20210712135410-b7ebb55feece
github.com/clbanning/mxj v1.8.4 // indirect
github.com/dustin/go-humanize v1.0.0
github.com/fatih/color v1.13.0
github.com/go-ini/ini v1.67.0
@ -49,6 +51,7 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/forPelevin/gomoji v1.1.3 // indirect
github.com/go-fed/httpsig v0.1.1-0.20200204213531-0ef28562fabe // indirect
github.com/go-test/deep v1.0.1 // indirect
github.com/gofrs/uuid v3.3.0+incompatible // indirect
@ -64,6 +67,7 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sasha-s/go-deadlock v0.3.1 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect

9
go.sum
View File

@ -1,5 +1,8 @@
code.as/core/socks v1.0.0 h1:SPQXNp4SbEwjOAP9VzUahLHak8SDqy5n+9cm9tpjZOs=
code.as/core/socks v1.0.0/go.mod h1:BAXBy5O9s2gmw6UxLqNJcVbWY7C/UPs+801CcSsfWOY=
git.mills.io/prologic/go-gopher v0.0.0-20210712135410-b7ebb55feece h1:0esmnntqeuM1iBgHH0HOeSynsLA1l28p2K3h/WZuIfQ=
git.mills.io/prologic/go-gopher v0.0.0-20210712135410-b7ebb55feece/go.mod h1:EMXlYOIbYJQhPTtIltgaaHtCYDawV/HL0dYf8ShzAck=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
@ -29,6 +32,8 @@ github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/forPelevin/gomoji v1.1.3 h1:7c3dYzVmYhpOL3bS4riXqSWJBX3BhSvH68yoNNf3FH0=
github.com/forPelevin/gomoji v1.1.3/go.mod h1:ypB7Kz3Fsp+LVR7KoT7mEFOioYBuTuAtaAT4RGl+ASY=
github.com/go-fed/httpsig v0.1.0/go.mod h1:T56HUNYZUQ1AGUzhAYPugZfp36sKApVnGBgKlIY+aIE=
github.com/go-fed/httpsig v0.1.1-0.20200204213531-0ef28562fabe h1:U71giCx5NjRn4Lb71UuprPHqhjxGv3Jqonb9fgcaJH8=
github.com/go-fed/httpsig v0.1.1-0.20200204213531-0ef28562fabe/go.mod h1:T56HUNYZUQ1AGUzhAYPugZfp36sKApVnGBgKlIY+aIE=
@ -101,6 +106,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0=
@ -156,8 +163,6 @@ github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b h1:h3NzB8OZ5
github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b/go.mod h1:T2UVVzt+R5KSSZe2xRSytnwc2M9AoDegi7foeIsik+M=
github.com/writefreely/go-nodeinfo v1.2.0 h1:La+YbTCvmpTwFhBSlebWDDL81N88Qf/SCAvRLR7F8ss=
github.com/writefreely/go-nodeinfo v1.2.0/go.mod h1:UTvE78KpcjYOlRHupZIiSEFcXHioTXuacCbHU+CAcPg=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
golang.org/x/crypto v0.0.0-20180527072434-ab813273cd59/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200109152110-61a87790db17/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=

View File

@ -23,12 +23,16 @@ import (
"unicode"
"unicode/utf8"
hashtag "github.com/abhinav/goldmark-hashtag"
"github.com/microcosm-cc/bluemonday"
stripmd "github.com/writeas/go-strip-markdown/v2"
"github.com/writeas/impart"
blackfriday "github.com/writeas/saturday"
"github.com/writeas/web-core/log"
"github.com/writeas/web-core/stringmanip"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/parser"
"github.com/writefreely/writefreely/config"
"github.com/writefreely/writefreely/parse"
)
@ -83,7 +87,7 @@ func (p *Post) formatContent(cfg *config.Config, c *Collection, isOwner bool, is
p.handlePremiumContent(c, isOwner, isPostPage, cfg)
p.Content = strings.Replace(p.Content, "&lt;!--paid-->", "<!--paid-->", 1)
p.HTMLTitle = template.HTML(applyBasicMarkdown([]byte(p.Title.String)))
p.HTMLTitle = template.HTML(applyBasicMarkdown([]byte(p.Title.String), cfg))
p.HTMLContent = template.HTML(applyMarkdown([]byte(p.Content), baseURL, cfg))
if exc := strings.Index(string(p.Content), "<!--more-->"); exc > -1 {
p.HTMLExcerpt = template.HTML(applyMarkdown([]byte(p.Content[:exc]), baseURL, cfg))
@ -120,7 +124,10 @@ func (p *PublicPost) augmentReadingDestination() {
}
func applyMarkdown(data []byte, baseURL string, cfg *config.Config) string {
return applyMarkdownSpecial(data, false, baseURL, cfg)
if cfg.App.MarkdownRenderer() == "goldmark" {
return applyCommonmarkSpecial(data, false, baseURL, cfg)
}
return applySaturdaySpecial(data, false, baseURL, cfg)
}
func disableYoutubeAutoplay(outHTML string) string {
@ -142,7 +149,68 @@ func disableYoutubeAutoplay(outHTML string) string {
return outHTML
}
type hashtagResolver struct {
Prefix string
}
var _ hashtag.Resolver = hashtagResolver{}
func (h hashtagResolver) ResolveHashtag(node *hashtag.Node) (destination []byte, err error) {
var buf bytes.Buffer
buf.WriteString(h.Prefix)
buf.Write(node.Tag)
return buf.Bytes(), nil
}
func applyMarkdownSpecial(data []byte, skipNoFollow bool, baseURL string, cfg *config.Config) string {
if cfg.App.MarkdownRenderer() == "goldmark" {
return applyCommonmarkSpecial(data, skipNoFollow, baseURL, cfg)
} else {
return applySaturdaySpecial(data, skipNoFollow, baseURL, cfg)
}
}
func applyCommonmarkSpecial(data []byte, skipNoFollow bool, baseURL string, cfg *config.Config) string {
extensions := cfg.App.RendererExtensions()
if baseURL != "" {
tagPrefix := baseURL + "tag:"
if cfg.App.Chorus {
tagPrefix = "/read/t"
}
extensions = append(extensions, &hashtag.Extender{
Resolver: hashtagResolver{Prefix: tagPrefix},
})
}
md := goldmark.New(
goldmark.WithExtensions(extensions...),
goldmark.WithParserOptions(
parser.WithAutoHeadingID(),
),
)
var buf bytes.Buffer
if err := md.Convert(data, &buf); err != nil {
log.Info("error rendering CommonMark: %v", err)
}
htm := buf.Bytes()
if baseURL != "" {
handlePrefix := cfg.App.Host + "/@/"
htm = []byte(mentionReg.ReplaceAll(htm, []byte("<a href=\""+handlePrefix+"$1$2\" class=\"u-url mention\">@<span>$1$2</span></a>")))
}
// Strip out bad HTML
policy := getSanitizationPolicy()
// Enable GFM checkboxes for CommonMark
// Technically we could skip this if the
policy.AllowAttrs("type", "disabled", "checked").OnElements("input")
policy.RequireNoFollowOnLinks(!skipNoFollow)
outHTML := string(policy.SanitizeBytes(htm))
// Strip newlines on certain block elements that render with them
outHTML = blockReg.ReplaceAllString(outHTML, "<$1>")
outHTML = endBlockReg.ReplaceAllString(outHTML, "</$1></$2>")
outHTML = disableYoutubeAutoplay(outHTML)
return outHTML
}
func applySaturdaySpecial(data []byte, skipNoFollow bool, baseURL string, cfg *config.Config) string {
mdExtensions := 0 |
blackfriday.EXTENSION_TABLES |
blackfriday.EXTENSION_FENCED_CODE |
@ -181,7 +249,41 @@ func applyMarkdownSpecial(data []byte, skipNoFollow bool, baseURL string, cfg *c
return outHTML
}
func applyBasicMarkdown(data []byte) string {
func applyBasicMarkdown(data []byte, cfg *config.Config) string {
if cfg.App.MarkdownRenderer() == "goldmark" {
return applyBasicCommonmark(data, cfg)
} else {
return applyBasicSaturday(data)
}
}
func applyBasicCommonmark(data []byte, cfg *config.Config) string {
md := goldmark.New(
goldmark.WithExtensions(
cfg.App.RendererExtensions()...,
),
)
var inbuf bytes.Buffer
inbuf.WriteString("# ")
inbuf.Write(data)
var outbuf bytes.Buffer
if err := md.Convert(inbuf.Bytes(), &outbuf); err != nil {
log.Info("error rendering basic CommonMark: %v", err)
}
htm := outbuf.Bytes()
htm = bytes.TrimSpace(htm)
htm = htm[len("<h1>") : len(htm)-len("</h1>")]
// Strip out bad HTML
policy := bluemonday.UGCPolicy()
policy.AllowAttrs("class", "id").Globally()
outHTML := string(policy.SanitizeBytes(htm))
outHTML = markeddownReg.ReplaceAllString(outHTML, "$1")
outHTML = strings.TrimRightFunc(outHTML, unicode.IsSpace)
return outHTML
}
func applyBasicSaturday(data []byte) string {
if len(bytes.TrimSpace(data)) == 0 {
return ""
}
@ -284,12 +386,13 @@ func sanitizePost(content string) string {
// choosing what to generate. In case a post has a title, this function will
// fail, and logic should instead be implemented to skip this when there's no
// title, like so:
// var desc string
// if title == "" {
// desc = postDescription(content, title, friendlyId)
// } else {
// desc = shortPostDescription(content)
// }
//
// var desc string
// if title == "" {
// desc = postDescription(content, title, friendlyId)
// } else {
// desc = shortPostDescription(content)
// }
func postDescription(content, title, friendlyId string) string {
maxLen := 140

View File

@ -10,7 +10,11 @@
package writefreely
import "testing"
import (
"testing"
"github.com/writefreely/writefreely/config"
)
func TestApplyBasicMarkdown(t *testing.T) {
tests := []struct {
@ -32,12 +36,19 @@ func TestApplyBasicMarkdown(t *testing.T) {
{"date", "12. April", `12. April`},
{"table", "| Hi | There |", `| Hi | There |`},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
res := applyBasicMarkdown([]byte(test.in))
if res != test.result {
t.Errorf("%s: wanted %s, got %s", test.name, test.result, res)
}
})
for _, renderer := range []string{"saturday", "goldmark"} {
cfg := &config.Config{
App: config.AppCfg{
Renderer: renderer,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
res := applyBasicMarkdown([]byte(test.in), cfg)
if res != test.result {
t.Errorf("%s: wanted %s, got %s", test.name, test.result, res)
}
})
}
}
}

View File

@ -23,6 +23,7 @@ import (
"github.com/dustin/go-humanize"
"github.com/writeas/web-core/l10n"
"github.com/writeas/web-core/log"
"github.com/writefreely/writefreely/config"
)
@ -136,7 +137,7 @@ func InitTemplates(cfg *config.Config) error {
log.Info("Loading pages...")
// Initialize all static pages that use the base template
filepath.Walk(filepath.Join(cfg.Server.PagesParentDir, pagesDir), func(path string, i os.FileInfo, err error) error {
if !i.IsDir() && !strings.HasPrefix(i.Name(), ".") {
if i != nil && !i.IsDir() && !strings.HasPrefix(i.Name(), ".") {
key := i.Name()
initPage(cfg.Server.PagesParentDir, path, key)
}