Browse Source

added images to landing page

master
Silberengel 4 weeks ago
parent
commit
48bc9d6b8c
  1. 25
      cmd/server/main.go
  2. 48
      internal/generator/html.go
  3. 18
      internal/nostr/events.go
  4. 41
      static/css/main.css
  5. 46
      templates/landing.html

25
cmd/server/main.go

@ -88,7 +88,25 @@ func main() { @@ -88,7 +88,25 @@ func main() {
time.Duration(cfg.Feed.PollIntervalMinutes)*time.Minute,
)
// Start cache rewarming
// Generate minimal landing page immediately so server can start serving
logger.Info("Generating initial landing page...")
initialLandingHTML, err := htmlGenerator.GenerateLandingPage(
[]generator.WikiPageInfo{},
[]generator.FeedItemInfo{},
nil, // newestBlogItem
nil, // newestArticleItem
)
if err == nil {
if err := pageCache.Set("/", initialLandingHTML); err != nil {
logger.Warnf("Failed to cache initial landing page: %v", err)
} else {
logger.Info("Initial landing page cached")
}
} else {
logger.Warnf("Failed to generate initial landing page: %v", err)
}
// Start cache rewarming (runs in background)
rewarmer.Start(ctx)
// Initialize HTTP server
@ -102,10 +120,7 @@ func main() { @@ -102,10 +120,7 @@ func main() {
}()
logger.Infof("Server started on port %d", cfg.Server.Port)
logger.Info("Waiting for initial cache population...")
// Wait a bit for initial cache
time.Sleep(5 * time.Second)
logger.Info("Cache rewarming in progress... (pages will update as they become available)")
// Wait for shutdown signal
httpServer.WaitForShutdown()

48
internal/generator/html.go

@ -120,6 +120,7 @@ type BlogItemInfo struct { @@ -120,6 +120,7 @@ type BlogItemInfo struct {
Summary string
Content template.HTML
Author string
Image string
CreatedAt int64
Time string // Formatted time
TimeISO string // ISO time
@ -132,6 +133,7 @@ type ArticleItemInfo struct { @@ -132,6 +133,7 @@ type ArticleItemInfo struct {
Summary string
Content template.HTML
Author string
Image string
CreatedAt int64
Time string // Formatted time
TimeISO string // ISO time
@ -299,7 +301,7 @@ func (g *HTMLGenerator) ProcessMarkdown(markdownContent string) (string, error) @@ -299,7 +301,7 @@ func (g *HTMLGenerator) ProcessMarkdown(markdownContent string) (string, error)
}
// GenerateLandingPage generates the static landing page
func (g *HTMLGenerator) GenerateLandingPage(wikiPages []WikiPageInfo, feedItems []FeedItemInfo) (string, error) {
func (g *HTMLGenerator) GenerateLandingPage(wikiPages []WikiPageInfo, feedItems []FeedItemInfo, newestBlogItem *BlogItemInfo, newestArticleItem *ArticleItemInfo) (string, error) {
// Collect pubkeys from feed items
pubkeys := make([]string, 0, len(feedItems))
for _, item := range feedItems {
@ -308,7 +310,15 @@ func (g *HTMLGenerator) GenerateLandingPage(wikiPages []WikiPageInfo, feedItems @@ -308,7 +310,15 @@ func (g *HTMLGenerator) GenerateLandingPage(wikiPages []WikiPageInfo, feedItems
}
}
// Fetch profiles for feed authors
// Add blog and article author pubkeys if available
if newestBlogItem != nil && newestBlogItem.Author != "" {
pubkeys = append(pubkeys, newestBlogItem.Author)
}
if newestArticleItem != nil && newestArticleItem.Author != "" {
pubkeys = append(pubkeys, newestArticleItem.Author)
}
// Fetch profiles for feed authors, blog author, and article author
ctx := context.Background()
profiles := g.fetchProfilesBatch(ctx, pubkeys)
@ -326,7 +336,39 @@ func (g *HTMLGenerator) GenerateLandingPage(wikiPages []WikiPageInfo, feedItems @@ -326,7 +336,39 @@ func (g *HTMLGenerator) GenerateLandingPage(wikiPages []WikiPageInfo, feedItems
Profiles: profiles,
}
return g.renderTemplate("landing.html", data)
// Add newest blog and article items to template data
type LandingPageData struct {
PageData
NewestBlogItem *BlogItemInfo
NewestArticleItem *ArticleItemInfo
}
landingData := LandingPageData{
PageData: data,
NewestBlogItem: newestBlogItem,
NewestArticleItem: newestArticleItem,
}
// Use renderTemplate but with custom data - need to include base.html for DOCTYPE
renderTmpl := template.New("landing-render").Funcs(getTemplateFuncs())
files := []string{
filepath.Join(g.templateDir, "components.html"),
filepath.Join(g.templateDir, "base.html"),
filepath.Join(g.templateDir, "landing.html"),
}
_, err := renderTmpl.ParseFiles(files...)
if err != nil {
return "", fmt.Errorf("failed to parse landing templates: %w", err)
}
var buf bytes.Buffer
if err := renderTmpl.ExecuteTemplate(&buf, "base.html", landingData); err != nil {
return "", fmt.Errorf("failed to render landing template: %w", err)
}
return buf.String(), nil
}
// GenerateWikiPage generates a wiki article page

18
internal/nostr/events.go

@ -186,6 +186,7 @@ type BlogEvent struct { @@ -186,6 +186,7 @@ type BlogEvent struct {
Title string
Summary string
Content string
Image string
}
// ParseBlogEvent parses a blog event
@ -226,6 +227,14 @@ func ParseBlogEvent(event *nostr.Event, expectedKind int) (*BlogEvent, error) { @@ -226,6 +227,14 @@ func ParseBlogEvent(event *nostr.Event, expectedKind int) (*BlogEvent, error) {
}
}
// Extract image tag (optional)
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "image" && len(tag) > 1 {
blog.Image = tag[1]
break
}
}
return blog, nil
}
@ -236,6 +245,7 @@ type LongformEvent struct { @@ -236,6 +245,7 @@ type LongformEvent struct {
Title string
Summary string
Content string
Image string
}
// ParseLongformEvent parses a longform article event
@ -276,6 +286,14 @@ func ParseLongformEvent(event *nostr.Event, expectedKind int) (*LongformEvent, e @@ -276,6 +286,14 @@ func ParseLongformEvent(event *nostr.Event, expectedKind int) (*LongformEvent, e
}
}
// Extract image tag (optional)
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "image" && len(tag) > 1 {
longform.Image = tag[1]
break
}
}
return longform, nil
}

41
static/css/main.css

@ -518,6 +518,47 @@ a:focus { @@ -518,6 +518,47 @@ a:focus {
align-self: flex-start;
}
/* Feature Image Container */
.feature-image-container {
margin-bottom: 1rem;
width: 100%;
}
.feature-image-wrapper {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
overflow: hidden;
border-radius: 8px;
background: var(--bg-primary);
}
.feature-image {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.feature-image-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(to top, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0.4) 50%, transparent 100%);
padding: 1.5rem 1rem 1rem;
color: #ffffff;
}
.feature-image-title {
margin: 0;
font-size: 1.1rem;
font-weight: 600;
color: #ffffff;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
line-height: 1.3;
}
/* Articles and Pages */
article {
background: var(--bg-secondary);

46
templates/landing.html

@ -26,12 +26,58 @@ @@ -26,12 +26,58 @@
<div class="feature-card">
<h3><span class="icon-inline">{{icon "file-text"}}</span> Project Blog</h3>
{{if .NewestBlogItem}}
<div class="feature-image-container">
{{$item := .NewestBlogItem}}
{{if $item.Image}}
<div class="feature-image-wrapper">
<img src="{{$item.Image}}" alt="{{$item.Title}}" class="feature-image">
<div class="feature-image-overlay">
<h4 class="feature-image-title">{{$item.Title}}</h4>
</div>
</div>
{{else}}
{{$profile := index $.Profiles $item.Author}}
{{if $profile.Picture}}
<div class="feature-image-wrapper">
<img src="{{$profile.Picture}}" alt="{{$item.Title}}" class="feature-image">
<div class="feature-image-overlay">
<h4 class="feature-image-title">{{$item.Title}}</h4>
</div>
</div>
{{end}}
{{end}}
</div>
{{end}}
<p>Read the latest articles and updates from the GitCitadel project blog.</p>
<a href="/blog" class="btn"><span class="icon-inline">{{icon "arrow-right"}}</span> View Blog</a>
</div>
<div class="feature-card">
<h3><span class="icon-inline">{{icon "file-text"}}</span> Articles</h3>
{{if .NewestArticleItem}}
<div class="feature-image-container">
{{$item := .NewestArticleItem}}
{{if $item.Image}}
<div class="feature-image-wrapper">
<img src="{{$item.Image}}" alt="{{$item.Title}}" class="feature-image">
<div class="feature-image-overlay">
<h4 class="feature-image-title">{{$item.Title}}</h4>
</div>
</div>
{{else}}
{{$profile := index $.Profiles $item.Author}}
{{if $profile.Picture}}
<div class="feature-image-wrapper">
<img src="{{$profile.Picture}}" alt="{{$item.Title}}" class="feature-image">
<div class="feature-image-overlay">
<h4 class="feature-image-title">{{$item.Title}}</h4>
</div>
</div>
{{end}}
{{end}}
</div>
{{end}}
<p>Longform markdown articles from the TheForest relay.</p>
<a href="/articles" class="btn"><span class="icon-inline">{{icon "arrow-right"}}</span> View Articles</a>
</div>

Loading…
Cancel
Save