Browse Source

bug-fixes

master
Silberengel 4 weeks ago
parent
commit
c5d215ce04
  1. 2
      cmd/server/main.go
  2. 46
      internal/generator/html.go
  3. 262
      static/css/main.css
  4. 20
      static/css/responsive.css
  5. 69
      templates/articles.html
  6. 17
      templates/base.html
  7. 69
      templates/blog.html
  8. 35
      templates/components.html
  9. 48
      templates/ebooks.html
  10. 116
      templates/landing.html
  11. 9
      templates/wiki.html

2
cmd/server/main.go

@ -101,6 +101,8 @@ func main() {
[]generator.FeedItemInfo{}, []generator.FeedItemInfo{},
nil, // newestBlogItem nil, // newestBlogItem
nil, // newestArticleItem nil, // newestArticleItem
[]generator.ArticleItemInfo{}, // allArticleItems
[]generator.EBookInfo{}, // allEBooks
) )
if err == nil { if err == nil {
if err := pageCache.Set("/", initialLandingHTML); err != nil { if err := pageCache.Set("/", initialLandingHTML); err != nil {

46
internal/generator/html.go

@ -161,6 +161,7 @@ type EBookInfo struct {
DTag string DTag string
Author string Author string
Summary string Summary string
Image string
Type string Type string
CreatedAt int64 CreatedAt int64
Naddr string Naddr string
@ -308,7 +309,7 @@ func (g *HTMLGenerator) ProcessMarkdown(markdownContent string) (string, error)
} }
// GenerateLandingPage generates the static landing page // 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 // Collect pubkeys from feed items
pubkeys := make([]string, 0, len(feedItems)) pubkeys := make([]string, 0, len(feedItems))
for _, item := range feedItems { for _, item := range feedItems {
@ -325,10 +326,45 @@ func (g *HTMLGenerator) GenerateLandingPage(wikiPages []WikiPageInfo, feedItems
pubkeys = append(pubkeys, newestArticleItem.Author) 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() ctx := context.Background()
profiles := g.fetchProfilesBatch(ctx, pubkeys) 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{ data := PageData{
Title: "Home", Title: "Home",
Description: "Welcome to " + g.siteName, Description: "Welcome to " + g.siteName,
@ -343,17 +379,21 @@ func (g *HTMLGenerator) GenerateLandingPage(wikiPages []WikiPageInfo, feedItems
Profiles: profiles, 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 { type LandingPageData struct {
PageData PageData
NewestBlogItem *BlogItemInfo NewestBlogItem *BlogItemInfo
NewestArticleItem *ArticleItemInfo NewestArticleItem *ArticleItemInfo
AllArticleItems []ArticleItemInfo
AllEBooks []EBookInfo
} }
landingData := LandingPageData{ landingData := LandingPageData{
PageData: data, PageData: data,
NewestBlogItem: newestBlogItem, NewestBlogItem: newestBlogItem,
NewestArticleItem: newestArticleItem, NewestArticleItem: newestArticleItem,
AllArticleItems: formattedArticleItems,
AllEBooks: formattedEBooks,
} }
// Use renderTemplate but with custom data - need to include base.html for DOCTYPE // Use renderTemplate but with custom data - need to include base.html for DOCTYPE

262
static/css/main.css

@ -314,6 +314,70 @@ header {
color: var(--bg-primary); 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 { .main-content {
flex: 1; flex: 1;
min-width: 0; 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; align-items: center;
gap: 0.5rem; 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;
}

20
static/css/responsive.css

@ -11,6 +11,26 @@
display: none; 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 { .layout-container {
flex-direction: column; flex-direction: column;
padding: 1rem; padding: 1rem;

69
templates/articles.html

@ -1,4 +1,9 @@
{{define "content"}} {{define "content"}}
<!-- Mobile Dropdown Selector -->
{{if .ArticleItems}}
{{template "mobile-dropdown" (dict "Id" "mobile-articles-selector" "Items" .ArticleItems "Profiles" $.Profiles "Type" "article")}}
{{end}}
<div class="blog-layout"> <div class="blog-layout">
<!-- Left Sidebar --> <!-- Left Sidebar -->
<aside class="blog-sidebar"> <aside class="blog-sidebar">
@ -123,6 +128,70 @@
} }
} }
}); });
// Handle mobile custom dropdown
const mobileToggle = document.getElementById('mobile-articles-selector-toggle');
const mobileMenu = document.getElementById('mobile-articles-selector-menu');
if (mobileToggle && mobileMenu) {
mobileToggle.addEventListener('click', function(e) {
e.stopPropagation();
const isExpanded = this.getAttribute('aria-expanded') === 'true';
this.setAttribute('aria-expanded', !isExpanded);
mobileMenu.classList.toggle('active');
});
// Close dropdown when clicking outside
document.addEventListener('click', function(e) {
if (!mobileToggle.contains(e.target) && !mobileMenu.contains(e.target)) {
mobileToggle.setAttribute('aria-expanded', 'false');
mobileMenu.classList.remove('active');
}
});
// Handle option selection
mobileMenu.querySelectorAll('li[role="option"]').forEach(function(option) {
option.addEventListener('click', function() {
const dtag = this.dataset.value;
showArticle(dtag);
// Update selected state
mobileMenu.querySelectorAll('li').forEach(li => li.classList.remove('selected'));
this.classList.add('selected');
this.setAttribute('aria-selected', 'true');
// Update toggle button
const avatar = this.querySelector('.mobile-dropdown-avatar, .mobile-dropdown-avatar-placeholder');
const title = this.querySelector('.mobile-dropdown-title').textContent;
const selected = mobileToggle.querySelector('.mobile-dropdown-selected');
selected.innerHTML = avatar.outerHTML + '<span class="mobile-dropdown-title">' + title + '</span>';
// Close dropdown
mobileToggle.setAttribute('aria-expanded', 'false');
mobileMenu.classList.remove('active');
});
});
// Update dropdown when article changes
const originalShowArticle = showArticle;
showArticle = function(dtag) {
originalShowArticle(dtag);
const selectedOption = mobileMenu.querySelector(`li[data-value="${dtag}"]`);
if (selectedOption) {
mobileMenu.querySelectorAll('li').forEach(li => {
li.classList.remove('selected');
li.setAttribute('aria-selected', 'false');
});
selectedOption.classList.add('selected');
selectedOption.setAttribute('aria-selected', 'true');
// Update toggle button
const avatar = selectedOption.querySelector('.mobile-dropdown-avatar, .mobile-dropdown-avatar-placeholder');
const title = selectedOption.querySelector('.mobile-dropdown-title').textContent;
const selected = mobileToggle.querySelector('.mobile-dropdown-selected');
selected.innerHTML = avatar.outerHTML + '<span class="mobile-dropdown-title">' + title + '</span>';
}
};
}
})(); })();
</script> </script>
{{end}} {{end}}

17
templates/base.html

@ -71,6 +71,15 @@
<div class="layout-container{{if and .WikiPages (or (eq .CanonicalURL (printf "%s/wiki" .SiteURL)) (hasPrefix .CanonicalURL (printf "%s/wiki/" .SiteURL)))}} wiki-layout{{end}}"> <div class="layout-container{{if and .WikiPages (or (eq .CanonicalURL (printf "%s/wiki" .SiteURL)) (hasPrefix .CanonicalURL (printf "%s/wiki/" .SiteURL)))}} wiki-layout{{end}}">
{{if and .WikiPages (or (eq .CanonicalURL (printf "%s/wiki" .SiteURL)) (hasPrefix .CanonicalURL (printf "%s/wiki/" .SiteURL)))}} {{if and .WikiPages (or (eq .CanonicalURL (printf "%s/wiki" .SiteURL)) (hasPrefix .CanonicalURL (printf "%s/wiki/" .SiteURL)))}}
<!-- Mobile Wiki Dropdown -->
<div class="mobile-selector">
<select id="mobile-wiki-selector" class="mobile-select" aria-label="Select wiki page">
<option value="/wiki"{{if eq .CanonicalURL (printf "%s/wiki" .SiteURL)}} selected{{end}}>Project Overview</option>
{{range .WikiPages}}
<option value="/wiki/{{.DTag}}"{{if eq $.CanonicalURL (printf "%s/wiki/%s" $.SiteURL .DTag)}} selected{{end}}>{{.Title}}</option>
{{end}}
</select>
</div>
{{template "wiki-sidebar" .}} {{template "wiki-sidebar" .}}
{{end}} {{end}}
@ -106,6 +115,14 @@
e.preventDefault(); e.preventDefault();
}); });
}); });
// Mobile wiki selector
const wikiSelector = document.getElementById('mobile-wiki-selector');
if (wikiSelector) {
wikiSelector.addEventListener('change', function() {
window.location.href = this.value;
});
}
</script> </script>
</body> </body>
</html> </html>

