Eric Bower: 1 feat: images can now be uploaded from all services 7 files changed, 138 insertions(+), 71 deletions(-)
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~erock/pico.sh/patches/34799/mbox | git am -3Learn more about email & git
Prose will also replace all relative paths to images with a url to their imgs.sh blog. --- This patch integration prose and imgs together so it's very easy to display images on your blog. filehandlers/imgs/md.go | 2 +- imgs/api.go | 71 +++++++++-------------------------------- imgs/config.go | 9 ++++++ prose/api.go | 25 ++++++++------- prose/scp_hooks.go | 2 +- shared/config.go | 39 ++++++++++++++++++++++ shared/mdparser.go | 61 ++++++++++++++++++++++++++++++++++- 7 files changed, 138 insertions(+), 71 deletions(-) diff --git a/filehandlers/imgs/md.go b/filehandlers/imgs/md.go index 3f43fcb..130d466 100644 --- a/filehandlers/imgs/md.go +++ b/filehandlers/imgs/md.go @@ -29,7 +29,7 @@ func (h *UploadImgHandler) validateMd(data *PostMetaData) (bool, error) { } func (h *UploadImgHandler) metaMd(data *PostMetaData) error { - parsedText, err := shared.ParseText(data.Text) + parsedText, err := shared.ParseText(data.Text, "") // we return nil here because we don't want the file upload to fail if err != nil { return nil diff --git a/imgs/api.go b/imgs/api.go index 35008a9..08c2057 100644 --- a/imgs/api.go +++ b/imgs/api.go @@ -113,45 +113,6 @@ type MergePost struct { var allTag = "all" -func ImgURL(c *shared.ConfigSite, username string, slug string, onSubdomain bool, withUserName bool) string { - fname := url.PathEscape(strings.TrimLeft(slug, "/")) - if c.IsSubdomains() && onSubdomain { - return fmt.Sprintf("%s://%s.%s/%s", c.Protocol, username, c.Domain, fname) - } - - if withUserName { - return fmt.Sprintf("/%s/%s", username, fname) - } - - return fmt.Sprintf("/%s", fname) -} - -func TagURL(c *shared.ConfigSite, username, tag string, onSubdomain, withUserName bool) string { - tg := url.PathEscape(tag) - if c.IsSubdomains() && onSubdomain { - return fmt.Sprintf("%s://%s.%s/t/%s", c.Protocol, username, c.Domain, tg) - } - - if withUserName { - return fmt.Sprintf("/%s/t/%s", username, tg) - } - - return fmt.Sprintf("/t/%s", tg) -} - -func TagPostURL(c *shared.ConfigSite, username, tag, slug string, onSubdomain, withUserName bool) string { - fname := url.PathEscape(strings.TrimLeft(slug, "/")) - if c.IsSubdomains() && onSubdomain { - return fmt.Sprintf("%s://%s.%s/%s/%s", c.Protocol, username, c.Domain, tag, fname) - } - - if withUserName { - return fmt.Sprintf("/%s/%s/%s", username, tag, fname) - } - - return fmt.Sprintf("/%s/%s", tag, fname) -} - func GetPostTitle(post *db.Post) string { if post.Description == "" { return post.Title @@ -239,8 +200,8 @@ func blogHandler(w http.ResponseWriter, r *http.Request) { for key, post := range tagMap { postCollection = append(postCollection, &PostTagData{ Tag: key, - URL: template.URL(TagURL(cfg, post.Username, key, onSubdomain, withUserName)), - ImgURL: template.URL(ImgURL(cfg, post.Username, post.Filename, onSubdomain, withUserName)), + URL: template.URL(cfg.TagURL(post.Username, key, onSubdomain, withUserName)), + ImgURL: template.URL(cfg.ImgURL(post.Username, post.Filename, onSubdomain, withUserName)), PublishAt: post.PublishAt, }) } @@ -370,8 +331,8 @@ func tagHandler(w http.ResponseWriter, r *http.Request) { continue } mergedPosts = append(mergedPosts, TagPostData{ - URL: template.URL(TagPostURL(cfg, username, tag, post.Slug, onSubdomain, withUserName)), - ImgURL: template.URL(ImgURL(cfg, username, post.Filename, onSubdomain, withUserName)), + URL: template.URL(cfg.TagPostURL(username, tag, post.Slug, onSubdomain, withUserName)), + ImgURL: template.URL(cfg.ImgURL(username, post.Filename, onSubdomain, withUserName)), Caption: post.Title, }) } @@ -383,7 +344,7 @@ func tagHandler(w http.ResponseWriter, r *http.Request) { Site: *cfg.GetSiteData(), Tag: tag, Posts: mergedPosts, - URL: template.URL(TagURL(cfg, username, tag, onSubdomain, withUserName)), + URL: template.URL(cfg.TagURL(username, tag, onSubdomain, withUserName)), } ts, err := shared.RenderTemplate(cfg, []string{ @@ -460,8 +421,7 @@ func tagPostHandler(w http.ResponseWriter, r *http.Request) { } if i+1 < len(mergedPosts) { - nextPage = TagPostURL( - cfg, + nextPage = cfg.TagPostURL( username, tag, mergedPosts[i+1].Slug, @@ -471,8 +431,7 @@ func tagPostHandler(w http.ResponseWriter, r *http.Request) { } if i-1 >= 0 { - prevPage = TagPostURL( - cfg, + prevPage = cfg.TagPostURL( username, tag, mergedPosts[i-1].Slug, @@ -489,7 +448,7 @@ func tagPostHandler(w http.ResponseWriter, r *http.Request) { return } - parsed, err := shared.ParseText(post.Text) + parsed, err := shared.ParseText(post.Text, cfg.ImgURL(username, "", true, false)) if err != nil { logger.Error(err) } @@ -501,7 +460,7 @@ func tagPostHandler(w http.ResponseWriter, r *http.Request) { tagLinks := make([]Link, 0, len(post.Tags)) for _, tag := range post.Tags { tagLinks = append(tagLinks, Link{ - URL: template.URL(TagURL(cfg, username, tag, onSubdomain, withUserName)), + URL: template.URL(cfg.TagURL(username, tag, onSubdomain, withUserName)), Text: tag, }) } @@ -519,7 +478,7 @@ func tagPostHandler(w http.ResponseWriter, r *http.Request) { Username: username, BlogName: blogName, Contents: template.HTML(text), - ImgURL: template.URL(ImgURL(cfg, username, post.Filename, onSubdomain, withUserName)), + ImgURL: template.URL(cfg.ImgURL(username, post.Filename, onSubdomain, withUserName)), Tags: tagLinks, PrevPage: template.URL(prevPage), NextPage: template.URL(nextPage), @@ -572,7 +531,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) { var data PostPageData post, err := dbpool.FindPostWithSlug(slug, user.ID, cfg.Space) if err == nil { - parsed, err := shared.ParseText(post.Text) + parsed, err := shared.ParseText(post.Text, cfg.ImgURL(username, "", true, false)) if err != nil { logger.Error(err) } @@ -584,7 +543,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) { tagLinks := make([]Link, 0, len(post.Tags)) for _, tag := range post.Tags { tagLinks = append(tagLinks, Link{ - URL: template.URL(TagURL(cfg, username, tag, onSubdomain, withUserName)), + URL: template.URL(cfg.TagURL(username, tag, onSubdomain, withUserName)), Text: tag, }) } @@ -602,7 +561,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) { Username: username, BlogName: blogName, Contents: template.HTML(text), - ImgURL: template.URL(ImgURL(cfg, username, post.Filename, onSubdomain, withUserName)), + ImgURL: template.URL(cfg.ImgURL(username, post.Filename, onSubdomain, withUserName)), Tags: tagLinks, } } else { @@ -722,7 +681,7 @@ func rssBlogHandler(w http.ResponseWriter, r *http.Request) { } var tpl bytes.Buffer data := &PostPageData{ - ImgURL: template.URL(ImgURL(cfg, username, post.Filename, onSubdomain, withUserName)), + ImgURL: template.URL(cfg.ImgURL(username, post.Filename, onSubdomain, withUserName)), } if err := ts.Execute(&tpl, data); err != nil { continue @@ -801,7 +760,7 @@ func rssHandler(w http.ResponseWriter, r *http.Request) { for _, post := range pager.Data { var tpl bytes.Buffer data := &PostPageData{ - ImgURL: template.URL(ImgURL(cfg, post.Username, post.Filename, onSubdomain, withUserName)), + ImgURL: template.URL(cfg.ImgURL(post.Username, post.Filename, onSubdomain, withUserName)), } if err := ts.Execute(&tpl, data); err != nil { continue diff --git a/imgs/config.go b/imgs/config.go index 7cce9dd..01cc736 100644 --- a/imgs/config.go +++ b/imgs/config.go @@ -7,6 +7,15 @@ import ( "git.sr.ht/~erock/pico/wish/cms/config" ) +func ImgBaseURL(username string) string { + cfg := NewConfigSite() + if cfg.IsSubdomains() { + return fmt.Sprintf("%s://%s.%s", cfg.Protocol, username, cfg.Domain) + } + + return "/" +} + func NewConfigSite() *shared.ConfigSite { domain := shared.GetEnv("IMGS_DOMAIN", "prose.sh") email := shared.GetEnv("IMGS_EMAIL", "hello@prose.sh") diff --git a/prose/api.go b/prose/api.go index b3c1d50..489a67c 100644 --- a/prose/api.go +++ b/prose/api.go @@ -13,6 +13,7 @@ import ( "git.sr.ht/~erock/pico/db" "git.sr.ht/~erock/pico/db/postgres" + "git.sr.ht/~erock/pico/imgs" "git.sr.ht/~erock/pico/shared" "github.com/gorilla/feeds" "golang.org/x/exp/slices" @@ -191,7 +192,7 @@ func blogHandler(w http.ResponseWriter, r *http.Request) { if post.Filename == "_styles.css" && len(post.Text) > 0 { hasCSS = true } else if post.Filename == "_readme.md" { - parsedText, err := shared.ParseText(post.Text) + parsedText, err := shared.ParseText(post.Text, imgs.ImgBaseURL(post.Username)) if err != nil { logger.Error(err) } @@ -328,7 +329,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) { var data PostPageData post, err := dbpool.FindPostWithSlug(slug, user.ID, cfg.Space) if err == nil { - parsedText, err := shared.ParseText(post.Text) + parsedText, err := shared.ParseText(post.Text, imgs.ImgBaseURL(username)) if err != nil { logger.Error(err) } @@ -336,7 +337,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) { // we need the blog name from the readme unfortunately readme, err := dbpool.FindPostWithFilename("_readme.md", user.ID, cfg.Space) if err == nil { - readmeParsed, err := shared.ParseText(readme.Text) + readmeParsed, err := shared.ParseText(readme.Text, imgs.ImgBaseURL(username)) if err != nil { logger.Error(err) } @@ -540,9 +541,15 @@ func rssBlogHandler(w http.ResponseWriter, r *http.Request) { Title: GetBlogName(username), } + hostDomain := strings.Split(r.Host, ":")[0] + appDomain := strings.Split(cfg.ConfigCms.Domain, ":")[0] + + onSubdomain := cfg.IsSubdomains() && strings.Contains(hostDomain, appDomain) + withUserName := (!onSubdomain && hostDomain == appDomain) || !cfg.IsCustomdomains() + for _, post := range posts { if post.Filename == "_readme.md" { - parsedText, err := shared.ParseText(post.Text) + parsedText, err := shared.ParseText(post.Text, imgs.ImgBaseURL(post.Username)) if err != nil { logger.Error(err) } @@ -558,12 +565,6 @@ func rssBlogHandler(w http.ResponseWriter, r *http.Request) { } } - hostDomain := strings.Split(r.Host, ":")[0] - appDomain := strings.Split(cfg.ConfigCms.Domain, ":")[0] - - onSubdomain := cfg.IsSubdomains() && strings.Contains(hostDomain, appDomain) - withUserName := (!onSubdomain && hostDomain == appDomain) || !cfg.IsCustomdomains() - feed := &feeds.Feed{ Title: headerTxt.Title, Link: &feeds.Link{Href: cfg.FullBlogURL(username, onSubdomain, withUserName)}, @@ -577,7 +578,7 @@ func rssBlogHandler(w http.ResponseWriter, r *http.Request) { if slices.Contains(cfg.HiddenPosts, post.Filename) { continue } - parsed, err := shared.ParseText(post.Text) + parsed, err := shared.ParseText(post.Text, imgs.ImgBaseURL(post.Username)) if err != nil { logger.Error(err) } @@ -660,7 +661,7 @@ func rssHandler(w http.ResponseWriter, r *http.Request) { var feedItems []*feeds.Item for _, post := range pager.Data { - parsed, err := shared.ParseText(post.Text) + parsed, err := shared.ParseText(post.Text, imgs.ImgBaseURL(post.Username)) if err != nil { logger.Error(err) } diff --git a/prose/scp_hooks.go b/prose/scp_hooks.go index 08dcb21..9e851f4 100644 --- a/prose/scp_hooks.go +++ b/prose/scp_hooks.go @@ -45,7 +45,7 @@ func (p *MarkdownHooks) FileValidate(data *filehandlers.PostMetaData) (bool, err } func (p *MarkdownHooks) FileMeta(data *filehandlers.PostMetaData) error { - parsedText, err := shared.ParseText(data.Text) + parsedText, err := shared.ParseText(data.Text, "") // we return nil here because we don't want the file upload to fail if err != nil { return nil diff --git a/shared/config.go b/shared/config.go index e77cc1f..89bac2a 100644 --- a/shared/config.go +++ b/shared/config.go @@ -143,6 +143,45 @@ func (c *ConfigSite) RawPostURL(username, slug string) string { return fmt.Sprintf("/raw/%s/%s", username, fname) } +func (c *ConfigSite) ImgURL(username string, slug string, onSubdomain bool, withUserName bool) string { + fname := url.PathEscape(strings.TrimLeft(slug, "/")) + if c.IsSubdomains() && onSubdomain { + return fmt.Sprintf("%s://%s.%s/%s", c.Protocol, username, c.Domain, fname) + } + + if withUserName { + return fmt.Sprintf("/%s/%s", username, fname) + } + + return fmt.Sprintf("/%s", fname) +} + +func (c *ConfigSite) TagURL(username, tag string, onSubdomain, withUserName bool) string { + tg := url.PathEscape(tag) + if c.IsSubdomains() && onSubdomain { + return fmt.Sprintf("%s://%s.%s/t/%s", c.Protocol, username, c.Domain, tg) + } + + if withUserName { + return fmt.Sprintf("/%s/t/%s", username, tg) + } + + return fmt.Sprintf("/t/%s", tg) +} + +func (c *ConfigSite) TagPostURL(username, tag, slug string, onSubdomain, withUserName bool) string { + fname := url.PathEscape(strings.TrimLeft(slug, "/")) + if c.IsSubdomains() && onSubdomain { + return fmt.Sprintf("%s://%s.%s/%s/%s", c.Protocol, username, c.Domain, tag, fname) + } + + if withUserName { + return fmt.Sprintf("/%s/%s/%s", username, tag, fname) + } + + return fmt.Sprintf("/%s/%s", tag, fname) +} + func CreateLogger() *zap.SugaredLogger { logger, err := zap.NewProduction() if err != nil { diff --git a/shared/mdparser.go b/shared/mdparser.go index 4129923..00cf593 100644 --- a/shared/mdparser.go +++ b/shared/mdparser.go @@ -12,9 +12,12 @@ import ( "github.com/yuin/goldmark" highlighting "github.com/yuin/goldmark-highlighting" meta "github.com/yuin/goldmark-meta" + "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/extension" "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/renderer" ghtml "github.com/yuin/goldmark/renderer/html" + "github.com/yuin/goldmark/util" ) type Link struct { @@ -107,7 +110,60 @@ func toTags(obj interface{}) ([]string, error) { return arr, nil } -func ParseText(text string) (*ParsedText, error) { +type ImgRender struct { + ghtml.Config + ImgURL func(url []byte) []byte +} + +func NewImgsRenderer(url func([]byte) []byte) renderer.NodeRenderer { + return &ImgRender{ + ImgURL: url, + } +} + +func (r *ImgRender) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { + reg.Register(ast.KindImage, r.renderImage) +} + +func (r *ImgRender) renderImage(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { + if !entering { + return ast.WalkContinue, nil + } + n := node.(*ast.Image) + _, _ = w.WriteString("<img src=\"") + if r.Unsafe || !ghtml.IsDangerousURL(n.Destination) { + dest := r.ImgURL(n.Destination) + _, _ = w.Write(util.EscapeHTML(util.URLEscape(dest, true))) + } + _, _ = w.WriteString(`" alt="`) + _, _ = w.Write(util.EscapeHTML(n.Text(source))) + _ = w.WriteByte('"') + if n.Title != nil { + _, _ = w.WriteString(` title="`) + r.Writer.Write(w, n.Title) + _ = w.WriteByte('"') + } + if n.Attributes() != nil { + ghtml.RenderAttributes(w, n, ghtml.ImageAttributeFilter) + } + if r.XHTML { + _, _ = w.WriteString(" />") + } else { + _, _ = w.WriteString(">") + } + return ast.WalkSkipChildren, nil +} + +func createImgURL(absURL string) func([]byte) []byte { + return func(url []byte) []byte { + if url[0] != '/' { + return url + } + return []byte(fmt.Sprintf("%s%s", absURL, string(url))) + } +} + +func ParseText(text string, absURL string) (*ParsedText, error) { parsed := ParsedText{ MetaData: &MetaData{ Tags: []string{}, @@ -131,6 +187,9 @@ func ParseText(text string) (*ParsedText, error) { ), goldmark.WithRendererOptions( ghtml.WithUnsafe(), + renderer.WithNodeRenderers( + util.Prioritized(NewImgsRenderer(createImgURL(absURL)), 0), + ), ), ) context := parser.NewContext() -- 2.37.1