this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

athome: cleanups, better display

+267 -208
+142 -53
cmd/athome/handlers.go
··· 3 3 import ( 4 4 "fmt" 5 5 "net/http" 6 + "strings" 6 7 7 8 appbsky "github.com/bluesky-social/indigo/api/bsky" 9 + "github.com/bluesky-social/indigo/atproto/syntax" 8 10 9 11 "github.com/flosch/pongo2/v6" 10 12 "github.com/labstack/echo/v4" 11 13 ) 14 + 15 + func (srv *Server) reqHandle(c echo.Context) syntax.Handle { 16 + host := c.Request().Host 17 + host = strings.SplitN(host, ":", 2)[0] 18 + handle, err := syntax.ParseHandle(host) 19 + if err != nil { 20 + slog.Warn("host is not a valid handle, fallback to default", "host", host) 21 + handle = srv.defaultHandle 22 + } 23 + return handle 24 + } 12 25 13 26 func (srv *Server) WebHome(c echo.Context) error { 14 - data := pongo2.Context{} 15 - return c.Render(http.StatusOK, "home.html", data) 27 + return c.Redirect(http.StatusFound, "/bsky") 28 + } 29 + 30 + func (srv *Server) WebRepoCar(c echo.Context) error { 31 + handle := srv.reqHandle(c) 32 + ident, err := srv.dir.LookupHandle(c.Request().Context(), handle) 33 + if err != nil { 34 + return err 35 + } 36 + return c.Redirect(http.StatusFound, ident.PDSEndpoint()+"/xrpc/com.atproto.sync.getRepo?did="+ident.DID.String()) 16 37 } 17 38 18 39 func (srv *Server) WebPost(c echo.Context) error { 40 + ctx := c.Request().Context() 41 + req := c.Request() 19 42 data := pongo2.Context{} 20 - handle := c.Param("handle") 43 + handle := srv.reqHandle(c) 44 + // TODO: parse rkey 21 45 rkey := c.Param("rkey") 22 - // sanity check argument 23 - if len(handle) > 4 && len(handle) < 128 && len(rkey) > 0 { 24 - ctx := c.Request().Context() 25 - // requires two fetches: first fetch profile (!) 26 - pv, err := appbsky.ActorGetProfile(ctx, srv.xrpcc, handle) 27 - if err != nil { 28 - slog.Warn("failed to fetch handle", "handle", handle, "err", err) 29 - // TODO: only if "not found" 30 - return echo.NewHTTPError(404, "handle not found: %s", handle) 31 - } else { 32 - did := pv.Did 33 - data["did"] = did 34 46 35 - // then fetch the post thread (with extra context) 36 - uri := fmt.Sprintf("at://%s/app.bsky.feed.post/%s", did, rkey) 37 - // TODO: more of thread? 38 - tpv, err := appbsky.FeedGetPostThread(ctx, srv.xrpcc, 1, 1, uri) 39 - if err != nil { 40 - slog.Warn("failed to fetch post", "uri", uri, "err", err) 41 - // TODO: only if "not found" 42 - return echo.NewHTTPError(404, "post not found: %s", handle) 43 - } else { 44 - req := c.Request() 45 - postView := tpv.Thread.FeedDefs_ThreadViewPost.Post 46 - data["postView"] = postView 47 - data["requestURI"] = fmt.Sprintf("https://%s%s", req.Host, req.URL.Path) 48 - if postView.Embed != nil && postView.Embed.EmbedImages_View != nil { 49 - data["imgThumbUrl"] = postView.Embed.EmbedImages_View.Images[0].Thumb 50 - } 51 - } 52 - } 47 + // requires two fetches: first fetch profile (!) 48 + pv, err := appbsky.ActorGetProfile(ctx, srv.xrpcc, handle.String()) 49 + if err != nil { 50 + slog.Warn("failed to fetch handle", "handle", handle, "err", err) 51 + // TODO: only if "not found" 52 + return echo.NewHTTPError(404, "handle not found: %s", handle) 53 + } 54 + did := pv.Did 55 + data["did"] = did 53 56 57 + // then fetch the post thread (with extra context) 58 + aturi := fmt.Sprintf("at://%s/app.bsky.feed.post/%s", did, rkey) 59 + tpv, err := appbsky.FeedGetPostThread(ctx, srv.xrpcc, 8, 8, aturi) 60 + if err != nil { 61 + slog.Warn("failed to fetch post", "aturi", aturi, "err", err) 62 + // TODO: only if "not found" 63 + return echo.NewHTTPError(404, "post not found: %s", handle) 54 64 } 65 + data["postView"] = tpv.Thread.FeedDefs_ThreadViewPost 66 + data["requestURI"] = fmt.Sprintf("https://%s%s", req.Host, req.URL.Path) 55 67 return c.Render(http.StatusOK, "post.html", data) 56 68 } 57 69 58 70 func (srv *Server) WebProfile(c echo.Context) error { 71 + ctx := c.Request().Context() 59 72 data := pongo2.Context{} 60 - handle := c.Param("handle") 61 - // sanity check argument 62 - if len(handle) > 4 && len(handle) < 128 { 63 - ctx := c.Request().Context() 64 - pv, err := appbsky.ActorGetProfile(ctx, srv.xrpcc, handle) 73 + handle := srv.reqHandle(c) 74 + 75 + pv, err := appbsky.ActorGetProfile(ctx, srv.xrpcc, handle.String()) 76 + if err != nil { 77 + slog.Warn("failed to fetch handle", "handle", handle, "err", err) 78 + // TODO: only if "not found" 79 + return echo.NewHTTPError(404, "handle not found: %s", handle) 80 + } else { 81 + req := c.Request() 82 + data["profileView"] = pv 83 + data["requestURI"] = fmt.Sprintf("https://%s%s", req.Host, req.URL.Path) 84 + } 85 + did := pv.Did 86 + data["did"] = did 87 + 88 + af, err := appbsky.FeedGetAuthorFeed(ctx, srv.xrpcc, handle.String(), "", "", 100) 89 + if err != nil { 90 + slog.Warn("failed to fetch author feed", "handle", handle, "err", err) 91 + // TODO: show some error? 92 + } else { 93 + data["authorFeed"] = af.Feed 94 + //slog.Warn("author feed", "feed", af.Feed) 95 + } 96 + 97 + return c.Render(http.StatusOK, "profile.html", data) 98 + } 99 + 100 + // https://medium.com/@etiennerouzeaud/a-rss-feed-valid-in-go-edfc22e410c7 101 + type Item struct { 102 + Title string `xml:"title"` 103 + Link string `xml:"link"` 104 + Description string `xml:"description"` 105 + PubDate string `xml:"pubDate"` 106 + } 107 + 108 + type rss struct { 109 + Version string `xml:"version,attr"` 110 + Description string `xml:"channel>description"` 111 + Link string `xml:"channel>link"` 112 + Title string `xml:"channel>title"` 113 + 114 + Item []Item `xml:"channel>item"` 115 + } 116 + 117 + func (srv *Server) WebRepoRSS(c echo.Context) error { 118 + ctx := c.Request().Context() 119 + handle := srv.reqHandle(c) 120 + 121 + pv, err := appbsky.ActorGetProfile(ctx, srv.xrpcc, handle.String()) 122 + if err != nil { 123 + slog.Warn("failed to fetch handle", "handle", handle, "err", err) 124 + // TODO: only if "not found" 125 + return echo.NewHTTPError(404, "handle not found: %s", handle) 126 + //return err 127 + } 128 + 129 + af, err := appbsky.FeedGetAuthorFeed(ctx, srv.xrpcc, handle.String(), "", "", 30) 130 + if err != nil { 131 + slog.Warn("failed to fetch author feed", "handle", handle, "err", err) 132 + return err 133 + } 134 + 135 + posts := []Item{} 136 + for _, p := range af.Feed { 137 + // only include own posts in RSS 138 + if p.Post.Author.Did != pv.Did { 139 + continue 140 + } 141 + aturi, err := syntax.ParseATURI(p.Post.Uri) 65 142 if err != nil { 66 - slog.Warn("failed to fetch handle", "handle", handle, "err", err) 67 - // TODO: only if "not found" 68 - return echo.NewHTTPError(404, "handle not found: %s", handle) 69 - } else { 70 - req := c.Request() 71 - data["profileView"] = pv 72 - data["requestURI"] = fmt.Sprintf("https://%s%s", req.Host, req.URL.Path) 143 + return err 73 144 } 74 - af, err := appbsky.FeedGetAuthorFeed(ctx, srv.xrpcc, handle, "", "", 100) 75 - if err != nil { 76 - slog.Warn("failed to fetch author feed", "handle", handle, "err", err) 77 - // TODO: show some error? 78 - } else { 79 - data["authorFeed"] = af.Feed 80 - //slog.Warn("author feed", "feed", af.Feed) 145 + rec := p.Post.Record.Val.(*appbsky.FeedPost) 146 + // only top-level posts in RSS 147 + if rec.Reply != nil { 148 + continue 81 149 } 150 + posts = append(posts, Item{ 151 + Title: "@" + handle.String() + " post", 152 + Link: fmt.Sprintf("https://%s/bsky/post/%s", handle, aturi.RecordKey().String()), 153 + Description: rec.Text, 154 + PubDate: rec.CreatedAt, 155 + }) 82 156 } 83 157 84 - return c.Render(http.StatusOK, "profile.html", data) 158 + title := "@" + handle.String() 159 + if pv.DisplayName != nil { 160 + title = title + " - " + *pv.DisplayName 161 + } 162 + desc := "" 163 + if pv.Description != nil { 164 + desc = *pv.Description 165 + } 166 + feed := &rss{ 167 + Version: "2.0", 168 + Description: desc, 169 + Link: fmt.Sprintf("https://%s/bsky", handle.String()), 170 + Title: title, 171 + Item: posts, 172 + } 173 + return c.XML(http.StatusOK, feed) 85 174 }
+10 -11
cmd/athome/main.go
··· 1 1 package main 2 2 3 3 import ( 4 + "fmt" 4 5 slogging "log/slog" 5 6 "os" 6 - "fmt" 7 7 8 8 "github.com/carlmjohnson/versioninfo" 9 9 "github.com/urfave/cli/v2" ··· 11 11 _ "github.com/joho/godotenv/autoload" 12 12 ) 13 13 14 - 15 14 var ( 16 - slog = slogging.New(slogging.NewJSONHandler(os.Stdout, nil)) 17 - version = versioninfo.Short() 15 + slog = slogging.New(slogging.NewJSONHandler(os.Stdout, nil)) 16 + version = versioninfo.Short() 18 17 ) 19 18 20 19 func main() { ··· 60 59 }, 61 60 }, 62 61 &cli.Command{ 63 - Name: "version", 64 - Usage: "print version", 65 - Action: func(cctx *cli.Context) error { 66 - fmt.Println(version) 67 - return nil 68 - }, 69 - }, 62 + Name: "version", 63 + Usage: "print version", 64 + Action: func(cctx *cli.Context) error { 65 + fmt.Println(version) 66 + return nil 67 + }, 68 + }, 70 69 } 71 70 72 71 return app.Run(args)
+31 -42
cmd/athome/service.go
··· 2 2 3 3 import ( 4 4 "context" 5 + "embed" 6 + "errors" 5 7 "io/fs" 6 8 "net/http" 7 9 "os" 8 - "time" 9 - "embed" 10 10 "os/signal" 11 - "errors" 12 11 "syscall" 12 + "time" 13 13 14 - "github.com/bluesky-social/indigo/xrpc" 14 + "github.com/bluesky-social/indigo/atproto/identity" 15 + "github.com/bluesky-social/indigo/atproto/syntax" 15 16 "github.com/bluesky-social/indigo/util" 17 + "github.com/bluesky-social/indigo/xrpc" 16 18 17 - "github.com/urfave/cli/v2" 18 19 "github.com/flosch/pongo2/v6" 20 + "github.com/labstack/echo-contrib/echoprometheus" 19 21 "github.com/labstack/echo/v4" 20 22 "github.com/labstack/echo/v4/middleware" 21 - "github.com/labstack/echo-contrib/echoprometheus" 22 23 slogecho "github.com/samber/slog-echo" 24 + "github.com/urfave/cli/v2" 23 25 ) 24 26 25 27 //go:embed static/* 26 28 var StaticFS embed.FS 27 29 28 30 type Server struct { 29 - echo *echo.Echo 30 - httpd *http.Server 31 - xrpcc *xrpc.Client 31 + echo *echo.Echo 32 + httpd *http.Server 33 + dir identity.Directory // TODO: unused? 34 + xrpcc *xrpc.Client 35 + defaultHandle syntax.Handle 32 36 } 33 37 34 38 func serve(cctx *cli.Context) error { 35 39 debug := cctx.Bool("debug") 36 40 httpAddress := cctx.String("bind") 37 41 appviewHost := cctx.String("appview-host") 42 + 43 + dh, err := syntax.ParseHandle("atproto.com") 44 + if err != nil { 45 + return err 46 + } 38 47 39 48 xrpcc := &xrpc.Client{ 40 49 Client: util.RobustHTTPClient(), ··· 50 59 ) 51 60 52 61 srv := &Server{ 53 - echo: e, 54 - xrpcc: xrpcc, 62 + echo: e, 63 + xrpcc: xrpcc, 64 + dir: identity.DefaultDirectory(), 65 + defaultHandle: dh, 55 66 } 56 67 srv.httpd = &http.Server{ 57 68 Handler: srv, ··· 67 78 e.Use(echoprometheus.NewMiddleware("athome")) 68 79 e.Use(middleware.BodyLimit("64M")) 69 80 e.HTTPErrorHandler = srv.errorHandler 81 + e.Renderer = NewRenderer("templates/", &TemplateFS, debug) 70 82 e.Use(middleware.SecureWithConfig(middleware.SecureConfig{ 71 83 ContentTypeNosniff: "nosniff", 72 84 XFrameOptions: "SAMEORIGIN", ··· 94 106 return http.FS(fsys) 95 107 }()) 96 108 97 - e.Renderer = NewRenderer("templates/", &TemplateFS, debug) 98 109 e.GET("/static/*", echo.WrapHandler(http.StripPrefix("/static/", staticHandler))) 99 - 100 110 e.GET("/_health", srv.HandleHealthCheck) 101 111 e.GET("/metrics", echoprometheus.NewHandler()) 102 112 103 113 // basic static routes 104 114 e.GET("/robots.txt", echo.WrapHandler(staticHandler)) 105 115 e.GET("/favicon.ico", echo.WrapHandler(staticHandler)) 116 + 117 + // actual content 106 118 e.GET("/", srv.WebHome) 107 - 108 - // generic routes 109 - //e.GET("/search", srv.WebGeneric) 110 - //e.GET("/support", srv.WebGeneric) 111 - //e.GET("/support/privacy", srv.WebGeneric) 112 - //e.GET("/support/tos", srv.WebGeneric) 113 - //e.GET("/support/community-guidelines", srv.WebGeneric) 114 - //e.GET("/support/copyright", srv.WebGeneric) 115 - 116 - // profile endpoints; only first populates info 117 - e.GET("/profile/:handle", srv.WebProfile) 118 - //e.GET("/profile/:handle/repo.car.gz", srv.WebProfile) 119 - //e.GET("/profile/:handle/follows", srv.WebGeneric) 120 - //e.GET("/profile/:handle/followers", srv.WebGeneric) 121 - 122 - // post endpoints; only first populates info 123 - e.GET("/profile/:handle/post/:rkey", srv.WebPost) 124 - //e.GET("/profile/:handle/post/:rkey/liked-by", srv.WebGeneric) 125 - //e.GET("/profile/:handle/post/:rkey/reposted-by", srv.WebGeneric) 126 - 127 - // feeds 128 - //e.GET("/feed/:name", srv.WebFeed) 129 - 130 - // redirect 131 - //e.GET("/at://:account", srv.WebAccountURI) 132 - //e.GET("/at://:account/:nsid/:rkey", srv.WebRecordURI) 119 + e.GET("/bsky", srv.WebProfile) 120 + e.GET("/bsky/post/:rkey", srv.WebPost) 121 + e.GET("/bsky/repo.car", srv.WebRepoCar) 122 + e.GET("/bsky/rss.xml", srv.WebRepoRSS) 133 123 134 124 // Start the server 135 125 slog.Info("starting server", "bind", httpAddress) ··· 175 165 code = he.Code 176 166 } 177 167 if code >= 500 { 178 - slog.Warn("abyss-http-internal-error", "err", err) 168 + slog.Warn("athome-http-internal-error", "err", err) 179 169 } 180 170 data := pongo2.Context{ 181 171 "statusCode": code, ··· 197 187 } 198 188 199 189 func (s *Server) HandleHealthCheck(c echo.Context) error { 200 - return c.JSON(200, GenericStatus{Status: "ok", Daemon: "abyss"}) 190 + return c.JSON(200, GenericStatus{Status: "ok", Daemon: "athome"}) 201 191 } 202 -
+5 -7
cmd/athome/templates/base.html
··· 29 29 <link rel="icon" type="image/png" sizes="16x16" href="/static/favicon-16x16.png"/> 30 30 {% block html_head_extra -%}{%- endblock %} 31 31 <meta name="application-name" name="Bluesky"> 32 - <meta name="generator" name="pubweb"> 32 + <meta name="generator" name="athome"> 33 33 </head> 34 34 <body> 35 35 {%- block body_all %} ··· 37 37 <div class="ui grid"> 38 38 <div class="fixed four wide column"> 39 39 <div class="ui vertical text menu" style="padding-top: 2em; font-size: 1.3rem;"> 40 - <h2 style="color: blue;">Bluesky</h2> 41 - <a href="/feed/whatshot" class="item">What's Hot</a> 42 - <a href="/profile/nori.gay/post/3juzlwllznd24" class="item">Hell Thread</a> 43 - <a href="/profile/bnewbold.bsky.team" class="item">Prod bnewbold</a> 44 - <a href="/profile/bnewbold.staging.bsky.dev" class="item">Staging bnewbold</a> 45 - <a href="/profile/paul.staging.bsky.dev" class="item">Staging paul</a> 40 + <h2 style="color: blue;">{%- block sidebar_title -%}Bluesky{%- endblock -%}</h2> 41 + <a href="/bsky" class="item">Profile</a> 42 + <a href="/bsky/repo.car" class="item">repo.car</a> 43 + <a href="/bsky/rss.xml" class="item">RSS</a> 46 44 </div> 47 45 </div> 48 46 <div class="ten wide column">
+1 -1
cmd/athome/templates/error.html
··· 7 7 <center> 8 8 <h1 style="font-size: 8em;">{{ statusCode }}</h1> 9 9 <h2 style="font-size: 3em;">Error!</h2> 10 - <p>Sorry about that! Our <a href="https://bluesky.statuspage.io/">Status Page</a> might have more context. 10 + <p>Sorry about that! The <a href="https://bluesky.statuspage.io/">Bluesky Status Page</a> might have more context. 11 11 </center> 12 12 {% endblock %}
+43 -56
cmd/athome/templates/feed_macros.html
··· 1 1 2 - {% macro feed_post(feedItem) export %} 2 + {% macro feed_post(feedItem, selfDID, primary) export %} 3 + {% if primary %} 4 + <div class="event" id="primary_post" style="background-color: lightyellow;"> 5 + {% else %} 3 6 <div class="event"> 7 + {% endif %} 4 8 <div class="label"> 5 9 {% if feedItem.Post.Author.Avatar %} 6 10 <img src="{{ feedItem.Post.Author.Avatar }}"> ··· 8 12 <img src="/static/default-avatar.png"> 9 13 {% endif %} 10 14 </div> 11 - <div class="content"> 15 + <div class="content" style="margin-top: 0px;"> 12 16 {% if feedItem.Reason %} 13 17 {{ feedItem.Reason.FeedDefs_ReasonRepost }} 14 18 {% endif %} 15 19 <div class="summary"> 16 - <a href="/profile/{{ feedItem.Post.Author.Handle }}" class="user"> 20 + {% if feedItem.Post.Author.Did == selfDID %} 21 + <a href="/bsky" class="user"> 22 + {% else %} 23 + <a href="https://bsky.app/profile/{{ feedItem.Post.Author.Handle }}" class="user"> 24 + {% endif %} 17 25 {% if feedItem.Post.Author.DisplayName %} 18 26 <b>{{ feedItem.Post.Author.DisplayName }}</b> 27 + <span style="font-weight: normal;"> 28 + {% else %} 29 + <span> 19 30 {% endif %} 20 - @{{ feedItem.Post.Author.Handle }} 31 + @{{ feedItem.Post.Author.Handle }}</span> 21 32 </a> 22 33 23 - 24 34 <div class="date"> 25 35 {# TODO: relative time#} 26 36 {# TODO: parse and fix link (custom filter?) #} 27 - <a href="/profile/{{ feedItem.Author.Handle }}/post/{{ feedItem.Post.Uri }}">{{ feedItem.Post.IndexedAt }}</a> 37 + {% if feedItem.Post.Author.Did == selfDID %} 38 + <a href="/bsky/post/{{ feedItem.Post.Uri|split:"/"|last }}">{{ feedItem.Post.IndexedAt }}</a> 39 + {% else %} 40 + <a href="https://bsky.app/profile/{{ feedItem.Post.Author.Handle }}/post/{{ feedItem.Post.Uri|split:"/"|last }}">{{ feedItem.Post.IndexedAt }}</a> 41 + {% endif %} 28 42 </div> 29 43 </div> 30 44 <div class="extra text"> ··· 50 64 </div> 51 65 </div> 52 66 </div> 67 + 68 + {% if primary %} 69 + <script> 70 + window.onload = (event) => { 71 + setTimeout(function(){ 72 + document.getElementById("primary_post").scrollIntoView(true); 73 + }, 250); 74 + }; 75 + </script> 76 + {% endif %} 53 77 {% endmacro %} 54 78 55 - {% macro primary_post(feedItem) export %} 56 - <div class="event"> 57 - <div class="label"> 58 - {% if feedItem.Post.Author.Avatar %} 59 - <img src="{{ feedItem.Post.Author.Avatar }}"> 60 - {% else %} 61 - <img src="/static/default-avatar.png"> 62 - {% endif %} 63 - </div> 64 - <div class="content"> 65 - {% if feedItem.Reason %} 66 - {{ feedItem.Reason.FeedDefs_ReasonRepost }} 67 - {% endif %} 68 - <div class="summary"> 69 - <a href="/profile/{{ feedItem.Post.Author.Handle }}" class="user"> 70 - {% if feedItem.Post.Author.DisplayName %} 71 - <b>{{ feedItem.Post.Author.DisplayName }}</b> 72 - {% endif %} 73 - @{{ feedItem.Post.Author.Handle }} 74 - </a> 79 + {% macro thread_parents(post, selfDID, primary) export %} 80 + {% if post.Parent %} 81 + {{ thread_parents(post.Parent.FeedDefs_ThreadViewPost, selfDID, false) }} 82 + <div class="ui divider"></div> 83 + {% endif %} 84 + {{ feed_post(post, selfDID, primary) }} 85 + {% endmacro %} 75 86 76 - 77 - <div class="date"> 78 - {# TODO: relative time#} 79 - {# TODO: parse and fix link (custom filter?) #} 80 - <a href="/profile/{{ feedItem.Author.Handle }}/post/{{ feedItem.Post.Uri }}">{{ feedItem.Post.IndexedAt }}</a> 81 - </div> 82 - </div> 83 - <div class="extra text"> 84 - {{ feedItem.Post.Record.Val.Text }} 85 - {% if feedItem.Post.Embed and feedItem.Post.Embed.EmbedImages_View %} 86 - <div class="ui four cards"> 87 - {% for image in feedItem.Post.Embed.EmbedImages_View.Images %} 88 - <div class="card"> 89 - <div class="image"> 90 - <a href="{{ image.Fullsize }}"> 91 - <img alt="{{ image.Alt }}" src="{{ image.Thumb }}" style="width: 100%;"> 92 - </a> 93 - </div> 94 - </div> 95 - {% endfor %} 96 - </div> 97 - {% endif %} 98 - </div> 99 - <div class="meta"> 100 - <a class="like"><i class="reply icon"></i> {{ feedItem.Post.ReplyCount }}</a> 101 - <a class="like"><i class="comment outline icon"></i> {{ feedItem.Post.RepostCount }}</a> 102 - <a class="like"><i class="like outline icon"></i> {{ feedItem.Post.LikeCount }}</a> 103 - </div> 104 - </div> 105 - </div> 87 + {% macro thread_children(post, selfDID) export %} 88 + {% for child in post.Replies %} 89 + <div class="ui divider"></div> 90 + {{ feed_post(child.FeedDefs_ThreadViewPost, selfDID) }} 91 + {{ thread_children(child.FeedDefs_ThreadViewPost, selfDID) }} 92 + {% endfor %} 106 93 {% endmacro %}
-20
cmd/athome/templates/home.html
··· 1 - {% extends "base.html" %} 2 - 3 - {% block head_title %}Bluesky{% endblock %} 4 - 5 - {% block html_head_extra -%} 6 - <meta name="description" content="See what's next."/> 7 - <meta property="og:type" content="website"/> 8 - <meta property="og:title" content="Bluesky Social"/> 9 - <meta property="og:description" content="See what's next."/> 10 - <meta property="og:image" content="/static/social-card-default.png"/> 11 - <meta name="twitter:card" content="summary"/> 12 - <meta name="twitter:site" content="@bluesky"/> 13 - {%- endblock %} 14 - 15 - {% block main_content %} 16 - <center style="padding-top: 8em;"> 17 - <h1 style="font-size: 3em; color: blue;">See what’s next</h1> 18 - <h1 style="font-size: 3em; color: black;">Bluesky Social</h1> 19 - </center> 20 - {% endblock %}
+25 -16
cmd/athome/templates/post.html
··· 1 1 {% extends "base.html" %} 2 2 3 3 {% block head_title %} 4 - {%- if postView -%} 5 - @{{ postView.Author.Handle }} on Bluesky 4 + {%- if postView.Post -%} 5 + @{{ postView.Post.Author.Handle }} on Bluesky 6 + {%- else -%} 7 + Bluesky 8 + {%- endif -%} 9 + {% endblock %} 10 + 11 + {% block sidebar_title %} 12 + {%- if postView.Post -%} 13 + {{ postView.Post.Author.Handle }} 6 14 {%- else -%} 7 15 Bluesky 8 16 {%- endif -%} 9 17 {% endblock %} 10 18 11 19 {% block html_head_extra -%} 12 - {%- if postView -%} 20 + {%- if postView.Post -%} 13 21 <meta property="og:type" content="website"> 14 22 <meta property="og:site_name" content="Bluesky Social"> 15 23 {%- if requestURI %} 16 24 <meta property="og:url" content="{{ requestURI }}"> 17 25 {% endif -%} 18 - {%- if postView.Author.DisplayName %} 19 - <meta property="og:title" content="{{ postView.Author.DisplayName }} (@{{ postView.Author.Handle }})"> 26 + {%- if postView.Post.Author.DisplayName %} 27 + <meta property="og:title" content="{{ postView.Post.Author.DisplayName }} (@{{ postView.Post.Author.Handle }})"> 20 28 {% else %} 21 - <meta property="og:title" content="@{{ postView.Author.Handle }}"> 29 + <meta property="og:title" content="@{{ postView.Post.Author.Handle }}"> 22 30 {% endif -%} 23 - {%- if postView.Record.Val.Text %} 24 - <meta name="description" content="{{ postView.Record.Val.Text }}"> 25 - <meta property="og:description" content="{{ postView.Record.Val.Text }}"> 31 + {%- if postView.Post.Record.Val.Text %} 32 + <meta name="description" content="{{ postView.Post.Record.Val.Text }}"> 33 + <meta property="og:description" content="{{ postView.Post.Record.Val.Text }}"> 26 34 {% endif -%} 27 35 {%- if imgThumbUrl %} 28 36 <meta property="og:image" content="{{ imgThumbUrl }}"> 29 37 <meta name="twitter:card" content="summary_large_image"> 30 - {%- elif postView.Author.Avatar %} 38 + {%- elif postView.Post.Author.Avatar %} 31 39 {# Don't use avatar image in cards; usually looks bad #} 32 40 <meta name="twitter:card" content="summary"> 33 41 {% endif %} 34 42 <meta name="twitter:label1" content="Posted At"> 35 - <meta name="twitter:value1" content="{{ postView.CreatedAt }}"> 43 + <meta name="twitter:value1" content="{{ postView.Post.CreatedAt }}"> 36 44 <meta name="twitter:site" content="@bluesky"> 37 45 {% endif -%} 38 46 {%- endblock %} 39 47 40 48 {% block main_content %} 41 - <h3>Post</h3> 42 - <p id="bsky_display_name">{{ postView.Author.DisplayName }}</p> 43 - <p id="bsky_handle">{{ postView.Author.Handle }}</p> 44 - <p id="bsky_did">{{ postView.Author.Did }}</p> 45 - <p id="bsky_post_text">{{ postView.Record.Val.Text }}</p> 49 + {% import "feed_macros.html" feed_post, thread_parents, thread_children %} 50 + <div class="ui divider"></div> 51 + <div class="ui large feed"> 52 + {{ thread_parents(postView, did, true) }} 53 + {{ thread_children(postView) }} 54 + </div> 46 55 {%- endblock %}
+10 -2
cmd/athome/templates/profile.html
··· 8 8 {%- endif -%} 9 9 {% endblock %} 10 10 11 + {% block sidebar_title %} 12 + {%- if profileView -%} 13 + {{ profileView.Handle }} 14 + {%- else -%} 15 + Bluesky 16 + {%- endif -%} 17 + {% endblock %} 18 + 11 19 {% block html_head_extra -%} 12 20 {%- if profileView -%} 13 21 <meta property="og:type" content="website"> ··· 58 66 <p>{{ profileView.Description }}</p> 59 67 60 68 <div class="ui divider"></div> 61 - <div class="ui feed"> 69 + <div class="ui large feed"> 62 70 {% for feedItem in authorFeed %} 63 - {{ feed_post(feedItem) }} 71 + {{ feed_post(feedItem, did) }} 64 72 <div class="ui divider"></div> 65 73 {% endfor %} 66 74 </div>