69
templates/blog.html

@ -1,4 +1,9 @@
{{define "content"}} {{define "content"}}
<!-- Mobile Dropdown Selector -->
{{if .BlogItems}}
{{template "mobile-dropdown" (dict "Id" "mobile-blog-selector" "Items" .BlogItems "Profiles" $.Profiles "Type" "blog")}}
{{end}}
<div class="blog-layout"> <div class="blog-layout">
<!-- Left Sidebar --> <!-- Left Sidebar -->
<aside class="blog-sidebar"> <aside class="blog-sidebar">
@ -136,6 +141,70 @@
} }
} }
}); });
// Handle mobile custom dropdown
const mobileToggle = document.getElementById('mobile-blog-selector-toggle');
const mobileMenu = document.getElementById('mobile-blog-selector-menu');
if (mobileToggle && mobileMenu) {
mobileToggle.addEventListener('click', function(e) {
e.stopPropagation();
const isExpanded = this.getAttribute('aria-expanded') === 'true';
this.setAttribute('aria-expanded', !isExpanded);
mobileMenu.classList.toggle('active');
});
// Close dropdown when clicking outside
document.addEventListener('click', function(e) {
if (!mobileToggle.contains(e.target) && !mobileMenu.contains(e.target)) {
mobileToggle.setAttribute('aria-expanded', 'false');
mobileMenu.classList.remove('active');
}
});
// Handle option selection
mobileMenu.querySelectorAll('li[role="option"]').forEach(function(option) {
option.addEventListener('click', function() {
const dtag = this.dataset.value;
showArticle(dtag);
// Update selected state
mobileMenu.querySelectorAll('li').forEach(li => li.classList.remove('selected'));
this.classList.add('selected');
this.setAttribute('aria-selected', 'true');
// Update toggle button
const avatar = this.querySelector('.mobile-dropdown-avatar, .mobile-dropdown-avatar-placeholder');
const title = this.querySelector('.mobile-dropdown-title').textContent;
const selected = mobileToggle.querySelector('.mobile-dropdown-selected');
selected.innerHTML = avatar.outerHTML + '<span class="mobile-dropdown-title">' + title + '</span>';
// Close dropdown
mobileToggle.setAttribute('aria-expanded', 'false');
mobileMenu.classList.remove('active');
});
});
// Update dropdown when article changes
const originalShowArticle = showArticle;
showArticle = function(dtag) {
originalShowArticle(dtag);
const selectedOption = mobileMenu.querySelector(`li[data-value="${dtag}"]`);
if (selectedOption) {
mobileMenu.querySelectorAll('li').forEach(li => {
li.classList.remove('selected');
li.setAttribute('aria-selected', 'false');
});
selectedOption.classList.add('selected');
selectedOption.setAttribute('aria-selected', 'true');
// Update toggle button
const avatar = selectedOption.querySelector('.mobile-dropdown-avatar, .mobile-dropdown-avatar-placeholder');
const title = selectedOption.querySelector('.mobile-dropdown-title').textContent;
const selected = mobileToggle.querySelector('.mobile-dropdown-selected');
selected.innerHTML = avatar.outerHTML + '<span class="mobile-dropdown-title">' + title + '</span>';
}
};
}
})(); })();
</script> </script>
{{end}} {{end}}

