More changes to uploads:
- Have got upload images appearing in the post list - Allowed for deleting uploads - Allowed for seeing the upload progress - Fixed the setting of upload properties like the MIME type - Removed the stripe exif logic with just re-encoding PNGs and JPEGs by loading them and saving them
This commit is contained in:
parent
d0cebe6564
commit
199ff9feb9
21 changed files with 471 additions and 65 deletions
27
providers/markdown/htmltransforms.go
Normal file
27
providers/markdown/htmltransforms.go
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package markdown
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
)
|
||||
|
||||
type htmlTransform func(ctx context.Context, dom *goquery.Document) error
|
||||
|
||||
func applyTransforms(ctx context.Context, inHTML string, transforms []htmlTransform) string {
|
||||
dom, err := goquery.NewDocumentFromReader(strings.NewReader(inHTML))
|
||||
if err != nil {
|
||||
return inHTML
|
||||
}
|
||||
for _, transform := range transforms {
|
||||
if err := transform(ctx, dom); err != nil {
|
||||
return inHTML
|
||||
}
|
||||
}
|
||||
res, err := dom.Html()
|
||||
if err != nil {
|
||||
return inHTML
|
||||
}
|
||||
return res
|
||||
}
|
||||
86
providers/markdown/renderer.go
Normal file
86
providers/markdown/renderer.go
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
package markdown
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
"github.com/yuin/goldmark/parser"
|
||||
gm_html "github.com/yuin/goldmark/renderer/html"
|
||||
"lmika.dev/lmika/weiro/models"
|
||||
)
|
||||
|
||||
type Renderer struct {
|
||||
mdParser goldmark.Markdown
|
||||
bmPolicy *bluemonday.Policy
|
||||
htmlTransforms []htmlTransform
|
||||
}
|
||||
|
||||
func NewRendererForUI() *Renderer {
|
||||
mdParser := goldmark.New(
|
||||
goldmark.WithExtensions(extension.GFM),
|
||||
goldmark.WithRendererOptions(
|
||||
gm_html.WithUnsafe(),
|
||||
),
|
||||
)
|
||||
bmPolicy := bluemonday.UGCPolicy()
|
||||
|
||||
return &Renderer{
|
||||
mdParser: mdParser,
|
||||
bmPolicy: bmPolicy,
|
||||
htmlTransforms: []htmlTransform{
|
||||
rewriteImgSrc(func(ctx context.Context, src string) string {
|
||||
if strings.HasPrefix(src, "/uploads/") {
|
||||
if site, ok := models.GetSite(ctx); ok {
|
||||
return fmt.Sprintf("/sites/%v/uploads/slug/%v", site.ID, strings.TrimPrefix(src, "/uploads/"))
|
||||
}
|
||||
return src
|
||||
}
|
||||
return src
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewRendererForSite() *Renderer {
|
||||
mdParser := goldmark.New(
|
||||
goldmark.WithExtensions(extension.GFM),
|
||||
goldmark.WithParserOptions(
|
||||
parser.WithAutoHeadingID(),
|
||||
),
|
||||
goldmark.WithRendererOptions(
|
||||
gm_html.WithUnsafe(),
|
||||
),
|
||||
)
|
||||
|
||||
return &Renderer{
|
||||
mdParser: mdParser,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Renderer) Render(ctx context.Context, in string) (string, error) {
|
||||
var sb strings.Builder
|
||||
if err := r.mdParser.Convert([]byte(in), &sb); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
outHTML := applyTransforms(ctx, sb.String(), r.htmlTransforms)
|
||||
|
||||
if r.bmPolicy != nil {
|
||||
return r.bmPolicy.Sanitize(outHTML), nil
|
||||
}
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
func (r *Renderer) RenderTo(ctx context.Context, w io.Writer, in string) error {
|
||||
s, err := r.Render(ctx, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write([]byte(s))
|
||||
return err
|
||||
}
|
||||
21
providers/markdown/uiexts.go
Normal file
21
providers/markdown/uiexts.go
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
package markdown
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
)
|
||||
|
||||
func rewriteImgSrc(transform func(ctx context.Context, in string) string) htmlTransform {
|
||||
return func(ctx context.Context, dom *goquery.Document) error {
|
||||
dom.Find("img").Each(func(i int, s *goquery.Selection) {
|
||||
s.SetAttr("src", transform(ctx, s.AttrOr("src", "")))
|
||||
})
|
||||
dom.Find("img").Each(func(i int, s *goquery.Selection) {
|
||||
s.AddClass("img-fluid")
|
||||
})
|
||||
//SetAttr("style", "max-width: 200px;max-height: 200px;height: auto;")
|
||||
//log.Printf("Rewritten image src: %s", s.AttrOr("style", ""))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package sitebuilder
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
|
|
@ -10,18 +11,15 @@ import (
|
|||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
"github.com/yuin/goldmark/parser"
|
||||
"github.com/yuin/goldmark/renderer/html"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"lmika.dev/lmika/weiro/models"
|
||||
"lmika.dev/lmika/weiro/models/pubmodel"
|
||||
"lmika.dev/lmika/weiro/providers/markdown"
|
||||
)
|
||||
|
||||
type Builder struct {
|
||||
site pubmodel.Site
|
||||
gmMarkdown goldmark.Markdown
|
||||
mdRenderer *markdown.Renderer
|
||||
opts Options
|
||||
tmpls *template.Template
|
||||
}
|
||||
|
|
@ -35,18 +33,10 @@ func New(site pubmodel.Site, opts Options) (*Builder, error) {
|
|||
}
|
||||
|
||||
return &Builder{
|
||||
site: site,
|
||||
opts: opts,
|
||||
tmpls: tmpls,
|
||||
gmMarkdown: goldmark.New(
|
||||
goldmark.WithExtensions(extension.GFM),
|
||||
goldmark.WithParserOptions(
|
||||
parser.WithAutoHeadingID(),
|
||||
),
|
||||
goldmark.WithRendererOptions(
|
||||
html.WithUnsafe(),
|
||||
),
|
||||
),
|
||||
site: site,
|
||||
opts: opts,
|
||||
tmpls: tmpls,
|
||||
mdRenderer: markdown.NewRendererForSite(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -121,7 +111,7 @@ func (b *Builder) renderPost(post *models.Post) (postSingleData, error) {
|
|||
}
|
||||
|
||||
var md bytes.Buffer
|
||||
if err := b.gmMarkdown.Convert([]byte(post.Body), &md); err != nil {
|
||||
if err := b.mdRenderer.RenderTo(context.Background(), &md, post.Body); err != nil {
|
||||
return postSingleData{}, fmt.Errorf("failed to write post %s: %w", post.Slug, err)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,31 +1,57 @@
|
|||
package uploadfiles
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"emperror.dev/errors"
|
||||
"github.com/barasher/go-exiftool"
|
||||
"github.com/disintegration/imaging"
|
||||
"lmika.dev/lmika/weiro/models"
|
||||
)
|
||||
|
||||
const (
|
||||
applicationOctetStream = "application/octet-stream"
|
||||
)
|
||||
|
||||
var supportedRencodableImageTypes = map[string]bool{
|
||||
"image/jpeg": true,
|
||||
"image/png": true,
|
||||
applicationOctetStream: true,
|
||||
}
|
||||
var supportedReencoableExtensions = map[string]bool{
|
||||
".jpg": true,
|
||||
".jpeg": true,
|
||||
".png": true,
|
||||
}
|
||||
|
||||
func (p *Provider) StripeEXIFData(site models.Site, up models.Upload) error {
|
||||
uploadFilename := p.uploadFileName(site, up)
|
||||
|
||||
et, err := exiftool.NewExiftool()
|
||||
if !supportedRencodableImageTypes[up.MIMEType] {
|
||||
return errors.New("unsupported image format: " + up.MIMEType)
|
||||
}
|
||||
if up.MIMEType == applicationOctetStream && !supportedReencoableExtensions[filepath.Ext(uploadFilename)] {
|
||||
return errors.New("unsupported image format")
|
||||
}
|
||||
|
||||
img, err := imaging.Open(uploadFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "failed to open image file")
|
||||
}
|
||||
defer et.Close()
|
||||
|
||||
fileInfos := et.ExtractMetadata(uploadFilename)
|
||||
if len(fileInfos) == 0 {
|
||||
return errors.New("no exif data found")
|
||||
tmpName := strings.TrimSuffix(uploadFilename, filepath.Ext(uploadFilename)) + ".tmp." + filepath.Ext(uploadFilename)
|
||||
if err := imaging.Save(img, tmpName); err != nil {
|
||||
return errors.Wrap(err, "failed to save image file")
|
||||
}
|
||||
fileInfo := fileInfos[0]
|
||||
fileInfo.ClearAll()
|
||||
|
||||
fileOut := []exiftool.FileMetadata{fileInfo}
|
||||
et.WriteMetadata(fileOut)
|
||||
if fileOut[0].Err != nil {
|
||||
return fileOut[0].Err
|
||||
if err := os.Remove(uploadFilename); err != nil {
|
||||
_ = os.Remove(tmpName)
|
||||
return errors.Wrap(err, "failed to remove image file")
|
||||
}
|
||||
|
||||
if err := os.Rename(tmpName, uploadFilename); err != nil {
|
||||
return errors.Wrap(err, "failed to rename image file")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -37,6 +37,17 @@ func (p *Provider) OpenUpload(site models.Site, up models.Upload) (io.ReadCloser
|
|||
return os.Open(fullPath)
|
||||
}
|
||||
|
||||
func (p *Provider) DeleteUpload(site models.Site, up models.Upload) error {
|
||||
fullPath := p.uploadFileName(site, up)
|
||||
if err := os.Remove(fullPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) UploadDir(site models.Site) string {
|
||||
return filepath.Join(p.baseDir, site.GUID)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue