Compare commits
2 commits
3da0249555
...
faf6095d16
Author | SHA1 | Date | |
---|---|---|---|
faf6095d16 | |||
3d64333b4f |
83
controller/blog.go
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"arimelody-web/model"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetBlogPost(db *sqlx.DB, id string) (*model.BlogPost, error) {
|
||||||
|
var blog = model.BlogPost{}
|
||||||
|
|
||||||
|
err := db.Get(&blog, "SELECT * FROM blogpost WHERE id=$1", id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &blog, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBlogPosts(db *sqlx.DB, onlyVisible bool, limit int, offset int) ([]*model.BlogPost, error) {
|
||||||
|
var blogs = []*model.BlogPost{}
|
||||||
|
|
||||||
|
query := "SELECT * FROM blogpost ORDER BY created_at"
|
||||||
|
if onlyVisible {
|
||||||
|
query = "SELECT * FROM blogpost WHERE visible=true ORDER BY created_at"
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if limit < 0 {
|
||||||
|
err = db.Select(&blogs, query)
|
||||||
|
} else {
|
||||||
|
err = db.Select(&blogs, query + " LIMIT $1 OFFSET $2", limit, offset)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// for range 4 {
|
||||||
|
// blog := *blogs[len(blogs)-1]
|
||||||
|
// blog.CreatedAt = blog.CreatedAt.Add(time.Hour * -5000)
|
||||||
|
// blogs = append(blogs, &blog)
|
||||||
|
// }
|
||||||
|
|
||||||
|
return blogs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateBlogPost(db *sqlx.DB, post *model.BlogPost) error {
|
||||||
|
_, err := db.Exec(
|
||||||
|
"INSERT INTO blogpost (id,title,description,visible,author,markdown,html,bluesky_actor,bluesky_post) " +
|
||||||
|
"VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)",
|
||||||
|
post.ID,
|
||||||
|
post.Title,
|
||||||
|
post.Description,
|
||||||
|
post.Visible,
|
||||||
|
post.AuthorID,
|
||||||
|
post.Markdown,
|
||||||
|
post.HTML,
|
||||||
|
post.BlueskyActorID,
|
||||||
|
post.BlueskyPostID,
|
||||||
|
)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateBlogPost(db *sqlx.DB, postID string, post *model.BlogPost) error {
|
||||||
|
_, err := db.Exec(
|
||||||
|
"UPDATE blogpost SET " +
|
||||||
|
"id=$2,title=$3,description=$4,visible=$5,author=$6,markdown=$7,html=$8,bluesky_actor=$9,bluesky_post=$10,modified_at=CURRENT_TIMESTAMP " +
|
||||||
|
"WHERE id=$1",
|
||||||
|
postID,
|
||||||
|
post.ID,
|
||||||
|
post.Title,
|
||||||
|
post.Description,
|
||||||
|
post.Visible,
|
||||||
|
post.AuthorID,
|
||||||
|
post.Markdown,
|
||||||
|
post.HTML,
|
||||||
|
post.BlueskyActorID,
|
||||||
|
post.BlueskyPostID,
|
||||||
|
)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DB_VERSION int = 4
|
const DB_VERSION int = 5
|
||||||
|
|
||||||
func CheckDBVersionAndMigrate(db *sqlx.DB) {
|
func CheckDBVersionAndMigrate(db *sqlx.DB) {
|
||||||
db.MustExec("CREATE SCHEMA IF NOT EXISTS arimelody")
|
db.MustExec("CREATE SCHEMA IF NOT EXISTS arimelody")
|
||||||
|
@ -49,6 +49,10 @@ func CheckDBVersionAndMigrate(db *sqlx.DB) {
|
||||||
ApplyMigration(db, "003-fail-lock")
|
ApplyMigration(db, "003-fail-lock")
|
||||||
oldDBVersion = 4
|
oldDBVersion = 4
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
ApplyMigration(db, "004-blog")
|
||||||
|
oldDBVersion = 5
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -9,20 +10,22 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Blog struct {
|
BlogPost struct {
|
||||||
Title string `db:"title"`
|
ID string `db:"id"`
|
||||||
Description string `db:"description"`
|
Title string `db:"title"`
|
||||||
Visible bool `db:"visible"`
|
Description string `db:"description"`
|
||||||
Date time.Time `db:"date"`
|
Visible bool `db:"visible"`
|
||||||
AuthorID string `db:"author"`
|
CreatedAt time.Time `db:"created_at"`
|
||||||
Markdown string `db:"markdown"`
|
ModifiedAt sql.NullTime `db:"modified_at"`
|
||||||
HTML template.HTML `db:"html"`
|
AuthorID string `db:"author"`
|
||||||
BlueskyActorID string `db:"bsky_actor"`
|
Markdown string `db:"markdown"`
|
||||||
BlueskyPostID string `db:"bsky_post"`
|
HTML template.HTML `db:"html"`
|
||||||
|
BlueskyActorID *string `db:"bluesky_actor"`
|
||||||
|
BlueskyPostID *string `db:"bluesky_post"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *Blog) TitleNormalised() string {
|
func (b *BlogPost) TitleNormalised() string {
|
||||||
rgx := regexp.MustCompile(`[^a-z0-9\-]`)
|
rgx := regexp.MustCompile(`[^a-z0-9\-]`)
|
||||||
return rgx.ReplaceAllString(
|
return rgx.ReplaceAllString(
|
||||||
strings.ReplaceAll(
|
strings.ReplaceAll(
|
||||||
|
@ -32,10 +35,17 @@ func (b *Blog) TitleNormalised() string {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Blog) GetMonth() string {
|
func (b *BlogPost) GetMonth() string {
|
||||||
return fmt.Sprintf("%02d", int(b.Date.Month()))
|
return fmt.Sprintf("%02d", int(b.CreatedAt.Month()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Blog) PrintDate() string {
|
func (b *BlogPost) PrintDate() string {
|
||||||
return b.Date.Format("02 January 2006")
|
return b.CreatedAt.Format("2 January 2006, 03:04")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BlogPost) PrintModifiedDate() string {
|
||||||
|
if !b.ModifiedAt.Valid {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return b.ModifiedAt.Time.Format("2 January 2006, 03:04")
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ type (
|
||||||
func (record *Record) CreatedAtPrint() (string, error) {
|
func (record *Record) CreatedAtPrint() (string, error) {
|
||||||
t, err := record.CreatedAtTime()
|
t, err := record.CreatedAtTime()
|
||||||
if err != nil { return "", err }
|
if err != nil { return "", err }
|
||||||
return t.Format("15:04, 2 February 2006"), nil
|
return t.Format("2 Jan 2006, 15:04"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (record *Record) CreatedAtTime() (time.Time, error) {
|
func (record *Record) CreatedAtTime() (time.Time, error) {
|
||||||
|
|
1
public/img/blog/bluesky-dark.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 31 31" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><rect id="bluesky-dark" x="0" y="0" width="30.72" height="30.72" style="fill:none;"/><path d="M9.176,7.042c2.503,1.879 5.195,5.69 6.184,7.734c0.989,-2.044 3.681,-5.855 6.184,-7.734c1.806,-1.356 4.733,-2.405 4.733,0.933c-0,0.667 -0.383,5.601 -0.607,6.403c-0.779,2.785 -3.619,3.495 -6.145,3.065c4.415,0.752 5.539,3.241 3.113,5.73c-4.608,4.728 -6.622,-1.186 -7.138,-2.701c-0.095,-0.278 -0.139,-0.408 -0.14,-0.298c-0.001,-0.11 -0.045,0.02 -0.14,0.298c-0.516,1.515 -2.53,7.429 -7.138,2.701c-2.426,-2.489 -1.302,-4.978 3.113,-5.73c-2.526,0.43 -5.366,-0.28 -6.145,-3.065c-0.224,-0.802 -0.607,-5.736 -0.607,-6.403c0,-3.338 2.927,-2.289 4.733,-0.933Z" style="fill:#fff;fill-rule:nonzero;"/></svg>
|
After Width: | Height: | Size: 1.1 KiB |
1
public/img/blog/bluesky-light.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 31 31" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><rect id="bluesky-light" x="0" y="0" width="30.72" height="30.72" style="fill:none;"/><path d="M9.176,7.042c2.503,1.879 5.195,5.69 6.184,7.734c0.989,-2.044 3.681,-5.855 6.184,-7.734c1.806,-1.356 4.733,-2.405 4.733,0.933c-0,0.667 -0.383,5.601 -0.607,6.403c-0.779,2.785 -3.619,3.495 -6.145,3.065c4.415,0.752 5.539,3.241 3.113,5.73c-4.608,4.728 -6.622,-1.186 -7.138,-2.701c-0.095,-0.278 -0.139,-0.408 -0.14,-0.298c-0.001,-0.11 -0.045,0.02 -0.14,0.298c-0.516,1.515 -2.53,7.429 -7.138,2.701c-2.426,-2.489 -1.302,-4.978 3.113,-5.73c-2.526,0.43 -5.366,-0.28 -6.145,-3.065c-0.224,-0.802 -0.607,-5.736 -0.607,-6.403c0,-3.338 2.927,-2.289 4.733,-0.933Z" style="fill-rule:nonzero;"/></svg>
|
After Width: | Height: | Size: 1.1 KiB |
1
public/img/blog/boost-dark.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 31 31" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><rect id="boost" x="0" y="0" width="30.72" height="30.72" style="fill:none;"/><path d="M20.14,5.466c1.708,-0.458 4.028,2.658 5.179,6.953c1.151,4.295 0.699,8.154 -1.008,8.611c-1.708,0.458 -4.029,-2.658 -5.179,-6.953c-1.151,-4.295 -0.699,-8.153 1.008,-8.611Zm0.954,3.56c-0.08,1.236 0.101,2.772 0.532,4.383c0.432,1.611 1.043,3.031 1.731,4.062c0.08,-1.237 -0.101,-2.772 -0.533,-4.383c-0.432,-1.611 -1.042,-3.031 -1.73,-4.062Zm-12.3,12.963l-0.907,0.056c-1.177,0.073 -2.242,-0.693 -2.548,-1.833l-0.468,-1.747c-0.305,-1.139 0.234,-2.336 1.29,-2.861l10.773,-8.542c0.173,-0.086 0.379,-0.064 0.53,0.056c0.151,0.12 0.218,0.316 0.174,0.503c-0.318,1.746 -0.19,4.164 0.496,6.724c0.685,2.559 1.784,4.717 2.928,6.074c0.132,0.138 0.171,0.34 0.101,0.517c-0.07,0.178 -0.236,0.299 -0.427,0.311c-1.7,0.108 -5.142,0.322 -8.287,0.516c-0.044,0.385 -0.09,0.771 -0.131,1.119c-0.091,0.771 -0.713,1.371 -1.486,1.435l-0.219,0.018c-0.647,0.054 -1.241,-0.355 -1.422,-0.978l-0.397,-1.368Z" style="fill:#fff;"/></svg>
|
After Width: | Height: | Size: 1.4 KiB |
1
public/img/blog/boost-light.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 31 31" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><rect id="boost" x="0" y="0" width="30.72" height="30.72" style="fill:none;"/><path d="M20.14,5.466c1.708,-0.458 4.028,2.658 5.179,6.953c1.151,4.295 0.699,8.154 -1.008,8.611c-1.708,0.458 -4.029,-2.658 -5.179,-6.953c-1.151,-4.295 -0.699,-8.153 1.008,-8.611Zm0.954,3.56c-0.08,1.236 0.101,2.772 0.532,4.383c0.432,1.611 1.043,3.031 1.731,4.062c0.08,-1.237 -0.101,-2.772 -0.533,-4.383c-0.432,-1.611 -1.042,-3.031 -1.73,-4.062Zm-12.3,12.963l-0.907,0.056c-1.177,0.073 -2.242,-0.693 -2.548,-1.833l-0.468,-1.747c-0.305,-1.139 0.234,-2.336 1.29,-2.861l10.773,-8.542c0.173,-0.086 0.379,-0.064 0.53,0.056c0.151,0.12 0.218,0.316 0.174,0.503c-0.318,1.746 -0.19,4.164 0.496,6.724c0.685,2.559 1.784,4.717 2.928,6.074c0.132,0.138 0.171,0.34 0.101,0.517c-0.07,0.178 -0.236,0.299 -0.427,0.311c-1.7,0.108 -5.142,0.322 -8.287,0.516c-0.044,0.385 -0.09,0.771 -0.131,1.119c-0.091,0.771 -0.713,1.371 -1.486,1.435l-0.219,0.018c-0.647,0.054 -1.241,-0.355 -1.422,-0.978l-0.397,-1.368Z"/></svg>
|
After Width: | Height: | Size: 1.4 KiB |
1
public/img/blog/comment-dark.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 31 31" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><rect id="comment" x="0" y="0" width="30.72" height="30.72" style="fill:none;"/><path d="M26.117,14.576c-0,4.724 -4.82,8.559 -10.757,8.559c-1.015,-0 -2.025,-0.114 -3,-0.34l-5.008,1.907l-0.007,-4.418c-1.765,-1.569 -2.742,-3.601 -2.742,-5.708c0,-4.723 4.82,-8.558 10.757,-8.558c5.937,-0 10.757,3.835 10.757,8.558Zm-15.557,-1.135c-0.795,-0 -1.44,0.645 -1.44,1.44c0,0.795 0.645,1.44 1.44,1.44c0.795,-0 1.44,-0.645 1.44,-1.44c-0,-0.795 -0.645,-1.44 -1.44,-1.44Zm9.6,-0c-0.795,-0 -1.44,0.645 -1.44,1.44c-0,0.795 0.645,1.44 1.44,1.44c0.795,-0 1.44,-0.645 1.44,-1.44c-0,-0.795 -0.645,-1.44 -1.44,-1.44Zm-4.8,-0c-0.795,-0 -1.44,0.645 -1.44,1.44c-0,0.795 0.645,1.44 1.44,1.44c0.795,-0 1.44,-0.645 1.44,-1.44c-0,-0.795 -0.645,-1.44 -1.44,-1.44Z" style="fill:#fff;"/></svg>
|
After Width: | Height: | Size: 1.2 KiB |
1
public/img/blog/comment-light.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 31 31" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><rect id="comment" x="0" y="0" width="30.72" height="30.72" style="fill:none;"/><path d="M26.117,14.576c-0,4.724 -4.82,8.559 -10.757,8.559c-1.015,-0 -2.025,-0.114 -3,-0.34l-5.008,1.907l-0.007,-4.418c-1.765,-1.569 -2.742,-3.601 -2.742,-5.708c0,-4.723 4.82,-8.558 10.757,-8.558c5.937,-0 10.757,3.835 10.757,8.558Zm-15.557,-1.135c-0.795,-0 -1.44,0.645 -1.44,1.44c-0,0.795 0.645,1.44 1.44,1.44c0.795,-0 1.44,-0.645 1.44,-1.44c-0,-0.795 -0.645,-1.44 -1.44,-1.44Zm9.6,-0c-0.795,-0 -1.44,0.645 -1.44,1.44c-0,0.795 0.645,1.44 1.44,1.44c0.795,-0 1.44,-0.645 1.44,-1.44c-0,-0.795 -0.645,-1.44 -1.44,-1.44Zm-4.8,-0c-0.795,-0 -1.44,0.645 -1.44,1.44c-0,0.795 0.645,1.44 1.44,1.44c0.795,-0 1.44,-0.645 1.44,-1.44c-0,-0.795 -0.645,-1.44 -1.44,-1.44Z"/></svg>
|
After Width: | Height: | Size: 1.2 KiB |
1
public/img/blog/copy-link-dark.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 31 31" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><rect id="copy-link" serif:id="copy link" x="0" y="0" width="30.72" height="30.72" style="fill:none;"/><path d="M10.578,14.996c-0.133,-1.199 0.107,-2.433 0.717,-3.512c0.254,-0.448 0.571,-0.869 0.953,-1.251l3.502,-3.503c2.274,-2.273 5.966,-2.273 8.24,0c2.273,2.274 2.273,5.966 -0,8.24l-1.962,1.962c-0.145,0.144 -0.364,0.186 -0.552,0.105c-0.188,-0.081 -0.307,-0.269 -0.302,-0.473c0.023,-0.778 -0.087,-1.559 -0.329,-2.307c-0.058,-0.179 -0.011,-0.375 0.122,-0.507l0.901,-0.902c1.103,-1.103 1.103,-2.893 0,-3.996c-1.103,-1.103 -2.893,-1.103 -3.996,-0l-3.503,3.502c-0.306,0.306 -0.527,0.665 -0.663,1.048c-0.355,0.996 -0.134,2.152 0.663,2.949c0.283,0.283 0.611,0.493 0.962,0.631c0.157,0.062 0.272,0.198 0.306,0.364c0.035,0.165 -0.016,0.336 -0.136,0.455l-1.491,1.491c-0.161,0.161 -0.411,0.193 -0.608,0.077c-0.412,-0.244 -0.8,-0.543 -1.154,-0.897c-0.973,-0.973 -1.53,-2.206 -1.67,-3.476Zm8.847,4.24c-0.254,0.448 -0.571,0.869 -0.953,1.251l-3.502,3.503c-2.274,2.273 -5.966,2.273 -8.24,-0c-2.273,-2.274 -2.273,-5.966 0,-8.24l1.962,-1.962c0.145,-0.144 0.364,-0.186 0.552,-0.105c0.188,0.081 0.307,0.269 0.302,0.473c-0.023,0.778 0.087,1.559 0.329,2.307c0.058,0.179 0.011,0.375 -0.122,0.507l-0.901,0.902c-1.103,1.103 -1.103,2.893 -0,3.996c1.103,1.103 2.893,1.103 3.996,0l3.503,-3.502c0.306,-0.306 0.527,-0.665 0.663,-1.048c0.355,-0.996 0.134,-2.152 -0.663,-2.949c-0.283,-0.283 -0.611,-0.493 -0.962,-0.631c-0.157,-0.062 -0.272,-0.198 -0.306,-0.364c-0.035,-0.165 0.016,-0.336 0.136,-0.455l1.491,-1.491c0.161,-0.161 0.411,-0.193 0.608,-0.077c0.412,0.244 0.8,0.543 1.154,0.897c0.973,0.973 1.53,2.205 1.67,3.476c0.133,1.199 -0.107,2.433 -0.717,3.512Z" style="fill:#fff;"/></svg>
|
After Width: | Height: | Size: 2 KiB |
1
public/img/blog/copy-link-light.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 31 31" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><rect id="copy-link" serif:id="copy link" x="0" y="-0" width="30.72" height="30.72" style="fill:none;"/><g id="copy-link1" serif:id="copy link"><path d="M10.578,14.996c-0.133,-1.199 0.107,-2.433 0.717,-3.512c0.254,-0.448 0.571,-0.869 0.953,-1.251l3.502,-3.503c2.274,-2.273 5.966,-2.273 8.24,0c2.273,2.274 2.273,5.966 -0,8.24l-1.962,1.962c-0.145,0.144 -0.364,0.186 -0.552,0.105c-0.188,-0.081 -0.307,-0.269 -0.302,-0.473c0.023,-0.778 -0.087,-1.559 -0.329,-2.307c-0.058,-0.179 -0.011,-0.375 0.122,-0.507l0.901,-0.902c1.103,-1.103 1.103,-2.893 0,-3.996c-1.103,-1.103 -2.893,-1.103 -3.996,-0l-3.503,3.502c-0.306,0.306 -0.527,0.665 -0.663,1.048c-0.355,0.996 -0.134,2.152 0.663,2.949c0.283,0.283 0.611,0.493 0.962,0.631c0.157,0.062 0.272,0.198 0.306,0.364c0.035,0.165 -0.016,0.336 -0.136,0.455l-1.491,1.491c-0.161,0.161 -0.411,0.193 -0.608,0.077c-0.412,-0.244 -0.8,-0.543 -1.154,-0.897c-0.973,-0.973 -1.53,-2.206 -1.67,-3.476Zm8.847,4.24c-0.254,0.448 -0.571,0.869 -0.953,1.251l-3.502,3.503c-2.274,2.273 -5.966,2.273 -8.24,-0c-2.273,-2.274 -2.273,-5.966 0,-8.24l1.962,-1.962c0.145,-0.144 0.364,-0.186 0.552,-0.105c0.188,0.081 0.307,0.269 0.302,0.473c-0.023,0.778 0.087,1.559 0.329,2.307c0.058,0.179 0.011,0.375 -0.122,0.507l-0.901,0.902c-1.103,1.103 -1.103,2.893 -0,3.996c1.103,1.103 2.893,1.103 3.996,0l3.503,-3.502c0.306,-0.306 0.527,-0.665 0.663,-1.048c0.355,-0.996 0.134,-2.152 -0.663,-2.949c-0.283,-0.283 -0.611,-0.493 -0.962,-0.631c-0.157,-0.062 -0.272,-0.198 -0.306,-0.364c-0.035,-0.165 0.016,-0.336 0.136,-0.455l1.491,-1.491c0.161,-0.161 0.411,-0.193 0.608,-0.077c0.412,0.244 0.8,0.543 1.154,0.897c0.973,0.973 1.53,2.205 1.67,3.476c0.133,1.199 -0.107,2.433 -0.717,3.512Z"/></g></svg>
|
After Width: | Height: | Size: 2.1 KiB |
1
public/img/blog/like-dark.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 31 31" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><rect id="like" x="0" y="0" width="30.72" height="30.72" style="fill:none;"/><path d="M15.36,9.159c2.176,-4.134 6.527,-4.134 8.703,-2.067c2.176,2.067 2.176,6.201 0,10.335c-1.523,3.1 -5.439,6.201 -8.703,8.268c-3.264,-2.067 -7.18,-5.168 -8.703,-8.268c-2.176,-4.134 -2.176,-8.268 -0,-10.335c2.176,-2.067 6.527,-2.067 8.703,2.067Z" style="fill:#fff;"/></svg>
|
After Width: | Height: | Size: 794 B |
1
public/img/blog/like-light.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 31 31" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><rect id="like" x="0" y="0" width="30.72" height="30.72" style="fill:none;"/><path d="M15.36,9.159c2.176,-4.134 6.527,-4.134 8.703,-2.067c2.176,2.067 2.176,6.201 0,10.335c-1.523,3.1 -5.439,6.201 -8.703,8.268c-3.264,-2.067 -7.18,-5.168 -8.703,-8.268c-2.176,-4.134 -2.176,-8.268 -0,-10.335c2.176,-2.067 6.527,-2.067 8.703,2.067Z"/></svg>
|
After Width: | Height: | Size: 775 B |
|
@ -1,19 +1,15 @@
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
import { hijackClickEvent } from "./main.js";
|
||||||
document.querySelectorAll('.comment-hover').forEach((/** @type {HTMLDivElement} */ comment) => {
|
|
||||||
/** @type {HTMLLinkElement} */
|
|
||||||
const commentBody = comment.querySelector('a.comment-body');
|
|
||||||
|
|
||||||
comment.querySelectorAll('a').forEach((/** @type {HTMLLinkElement} */ element) => {
|
document.querySelectorAll('article.blog-post').forEach(element => {
|
||||||
element.addEventListener('click', event => {
|
const link = element.querySelector('.blog-title a');
|
||||||
event.stopPropagation();
|
hijackClickEvent(element, link);
|
||||||
});
|
});
|
||||||
});
|
document.querySelectorAll('article.blog-post').forEach(element => {
|
||||||
|
const link = element.querySelector('.blog-title a');
|
||||||
comment.addEventListener('click', () => {
|
hijackClickEvent(element, link);
|
||||||
commentBody.click();
|
});
|
||||||
});
|
|
||||||
|
document.getElementById('load-more').addEventListener('click', event => {
|
||||||
comment.style.cursor = 'pointer';
|
event.preventDefault();
|
||||||
comment.role = 'link';
|
alert('ok');
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
16
public/script/blogpost.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { hijackClickEvent } from "./main.js";
|
||||||
|
|
||||||
|
document.querySelectorAll('.comment-hover').forEach((/** @type {HTMLDivElement} */ comment) => {
|
||||||
|
/** @type {HTMLLinkElement} */
|
||||||
|
const commentDate = comment.querySelector('.comment-date');
|
||||||
|
hijackClickEvent(comment, commentDate);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('blog-copy-link').addEventListener('click', event => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (navigator.clipboard === undefined) {
|
||||||
|
console.error("clipboard is not supported by this browser!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigator.clipboard.writeText(location.protocol + "//" + location.host + location.pathname);
|
||||||
|
});
|
|
@ -1,216 +1,70 @@
|
||||||
:root {
|
article.blog-post {
|
||||||
--like: rgb(223, 104, 104);
|
margin-bottom: 1rem;
|
||||||
--repost: rgb(162, 223, 73);
|
padding: 1.5rem;
|
||||||
--bluesky: rgb(16, 131, 254);
|
border: 1px solid #8882;
|
||||||
--mastodon: rgb(86, 58, 204);
|
border-radius: 4px;
|
||||||
|
background-color: #ffffff08;
|
||||||
|
transition: background-color .1s;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
.blog-post h2:hover,
|
||||||
width: min(calc(100% - 4rem), 1200px);
|
.blog-post p:hover {
|
||||||
margin: 0 auto 1rem auto;
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blog p:hover,
|
.blog-title {
|
||||||
.comment p:hover {
|
margin: 0;
|
||||||
background: inherit;
|
}
|
||||||
|
.blog-title a {
|
||||||
|
display: inherit;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
article.blog {
|
.blog-meta {
|
||||||
font-family: 'Lora', serif;
|
margin: 0;
|
||||||
font-size: 24px;
|
}
|
||||||
|
|
||||||
|
.blog-author {
|
||||||
|
margin: 0;
|
||||||
|
font-size: .8em;
|
||||||
|
}
|
||||||
|
.blog-author img {
|
||||||
|
width: 1.3em;
|
||||||
|
height: 1.3em;
|
||||||
|
display: inline-block;
|
||||||
|
transform: translate(0, 4px);
|
||||||
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blog-date {
|
.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: var(--background-alt);
|
|
||||||
}
|
|
||||||
|
|
||||||
.blog p code {
|
|
||||||
padding: .2em .3em;
|
|
||||||
font-size: .9em;
|
|
||||||
border: 1px solid #8884;
|
|
||||||
border-radius: 2px;
|
|
||||||
background: var(--background-alt);
|
|
||||||
}
|
|
||||||
|
|
||||||
.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);
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-callout {
|
|
||||||
padding: 1em;
|
|
||||||
background: var(--background-alt);
|
|
||||||
border-radius: 2px;
|
|
||||||
border: 1px solid #8884;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.comment-callout:hover {
|
|
||||||
background: var(--background-alt);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn.bluesky,
|
|
||||||
.btn.mastodon {
|
|
||||||
font-family: monospace;
|
|
||||||
text-wrap: nowrap;
|
|
||||||
text-decoration: none;
|
|
||||||
transition-property: color background-color;
|
|
||||||
transition-duration: .1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn.bluesky {
|
|
||||||
color: var(--bluesky);
|
|
||||||
border-color: var(--bluesky);
|
|
||||||
}
|
|
||||||
.btn.bluesky:hover {
|
|
||||||
background-color: var(--bluesky);
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn.mastodon {
|
|
||||||
color: var(--mastodon);
|
|
||||||
border-color: var(--mastodon);
|
|
||||||
}
|
|
||||||
.btn.mastodon:hover {
|
|
||||||
background-color: var(--mastodon);
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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 {
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: .8em;
|
font-size: .8em;
|
||||||
opacity: .5;
|
opacity: .75;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment .comment-replies {
|
.blog-description {
|
||||||
margin-left: 1em;
|
margin: .5em 0 0 0;
|
||||||
border-left: 2px solid #8884;
|
display: -webkit-box;
|
||||||
|
font-size: .8em;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#load-more {
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: .5em 2em;
|
||||||
|
display: block;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1500px) {
|
||||||
|
#blog-sidebar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
314
public/style/blogpost.css
Normal file
|
@ -0,0 +1,314 @@
|
||||||
|
:root {
|
||||||
|
--like: rgb(223, 104, 104);
|
||||||
|
--boost: rgb(162, 223, 73);
|
||||||
|
--bluesky: rgb(16, 131, 254);
|
||||||
|
--mastodon: rgb(86, 58, 204);
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
width: min(calc(100% - 4rem), 1200px);
|
||||||
|
margin: 0 auto 1rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blog-sidebar {
|
||||||
|
position: fixed;
|
||||||
|
width: 3em;
|
||||||
|
padding: 3em;
|
||||||
|
transform: translate(-9em, -1em);
|
||||||
|
overflow: clip;
|
||||||
|
opacity: .5;
|
||||||
|
transition: opacity .2s;
|
||||||
|
}
|
||||||
|
#blog-sidebar:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blog-sidebar ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: .3em;
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: .3em;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--on-background);
|
||||||
|
box-shadow: 4px 4px 4px #0001;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blog-sidebar a {
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
display: block;
|
||||||
|
padding: .2em;
|
||||||
|
border-radius: 2px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
#blog-sidebar a:hover {
|
||||||
|
background: #0001;
|
||||||
|
}
|
||||||
|
#blog-sidebar a:active {
|
||||||
|
background: #0002;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blog-sidebar a img {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blog-sidebar span {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blog-sidebar hr {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog p:hover,
|
||||||
|
.comment p:hover {
|
||||||
|
background: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
article.blog {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog h1 {
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 1.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-author {
|
||||||
|
margin: .2em 0;
|
||||||
|
}
|
||||||
|
.blog .blog-author img {
|
||||||
|
width: 1.3em;
|
||||||
|
height: 1.3em;
|
||||||
|
display: inline-block;
|
||||||
|
transform: translate(0, 6px);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-date {
|
||||||
|
margin: .5em 0;
|
||||||
|
font-size: .7em;
|
||||||
|
}
|
||||||
|
.blog-modified-date {
|
||||||
|
font-style: italic;
|
||||||
|
opacity: .75;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blog-content {
|
||||||
|
font-family: 'Lora', serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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: var(--background-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog p code {
|
||||||
|
padding: .2em .3em;
|
||||||
|
font-size: .9em;
|
||||||
|
border: 1px solid #8884;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: var(--background-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 button {
|
||||||
|
min-width: fit-content;
|
||||||
|
padding: 0 .75em 0 .5em;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: .5em;
|
||||||
|
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: inherit;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 2em;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
|
||||||
|
color: inherit;
|
||||||
|
background: none;
|
||||||
|
border: 1px solid var(--on-background);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
#interactions button:hover {
|
||||||
|
background: #0001;
|
||||||
|
}
|
||||||
|
#interactions button:active {
|
||||||
|
background: #0002;
|
||||||
|
}
|
||||||
|
|
||||||
|
#interactions img {
|
||||||
|
width: 1.5em;
|
||||||
|
height: 1.5em;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-callout {
|
||||||
|
margin: 0 0 0 1em;
|
||||||
|
}
|
||||||
|
.comment-callout:hover {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bluesky {
|
||||||
|
color: var(--bluesky);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mastodon {
|
||||||
|
color: var(--mastodon);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment {
|
||||||
|
font-family: 'Inter', 'Arial', sans-serif;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment .comment-hover {
|
||||||
|
padding: 1em;
|
||||||
|
transition: background-color .1s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment .comment-body {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment p.comment-text {
|
||||||
|
margin: .5em 0;
|
||||||
|
white-space: break-spaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment .comment-footer {
|
||||||
|
margin: 0;
|
||||||
|
font-size: .8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment .comment-footer .comment-footer-static {
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment .comment-replies {
|
||||||
|
margin-left: 1em;
|
||||||
|
border-left: 2px solid #8884;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment .comment-date {
|
||||||
|
transition: opacity .2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (prefers-color-scheme: dark) {
|
||||||
|
#blog-sidebar a:hover {
|
||||||
|
background: #fff2;
|
||||||
|
}
|
||||||
|
#blog-sidebar a:active {
|
||||||
|
background: #fff4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-date {
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,7 +38,7 @@ a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.link-button {
|
.link-button {
|
||||||
padding: .3em .5em;
|
padding: .3em .5em;
|
||||||
border: 1px solid var(--links);
|
border: 1px solid var(--links);
|
||||||
color: var(--links);
|
color: var(--links);
|
||||||
|
@ -51,7 +51,7 @@ a.link-button {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.link-button:hover {
|
.link-button:hover {
|
||||||
color: #eee;
|
color: #eee;
|
||||||
border-color: #eee;
|
border-color: #eee;
|
||||||
background-color: var(--links) !important;
|
background-color: var(--links) !important;
|
||||||
|
@ -141,8 +141,23 @@ a#backtotop:hover {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.light-only {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-only {
|
||||||
|
display: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
@media (prefers-color-scheme: light) {
|
||||||
|
.light-only {
|
||||||
|
display: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-only {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
a.link-button:hover {
|
a.link-button:hover {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
@ -161,6 +176,14 @@ a#backtotop:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.light-only {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-only {
|
||||||
|
display: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
body.crt {
|
body.crt {
|
||||||
text-shadow: 0 0 3em;
|
text-shadow: 0 0 3em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,17 +127,39 @@ ALTER TABLE arimelody.musicreleasetrack ADD CONSTRAINT musicreleasetrack_pk PRIM
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE arimelody.blogpost (
|
||||||
|
id TEXT NOT NULL UNIQUE,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
description TEXT NOT NULL,
|
||||||
|
visible BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT current_timestamp,
|
||||||
|
modified_at TIMESTAMP,
|
||||||
|
author UUID NOT NULL,
|
||||||
|
markdown TEXT NOT NULL,
|
||||||
|
html TEXT NOT NULL,
|
||||||
|
bluesky_actor TEXT,
|
||||||
|
bluesky_post TEXT
|
||||||
|
);
|
||||||
|
ALTER TABLE arimelody.blogpost ADD CONSTRAINT blogpost_pk PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Foreign keys
|
-- Foreign keys
|
||||||
--
|
--
|
||||||
|
|
||||||
|
-- Account
|
||||||
ALTER TABLE arimelody.privilege ADD CONSTRAINT privilege_account_fk FOREIGN KEY (account) REFERENCES account(id) ON DELETE CASCADE;
|
ALTER TABLE arimelody.privilege ADD CONSTRAINT privilege_account_fk FOREIGN KEY (account) REFERENCES account(id) ON DELETE CASCADE;
|
||||||
ALTER TABLE arimelody.session ADD CONSTRAINT session_account_fk FOREIGN KEY (account) REFERENCES account(id) ON DELETE CASCADE;
|
ALTER TABLE arimelody.session ADD CONSTRAINT session_account_fk FOREIGN KEY (account) REFERENCES account(id) ON DELETE CASCADE;
|
||||||
ALTER TABLE arimelody.session ADD CONSTRAINT session_attempt_account_fk FOREIGN KEY (account) REFERENCES account(id) ON DELETE CASCADE;
|
ALTER TABLE arimelody.session ADD CONSTRAINT session_attempt_account_fk FOREIGN KEY (account) REFERENCES account(id) ON DELETE CASCADE;
|
||||||
ALTER TABLE arimelody.totp ADD CONSTRAINT totp_account_fk FOREIGN KEY (account) REFERENCES account(id) ON DELETE CASCADE;
|
ALTER TABLE arimelody.totp ADD CONSTRAINT totp_account_fk FOREIGN KEY (account) REFERENCES account(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
-- Music
|
||||||
ALTER TABLE arimelody.musiccredit ADD CONSTRAINT musiccredit_artist_fk FOREIGN KEY (artist) REFERENCES artist(id) ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE arimelody.musiccredit ADD CONSTRAINT musiccredit_artist_fk FOREIGN KEY (artist) REFERENCES artist(id) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
ALTER TABLE arimelody.musiccredit ADD CONSTRAINT musiccredit_release_fk FOREIGN KEY (release) REFERENCES musicrelease(id) ON DELETE CASCADE;
|
ALTER TABLE arimelody.musiccredit ADD CONSTRAINT musiccredit_release_fk FOREIGN KEY (release) REFERENCES musicrelease(id) ON DELETE CASCADE;
|
||||||
ALTER TABLE arimelody.musiclink ADD CONSTRAINT musiclink_release_fk FOREIGN KEY (release) REFERENCES musicrelease(id) ON UPDATE CASCADE ON DELETE CASCADE;
|
ALTER TABLE arimelody.musiclink ADD CONSTRAINT musiclink_release_fk FOREIGN KEY (release) REFERENCES musicrelease(id) ON UPDATE CASCADE ON DELETE CASCADE;
|
||||||
ALTER TABLE arimelody.musicreleasetrack ADD CONSTRAINT music_pair_trackref_fk FOREIGN KEY (release) REFERENCES musicrelease(id) ON DELETE CASCADE;
|
ALTER TABLE arimelody.musicreleasetrack ADD CONSTRAINT music_pair_trackref_fk FOREIGN KEY (release) REFERENCES musicrelease(id) ON DELETE CASCADE;
|
||||||
ALTER TABLE arimelody.musicreleasetrack ADD CONSTRAINT music_pair_releaseref_fk FOREIGN KEY (track) REFERENCES musictrack(id) ON DELETE CASCADE;
|
ALTER TABLE arimelody.musicreleasetrack ADD CONSTRAINT music_pair_releaseref_fk FOREIGN KEY (track) REFERENCES musictrack(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
-- Blog
|
||||||
|
ALTER TABLE arimelody.blogpost ADD CONSTRAINT blogpost_author_fk FOREIGN KEY (author) REFERENCES account(id) ON DELETE CASCADE;
|
||||||
|
|
15
schema-migration/004-blog.sql
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
CREATE TABLE arimelody.blogpost (
|
||||||
|
id TEXT NOT NULL UNIQUE,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
description TEXT NOT NULL,
|
||||||
|
visible BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT current_timestamp,
|
||||||
|
modified_at TIMESTAMP,
|
||||||
|
author UUID NOT NULL,
|
||||||
|
markdown TEXT NOT NULL,
|
||||||
|
html TEXT NOT NULL,
|
||||||
|
bluesky_actor TEXT,
|
||||||
|
bluesky_post TEXT
|
||||||
|
);
|
||||||
|
ALTER TABLE arimelody.blogpost ADD CONSTRAINT blogpost_pk PRIMARY KEY (id);
|
||||||
|
ALTER TABLE arimelody.blogpost ADD CONSTRAINT blogpost_author_fk FOREIGN KEY (author) REFERENCES account(id) ON DELETE CASCADE;
|
|
@ -33,3 +33,10 @@ var BlogTemplate = template.Must(template.ParseFiles(
|
||||||
filepath.Join("view", "prideflag.html"),
|
filepath.Join("view", "prideflag.html"),
|
||||||
filepath.Join("view", "blog.html"),
|
filepath.Join("view", "blog.html"),
|
||||||
))
|
))
|
||||||
|
var BlogPostTemplate = 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", "blogpost.html"),
|
||||||
|
))
|
||||||
|
|
224
view/blog.go
|
@ -5,7 +5,9 @@ import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
|
@ -16,14 +18,26 @@ import (
|
||||||
"github.com/gomarkdown/markdown/parser"
|
"github.com/gomarkdown/markdown/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BlogView struct {
|
type (
|
||||||
*model.Blog
|
BlogView struct {
|
||||||
Comments []*model.ThreadViewPost
|
Collections []*BlogViewPostCollection
|
||||||
Likes int
|
}
|
||||||
Reposts int
|
|
||||||
BlueskyURL string
|
BlogViewPostCollection struct {
|
||||||
MastodonURL string
|
Name string
|
||||||
}
|
Posts []*BlogPostView
|
||||||
|
}
|
||||||
|
|
||||||
|
BlogPostView struct {
|
||||||
|
*model.BlogPost
|
||||||
|
Author *model.Account
|
||||||
|
Comments []*model.ThreadViewPost
|
||||||
|
Likes int
|
||||||
|
Boosts int
|
||||||
|
BlueskyURL string
|
||||||
|
MastodonURL string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
var mdRenderer = html.NewRenderer(html.RendererOptions{
|
var mdRenderer = html.NewRenderer(html.RendererOptions{
|
||||||
Flags: html.CommonFlags | html.HrefTargetBlank,
|
Flags: html.CommonFlags | html.HrefTargetBlank,
|
||||||
|
@ -31,78 +45,105 @@ var mdRenderer = html.NewRenderer(html.RendererOptions{
|
||||||
|
|
||||||
func BlogHandler(app *model.AppState) http.Handler {
|
func BlogHandler(app *model.AppState) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := model.Blog{
|
if strings.Count(r.URL.Path, "/") > 1 {
|
||||||
Title: "hello world!",
|
http.NotFound(w, r)
|
||||||
Description: "lorem ipsum yadda yadda something boobies babababababababa",
|
return
|
||||||
Visible: true,
|
}
|
||||||
Date: time.Now(),
|
|
||||||
AuthorID: "ari",
|
|
||||||
Markdown:
|
|
||||||
`
|
|
||||||
**i'm ari!**
|
|
||||||
|
|
||||||
she/her 🏳️⚧️🏳️🌈💫🦆🇮🇪
|
if len(r.URL.Path) > 1 {
|
||||||
|
ServeBlogPost(app, r.URL.Path[1:]).ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
i'm a musician, developer, streamer, youtuber, and probably a bunch of other things i forgot to mention!
|
dbPosts, err := controller.GetBlogPosts(app.DB, true, -1, 0)
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "no rows") {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch blog posts: %v\n", err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
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!
|
collections := []*BlogViewPostCollection{}
|
||||||
|
posts := []*BlogPostView{}
|
||||||
|
collectionYear := 0
|
||||||
|
for i, post := range dbPosts {
|
||||||
|
author, err := controller.GetAccountByID(app.DB, post.AuthorID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve author of blog %s: %v\n", post.ID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
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!! 💕
|
if i == 0 {
|
||||||
|
collectionYear = post.CreatedAt.Year()
|
||||||
|
}
|
||||||
|
|
||||||
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!
|
if post.CreatedAt.Year() != collectionYear || i == len(dbPosts) - 1 {
|
||||||
|
if i == len(dbPosts) - 1 {
|
||||||
|
posts = append(posts, &BlogPostView{
|
||||||
|
BlogPost: post,
|
||||||
|
Author: author,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
postsCopy := slices.Clone(posts)
|
||||||
|
collections = append(collections, &BlogViewPostCollection{
|
||||||
|
Name: strconv.Itoa(collectionYear),
|
||||||
|
Posts: postsCopy,
|
||||||
|
})
|
||||||
|
posts = []*BlogPostView{}
|
||||||
|
collectionYear = post.CreatedAt.Year()
|
||||||
|
}
|
||||||
|
|
||||||
thank you for stopping by- i hope you have a lovely rest of your day! 💫
|
posts = append(posts, &BlogPostView{
|
||||||
|
BlogPost: post,
|
||||||
|
Author: author,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
## metadata
|
err = templates.BlogTemplate.Execute(w, BlogView{
|
||||||
|
Collections: collections,
|
||||||
**my colours 🌈**
|
})
|
||||||
|
if err != nil {
|
||||||
- primary: <span class="col-primary">#b7fd49</span>
|
fmt.Fprintf(os.Stderr, "WARN: Error rendering blog post: %v\n", err)
|
||||||
- secondary: <span class="col-secondary">#f8e05b</span>
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
- tertiary: <span class="col-tertiary">#f788fe</span>
|
return
|
||||||
|
|
||||||
**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",
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ServeBlogPost(app *model.AppState, blogPostID string) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
blog, err := controller.GetBlogPost(app.DB, blogPostID)
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "no rows") {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch blog post Bluesky thread: %v\n", err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !blog.Visible {
|
||||||
|
session, err := controller.GetSessionFromRequest(app, r)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve session: %v\n", err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if session == nil || session.Account == nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
author, err := controller.GetAccountByID(app.DB, blog.AuthorID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve author of blog %s: %v\n", blog.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
// blog.Markdown += " <i class=\"end-mark\"></i>"
|
// blog.Markdown += " <i class=\"end-mark\"></i>"
|
||||||
|
|
||||||
|
@ -111,23 +152,32 @@ this is `+"`"+`aridoodle`+"`"+`. please take care of her.
|
||||||
blog.HTML = template.HTML(markdown.Render(md, mdRenderer))
|
blog.HTML = template.HTML(markdown.Render(md, mdRenderer))
|
||||||
|
|
||||||
comments := []*model.ThreadViewPost{}
|
comments := []*model.ThreadViewPost{}
|
||||||
blueskyPost, err := controller.FetchThreadViewPost(blog.BlueskyActorID, blog.BlueskyPostID)
|
likeCount := 0
|
||||||
if err != nil {
|
boostCount := 0
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch blog post Bluesky thread: %v\n", err)
|
var blueskyURL string
|
||||||
} else {
|
var blueskyPost *model.ThreadViewPost
|
||||||
comments = append(comments, blueskyPost.Replies...)
|
if blog.BlueskyActorID != nil && blog.BlueskyPostID != nil {
|
||||||
}
|
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...)
|
||||||
|
likeCount += blueskyPost.Post.LikeCount
|
||||||
|
boostCount += blueskyPost.Post.RepostCount
|
||||||
|
blueskyURL = fmt.Sprintf("https://bsky.app/profile/%s/post/%s", blueskyPost.Post.Author.Handle, *blog.BlueskyPostID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = templates.BlogTemplate.Execute(w, BlogView{
|
err = templates.BlogPostTemplate.Execute(w, BlogPostView{
|
||||||
Blog: &blog,
|
BlogPost: blog,
|
||||||
Comments: blueskyPost.Replies,
|
Author: author,
|
||||||
Likes: blueskyPost.Post.LikeCount,
|
Comments: comments,
|
||||||
Reposts: blueskyPost.Post.RepostCount,
|
Likes: likeCount,
|
||||||
BlueskyURL: fmt.Sprintf("https://bsky.app/profile/%s/post/%s", blog.BlueskyActorID, blog.BlueskyPostID),
|
Boosts: boostCount,
|
||||||
MastodonURL: "#",
|
BlueskyURL: blueskyURL,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error rendering blog post: %v\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Error rendering blog post: %v\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
102
view/blog.html
|
@ -1,15 +1,15 @@
|
||||||
{{define "head"}}
|
{{define "head"}}
|
||||||
<title>{{.Title}} - ari melody 💫</title>
|
<title>blog - ari melody 💫</title>
|
||||||
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
||||||
|
|
||||||
<meta name="description" content="{{.Description}}">
|
<meta name="description" content="thoughts from your local SPACEGIRL 💫">
|
||||||
|
|
||||||
<meta property="og:title" content="{{.Title}}">
|
<meta property="og:title" content="ari melody blog 💫">
|
||||||
<meta property="og:type" content="article">
|
<meta property="og:type" content="article">
|
||||||
<meta property="og:url" content="www.arimelody.me/blog/{{.Date.Year}}/{{.GetMonth}}/{{.TitleNormalised}}">
|
<meta property="og:url" content="https://arimelody.space/blog/">
|
||||||
<meta property="og:image" content="https://www.arimelody.me/img/favicon.png">
|
<meta property="og:image" content="https://arimelody.space/img/favicon.png">
|
||||||
<meta property="og:site_name" content="ari melody">
|
<meta property="og:site_name" content="ari melody">
|
||||||
<meta property="og:description" content="{{.Description}}">
|
<meta property="og:description" content="thoughts from your local SPACEGIRL 💫">
|
||||||
|
|
||||||
<link rel="stylesheet" href="/style/main.css">
|
<link rel="stylesheet" href="/style/main.css">
|
||||||
<link rel="stylesheet" href="/style/index.css">
|
<link rel="stylesheet" href="/style/index.css">
|
||||||
|
@ -18,78 +18,34 @@
|
||||||
|
|
||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
<main>
|
<main>
|
||||||
<article class="blog">
|
<h1 class="typeout"># blog</h1>
|
||||||
<h1 class="typeout">{{.Title}}</h1>
|
<p class="">thoughts from your local SPACEGIRL 💫</p>
|
||||||
<p class="blog-date">Posted by <a href="/blog/{{.AuthorID}}">{{.AuthorID}}</a> @ {{.PrintDate}}</p>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
{{.HTML}}
|
|
||||||
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div class="interactions">
|
<div id="posts">
|
||||||
<span class="likes">❤️ {{.Likes}}</span>
|
{{if eq (len .Collections) 0}}
|
||||||
<span class="reposts">🔁 {{.Reposts}}</span>
|
<p>there are no posts! 🍃</p>
|
||||||
</div>
|
{{end}}
|
||||||
|
{{range .Collections}}
|
||||||
<p class="comment-callout">
|
<h2 id="{{.Name}}" class="collection-name">{{.Name}}</h2>
|
||||||
join the conversation on
|
{{range .Posts}}
|
||||||
<a class="btn bluesky" href="{{.BlueskyURL}}" target="_blank">Bluesky 🦋</a>
|
<article class="blog-post">
|
||||||
<!-- TODO: mastodon support -->
|
<h3 class="blog-title"><a href="/blog/{{.ID}}">{{.Title}}</a></h3>
|
||||||
<!--
|
<p class="blog-meta">
|
||||||
or
|
<span class="blog-author"><img src="/img/favicon.png" alt="{{.Author.Username}}'s avatar" width="32" height="32"/> {{.Author.Username}}</span>
|
||||||
<a class="btn mastodon" href="{{.MastodonURL}}" target="_blank">Mastodon 🐘</a>
|
<span class="blog-date">• {{.PrintDate}}</span>
|
||||||
-->
|
</p>
|
||||||
</p>
|
{{if ne .Description ""}}
|
||||||
|
<p class="blog-description">{{.Description}}</p>
|
||||||
<div class="comments">
|
{{end}}
|
||||||
{{range .Comments}}
|
</article>
|
||||||
{{template "comment" .}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="module" src="/script/blog.js"></script>
|
<!-- <button type="submit" class="link-button" id="load-more">load more</button> -->
|
||||||
|
|
||||||
|
<script src="/script/blog.js" type="module" defer></script>
|
||||||
</main>
|
</main>
|
||||||
{{end}}
|
{{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}} like{{if ne .Post.LikeCount 1}}s{{end}}</span>
|
|
||||||
•
|
|
||||||
<span>{{.Post.RepostCount}} repost{{if ne .Post.RepostCount 1}}s{{end}}</span>
|
|
||||||
•
|
|
||||||
<span class="comment-date">{{.Post.Record.CreatedAtPrint}}</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="comment-replies">
|
|
||||||
{{range .Replies}}
|
|
||||||
{{template "comment" .}}
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
|
|
178
view/blogpost.html
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
{{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="https://arimelody.space/blog/{{.ID}}">
|
||||||
|
<meta property="og:image" content="https://arimelody.space/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/blogpost.css">
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "content"}}
|
||||||
|
<main>
|
||||||
|
<div id="blog-sidebar">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="#copy-link" id="blog-copy-link" title="copy link">
|
||||||
|
<div class="dark-only">
|
||||||
|
<img src="/img/blog/copy-link-dark.svg" alt="" width="36" height="36">
|
||||||
|
</div>
|
||||||
|
<div class="light-only">
|
||||||
|
<img src="/img/blog/copy-link-light.svg" alt="" width="36" height="36">
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{{if ne .BlueskyURL ""}}
|
||||||
|
<li>
|
||||||
|
<a href="{{.BlueskyURL}}" id="blog-share-bsky" title="share on bluesky">
|
||||||
|
<div class="dark-only">
|
||||||
|
<img src="/img/blog/bluesky-dark.svg" alt="" width="36" height="36">
|
||||||
|
</div>
|
||||||
|
<div class="light-only">
|
||||||
|
<img src="/img/blog/bluesky-light.svg" alt="" width="36" height="36">
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<hr>
|
||||||
|
<li>
|
||||||
|
<a href="{{.BlueskyURL}}" id="blog-like" title="like this post">
|
||||||
|
<div class="dark-only">
|
||||||
|
<img src="/img/blog/like-dark.svg" alt="" width="36" height="36">
|
||||||
|
</div>
|
||||||
|
<div class="light-only">
|
||||||
|
<img src="/img/blog/like-light.svg" alt="" width="36" height="36">
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{.BlueskyURL}}" id="blog-boost" title="boost this post">
|
||||||
|
<div class="dark-only">
|
||||||
|
<img src="/img/blog/boost-dark.svg" alt="" width="36" height="36">
|
||||||
|
</div>
|
||||||
|
<div class="light-only">
|
||||||
|
<img src="/img/blog/boost-light.svg" alt="" width="36" height="36">
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#comments" id="blog-comments" title="comments">
|
||||||
|
<div class="dark-only">
|
||||||
|
<img src="/img/blog/comment-dark.svg" alt="" width="36" height="36">
|
||||||
|
</div>
|
||||||
|
<div class="light-only">
|
||||||
|
<img src="/img/blog/comment-light.svg" alt="" width="36" height="36">
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div id="blog-container">
|
||||||
|
<article class="blog">
|
||||||
|
<div id="blog-header">
|
||||||
|
<h1 class="typeout"># {{.Title}}</h1>
|
||||||
|
<p class="blog-author">by <a href="/blog?author={{.Author.Username}}">{{.Author.Username}} <img src="/img/favicon.png" alt="{{.Author.Username}}'s avatar" width="32" height="32"/></a></p>
|
||||||
|
<p class="blog-date">posted {{.PrintDate}}{{if .ModifiedAt.Valid}} <span class="blog-modified-date">• updated {{.PrintModifiedDate}}</span>{{end}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div id="blog-content">
|
||||||
|
{{.HTML}}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
{{if ne .BlueskyURL ""}}
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div id="interactions">
|
||||||
|
<button class="likes" aria-label="{{.Likes}} likes">
|
||||||
|
<div class="dark-only">
|
||||||
|
<img src="/img/blog/like-dark.svg" alt="" width="32" height="32">
|
||||||
|
</div>
|
||||||
|
<div class="light-only">
|
||||||
|
<img src="/img/blog/like-light.svg" alt="" width="32" height="32">
|
||||||
|
</div>
|
||||||
|
{{.Likes}}
|
||||||
|
</button>
|
||||||
|
<button class="boosts" aria-label="{{.Boosts}} boosts">
|
||||||
|
<div class="dark-only">
|
||||||
|
<img src="/img/blog/boost-dark.svg" alt="" width="32" height="32">
|
||||||
|
</div>
|
||||||
|
<div class="light-only">
|
||||||
|
<img src="/img/blog/boost-light.svg" alt="" width="32" height="32">
|
||||||
|
</div>
|
||||||
|
{{.Boosts}}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<p class="comment-callout">
|
||||||
|
join the conversation on
|
||||||
|
<a class="bluesky" href="{{.BlueskyURL}}">Bluesky 🦋</a>
|
||||||
|
<!-- TODO: mastodon support -->
|
||||||
|
<!--
|
||||||
|
and
|
||||||
|
<a class="mastodon" href="{{.MastodonURL}}">Mastodon 🐘</a>
|
||||||
|
-->
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="comments">
|
||||||
|
{{range .Comments}}
|
||||||
|
{{template "comment" .}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<script type="module" src="/script/blogpost.js"></script>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "comment"}}
|
||||||
|
<article class="comment">
|
||||||
|
<div class="comment-hover">
|
||||||
|
<div class="comment-header">
|
||||||
|
<a href="https://bsky.app/profile/{{.Post.Author.Handle}}" target="_blank">
|
||||||
|
<img class="avatar" src="{{.Post.Author.Avatar}}" alt="{{.Post.Author.DisplayName}}'s avatar" width="32" height="32">
|
||||||
|
<span class="display-name">{{.Post.Author.DisplayName}}</span>
|
||||||
|
<span class="handle">@{{.Post.Author.Handle}}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="comment-body" 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 class="comment-footer-static">
|
||||||
|
<span>{{.Post.LikeCount}} like{{if ne .Post.LikeCount 1}}s{{end}}</span>
|
||||||
|
•
|
||||||
|
<span>{{.Post.RepostCount}} boost{{if ne .Post.RepostCount 1}}s{{end}}</span>
|
||||||
|
•
|
||||||
|
</span>
|
||||||
|
<a href="{{.Post.BskyURL}}" class="comment-date">{{.Post.Record.CreatedAtPrint}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="comment-replies">
|
||||||
|
{{range .Replies}}
|
||||||
|
{{template "comment" .}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{{end}}
|
|
@ -21,11 +21,11 @@
|
||||||
<a href="/" preload="mouseover">home</a>
|
<a href="/" preload="mouseover">home</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/music" preload="mouseover">music</a>
|
<a href="/music/" preload="mouseover">music</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<!-- coming later! -->
|
<!-- coming later! -->
|
||||||
<span title="coming later!">blog</span>
|
<a href="/blog/" preload="mouseover">blog</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<!-- coming later! -->
|
<!-- coming later! -->
|
||||||
|
|