35
templates/components.html

@ -104,3 +104,38 @@
{{end}} {{end}}
</span> </span>
{{end}} {{end}}
{{/* Mobile Custom Dropdown Component - Shows title and profile pic
Usage for blog/articles: {{template "mobile-dropdown" (dict "Id" "mobile-blog-selector" "Items" .BlogItems "Profiles" $.Profiles "Type" "blog")}}
Usage for ebooks: {{template "mobile-dropdown" (dict "Id" "mobile-ebooks-selector" "Items" .EBooks "Profiles" $.Profiles "Type" "ebook")}}
*/}}
{{define "mobile-dropdown"}}
<div class="mobile-selector-custom" id="{{.Id}}-container">
<button class="mobile-dropdown-toggle" id="{{.Id}}-toggle" aria-haspopup="true" aria-expanded="false" type="button">
<span class="mobile-dropdown-selected">
{{$firstItem := index .Items 0}}
{{$profile := index .Profiles $firstItem.Author}}
{{if and $profile $profile.Picture}}
<img src="{{$profile.Picture}}" alt="" class="mobile-dropdown-avatar">
{{else}}
<span class="mobile-dropdown-avatar-placeholder"><span class="icon-inline">{{icon "user"}}</span></span>
{{end}}
<span class="mobile-dropdown-title">{{$firstItem.Title}}</span>
</span>
<span class="mobile-dropdown-arrow"></span>
</button>
<ul class="mobile-dropdown-menu" id="{{.Id}}-menu" role="listbox" aria-labelledby="{{.Id}}-toggle">
{{range $index, $item := .Items}}
<li role="option" data-value="{{if eq $.Type "ebook"}}{{$item.Naddr}}{{else}}{{$item.DTag}}{{end}}" data-index="{{$index}}"{{if eq $index 0}} aria-selected="true" class="selected"{{end}}>
{{$profile := index $.Profiles $item.Author}}
{{if and $profile $profile.Picture}}
<img src="{{$profile.Picture}}" alt="" class="mobile-dropdown-avatar">
{{else}}
<span class="mobile-dropdown-avatar-placeholder"><span class="icon-inline">{{icon "user"}}</span></span>
{{end}}
<span class="mobile-dropdown-title">{{$item.Title}}</span>
</li>
{{end}}
</ul>
</div>
{{end}}

