Browse Source

bug-fixes

master
Silberengel 4 weeks ago
parent
commit
963e1c940e
  1. 1
      cmd/server/main.go
  2. 85
      internal/generator/html.go
  3. 12
      static/css/main.css
  4. 23
      templates/components.html
  5. 2
      templates/ebooks.html

1
cmd/server/main.go

@ -62,6 +62,7 @@ func main() {
cfg.SEO.SiteName, cfg.SEO.SiteName,
cfg.SEO.SiteURL, cfg.SEO.SiteURL,
cfg.SEO.DefaultImage, cfg.SEO.DefaultImage,
nostrClient,
) )
if err != nil { if err != nil {
log.Fatalf("Failed to initialize HTML generator: %v", err) log.Fatalf("Failed to initialize HTML generator: %v", err)

85
internal/generator/html.go

@ -2,10 +2,12 @@ package generator
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"html/template" "html/template"
"path/filepath" "path/filepath"
"sync"
"time" "time"
"gitcitadel-online/internal/asciidoc" "gitcitadel-online/internal/asciidoc"
@ -41,6 +43,7 @@ type HTMLGenerator struct {
siteName string siteName string
siteURL string siteURL string
defaultImage string defaultImage string
nostrClient *nostr.Client
} }
// PageData represents data for a wiki page // PageData represents data for a wiki page
@ -63,6 +66,7 @@ type PageData struct {
Content template.HTML Content template.HTML
Summary string Summary string
TableOfContents template.HTML TableOfContents template.HTML
Profiles map[string]*nostr.Profile // Map of pubkey -> profile
} }
// WikiPageInfo represents info about a wiki page for navigations // WikiPageInfo represents info about a wiki page for navigations
@ -116,7 +120,7 @@ type UserBadgeInfo struct {
} }
// NewHTMLGenerator creates a new HTML generator // NewHTMLGenerator creates a new HTML generator
func NewHTMLGenerator(templateDir string, linkBaseURL, siteName, siteURL, defaultImage string) (*HTMLGenerator, error) { func NewHTMLGenerator(templateDir string, linkBaseURL, siteName, siteURL, defaultImage string, nostrClient *nostr.Client) (*HTMLGenerator, error) {
tmpl := template.New("base").Funcs(getTemplateFuncs()) tmpl := template.New("base").Funcs(getTemplateFuncs())
// Load all templates // Load all templates
@ -148,9 +152,49 @@ func NewHTMLGenerator(templateDir string, linkBaseURL, siteName, siteURL, defaul
siteName: siteName, siteName: siteName,
siteURL: siteURL, siteURL: siteURL,
defaultImage: defaultImage, defaultImage: defaultImage,
nostrClient: nostrClient,
}, nil }, nil
} }
// fetchProfilesBatch fetches profiles for multiple pubkeys in parallel
func (g *HTMLGenerator) fetchProfilesBatch(ctx context.Context, pubkeys []string) map[string]*nostr.Profile {
if g.nostrClient == nil || len(pubkeys) == 0 {
return make(map[string]*nostr.Profile)
}
profiles := make(map[string]*nostr.Profile)
var mu sync.Mutex
var wg sync.WaitGroup
// Create a context with timeout for profile fetching
profileCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
for _, pubkey := range pubkeys {
if pubkey == "" {
continue
}
wg.Add(1)
go func(pk string) {
defer wg.Done()
// Convert pubkey to npub for fetching
npub, err := nostr.PubkeyToNpub(pk)
if err != nil {
return
}
profile, err := g.nostrClient.FetchProfile(profileCtx, npub)
if err == nil && profile != nil {
mu.Lock()
profiles[pk] = profile
mu.Unlock()
}
}(pubkey)
}
wg.Wait()
return profiles
}
// ProcessAsciiDoc processes AsciiDoc content to HTML // ProcessAsciiDoc processes AsciiDoc content to HTML
func (g *HTMLGenerator) ProcessAsciiDoc(content string) (string, error) { func (g *HTMLGenerator) ProcessAsciiDoc(content string) (string, error) {
return g.asciidocProc.Process(content) return g.asciidocProc.Process(content)
@ -158,6 +202,18 @@ func (g *HTMLGenerator) ProcessAsciiDoc(content string) (string, error) {
// GenerateLandingPage generates the static landing page // GenerateLandingPage generates the static landing page
func (g *HTMLGenerator) GenerateLandingPage(wikiPages []WikiPageInfo, feedItems []FeedItemInfo) (string, error) { func (g *HTMLGenerator) GenerateLandingPage(wikiPages []WikiPageInfo, feedItems []FeedItemInfo) (string, error) {
// Collect pubkeys from feed items
pubkeys := make([]string, 0, len(feedItems))
for _, item := range feedItems {
if item.Author != "" {
pubkeys = append(pubkeys, item.Author)
}
}
// Fetch profiles for feed authors
ctx := context.Background()
profiles := g.fetchProfilesBatch(ctx, pubkeys)
data := PageData{ data := PageData{
Title: "Home", Title: "Home",
Description: "Welcome to " + g.siteName, Description: "Welcome to " + g.siteName,
@ -169,6 +225,7 @@ func (g *HTMLGenerator) GenerateLandingPage(wikiPages []WikiPageInfo, feedItems
CurrentYear: time.Now().Year(), CurrentYear: time.Now().Year(),
WikiPages: wikiPages, WikiPages: wikiPages,
FeedItems: feedItems, FeedItems: feedItems,
Profiles: profiles,
} }
return g.renderTemplate("landing.html", data) return g.renderTemplate("landing.html", data)
@ -202,6 +259,7 @@ func (g *HTMLGenerator) GenerateWikiPage(wiki *nostr.WikiEvent, wikiPages []Wiki
FeedItems: []FeedItemInfo{}, // Empty - feed only on landing page FeedItems: []FeedItemInfo{}, // Empty - feed only on landing page
Content: template.HTML(htmlContent), Content: template.HTML(htmlContent),
Summary: wiki.Summary, Summary: wiki.Summary,
Profiles: make(map[string]*nostr.Profile), // Empty profiles for wiki pages
} }
// Add structured data for article // Add structured data for article
@ -219,8 +277,12 @@ func (g *HTMLGenerator) GenerateBlogPage(blogIndex *nostr.IndexEvent, blogItems
canonicalURL := g.siteURL + "/blog" canonicalURL := g.siteURL + "/blog"
// Format times for blog items // Format times for blog items and collect pubkeys
formattedBlogItems := make([]BlogItemInfo, len(blogItems)) formattedBlogItems := make([]BlogItemInfo, len(blogItems))
pubkeys := make([]string, 0, len(blogItems)+1)
if blogIndex.Author != "" {
pubkeys = append(pubkeys, blogIndex.Author)
}
for i, item := range blogItems { for i, item := range blogItems {
formattedBlogItems[i] = item formattedBlogItems[i] = item
if item.CreatedAt > 0 { if item.CreatedAt > 0 {
@ -228,8 +290,15 @@ func (g *HTMLGenerator) GenerateBlogPage(blogIndex *nostr.IndexEvent, blogItems
formattedBlogItems[i].Time = createdTime.Format("Jan 2, 2006") formattedBlogItems[i].Time = createdTime.Format("Jan 2, 2006")
formattedBlogItems[i].TimeISO = createdTime.Format(time.RFC3339) formattedBlogItems[i].TimeISO = createdTime.Format(time.RFC3339)
} }
if item.Author != "" {
pubkeys = append(pubkeys, item.Author)
}
} }
// Fetch profiles for blog authors
ctx := context.Background()
profiles := g.fetchProfilesBatch(ctx, pubkeys)
data := PageData{ data := PageData{
Title: "Blog", Title: "Blog",
Description: description, Description: description,
@ -243,6 +312,7 @@ func (g *HTMLGenerator) GenerateBlogPage(blogIndex *nostr.IndexEvent, blogItems
BlogSummary: blogIndex.Summary, BlogSummary: blogIndex.Summary,
FeedItems: []FeedItemInfo{}, // Empty - feed only on landing page FeedItems: []FeedItemInfo{}, // Empty - feed only on landing page
Content: template.HTML(""), Content: template.HTML(""),
Profiles: profiles,
} }
// Add blog index metadata to template data using a map // Add blog index metadata to template data using a map
@ -321,12 +391,20 @@ func (g *HTMLGenerator) GenerateEBooksPage(ebooks []EBookInfo, feedItems []FeedI
// Format times for display // Format times for display
formattedEBooks := make([]EBookInfo, len(ebooks)) formattedEBooks := make([]EBookInfo, len(ebooks))
pubkeys := make([]string, 0, len(ebooks))
for i, ebook := range ebooks { for i, ebook := range ebooks {
formattedEBooks[i] = ebook formattedEBooks[i] = ebook
createdTime := time.Unix(ebook.CreatedAt, 0) createdTime := time.Unix(ebook.CreatedAt, 0)
formattedEBooks[i].Time = createdTime.Format("2006-01-02 15:04:05") formattedEBooks[i].Time = createdTime.Format("2006-01-02 15:04:05")
formattedEBooks[i].TimeISO = createdTime.Format(time.RFC3339) formattedEBooks[i].TimeISO = createdTime.Format(time.RFC3339)
if ebook.Author != "" {
pubkeys = append(pubkeys, ebook.Author)
} }
}
// Fetch profiles for all authors
ctx := context.Background()
profiles := g.fetchProfilesBatch(ctx, pubkeys)
data := PageData{ data := PageData{
Title: "E-Books", Title: "E-Books",
@ -341,6 +419,7 @@ func (g *HTMLGenerator) GenerateEBooksPage(ebooks []EBookInfo, feedItems []FeedI
FeedItems: []FeedItemInfo{}, // Empty - feed only on landing page FeedItems: []FeedItemInfo{}, // Empty - feed only on landing page
EBooks: formattedEBooks, EBooks: formattedEBooks,
Content: template.HTML(""), // Content comes from template Content: template.HTML(""), // Content comes from template
Profiles: profiles,
} }
return g.renderTemplate("ebooks.html", data) return g.renderTemplate("ebooks.html", data)
@ -385,6 +464,7 @@ func (g *HTMLGenerator) GenerateContactPage(success bool, errorMsg string, event
CurrentYear: time.Now().Year(), CurrentYear: time.Now().Year(),
WikiPages: []WikiPageInfo{}, // Will be populated if needed WikiPages: []WikiPageInfo{}, // Will be populated if needed
FeedItems: []FeedItemInfo{}, // Empty - feed only on landing page FeedItems: []FeedItemInfo{}, // Empty - feed only on landing page
Profiles: make(map[string]*nostr.Profile), // Empty profiles for contact page
}, },
Success: success, Success: success,
Error: errorMsg, Error: errorMsg,
@ -428,6 +508,7 @@ func (g *HTMLGenerator) GenerateContactPage(success bool, errorMsg string, event
"Error": data.Error, "Error": data.Error,
"EventID": data.EventID, "EventID": data.EventID,
"FormData": data.FormData, "FormData": data.FormData,
"Profiles": make(map[string]*nostr.Profile), // Empty profiles for contact page
} }
// Add repo announcement data for JavaScript // Add repo announcement data for JavaScript

12
static/css/main.css

@ -43,18 +43,19 @@ body {
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding-top: 80px; /* Space for fixed header */
} }
/* Skip link for accessibility */ /* Skip link for accessibility */
.skip-link { .skip-link {
position: absolute; position: fixed;
top: -40px; top: -40px;
left: 0; left: 0;
background: var(--accent-color); background: var(--accent-color);
color: var(--bg-primary); color: var(--bg-primary);
padding: 8px; padding: 8px;
text-decoration: none; text-decoration: none;
z-index: 100; z-index: 1001; /* Above fixed header */
} }
.skip-link:focus { .skip-link:focus {
@ -65,9 +66,12 @@ body {
header { header {
background-color: var(--bg-secondary); background-color: var(--bg-secondary);
border-bottom: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color);
position: sticky; position: fixed;
top: 0; top: 0;
z-index: 50; left: 0;
right: 0;
width: 100%;
z-index: 1000;
} }
.navbar { .navbar {

23
templates/components.html

@ -76,10 +76,27 @@
</span> </span>
{{end}} {{end}}
{{/* Simple user badge - just takes a pubkey string */}} {{/* Simple user badge - takes a pubkey string and looks up profile from .Profiles map */}}
{{define "user-badge-simple"}} {{define "user-badge-simple"}}
<span class="user-badge" title="{{.}}"> {{$pubkey := .}}
{{$profile := index $.Profiles $pubkey}}
<span class="user-badge" title="{{$pubkey}}">
{{if and $profile $profile.Picture}}
<img src="{{$profile.Picture}}" alt="{{if $profile.DisplayName}}{{$profile.DisplayName}}{{else if $profile.Name}}{{$profile.Name}}{{else}}User{{end}}" class="user-badge-avatar" loading="lazy">
{{else}}
<span class="user-badge-avatar-placeholder"><i data-lucide="user" class="icon-inline"></i></span> <span class="user-badge-avatar-placeholder"><i data-lucide="user" class="icon-inline"></i></span>
<span class="user-badge-name">{{shortenPubkey .}}</span> {{end}}
<span class="user-badge-name">
{{if and $profile $profile.DisplayName}}
{{$profile.DisplayName}}
{{else if and $profile $profile.Name}}
{{$profile.Name}}
{{else}}
{{shortenPubkey $pubkey}}
{{end}}
</span>
{{if and $profile $profile.Name (not $profile.DisplayName)}}
<span class="user-badge-handle">@{{$profile.Name}}</span>
{{end}}
</span> </span>
{{end}} {{end}}

2
templates/ebooks.html

@ -29,7 +29,7 @@
<time datetime="{{.TimeISO}}">{{.Time}}</time> <time datetime="{{.TimeISO}}">{{.Time}}</time>
</td> </td>
<td> <td>
<a href="https://alexandria.gitcitadel.eu/naddr/{{.Naddr}}" target="_blank" rel="noopener noreferrer" class="btn btn-sm"><i data-lucide="external-link" class="icon-inline"></i> View</a> <a href="https://alexandria.gitcitadel.eu/publication/naddr/{{.Naddr}}" target="_blank" rel="noopener noreferrer" class="btn btn-sm"><i data-lucide="external-link" class="icon-inline"></i> View</a>
</td> </td>
</tr> </tr>
{{else}} {{else}}

Loading…
Cancel
Save