From c5d215ce0419a3a4a40163c02c39e91017658e14 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Mon, 16 Feb 2026 12:00:37 +0100 Subject: [PATCH] bug-fixes --- cmd/server/main.go | 6 +- internal/generator/html.go | 46 ++++++- static/css/main.css | 262 +++++++++++++++++++++++++++++++++++++ static/css/responsive.css | 20 +++ templates/articles.html | 69 ++++++++++ templates/base.html | 17 +++ templates/blog.html | 69 ++++++++++ templates/components.html | 35 +++++ templates/ebooks.html | 48 +++++++ templates/landing.html | 136 +++++++++++++++---- templates/wiki.html | 9 +- 11 files changed, 687 insertions(+), 30 deletions(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index d645093..4bc7540 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -99,8 +99,10 @@ func main() { initialLandingHTML, err := htmlGenerator.GenerateLandingPage( []generator.WikiPageInfo{}, []generator.FeedItemInfo{}, - nil, // newestBlogItem - nil, // newestArticleItem + nil, // newestBlogItem + nil, // newestArticleItem + []generator.ArticleItemInfo{}, // allArticleItems + []generator.EBookInfo{}, // allEBooks ) if err == nil { if err := pageCache.Set("/", initialLandingHTML); err != nil { diff --git a/internal/generator/html.go b/internal/generator/html.go index f6f27cc..fe50937 100644 --- a/internal/generator/html.go +++ b/internal/generator/html.go @@ -161,6 +161,7 @@ type EBookInfo struct { DTag string Author string Summary string + Image string Type string CreatedAt int64 Naddr string @@ -308,7 +309,7 @@ func (g *HTMLGenerator) ProcessMarkdown(markdownContent string) (string, error) } // GenerateLandingPage generates the static landing page -func (g *HTMLGenerator) GenerateLandingPage(wikiPages []WikiPageInfo, feedItems []FeedItemInfo, newestBlogItem *BlogItemInfo, newestArticleItem *ArticleItemInfo) (string, error) { +func (g *HTMLGenerator) GenerateLandingPage(wikiPages []WikiPageInfo, feedItems []FeedItemInfo, newestBlogItem *BlogItemInfo, newestArticleItem *ArticleItemInfo, allArticleItems []ArticleItemInfo, allEBooks []EBookInfo) (string, error) { // Collect pubkeys from feed items pubkeys := make([]string, 0, len(feedItems)) for _, item := range feedItems { @@ -325,10 +326,45 @@ func (g *HTMLGenerator) GenerateLandingPage(wikiPages []WikiPageInfo, feedItems pubkeys = append(pubkeys, newestArticleItem.Author) } - // Fetch profiles for feed authors, blog author, and article author + // Add all article author pubkeys + for _, item := range allArticleItems { + if item.Author != "" { + pubkeys = append(pubkeys, item.Author) + } + } + + // Add all e-book author pubkeys + for _, ebook := range allEBooks { + if ebook.Author != "" { + pubkeys = append(pubkeys, ebook.Author) + } + } + + // Fetch profiles for all authors ctx := context.Background() profiles := g.fetchProfilesBatch(ctx, pubkeys) + // Format times for article items and e-books + formattedArticleItems := make([]ArticleItemInfo, len(allArticleItems)) + for i, item := range allArticleItems { + formattedArticleItems[i] = item + if item.CreatedAt > 0 { + t := time.Unix(item.CreatedAt, 0) + formattedArticleItems[i].Time = t.Format("2006-01-02 15:04:05") + formattedArticleItems[i].TimeISO = t.Format(time.RFC3339) + } + } + + formattedEBooks := make([]EBookInfo, len(allEBooks)) + for i, ebook := range allEBooks { + formattedEBooks[i] = ebook + if ebook.CreatedAt > 0 { + t := time.Unix(ebook.CreatedAt, 0) + formattedEBooks[i].Time = t.Format("2006-01-02 15:04:05") + formattedEBooks[i].TimeISO = t.Format(time.RFC3339) + } + } + data := PageData{ Title: "Home", Description: "Welcome to " + g.siteName, @@ -343,17 +379,21 @@ func (g *HTMLGenerator) GenerateLandingPage(wikiPages []WikiPageInfo, feedItems Profiles: profiles, } - // Add newest blog and article items to template data + // Add newest blog and article items, plus all articles and e-books to template data type LandingPageData struct { PageData NewestBlogItem *BlogItemInfo NewestArticleItem *ArticleItemInfo + AllArticleItems []ArticleItemInfo + AllEBooks []EBookInfo } landingData := LandingPageData{ PageData: data, NewestBlogItem: newestBlogItem, NewestArticleItem: newestArticleItem, + AllArticleItems: formattedArticleItems, + AllEBooks: formattedEBooks, } // Use renderTemplate but with custom data - need to include base.html for DOCTYPE diff --git a/static/css/main.css b/static/css/main.css index 4f624fc..57791ce 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -314,6 +314,70 @@ header { color: var(--bg-primary); } +/* Wiki Index Page */ +.wiki-index-content { + margin-top: 1rem; +} + +.wiki-index-intro { + margin-bottom: 1rem; + color: var(--text-secondary); +} + +.wiki-index-links { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + margin-bottom: 1.5rem; +} + +.wiki-index-links .wiki-link { + display: inline-flex; + align-items: center; + padding: 0.75rem 1rem; + background: var(--bg-primary); + color: var(--text-primary); + text-decoration: none; + border-radius: 6px; + border: 1px solid var(--border-color); + font-size: 0.95rem; + transition: all 0.2s; + white-space: nowrap; + gap: 0.5rem; +} + +.wiki-index-links .wiki-link:hover { + background: var(--accent-color); + color: #ffffff; + border-color: var(--accent-color); + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); +} + +.wiki-index-links .wiki-link.active { + background: var(--accent-color); + color: var(--bg-primary); + font-weight: 500; + border-color: var(--accent-color); +} + +.wiki-index-links .wiki-link.active:hover { + background: var(--link-hover); + color: var(--bg-primary); +} + +.wiki-index-links .wiki-link:focus-visible { + outline: var(--focus-outline); + outline-offset: var(--focus-offset); +} + +.wiki-sidebar-note { + margin-top: 1rem; + color: var(--text-secondary); + font-size: 0.9rem; + font-style: italic; +} + .main-content { flex: 1; min-width: 0; @@ -1562,3 +1626,201 @@ p.icon-inline, h1.icon-inline, h2.icon-inline, h3.icon-inline, h4.icon-inline, h align-items: center; gap: 0.5rem; } + +/* Mobile Selector Dropdown */ +.mobile-selector { + display: none; /* Hidden on desktop, shown on mobile */ + margin-bottom: 1rem; + padding: 0 1rem; +} + +.mobile-select { + width: 100%; + padding: 0.75rem; + background: var(--bg-secondary); + color: var(--text-primary); + border: 1px solid var(--border-color); + border-radius: 4px; + font-size: 1rem; + font-family: inherit; + cursor: pointer; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23f0f0f0' d='M6 9L1 4h10z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 0.75rem center; + background-size: 12px; + padding-right: 2.5rem; +} + +.mobile-select:focus-visible { + outline: var(--focus-outline); + outline-offset: var(--focus-offset); + border-color: var(--focus-color); +} + +.mobile-select option { + background: var(--bg-secondary); + color: var(--text-primary); + padding: 0.5rem; +} + +/* Mobile Custom Dropdown with Profile Pictures */ +.mobile-selector-custom { + display: none; /* Hidden on desktop, shown on mobile */ + position: relative; + margin-bottom: 1rem; + padding: 0 1rem; + z-index: 100; +} + +.mobile-dropdown-toggle { + width: 100%; + padding: 0.75rem; + background: var(--bg-secondary); + color: var(--text-primary); + border: 1px solid var(--border-color); + border-radius: 4px; + font-size: 1rem; + font-family: inherit; + cursor: pointer; + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.75rem; + text-align: left; +} + +.mobile-dropdown-toggle:focus-visible { + outline: var(--focus-outline); + outline-offset: var(--focus-offset); + border-color: var(--focus-color); +} + +.mobile-dropdown-selected { + display: flex; + align-items: center; + gap: 0.75rem; + flex: 1; + min-width: 0; +} + +.mobile-dropdown-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + object-fit: cover; + flex-shrink: 0; + border: 1px solid var(--border-color); +} + +.mobile-dropdown-avatar-placeholder { + width: 32px; + height: 32px; + border-radius: 50%; + background: var(--bg-primary); + border: 1px solid var(--border-color); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.mobile-dropdown-avatar-placeholder .icon-inline { + width: 18px; + height: 18px; + margin: 0; + color: var(--text-secondary); +} + +.mobile-dropdown-title { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: 500; +} + +.mobile-dropdown-arrow { + flex-shrink: 0; + font-size: 0.75rem; + color: var(--text-secondary); + transition: transform 0.2s; +} + +.mobile-dropdown-toggle[aria-expanded="true"] .mobile-dropdown-arrow { + transform: rotate(180deg); +} + +.mobile-dropdown-menu { + position: absolute; + top: 100%; + left: 1rem; + right: 1rem; + margin-top: 0.25rem; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 4px; + list-style: none; + padding: 0.5rem 0; + margin-left: 0; + max-height: 60vh; + overflow-y: auto; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); + opacity: 0; + visibility: hidden; + transform: translateY(-10px); + transition: opacity 0.2s, visibility 0.2s, transform 0.2s; + z-index: 1000; +} + +.mobile-dropdown-menu.active { + opacity: 1; + visibility: visible; + transform: translateY(0); +} + +.mobile-dropdown-menu li { + padding: 0; + margin: 0; +} + +.mobile-dropdown-menu li[role="option"] { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 1rem; + cursor: pointer; + transition: background 0.2s; + border: none; + background: transparent; + width: 100%; + text-align: left; +} + +.mobile-dropdown-menu li[role="option"]:hover, +.mobile-dropdown-menu li[role="option"]:focus { + background: var(--bg-primary); + outline: none; +} + +.mobile-dropdown-menu li[role="option"].selected { + background: var(--bg-primary); + border-left: 3px solid var(--accent-color); +} + +.mobile-dropdown-menu li[role="option"] .mobile-dropdown-avatar, +.mobile-dropdown-menu li[role="option"] .mobile-dropdown-avatar-placeholder { + width: 28px; + height: 28px; +} + +.mobile-dropdown-menu li[role="option"] .mobile-dropdown-avatar-placeholder .icon-inline { + width: 16px; + height: 16px; +} + +.mobile-dropdown-menu li[role="option"] .mobile-dropdown-title { + font-weight: normal; + font-size: 0.95rem; +} diff --git a/static/css/responsive.css b/static/css/responsive.css index b60aed9..45c7a90 100644 --- a/static/css/responsive.css +++ b/static/css/responsive.css @@ -11,6 +11,26 @@ display: none; } + /* Hide sidebar note on mobile since sidebar is hidden */ + .wiki-sidebar-note { + display: none; + } + + /* Show mobile selector on mobile */ + .mobile-selector { + display: block; + } + + /* Show mobile custom dropdown on mobile */ + .mobile-selector-custom { + display: block; + } + + /* Hide e-books table on mobile, show dropdown instead */ + .ebooks-table { + display: none; + } + .layout-container { flex-direction: column; padding: 1rem; diff --git a/templates/articles.html b/templates/articles.html index 7fd5cb0..701dc8a 100644 --- a/templates/articles.html +++ b/templates/articles.html @@ -1,4 +1,9 @@ {{define "content"}} + +{{if .ArticleItems}} +{{template "mobile-dropdown" (dict "Id" "mobile-articles-selector" "Items" .ArticleItems "Profiles" $.Profiles "Type" "article")}} +{{end}} +