48
templates/ebooks.html

@ -5,6 +5,11 @@
<p class="page-summary">Top-level publications (index events) from Nostr. These are publications that are not contained in any higher-level index event.</p> <p class="page-summary">Top-level publications (index events) from Nostr. These are publications that are not contained in any higher-level index event.</p>
</header> </header>
<!-- Mobile E-Books Dropdown -->
{{if .EBooks}}
{{template "mobile-dropdown" (dict "Id" "mobile-ebooks-selector" "Items" .EBooks "Profiles" $.Profiles "Type" "ebook")}}
{{end}}
<div class="ebooks-container"> <div class="ebooks-container">
<table id="ebooks-table" class="ebooks-table" aria-label="E-Books listing"> <table id="ebooks-table" class="ebooks-table" aria-label="E-Books listing">
<thead> <thead>
@ -90,6 +95,49 @@
// Add cursor pointer style // Add cursor pointer style
header.style.cursor = 'pointer'; header.style.cursor = 'pointer';
}); });
// Handle mobile e-books custom dropdown
const ebooksToggle = document.getElementById('mobile-ebooks-selector-toggle');
const ebooksMenu = document.getElementById('mobile-ebooks-selector-menu');
if (ebooksToggle && ebooksMenu) {
ebooksToggle.addEventListener('click', function(e) {
e.stopPropagation();
const isExpanded = this.getAttribute('aria-expanded') === 'true';
this.setAttribute('aria-expanded', !isExpanded);
ebooksMenu.classList.toggle('active');
});
// Close dropdown when clicking outside
document.addEventListener('click', function(e) {
if (!ebooksToggle.contains(e.target) && !ebooksMenu.contains(e.target)) {
ebooksToggle.setAttribute('aria-expanded', 'false');
ebooksMenu.classList.remove('active');
}
});
// Handle option selection
ebooksMenu.querySelectorAll('li[role="option"]').forEach(function(option) {
option.addEventListener('click', function() {
const naddr = this.dataset.value;
window.open('https://alexandria.gitcitadel.eu/publication/naddr/' + naddr, '_blank', 'noopener,noreferrer');
// Update selected state
ebooksMenu.querySelectorAll('li').forEach(li => li.classList.remove('selected'));
this.classList.add('selected');
this.setAttribute('aria-selected', 'true');
// Update toggle button
const avatar = this.querySelector('.mobile-dropdown-avatar, .mobile-dropdown-avatar-placeholder');
const title = this.querySelector('.mobile-dropdown-title').textContent;
const selected = ebooksToggle.querySelector('.mobile-dropdown-selected');
selected.innerHTML = avatar.outerHTML + '<span class="mobile-dropdown-title">' + title + '</span>';
// Close dropdown
ebooksToggle.setAttribute('aria-expanded', 'false');
ebooksMenu.classList.remove('active');
});
});
}
})(); })();
</script> </script>
{{end}} {{end}}

116
templates/landing.html

