From 8eb432539ca220b1fc884b04c0288e949cc15a9d Mon Sep 17 00:00:00 2001 From: ari melody Date: Wed, 2 Apr 2025 21:21:19 +0100 Subject: [PATCH 1/3] consolidate `view` and `views` directories --- admin/templates.go | 24 +++++++++---------- templates/templates.go | 37 ++++++++++++++++++------------ {views => view}/404.html | 0 {views => view}/footer.html | 0 {views => view}/header.html | 0 {views => view}/index.html | 0 {views => view}/layout.html | 0 {views => view}/music-gateway.html | 0 {views => view}/music.html | 0 {views => view}/prideflag.html | 0 10 files changed, 34 insertions(+), 27 deletions(-) rename {views => view}/404.html (100%) rename {views => view}/footer.html (100%) rename {views => view}/header.html (100%) rename {views => view}/index.html (100%) rename {views => view}/layout.html (100%) rename {views => view}/music-gateway.html (100%) rename {views => view}/music.html (100%) rename {views => view}/prideflag.html (100%) diff --git a/admin/templates.go b/admin/templates.go index 12cdf08..ab4517e 100644 --- a/admin/templates.go +++ b/admin/templates.go @@ -11,44 +11,44 @@ import ( var indexTemplate = template.Must(template.ParseFiles( filepath.Join("admin", "views", "layout.html"), - filepath.Join("views", "prideflag.html"), + filepath.Join("view", "prideflag.html"), filepath.Join("admin", "components", "release", "release-list-item.html"), filepath.Join("admin", "views", "index.html"), )) var loginTemplate = template.Must(template.ParseFiles( filepath.Join("admin", "views", "layout.html"), - filepath.Join("views", "prideflag.html"), + filepath.Join("view", "prideflag.html"), filepath.Join("admin", "views", "login.html"), )) var loginTOTPTemplate = template.Must(template.ParseFiles( filepath.Join("admin", "views", "layout.html"), - filepath.Join("views", "prideflag.html"), + filepath.Join("view", "prideflag.html"), filepath.Join("admin", "views", "login-totp.html"), )) var registerTemplate = template.Must(template.ParseFiles( filepath.Join("admin", "views", "layout.html"), - filepath.Join("views", "prideflag.html"), + filepath.Join("view", "prideflag.html"), filepath.Join("admin", "views", "register.html"), )) var logoutTemplate = template.Must(template.ParseFiles( filepath.Join("admin", "views", "layout.html"), - filepath.Join("views", "prideflag.html"), + filepath.Join("view", "prideflag.html"), filepath.Join("admin", "views", "logout.html"), )) var accountTemplate = template.Must(template.ParseFiles( filepath.Join("admin", "views", "layout.html"), - filepath.Join("views", "prideflag.html"), + filepath.Join("view", "prideflag.html"), filepath.Join("admin", "views", "edit-account.html"), )) var totpSetupTemplate = template.Must(template.ParseFiles( filepath.Join("admin", "views", "layout.html"), - filepath.Join("views", "prideflag.html"), + filepath.Join("view", "prideflag.html"), filepath.Join("admin", "views", "totp-setup.html"), )) var totpConfirmTemplate = template.Must(template.ParseFiles( filepath.Join("admin", "views", "layout.html"), - filepath.Join("views", "prideflag.html"), + filepath.Join("view", "prideflag.html"), filepath.Join("admin", "views", "totp-confirm.html"), )) @@ -79,23 +79,23 @@ var logsTemplate = template.Must(template.New("layout.html").Funcs(template.Func }, }).ParseFiles( filepath.Join("admin", "views", "layout.html"), - filepath.Join("views", "prideflag.html"), + filepath.Join("view", "prideflag.html"), filepath.Join("admin", "views", "logs.html"), )) var releaseTemplate = template.Must(template.ParseFiles( filepath.Join("admin", "views", "layout.html"), - filepath.Join("views", "prideflag.html"), + filepath.Join("view", "prideflag.html"), filepath.Join("admin", "views", "edit-release.html"), )) var artistTemplate = template.Must(template.ParseFiles( filepath.Join("admin", "views", "layout.html"), - filepath.Join("views", "prideflag.html"), + filepath.Join("view", "prideflag.html"), filepath.Join("admin", "views", "edit-artist.html"), )) var trackTemplate = template.Must(template.ParseFiles( filepath.Join("admin", "views", "layout.html"), - filepath.Join("views", "prideflag.html"), + filepath.Join("view", "prideflag.html"), filepath.Join("admin", "components", "release", "release-list-item.html"), filepath.Join("admin", "views", "edit-track.html"), )) diff --git a/templates/templates.go b/templates/templates.go index 8d1a5ca..4c5793b 100644 --- a/templates/templates.go +++ b/templates/templates.go @@ -6,23 +6,30 @@ import ( ) var IndexTemplate = template.Must(template.ParseFiles( - filepath.Join("views", "layout.html"), - filepath.Join("views", "header.html"), - filepath.Join("views", "footer.html"), - filepath.Join("views", "prideflag.html"), - filepath.Join("views", "index.html"), + filepath.Join("view", "layout.html"), + filepath.Join("view", "header.html"), + filepath.Join("view", "footer.html"), + filepath.Join("view", "prideflag.html"), + filepath.Join("view", "index.html"), )) var MusicTemplate = template.Must(template.ParseFiles( - filepath.Join("views", "layout.html"), - filepath.Join("views", "header.html"), - filepath.Join("views", "footer.html"), - filepath.Join("views", "prideflag.html"), - filepath.Join("views", "music.html"), + filepath.Join("view", "layout.html"), + filepath.Join("view", "header.html"), + filepath.Join("view", "footer.html"), + filepath.Join("view", "prideflag.html"), + filepath.Join("view", "music.html"), )) var MusicGatewayTemplate = template.Must(template.ParseFiles( - filepath.Join("views", "layout.html"), - filepath.Join("views", "header.html"), - filepath.Join("views", "footer.html"), - filepath.Join("views", "prideflag.html"), - filepath.Join("views", "music-gateway.html"), + filepath.Join("view", "layout.html"), + filepath.Join("view", "header.html"), + filepath.Join("view", "footer.html"), + filepath.Join("view", "prideflag.html"), + filepath.Join("view", "music-gateway.html"), +)) +var BlogTemplate = template.Must(template.ParseFiles( + filepath.Join("view", "layout.html"), + filepath.Join("view", "header.html"), + filepath.Join("view", "footer.html"), + filepath.Join("view", "prideflag.html"), + filepath.Join("view", "blog.html"), )) diff --git a/views/404.html b/view/404.html similarity index 100% rename from views/404.html rename to view/404.html diff --git a/views/footer.html b/view/footer.html similarity index 100% rename from views/footer.html rename to view/footer.html diff --git a/views/header.html b/view/header.html similarity index 100% rename from views/header.html rename to view/header.html diff --git a/views/index.html b/view/index.html similarity index 100% rename from views/index.html rename to view/index.html diff --git a/views/layout.html b/view/layout.html similarity index 100% rename from views/layout.html rename to view/layout.html diff --git a/views/music-gateway.html b/view/music-gateway.html similarity index 100% rename from views/music-gateway.html rename to view/music-gateway.html diff --git a/views/music.html b/view/music.html similarity index 100% rename from views/music.html rename to view/music.html diff --git a/views/prideflag.html b/view/prideflag.html similarity index 100% rename from views/prideflag.html rename to view/prideflag.html From 1a8dc4d9ce873757e29d048b9dfc6dbceafe93bd Mon Sep 17 00:00:00 2001 From: ari melody Date: Wed, 2 Apr 2025 23:04:09 +0100 Subject: [PATCH 2/3] basic blog page! --- go.mod | 1 + go.sum | 2 + main.go | 1 + model/blog.go | 39 ++++++++++++++++++ public/img/aridoodle.png | Bin 0 -> 1110 bytes public/script/blog.js | 0 public/style/blog.css | 46 +++++++++++++++++++++ view/blog.go | 86 +++++++++++++++++++++++++++++++++++++++ view/blog.html | 34 ++++++++++++++++ 9 files changed, 209 insertions(+) create mode 100644 model/blog.go create mode 100644 public/img/aridoodle.png create mode 100644 public/script/blog.js create mode 100644 public/style/blog.css create mode 100644 view/blog.go create mode 100644 view/blog.html diff --git a/go.mod b/go.mod index a1c6c76..e9fd53c 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( require golang.org/x/crypto v0.27.0 // indirect require ( + github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect diff --git a/go.sum b/go.sum index f2ec7e7..d817720 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b h1:EY/KpStFl60qA17CptGXhwfZ+k1sFNJIUNR8DdbcuUk= +github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= diff --git a/main.go b/main.go index 2252622..8de29a3 100644 --- a/main.go +++ b/main.go @@ -448,6 +448,7 @@ func createServeMux(app *model.AppState) *http.ServeMux { mux.Handle("/admin/", http.StripPrefix("/admin", admin.Handler(app))) mux.Handle("/api/", http.StripPrefix("/api", api.Handler(app))) mux.Handle("/music/", http.StripPrefix("/music", view.MusicHandler(app))) + mux.Handle("/blog/", http.StripPrefix("/blog", view.BlogHandler(app))) mux.Handle("/uploads/", http.StripPrefix("/uploads", staticHandler(filepath.Join(app.Config.DataDirectory, "uploads")))) mux.Handle("/cursor-ws", cursor.Handler(app)) mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/model/blog.go b/model/blog.go new file mode 100644 index 0000000..89f37a0 --- /dev/null +++ b/model/blog.go @@ -0,0 +1,39 @@ +package model + +import ( + "fmt" + "html/template" + "regexp" + "strings" + "time" +) + +type ( + Blog struct { + Title string `db:"title"` + Description string `db:"description"` + Visible bool `db:"visible"` + Date time.Time `db:"date"` + AuthorID string `db:"author"` + Markdown string `db:"markdown"` + HTML template.HTML `db:"html"` + } +) + +func (b *Blog) TitleNormalised() string { + rgx := regexp.MustCompile(`[^a-z0-9\-]`) + return rgx.ReplaceAllString( + strings.ReplaceAll( + strings.ToLower(b.Title), " ", "-", + ), + "", + ) +} + +func (b *Blog) GetMonth() string { + return fmt.Sprintf("%02d", int(b.Date.Month())) +} + +func (b *Blog) PrintDate() string { + return b.Date.Format("02 January 2006") +} diff --git a/public/img/aridoodle.png b/public/img/aridoodle.png new file mode 100644 index 0000000000000000000000000000000000000000..c8328b55ab907893ab1ed1e9057a683ab5881059 GIT binary patch literal 1110 zcmV-c1gZOpP)Px#1ZP1_K>z@;j|==^1poj52~bQ_MF0Q*|Ns900048wgK+==00VSVPE-H?0N2V5 zK>z>%32;bRa{vGi!vFvd!vV){sAK>D1II~3K~!i%&63-495Dz5ZU6sAhjWnx8ZF<@zHvXA4RyiRPMq&thr6M6JDN_MwnpZkW5(_T zH?|#TBq=^^WD|TQ9G#%VETit+RG&~@aZZWZjRUN9cnq8zjh+c^J|*@kF|Cn{kBI|H z%rqt?_a1`_O3X4&#+u7lV2={h7{lWt@YxwG(J^qx>lPpOc&9i=twCNIrEYt)AuOuD zF>lS9#Q|;(WGerTd2N-dZsA2BmtKXLB0gj~9L(0tEEMB6kYUHiUVC?bx1YT8R zT>A^g61N|L?Pbg^;gvUyA2hOBjG_M!E{|7odIz z9$kzD?mdS$#duLNBwJhI5M#X6IF+MR8n6r8P8=sTBRBeT;0QRWbiYyMFfw|BX_RLc z0n^1b*$5SEd@Ay7=ME2ljN+qF*RBEkGu*)5*|_jcLt4-$-erVRD{B>$Cfl7Xl*y$x z?Q^5W3*CxBx%94eE(FcpwvkaRm)`4oxpY%Eg4wE&ORu-ndUv_4C>TY&a#@(SQ)x!9 z4^ocL!N{T>=F2ibkHGOXqA5p!p_z$4la-^R5Jog}=Ij2D1rxy>fzn|_y^i?JSHa50 z*D%)=+G^xjuZqQrW+B+FK*H$*tx2`4??@m73k1G_OR1BV+P1}mTqT#UOja1NXp2YRUshB2QJWVh@~)CJRcQQk_M z1?mD7NVJr2h)+T@;>1uCyf$V^f;mL`ZpNx~og=etElr{^y3Wx68957w5z2D(!YF)O z*^OGYkx53giEk^_sKtpiiO94|bh(p_+KWwpPe$*%+DRK`b=D+2zQr3-fYe))nDZN| zQ4NU3mQ3C0DMr-*oh_NV)RT;=1bSNv&y70=;rETFpZGJzSIVQ&ijD6Yac9NOUou*7 z;d{oLpZMS(^4E;MZ}<`8y|S6&#^()~-YVZVzWkBB(Z?+}Moz9nT==->sMMf0zWQ4S z-p(jK7>`a*i4P`6^yMjnU1!{F0GMmv8g; cjbC5?0Q}4$TpJSbz5oCK07*qoM6N<$f`cvjV*mgE literal 0 HcmV?d00001 diff --git a/public/script/blog.js b/public/script/blog.js new file mode 100644 index 0000000..e69de29 diff --git a/public/style/blog.css b/public/style/blog.css new file mode 100644 index 0000000..a333120 --- /dev/null +++ b/public/style/blog.css @@ -0,0 +1,46 @@ +main { + margin: 0 auto 1rem auto; +} + +article.blog { + font-family: 'Lora', serif; +} + +.blog-date { + margin-top: -1em; + opacity: .75; +} + +.blog p { + line-height: 1.5em; +} + +.blog p:hover { + background: inherit; +} + +.blog sub { + opacity: .75; +} + +.blog img { + margin: 0 auto; + max-height: 50%; + max-width: 100%; + + display: block; +} + +.blog i.end-mark { + width: 1.2em; + height: 1.2em; + margin-top: -.2em; + + display: inline-block; + transform: translateY(.2em); + + background: url("/img/aridoodle.png"); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} diff --git a/view/blog.go b/view/blog.go new file mode 100644 index 0000000..85da400 --- /dev/null +++ b/view/blog.go @@ -0,0 +1,86 @@ +package view + +import ( + "html/template" + "net/http" + "time" + + "arimelody-web/model" + "arimelody-web/templates" + + "github.com/gomarkdown/markdown" + "github.com/gomarkdown/markdown/html" + "github.com/gomarkdown/markdown/parser" +) + +var mdRenderer = html.NewRenderer(html.RendererOptions{ + Flags: html.CommonFlags | html.HrefTargetBlank, +}) + +func BlogHandler(app *model.AppState) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + blog := model.Blog{ + Title: "hello world!~", + Description: "lorem ipsum yadda yadda something boobies babababababababa", + Visible: true, + Date: time.Now(), + AuthorID: "ari", + Markdown: +` +# hello, world! + +i'm ari! + +she/her 🏳️‍⚧️🏳️‍🌈💫🦆🇮🇪 + +i'm a musician, developer, streamer, youtuber, and probably a bunch of other things i forgot to mention! + +you're very welcome to take a look around my little space on the internet here, or explore any of the other parts i inhabit! + +if you're looking to support me financially, that's so cool of you!! if you like, you can buy some of my music over on bandcamp so you can at least get something for your money. thank you very much either way!! 💕 + +for anything else, you can reach me for any and all communications through ari@arimelody.me. if your message contains anything beyond a silly gag, i strongly recommend encrypting your message using my public pgp key, listed below! + +thank you for stopping by- i hope you have a lovely rest of your day! 💫 + +## metadata + +**my colours 🌈** + +- primary: #b7fd49 +- secondary: #f8e05b +- tertiary: #f788fe + +**my keys 🔑** + +- pgp: [[link]](/keys/ari%20melody_0x92678188_public.asc) +- ssh (ed25519): [[link]](/keys/id_ari_ed25519.pub) + +**where to find me 🛰️** + +- youtube +- twitch +- spotify +- bandcamp +- github + +**projects i've worked on 🛠️** + +- catdance +- pride flag +- ipaddrgen +- impact meme +- OpenTerminal +- Silver.js +`, + } + + // blog.Markdown += " " + + mdParser := parser.NewWithExtensions(parser.CommonExtensions | parser.AutoHeadingIDs) + md := mdParser.Parse([]byte(blog.Markdown)) + blog.HTML = template.HTML(markdown.Render(md, mdRenderer)) + + templates.BlogTemplate.Execute(w, &blog) + }) +} diff --git a/view/blog.html b/view/blog.html new file mode 100644 index 0000000..60f3c4b --- /dev/null +++ b/view/blog.html @@ -0,0 +1,34 @@ +{{define "head"}} +{{.Title}} - ari melody 💫 + + + + + + + + + + + + + + +{{end}} + +{{define "content"}} +
+
+

{{.Title}}

+

Posted by {{.AuthorID}} @ {{.PrintDate}}

+ +
+ {{.HTML}} +
+ + +
+ + +
+{{end}} From 835dd344ca9b611d61af222e7b20d7dbd0ba5be0 Mon Sep 17 00:00:00 2001 From: ari melody Date: Wed, 2 Apr 2025 23:49:20 +0100 Subject: [PATCH 3/3] style improvements and bluesky comments! --- controller/bluesky.go | 48 +++++++++++++ model/blog.go | 2 + model/bluesky.go | 82 ++++++++++++++++++++++ public/style/blog.css | 157 ++++++++++++++++++++++++++++++++++++++++-- view/blog.go | 59 ++++++++++++++-- view/blog.html | 56 ++++++++++++++- view/music.go | 2 +- 7 files changed, 393 insertions(+), 13 deletions(-) create mode 100644 controller/bluesky.go create mode 100644 model/bluesky.go diff --git a/controller/bluesky.go b/controller/bluesky.go new file mode 100644 index 0000000..0e382df --- /dev/null +++ b/controller/bluesky.go @@ -0,0 +1,48 @@ +package controller + +import ( + "arimelody-web/model" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "strings" +) + +const BSKY_API_BASE = "https://public.api.bsky.app" + +func FetchThreadViewPost(actorID string, postID string) (*model.ThreadViewPost, error) { + uri := fmt.Sprintf("at://%s/app.bsky.feed.post/%s", actorID, postID) + + req, err := http.NewRequest( + http.MethodGet, + strings.Join([]string{BSKY_API_BASE, "xrpc", "app.bsky.feed.getPostThread"}, "/"), + nil, + ) + if err != nil { panic(err) } + + req.URL.RawQuery = url.Values{ + "uri": { uri }, + }.Encode() + req.Header.Set("User-Agent", "ari melody [https://arimelody.me]") + req.Header.Set("Accept", "application/json") + + client := &http.Client{} + + res, err := client.Do(req) + if err != nil { + return nil, errors.New(fmt.Sprintf("Failed to call Bluesky API: %v", err)) + } + + type Data struct { + Thread model.ThreadViewPost `json:"thread"` + } + data := Data{} + err = json.NewDecoder(res.Body).Decode(&data) + if err != nil { + return nil, errors.New(fmt.Sprintf("Invalid response from server: %v", err)) + } + + return &data.Thread, nil +} diff --git a/model/blog.go b/model/blog.go index 89f37a0..7f0c87b 100644 --- a/model/blog.go +++ b/model/blog.go @@ -17,6 +17,8 @@ type ( AuthorID string `db:"author"` Markdown string `db:"markdown"` HTML template.HTML `db:"html"` + BlueskyActorID string `db:"bsky_actor"` + BlueskyPostID string `db:"bsky_post"` } ) diff --git a/model/bluesky.go b/model/bluesky.go new file mode 100644 index 0000000..dd8391e --- /dev/null +++ b/model/bluesky.go @@ -0,0 +1,82 @@ +package model + +import ( + "fmt" + "strings" + "time" +) + +type ( + Record struct { + Type string `json:"$type"` + CreatedAt string `json:"createdAt"` + Text string `json:"text"` + } + + Profile struct { + DID string `json:"did"` + Handle string `json:"handle"` + Avatar string `json:"avatar"` + DisplayName string `json:"displayName"` + CreatedAt string `json:"createdAt"` + } + + PostImage struct { + Thumbnail string `json:"thumb"` + Fullsize string `json:"fullsize"` + Alt string `json:"alt"` + } + + EmbedMedia struct { + Images []PostImage `json:"images"` + } + + Embed struct { + Media EmbedMedia `json:"media"` + } + + Post struct { + Author Profile `json:"author"` + Record Record `json:"record"` + ReplyCount int `json:"replyCount"` + RepostCount int `json:"repostCount"` + LikeCount int `json:"likeCount"` + QuoteCount int `json:"quoteCount"` + Embed *Embed `json:"embed"` + URI string `json:"uri"` + } + + ThreadViewPost struct { + Post Post `json:"post"` + Replies []*ThreadViewPost `json:"replies"` + } +) + +func (record *Record) CreatedAtPrint() (string, error) { + t, err := record.CreatedAtTime() + if err != nil { return "", err } + return t.Format("15:04, 2 February 2006"), nil +} + +func (record *Record) CreatedAtTime() (time.Time, error) { + return time.Parse("2006-01-02T15:04:05Z", record.CreatedAt) +} + +func (post *Post) HasImage() bool { + return post.Embed != nil && len(post.Embed.Media.Images) > 0 +} + +func (post *Post) PostID() string { + return strings.TrimPrefix( + post.URI, + fmt.Sprintf("at://%s/app.bsky.feed.post/", post.Author.DID), + ) +} + +func (post *Post) BskyURL() string { + return fmt.Sprintf( + "https://bsky.app/profile/%s/post/%s", + post.Author.DID, + post.PostID(), + ) +} diff --git a/public/style/blog.css b/public/style/blog.css index a333120..7f424d5 100644 --- a/public/style/blog.css +++ b/public/style/blog.css @@ -1,7 +1,20 @@ +:root { + --like: rgb(223, 104, 104); + --repost: rgb(162, 223, 73); + --bluesky: rgb(16, 131, 254); + --mastodon: rgb(86, 58, 204); +} + main { + width: min(calc(100% - 4rem), 900px); margin: 0 auto 1rem auto; } +.blog p:hover, +.comment p:hover { + background: inherit; +} + article.blog { font-family: 'Lora', serif; } @@ -15,16 +28,29 @@ article.blog { line-height: 1.5em; } -.blog p:hover { - background: inherit; -} - .blog sub { opacity: .75; } +.blog pre { + max-height: 15em; + padding: .5em; + font-size: .9em; + border: 1px solid #8884; + border-radius: 2px; + overflow: scroll; + background: #060606; +} + +.blog p code { + padding: .2em .3em; + font-size: .9em; + border: 1px solid #8884; + border-radius: 2px; + background: #060606; +} + .blog img { - margin: 0 auto; max-height: 50%; max-width: 100%; @@ -44,3 +70,124 @@ article.blog { background-repeat: no-repeat; background-position: center; } + + + +/* COMMENTS */ + +.interactions { + margin: 1em 0; + display: flex; + flex-direction: row; + gap: .5em; + flex-wrap: wrap; +} + +.btn { + display: inline-block; + padding: .4em .6em; + border: 1px solid var(--on-background); + border-radius: 2px; + color: inherit; + text-decoration: none; + font-weight: 600; +} + +.interactions .likes, +.interactions .reposts { + padding: 0 .5em; + min-width: fit-content; + font-family: monospace; + text-align: center; + line-height: 2em; + border: 1px solid var(--on-background); + border-radius: 2px; + text-wrap: nowrap; +} + +.interactions .likes { + border-color: var(--on-background); +} +.interactions .reposts { + border-color: var(--on-background); +} + +.btn.bluesky, +.btn.mastodon { + font-family: monospace; + text-wrap: nowrap; +} + +.btn.bluesky { + color: var(--bluesky); + border-color: var(--bluesky); +} + +.btn.mastodon { + color: var(--mastodon); + border-color: var(--mastodon); +} + +.comment { + font-family: 'Inter', 'Arial', sans-serif; + font-size: 1em; +} + +.comment .comment-hover { + padding: 1em; + transition: background-color .1s; +} + +.comment .comment-hover:hover { + background-color: #8881; +} + +.comment .comment-header a { + display: flex; + gap: .5em; + font-weight: 600; + color: var(--primary); + text-decoration: none; + align-items: center; + overflow: hidden; + text-overflow: ellipsis; +} + +.comment .comment-header a .display-name { + overflow: inherit; + text-overflow: inherit; +} + +.comment .comment-header a .handle { + opacity: .5; + font-family: monospace; + font-size: .9em; + overflow: inherit; + text-overflow: inherit; +} + +.comment .comment-header img.avatar { + width: 1.5em; + height: 1.5em; +} + +.comment .comment-body { + color: inherit; + text-decoration: none; +} + +.comment p.comment-text { + margin: .5em 0; + white-space: break-spaces; +} + +.comment .comment-footer .comment-date { + margin: 0; + font-size: .8em; + opacity: .5; +} + +.comment .comment-replies { + margin-left: 1em; + border-left: 2px solid #8884; +} diff --git a/view/blog.go b/view/blog.go index 85da400..9ab52d2 100644 --- a/view/blog.go +++ b/view/blog.go @@ -1,10 +1,13 @@ package view import ( + "fmt" "html/template" "net/http" + "os" "time" + "arimelody-web/controller" "arimelody-web/model" "arimelody-web/templates" @@ -20,16 +23,14 @@ var mdRenderer = html.NewRenderer(html.RendererOptions{ func BlogHandler(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { blog := model.Blog{ - Title: "hello world!~", + Title: "hello world!", Description: "lorem ipsum yadda yadda something boobies babababababababa", Visible: true, Date: time.Now(), AuthorID: "ari", Markdown: ` -# hello, world! - -i'm ari! +**i'm ari!** she/her 🏳️‍⚧️🏳️‍🌈💫🦆🇮🇪 @@ -72,7 +73,26 @@ thank you for stopping by- i hope you have a lovely rest of your day! 💫 - impact meme - OpenTerminal - Silver.js + +### code block test + +~~~ c +#include + +int main(int argc, char *argv[]) { + printf("hello world!~\n"); + return 0; +} +~~~ + +### aridoodle + +this is `+"`"+`aridoodle`+"`"+`. please take care of her. + +![aridoodle](/img/aridoodle.png) `, + BlueskyActorID: "did:plc:yct6cvgfipngizry5umzkxr3", + BlueskyPostID: "3llsudsx7pc2u", } // blog.Markdown += " " @@ -81,6 +101,35 @@ thank you for stopping by- i hope you have a lovely rest of your day! 💫 md := mdParser.Parse([]byte(blog.Markdown)) blog.HTML = template.HTML(markdown.Render(md, mdRenderer)) - templates.BlogTemplate.Execute(w, &blog) + comments := []*model.ThreadViewPost{} + blueskyPost, err := controller.FetchThreadViewPost(blog.BlueskyActorID, blog.BlueskyPostID) + if err != nil { + fmt.Fprintf(os.Stderr, "WARN: Failed to fetch blog post Bluesky thread: %v\n", err) + } else { + comments = append(comments, blueskyPost.Replies...) + } + + type BlogView struct { + *model.Blog + Comments []*model.ThreadViewPost + Likes int + Reposts int + BlueskyURL string + MastodonURL string + } + + err = templates.BlogTemplate.Execute(w, BlogView{ + Blog: &blog, + Comments: blueskyPost.Replies, + Likes: 10, + Reposts: 10, + BlueskyURL: fmt.Sprintf("https://bsky.app/profile/%s/post/%s", blog.BlueskyActorID, blog.BlueskyPostID), + MastodonURL: "#", + }) + if err != nil { + fmt.Fprintf(os.Stderr, "Error rendering blog post: %v\n", err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } }) } diff --git a/view/blog.html b/view/blog.html index 60f3c4b..f858252 100644 --- a/view/blog.html +++ b/view/blog.html @@ -24,11 +24,63 @@
{{.HTML}} -
- +
+ +
+ + 🔁 {{.Reposts}} + Bluesky 🦋 + + Mastodon 🐘 +
+ +
+ {{range .Comments}} + {{template "comment" .}} + {{end}} +
+ {{end}} + +{{define "comment"}} + +{{end}} + diff --git a/view/music.go b/view/music.go index 2d40ef0..3c27b6e 100644 --- a/view/music.go +++ b/view/music.go @@ -89,7 +89,7 @@ func ServeGateway(app *model.AppState, release *model.Release) http.Handler { err := templates.MusicGatewayTemplate.Execute(w, response) if err != nil { - fmt.Printf("Error rendering music gateway for %s: %s\n", release.ID, err) + fmt.Fprintf(os.Stderr, "Error rendering music gateway for %s: %v\n", release.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return }