Write on the margins of the internet. Powered by the AT Protocol. margin.at
extension web atproto comments

make sure the collection url triggers the OG handler and add lucide to emoji to handle icons in og image

+90 -2
+1
backend/cmd/server/main.go
··· 97 97 r.Get("/og-image", ogHandler.HandleOGImage) 98 98 r.Get("/annotation/{did}/{rkey}", ogHandler.HandleAnnotationPage) 99 99 r.Get("/at/{did}/{rkey}", ogHandler.HandleAnnotationPage) 100 + r.Get("/collection/{uri}", ogHandler.HandleCollectionPage) 100 101 101 102 staticDir := getEnv("STATIC_DIR", "../web/dist") 102 103 serveStatic(r, staticDir)
+89 -2
backend/internal/api/og.go
··· 101 101 "Bluesky", 102 102 } 103 103 104 + var lucideToEmoji = map[string]string{ 105 + "folder": "๐Ÿ“", 106 + "star": "โญ", 107 + "heart": "โค๏ธ", 108 + "bookmark": "๐Ÿ”–", 109 + "lightbulb": "๐Ÿ’ก", 110 + "zap": "โšก", 111 + "coffee": "โ˜•", 112 + "music": "๐ŸŽต", 113 + "camera": "๐Ÿ“ท", 114 + "code": "๐Ÿ’ป", 115 + "globe": "๐ŸŒ", 116 + "flag": "๐Ÿšฉ", 117 + "tag": "๐Ÿท๏ธ", 118 + "box": "๐Ÿ“ฆ", 119 + "archive": "๐Ÿ—„๏ธ", 120 + "file": "๐Ÿ“„", 121 + "image": "๐Ÿ–ผ๏ธ", 122 + "video": "๐ŸŽฌ", 123 + "mail": "โœ‰๏ธ", 124 + "pin": "๐Ÿ“", 125 + "calendar": "๐Ÿ“…", 126 + "clock": "๐Ÿ•", 127 + "search": "๐Ÿ”", 128 + "settings": "โš™๏ธ", 129 + "user": "๐Ÿ‘ค", 130 + "users": "๐Ÿ‘ฅ", 131 + "home": "๐Ÿ ", 132 + "briefcase": "๐Ÿ’ผ", 133 + "gift": "๐ŸŽ", 134 + "award": "๐Ÿ†", 135 + "target": "๐ŸŽฏ", 136 + "trending": "๐Ÿ“ˆ", 137 + "activity": "๐Ÿ“Š", 138 + "cpu": "๐Ÿ”ฒ", 139 + "database": "๐Ÿ—ƒ๏ธ", 140 + "cloud": "โ˜๏ธ", 141 + "sun": "โ˜€๏ธ", 142 + "moon": "๐ŸŒ™", 143 + "flame": "๐Ÿ”ฅ", 144 + "leaf": "๐Ÿƒ", 145 + } 146 + 147 + func iconToEmoji(icon string) string { 148 + if strings.HasPrefix(icon, "icon:") { 149 + name := strings.TrimPrefix(icon, "icon:") 150 + if emoji, ok := lucideToEmoji[name]; ok { 151 + return emoji 152 + } 153 + return "๐Ÿ“" 154 + } 155 + return icon 156 + } 157 + 104 158 func isCrawler(userAgent string) bool { 105 159 ua := strings.ToLower(userAgent) 106 160 for _, bot := range crawlerUserAgents { ··· 153 207 154 208 collectionURI := fmt.Sprintf("at://%s/at.margin.collection/%s", did, rkey) 155 209 collection, err := h.db.GetCollectionByURI(collectionURI) 210 + if err == nil && collection != nil { 211 + h.serveCollectionOG(w, collection) 212 + return 213 + } 214 + 215 + h.serveIndexHTML(w, r) 216 + } 217 + 218 + func (h *OGHandler) HandleCollectionPage(w http.ResponseWriter, r *http.Request) { 219 + path := r.URL.Path 220 + prefix := "/collection/" 221 + if !strings.HasPrefix(path, prefix) { 222 + h.serveIndexHTML(w, r) 223 + return 224 + } 225 + 226 + uriParam := strings.TrimPrefix(path, prefix) 227 + if uriParam == "" { 228 + h.serveIndexHTML(w, r) 229 + return 230 + } 231 + 232 + uri, err := url.QueryUnescape(uriParam) 233 + if err != nil { 234 + uri = uriParam 235 + } 236 + 237 + if !isCrawler(r.UserAgent()) { 238 + h.serveIndexHTML(w, r) 239 + return 240 + } 241 + 242 + collection, err := h.db.GetCollectionByURI(uri) 156 243 if err == nil && collection != nil { 157 244 h.serveCollectionOG(w, collection) 158 245 return ··· 347 434 func (h *OGHandler) serveCollectionOG(w http.ResponseWriter, collection *db.Collection) { 348 435 icon := "๐Ÿ“" 349 436 if collection.Icon != nil && *collection.Icon != "" { 350 - icon = *collection.Icon 437 + icon = iconToEmoji(*collection.Icon) 351 438 } 352 439 353 440 title := fmt.Sprintf("%s %s", icon, collection.Name) ··· 663 750 664 751 icon := "๐Ÿ“" 665 752 if collection.Icon != nil && *collection.Icon != "" { 666 - icon = *collection.Icon 753 + icon = iconToEmoji(*collection.Icon) 667 754 } 668 755 text = fmt.Sprintf("%s %s", icon, collection.Name) 669 756 if collection.Description != nil && *collection.Description != "" {