@ -26,27 +26,33 @@
<div class="feature-card"> <div class="feature-card">
<h3><span class="icon-inline">{{icon "file-text"}}</span> Project Blog</h3> <h3><span class="icon-inline">{{icon "file-text"}}</span> Project Blog</h3>
{{if .NewestBlogItem}}
<div class="feature-image-container"> <div class="feature-image-container">
{{$image := "/static/GitCitadel_Icon_Gradient.svg"}}
{{$title := "Project Blog"}}
{{$summary := ""}}
{{if .NewestBlogItem}}
{{$item := .NewestBlogItem}} {{$item := .NewestBlogItem}}
{{$profile := index $.Profiles $item.Author}} {{$profile := index $.Profiles $item.Author}}
{{$image := "/static/GitCitadel_Icon_Gradient.svg"}} {{$title = $item.Title}}
{{if $item.Summary}}
{{$summary = $item.Summary}}
{{end}}
{{if and $item.Image (ne $item.Image "")}} {{if and $item.Image (ne $item.Image "")}}
{{$image = $item.Image}} {{$image = $item.Image}}
{{else if and $profile $profile.Picture (ne $profile.Picture "")}} {{else if and $profile $profile.Picture (ne $profile.Picture "")}}
{{$image = $profile.Picture}} {{$image = $profile.Picture}}
{{end}} {{end}}
{{end}}
<div class="feature-image-wrapper"> <div class="feature-image-wrapper">
<img src="{{$image}}" alt="{{$item.Title}}" class="feature-image"> <img src="{{$image}}" alt="{{$title}}" class="feature-image">
<div class="feature-image-overlay"> <div class="feature-image-overlay">
<h4 class="feature-image-title">{{$item.Title}}</h4> <h4 class="feature-image-title">{{$title}}</h4>
{{if $item.Summary}} {{if $summary}}
<p class="feature-image-summary">{{truncate $item.Summary 250}}</p> <p class="feature-image-summary">{{truncate $summary 250}}</p>
{{end}} {{end}}
</div> </div>
</div> </div>
</div> </div>
{{end}}
<div class="feature-card-content"> <div class="feature-card-content">
<p>Read the latest articles and updates from the GitCitadel project blog.</p> <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> <a href="/blog" class="btn"><span class="icon-inline">{{icon "arrow-right"}}</span> View Blog</a>
@ -55,27 +61,33 @@
<div class="feature-card"> <div class="feature-card">
<h3><span class="icon-inline">{{icon "file-text"}}</span> Articles</h3> <h3><span class="icon-inline">{{icon "file-text"}}</span> Articles</h3>
{{if .NewestArticleItem}}
<div class="feature-image-container"> <div class="feature-image-container">
{{$image := "/static/GitCitadel_Icon_Gradient.svg"}}
{{$title := "Articles"}}
{{$summary := ""}}
{{if .NewestArticleItem}}
{{$item := .NewestArticleItem}} {{$item := .NewestArticleItem}}
{{$profile := index $.Profiles $item.Author}} {{$profile := index $.Profiles $item.Author}}
{{$image := "/static/GitCitadel_Icon_Gradient.svg"}} {{$title = $item.Title}}
{{if $item.Summary}}
{{$summary = $item.Summary}}
{{end}}
{{if and $item.Image (ne $item.Image "")}} {{if and $item.Image (ne $item.Image "")}}
{{$image = $item.Image}} {{$image = $item.Image}}
{{else if and $profile $profile.Picture (ne $profile.Picture "")}} {{else if and $profile $profile.Picture (ne $profile.Picture "")}}
{{$image = $profile.Picture}} {{$image = $profile.Picture}}
{{end}} {{end}}
{{end}}
<div class="feature-image-wrapper"> <div class="feature-image-wrapper">
<img src="{{$image}}" alt="{{$item.Title}}" class="feature-image"> <img src="{{$image}}" alt="{{$title}}" class="feature-image">
<div class="feature-image-overlay"> <div class="feature-image-overlay">
<h4 class="feature-image-title">{{$item.Title}}</h4> <h4 class="feature-image-title">{{$title}}</h4>
{{if $item.Summary}} {{if $summary}}
<p class="feature-image-summary">{{truncate $item.Summary 250}}</p> <p class="feature-image-summary">{{truncate $summary 250}}</p>
{{end}} {{end}}
</div> </div>
</div> </div>
</div> </div>
{{end}}
<div class="feature-card-content"> <div class="feature-card-content">
<p>Longform markdown articles from the TheForest relay.</p> <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> <a href="/articles" class="btn"><span class="icon-inline">{{icon "arrow-right"}}</span> View Articles</a>
@ -101,6 +113,82 @@
</div> </div>
</div> </div>
</section> </section>
{{if .AllArticleItems}}
<section class="articles-section">
<h2>All Articles</h2>
<div class="feature-grid">
{{range .AllArticleItems}}
<div class="feature-card">
<div class="feature-image-container">
{{$item := .}}
{{$profile := index $.Profiles $item.Author}}
{{$image := "/static/GitCitadel_Icon_Gradient.svg"}}
{{if and $item.Image (ne $item.Image "")}}
{{$image = $item.Image}}
{{else if and $profile $profile.Picture (ne $profile.Picture "")}}
{{$image = $profile.Picture}}
{{end}}
<div class="feature-image-wrapper">
<img src="{{$image}}" alt="{{$item.Title}}" class="feature-image">
<div class="feature-image-overlay">
<h4 class="feature-image-title">{{$item.Title}}</h4>
{{if $item.Summary}}
<p class="feature-image-summary">{{truncate $item.Summary 250}}</p>
{{end}}
</div>
</div>
</div>
<div class="feature-card-content">
<h3>{{$item.Title}}</h3>
{{if $item.Summary}}
<p>{{truncate $item.Summary 150}}</p>
{{end}}
<a href="/articles#{{$item.DTag}}" class="btn"><span class="icon-inline">{{icon "arrow-right"}}</span> Read Article</a>
</div>
</div>
{{end}}
</div>
</section>
{{end}}
{{if .AllEBooks}}
<section class="ebooks-section">
<h2>All E-Books</h2>
<div class="feature-grid">
{{range .AllEBooks}}
<div class="feature-card">
<div class="feature-image-container">
{{$ebook := .}}
{{$profile := index $.Profiles $ebook.Author}}
{{$image := "/static/GitCitadel_Icon_Gradient.svg"}}
{{if and $ebook.Image (ne $ebook.Image "")}}
{{$image = $ebook.Image}}
{{else if and $profile $profile.Picture (ne $profile.Picture "")}}
{{$image = $profile.Picture}}
{{end}}
<div class="feature-image-wrapper">
<img src="{{$image}}" alt="{{$ebook.Title}}" class="feature-image">
<div class="feature-image-overlay">
<h4 class="feature-image-title">{{$ebook.Title}}</h4>
{{if $ebook.Summary}}
<p class="feature-image-summary">{{truncate $ebook.Summary 250}}</p>
{{end}}
</div>
</div>
</div>
<div class="feature-card-content">
<h3>{{$ebook.Title}}</h3>
{{if $ebook.Summary}}
<p>{{truncate $ebook.Summary 150}}</p>
{{end}}
<a href="https://alexandria.gitcitadel.eu/publication/naddr/{{$ebook.Naddr}}" target="_blank" rel="noopener noreferrer" class="btn"><span class="icon-inline">{{icon "external-link"}}</span> View on Alexandria</a>
</div>
</div>
{{end}}
</div>
</section>
{{end}}
</article> </article>
{{end}} {{end}}

9
templates/wiki.html

@ -7,7 +7,14 @@
<div class="wiki-index-content"> <div class="wiki-index-content">
{{if .WikiPages}} {{if .WikiPages}}
<p><span class="icon-inline">{{icon "arrow-right"}}</span> Select an article from the sidebar to get started.</p> <p class="wiki-index-intro"><span class="icon-inline">{{icon "info"}}</span> Browse our wiki articles:</p>
<div class="wiki-links wiki-index-links">
<a href="/wiki" class="wiki-link active"><span class="icon-inline">{{icon "info"}}</span> Project Overview</a>
{{range .WikiPages}}
<a href="/wiki/{{.DTag}}" class="wiki-link"><span class="icon-inline">{{icon "file-text"}}</span> {{.Title}}</a>
{{end}}
</div>
<p class="wiki-index-note wiki-sidebar-note"><span class="icon-inline">{{icon "arrow-right"}}</span> On larger screens, you can also select an article from the sidebar.</p>
{{else}} {{else}}
<p><span class="icon-inline">{{icon "inbox"}}</span> No wiki articles available yet.</p> <p><span class="icon-inline">{{icon "inbox"}}</span> No wiki articles available yet.</p>
{{end}} {{end}}

Loading…
Cancel
Save