Compare commits
3 commits
5cc9a1ca76
...
835dd344ca
Author | SHA1 | Date | |
---|---|---|---|
835dd344ca | |||
1a8dc4d9ce | |||
8eb432539c |
22 changed files with 624 additions and 28 deletions
|
@ -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"),
|
||||
))
|
||||
|
|
48
controller/bluesky.go
Normal file
48
controller/bluesky.go
Normal file
|
@ -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
|
||||
}
|
1
go.mod
1
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
|
||||
|
|
2
go.sum
2
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=
|
||||
|
|
1
main.go
1
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) {
|
||||
|
|
41
model/blog.go
Normal file
41
model/blog.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
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"`
|
||||
BlueskyActorID string `db:"bsky_actor"`
|
||||
BlueskyPostID string `db:"bsky_post"`
|
||||
}
|
||||
)
|
||||
|
||||
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")
|
||||
}
|
82
model/bluesky.go
Normal file
82
model/bluesky.go
Normal file
|
@ -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(),
|
||||
)
|
||||
}
|
BIN
public/img/aridoodle.png
Normal file
BIN
public/img/aridoodle.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
0
public/script/blog.js
Normal file
0
public/script/blog.js
Normal file
193
public/style/blog.css
Normal file
193
public/style/blog.css
Normal file
|
@ -0,0 +1,193 @@
|
|||
: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;
|
||||
}
|
||||
|
||||
.blog-date {
|
||||
margin-top: -1em;
|
||||
opacity: .75;
|
||||
}
|
||||
|
||||
.blog p {
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.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 {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 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;
|
||||
}
|
|
@ -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"),
|
||||
))
|
||||
|
|
135
view/blog.go
Normal file
135
view/blog.go
Normal file
|
@ -0,0 +1,135 @@
|
|||
package view
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"arimelody-web/controller"
|
||||
"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:
|
||||
`
|
||||
**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: <span class="col-primary">#b7fd49</span>
|
||||
- secondary: <span class="col-secondary">#f8e05b</span>
|
||||
- tertiary: <span class="col-tertiary">#f788fe</span>
|
||||
|
||||
**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
|
||||
|
||||
### code block test
|
||||
|
||||
~~~ c
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
printf("hello world!~\n");
|
||||
return 0;
|
||||
}
|
||||
~~~
|
||||
|
||||
### aridoodle
|
||||
|
||||
this is `+"`"+`aridoodle`+"`"+`. please take care of her.
|
||||
|
||||

|
||||
`,
|
||||
BlueskyActorID: "did:plc:yct6cvgfipngizry5umzkxr3",
|
||||
BlueskyPostID: "3llsudsx7pc2u",
|
||||
}
|
||||
|
||||
// blog.Markdown += " <i class=\"end-mark\"></i>"
|
||||
|
||||
mdParser := parser.NewWithExtensions(parser.CommonExtensions | parser.AutoHeadingIDs)
|
||||
md := mdParser.Parse([]byte(blog.Markdown))
|
||||
blog.HTML = template.HTML(markdown.Render(md, mdRenderer))
|
||||
|
||||
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
|
||||
}
|
||||
})
|
||||
}
|
86
view/blog.html
Normal file
86
view/blog.html
Normal file
|
@ -0,0 +1,86 @@
|
|||
{{define "head"}}
|
||||
<title>{{.Title}} - ari melody 💫</title>
|
||||
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
||||
|
||||
<meta name="description" content="{{.Description}}">
|
||||
|
||||
<meta property="og:title" content="{{.Title}}">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="og:url" content="www.arimelody.me/blog/{{.Date.Year}}/{{.GetMonth}}/{{.TitleNormalised}}">
|
||||
<meta property="og:image" content="https://www.arimelody.me/img/favicon.png">
|
||||
<meta property="og:site_name" content="ari melody">
|
||||
<meta property="og:description" content="{{.Description}}">
|
||||
|
||||
<link rel="stylesheet" href="/style/main.css">
|
||||
<link rel="stylesheet" href="/style/index.css">
|
||||
<link rel="stylesheet" href="/style/blog.css">
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<main>
|
||||
<article class="blog">
|
||||
<h1 class="typeout">{{.Title}}</h1>
|
||||
<p class="blog-date">Posted by <a href="/blog/{{.AuthorID}}">{{.AuthorID}}</a> @ {{.PrintDate}}</p>
|
||||
|
||||
<hr>
|
||||
{{.HTML}}
|
||||
|
||||
</article>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="interactions">
|
||||
<span class="likes">❤️ {{.Likes}}</span>
|
||||
<span class="reposts">🔁 {{.Reposts}}</span>
|
||||
<a class="btn bluesky" href="{{.BlueskyURL}}" target="_blank">Bluesky 🦋</a>
|
||||
<!-- TODO: mastodon support -->
|
||||
<a class="btn mastodon" href="{{.MastodonURL}}" target="_blank">Mastodon 🐘</a>
|
||||
</div>
|
||||
|
||||
<div class="comments">
|
||||
{{range .Comments}}
|
||||
{{template "comment" .}}
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<script type="module" src="/script/blog.js"></script>
|
||||
</main>
|
||||
{{end}}
|
||||
|
||||
{{define "comment"}}
|
||||
<article class="comment">
|
||||
<div class="comment-hover">
|
||||
<div class="comment-header">
|
||||
<a href="https://bsky.app/profile/{{.Post.Author.DID}}" target="_blank">
|
||||
<img class="avatar" src="{{.Post.Author.Avatar}}" alt="{{.Post.Author.DisplayName}}'s avatar">
|
||||
<span class="display-name">{{.Post.Author.DisplayName}}</span>
|
||||
<span class="handle">@{{.Post.Author.Handle}}</span>
|
||||
</a>
|
||||
</div>
|
||||
<a class="comment-body" href="{{.Post.BskyURL}}" target="_blank">
|
||||
<div>
|
||||
<p class="comment-text">{{.Post.Record.Text}}</p>
|
||||
{{if .Post.HasImage}}
|
||||
<p class="comment-images">
|
||||
{{range .Post.Embed.Media.Images}}
|
||||
<a href="{{.Fullsize}}" target="_blank">[image]</a>
|
||||
{{end}}
|
||||
</p>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="comment-footer">
|
||||
<!-- <span>{{.Post.LikeCount}} likes</span> -->
|
||||
<!-- <span>{{.Post.RepostCount}} reposts</span> -->
|
||||
<span class="comment-date">{{.Post.Record.CreatedAtPrint}}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="comment-replies">
|
||||
{{range .Replies}}
|
||||
{{template "comment" .}}
|
||||
{{end}}
|
||||
</div>
|
||||
</article>
|
||||
{{end}}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue