diff --git a/.gitignore b/.gitignore index c8b64a2..e2b5d01 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ static/assets/ # Local Netlify folder .netlify .env +.DS_Store diff --git a/Makefile b/Makefile index bbbe928..4c9bbae 100644 --- a/Makefile +++ b/Makefile @@ -25,4 +25,10 @@ build: frontend .Phony: run run: build - ./build/weiro \ No newline at end of file + ./build/weiro + +.Phony: setup_targets +setup_targets: build + SITE_ID=$$(DATA_DIR=$(BUILD_DIR)/data ./$(BUILD_DIR)/weiro sites | tail -n1 | awk '{ print $$1 }'); \ + DATA_DIR=$(BUILD_DIR)/data ./build/weiro pubtargets "$$SITE_ID"; \ + DATA_DIR=$(BUILD_DIR)/data ./build/weiro pubtargets add --site "$$SITE_ID" --type localfs --ref ./$(BUILD_DIR)/out --url http://localhost:8000 diff --git a/assets/js/controllers/show_upload.js b/assets/js/controllers/show_upload.js new file mode 100644 index 0000000..65f34bc --- /dev/null +++ b/assets/js/controllers/show_upload.js @@ -0,0 +1,19 @@ +import { Controller } from "@hotwired/stimulus" +import {showToast} from "../services/toast"; + +export default class ShowUploadController extends Controller { + static values = { + copySnippet: String, + }; + + async copy(ev) { + ev.preventDefault(); + + await navigator.clipboard.writeText(this.copySnippetValue); + + showToast({ + title: "️📋 HTML Snippet", + body: "Copied to clipboard.", + }); + } +} \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js index 3e8c345..d76c353 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -6,6 +6,7 @@ import PosteditController from "./controllers/postedit"; import LogoutController from "./controllers/logout"; import FirstRunController from "./controllers/firstrun"; import UploadController from "./controllers/upload"; +import ShowUploadController from "./controllers/show_upload"; window.Stimulus = Application.start() Stimulus.register("toast", ToastController); @@ -13,4 +14,5 @@ Stimulus.register("postlist", PostlistController); Stimulus.register("postedit", PosteditController); Stimulus.register("logout", LogoutController); Stimulus.register("first-run", FirstRunController); -Stimulus.register("upload", UploadController); \ No newline at end of file +Stimulus.register("upload", UploadController); +Stimulus.register("show-upload", ShowUploadController); \ No newline at end of file diff --git a/go.mod b/go.mod index 2afe966..35a2797 100644 --- a/go.mod +++ b/go.mod @@ -78,6 +78,7 @@ require ( golang.org/x/crypto v0.48.0 // indirect golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect golang.org/x/net v0.50.0 // indirect + golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.34.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 7138969..faa9baf 100644 --- a/go.sum +++ b/go.sum @@ -581,6 +581,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/models/pubmodel/sites.go b/models/pubmodel/sites.go index 74e7444..6fc3942 100644 --- a/models/pubmodel/sites.go +++ b/models/pubmodel/sites.go @@ -1,9 +1,16 @@ package pubmodel -import "lmika.dev/lmika/weiro/models" +import ( + "io" + + "lmika.dev/lmika/weiro/models" +) type Site struct { models.Site BaseURL string Posts []*models.Post + Uploads []models.Upload + + OpenUpload func(u models.Upload) (io.ReadCloser, error) } diff --git a/providers/sitebuilder/builder.go b/providers/sitebuilder/builder.go index b07ea79..90e8610 100644 --- a/providers/sitebuilder/builder.go +++ b/providers/sitebuilder/builder.go @@ -5,7 +5,6 @@ import ( "fmt" "html/template" "io" - "log" "os" "path/filepath" "sort" @@ -15,6 +14,7 @@ import ( "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" ) @@ -57,13 +57,32 @@ func (b *Builder) BuildSite(outDir string) error { return err } - for _, post := range b.site.Posts { - if err := b.writePost(buildCtx, post); err != nil { + eg := errgroup.Group{} + + eg.Go(func() error { + for _, post := range b.site.Posts { + if err := b.writePost(buildCtx, post); err != nil { + return err + } + } + return nil + }) + + eg.Go(func() error { + if err := b.renderPostList(buildCtx, b.site.Posts); err != nil { return err } - } + return nil + }) - if err := b.renderPostList(buildCtx, b.site.Posts); err != nil { + // Copy uploads + eg.Go(func() error { + if err := b.writeUploads(buildCtx, b.site.Uploads); err != nil { + return err + } + return nil + }) + if err := eg.Wait(); err != nil { return err } @@ -130,7 +149,6 @@ func (b *Builder) createAtPath(ctx buildContext, path string, fn func(f io.Write if filepath.Ext(outFile) == "" { outFile = filepath.Join(outFile, "index.html") } - log.Printf("Writing %s\n", outFile) // Render it within the template if err := os.MkdirAll(filepath.Dir(outFile), 0755); err != nil { @@ -158,6 +176,37 @@ func (b *Builder) renderTemplate(w io.Writer, name string, data interface{}) err }) } +func (b *Builder) writeUploads(ctx buildContext, uploads []models.Upload) error { + for _, u := range uploads { + fullPath := filepath.Join(ctx.outDir, "uploads", u.Slug) + if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil { + return err + } + + if err := func() error { + r, err := b.site.OpenUpload(u) + if err != nil { + return err + } + defer r.Close() + + w, err := os.Create(fullPath) + if err != nil { + return err + } + defer w.Close() + + if _, err := io.Copy(w, r); err != nil { + return err + } + return nil + }(); err != nil { + return err + } + } + return nil +} + type buildContext struct { outDir string } diff --git a/providers/uploadfiles/provider.go b/providers/uploadfiles/provider.go index bbc5d44..b0ed4c1 100644 --- a/providers/uploadfiles/provider.go +++ b/providers/uploadfiles/provider.go @@ -37,6 +37,10 @@ func (p *Provider) OpenUpload(site models.Site, up models.Upload) (io.ReadCloser return os.Open(fullPath) } +func (p *Provider) UploadDir(site models.Site) string { + return filepath.Join(p.baseDir, site.GUID) +} + func (p *Provider) uploadFileName(site models.Site, up models.Upload) string { return filepath.Join(p.baseDir, site.GUID, up.Slug) } diff --git a/services/publisher/service.go b/services/publisher/service.go index 2cc47c8..7026bca 100644 --- a/services/publisher/service.go +++ b/services/publisher/service.go @@ -2,6 +2,7 @@ package publisher import ( "context" + "io" "log" "os" @@ -16,15 +17,18 @@ import ( "lmika.dev/lmika/weiro/providers/db" "lmika.dev/lmika/weiro/providers/sitebuilder" "lmika.dev/lmika/weiro/providers/siteexporter" + "lmika.dev/lmika/weiro/providers/uploadfiles" ) type Publisher struct { db *db.Provider + up *uploadfiles.Provider } -func New(db *db.Provider) *Publisher { +func New(db *db.Provider, up *uploadfiles.Provider) *Publisher { return &Publisher{ db: db, + up: up, } } @@ -40,6 +44,12 @@ func (p *Publisher) Publish(ctx context.Context, site models.Site) error { return err } + // Fetch all uploads of site + uploads, err := p.db.SelectUploadsOfSite(ctx, site.ID) + if err != nil { + return err + } + for _, target := range targets { if !target.Enabled { continue @@ -49,6 +59,10 @@ func (p *Publisher) Publish(ctx context.Context, site models.Site) error { Site: site, Posts: posts, BaseURL: target.BaseURL, + Uploads: uploads, + OpenUpload: func(u models.Upload) (io.ReadCloser, error) { + return p.up.OpenUpload(site, u) + }, } if err := p.publishSite(ctx, pubSite, target); err != nil { diff --git a/services/services.go b/services/services.go index 78e6a0e..606e932 100644 --- a/services/services.go +++ b/services/services.go @@ -32,7 +32,7 @@ func New(cfg config.Config) (*Services, error) { ufp := uploadfiles.New(filepath.Join(cfg.DataDir, "uploads")) authSvc := auth.New(dbp) - publisherSvc := publisher.New(dbp) + publisherSvc := publisher.New(dbp, ufp) publisherQueue := publisher.NewQueue(publisherSvc) postService := posts.New(dbp, publisherQueue) siteService := sites.New(dbp) diff --git a/services/uploads/manage.go b/services/uploads/manage.go index 3bfe4e7..7176910 100644 --- a/services/uploads/manage.go +++ b/services/uploads/manage.go @@ -3,14 +3,22 @@ package uploads import ( "context" "fmt" + "html/template" "io" + "log" + "strings" "lmika.dev/lmika/weiro/models" ) +var ( + uploadCopyTemplate = template.Must(template.New("upload-copy").Parse(`{{.Alt}}`)) +) + type UploadWithURL struct { - Upload models.Upload - URL string + Upload models.Upload + CopyTemplate string + URL string } func (s *Service) FetchUpload(ctx context.Context, uploadID int64) (res UploadWithURL, _ error) { @@ -25,11 +33,22 @@ func (s *Service) FetchUpload(ctx context.Context, uploadID int64) (res UploadWi } return UploadWithURL{ - Upload: upload, - URL: fmt.Sprintf("/sites/%v/uploads/%v/raw", site.ID, upload.ID), + Upload: upload, + CopyTemplate: s.renderCopyTemplate(upload), + URL: fmt.Sprintf("/sites/%v/uploads/%v/raw", site.ID, upload.ID), }, nil } +func (s *Service) renderCopyTemplate(upload models.Upload) string { + var sb strings.Builder + + if err := uploadCopyTemplate.Execute(&sb, upload); err != nil { + log.Printf("error rendering upload copy template: %v", err) + return "" + } + return sb.String() +} + func (s *Service) ListUploads(ctx context.Context) (res []UploadWithURL, _ error) { site, _, err := s.fetchSiteAndUser(ctx) if err != nil { @@ -44,8 +63,9 @@ func (s *Service) ListUploads(ctx context.Context) (res []UploadWithURL, _ error res = make([]UploadWithURL, len(uploads)) for i, upload := range uploads { res[i] = UploadWithURL{ - Upload: upload, - URL: fmt.Sprintf("/sites/%v/uploads/%v/raw", site.ID, upload.ID), + Upload: upload, + CopyTemplate: s.renderCopyTemplate(upload), + URL: fmt.Sprintf("/sites/%v/uploads/%v/raw", site.ID, upload.ID), } } diff --git a/views/uploads/show.html b/views/uploads/show.html index 979b11e..9e778e2 100644 --- a/views/uploads/show.html +++ b/views/uploads/show.html @@ -1,8 +1,8 @@
- + data-controller="show-upload" data-show-upload-copy-snippet-value="{{ .upload.CopyTemplate }}"> +
- {{ .Upload.Alt }} + {{ .upload.Upload.Alt }}
\ No newline at end of file