Browse Source

bug-fixes

master
Silberengel 4 weeks ago
parent
commit
8c725244ac
  1. 10
      README.md
  2. 2
      internal/nostr/profile.go
  3. 4
      internal/nostr/wiki.go
  4. 4
      internal/server/handlers.go
  5. 57
      static/css/main.css
  6. 7164
      static/js/nostr.bundle.js
  7. 19
      templates/articles.html
  8. 25
      templates/blog.html
  9. 5
      templates/components.html
  10. 11
      templates/contact.html
  11. 2
      templates/ebooks.html
  12. 2
      templates/feed.html
  13. 2
      templates/landing.html
  14. 1
      templates/wiki.html

10
README.md

@ -36,11 +36,17 @@ A server-generated website that fetches kind 30818 wiki events from Nostr relays
```bash ```bash
npm install -g @asciidoctor/core npm install -g @asciidoctor/core
``` ```
4. Copy the example config: 4. Download nostr-tools bundle (for contact form):
```bash
mkdir -p static/js
curl -L -o static/js/nostr.bundle.js https://unpkg.com/nostr-tools@latest/lib/nostr.bundle.js
```
Note: The nostr-tools library is hosted locally to avoid dependency on external CDNs.
5. Copy the example config:
```bash ```bash
cp config.yaml.example config.yaml cp config.yaml.example config.yaml
``` ```
5. Edit `config.yaml` with your indices and settings 6. Edit `config.yaml` with your indices and settings
## Configuration ## Configuration

2
internal/nostr/profile.go

@ -118,7 +118,7 @@ func (c *Client) FetchProfilesBatch(ctx context.Context, pubkeys []string) (map[
"pubkeys": len(uniquePubkeys), "pubkeys": len(uniquePubkeys),
}).Debug("Batch fetching profiles") }).Debug("Batch fetching profiles")
// Fetch all profile events from fallback relays only (not theforest) // Fetch all profile events from fallback relays only (not The Forest)
profileRelays := c.GetProfileRelays() profileRelays := c.GetProfileRelays()
if len(profileRelays) == 0 { if len(profileRelays) == 0 {
// Fallback: if no profile relays configured, use all relays // Fallback: if no profile relays configured, use all relays

4
internal/nostr/wiki.go

@ -57,7 +57,7 @@ func (ws *WikiService) FetchWikiIndex(ctx context.Context, naddrStr string) (*In
filter := naddr.ToFilter() filter := naddr.ToFilter()
logFilter(filter, fmt.Sprintf("wiki index (kind %d)", ws.indexKind)) logFilter(filter, fmt.Sprintf("wiki index (kind %d)", ws.indexKind))
// Fetch the event from theforest only (primary relay) // Fetch the event from The Forest only (primary relay)
primaryRelay := ws.client.GetPrimaryRelay() primaryRelay := ws.client.GetPrimaryRelay()
if primaryRelay == "" { if primaryRelay == "" {
return nil, fmt.Errorf("primary relay not configured") return nil, fmt.Errorf("primary relay not configured")
@ -326,7 +326,7 @@ func (ws *WikiService) FetchWikiEventByDTag(ctx context.Context, pubkey, dTag st
} }
logFilter(filter, fmt.Sprintf("wiki by d-tag %s", dTag)) logFilter(filter, fmt.Sprintf("wiki by d-tag %s", dTag))
// Fetch from theforest only (primary relay) // Fetch from The Forest only (primary relay)
primaryRelay := ws.client.GetPrimaryRelay() primaryRelay := ws.client.GetPrimaryRelay()
if primaryRelay == "" { if primaryRelay == "" {
return nil, fmt.Errorf("primary relay not configured") return nil, fmt.Errorf("primary relay not configured")

4
internal/server/handlers.go

@ -468,8 +468,8 @@ func (s *Server) middleware(next http.Handler) http.Handler {
w.Header().Set("X-XSS-Protection", "1; mode=block") w.Header().Set("X-XSS-Protection", "1; mode=block")
w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin") w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
// CSP header - allow unpkg.com for Lucide icons // CSP header - allow unpkg.com for Lucide icons and jsdelivr.net for nostr-tools
w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline' https://unpkg.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:;") w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline' https://unpkg.com https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:;")
// Log request (only in debug mode to reduce noise) // Log request (only in debug mode to reduce noise)
start := time.Now() start := time.Now()

57
static/css/main.css

@ -138,6 +138,9 @@ header {
gap: 0.5rem; gap: 0.5rem;
padding: 0.5rem 0; padding: 0.5rem 0;
line-height: 1.2; line-height: 1.2;
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 600;
} }
.nav-menu a:hover { .nav-menu a:hover {
@ -229,6 +232,9 @@ header {
color: var(--text-primary); color: var(--text-primary);
text-decoration: none; text-decoration: none;
transition: background 0.2s, color 0.2s; transition: background 0.2s, color 0.2s;
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 600;
} }
.dropdown-menu a:hover { .dropdown-menu a:hover {
@ -291,6 +297,9 @@ header {
text-decoration: none; text-decoration: none;
border-radius: 4px; border-radius: 4px;
transition: background 0.2s, color 0.2s; transition: background 0.2s, color 0.2s;
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 600;
} }
.wiki-menu a:hover { .wiki-menu a:hover {
@ -511,7 +520,7 @@ a:focus {
position: relative; position: relative;
background-image: url('/static/GitCitadel_PFP.png'); background-image: url('/static/GitCitadel_PFP.png');
background-size: cover; background-size: cover;
background-position: center right; background-position: center center;
background-repeat: no-repeat; background-repeat: no-repeat;
padding: 3rem 2rem; padding: 3rem 2rem;
border-radius: 12px; border-radius: 12px;
@ -530,7 +539,7 @@ a:focus {
right: 0; right: 0;
bottom: 0; bottom: 0;
background: linear-gradient( background: linear-gradient(
to right, to bottom,
rgba(45, 45, 45, 0.95) 0%, rgba(45, 45, 45, 0.95) 0%,
rgba(45, 45, 45, 0.85) 30%, rgba(45, 45, 45, 0.85) 30%,
rgba(124, 158, 255, 0.4) 60%, rgba(124, 158, 255, 0.4) 60%,
@ -1018,6 +1027,7 @@ footer {
.article-menu li { .article-menu li {
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
display: block;
} }
.article-link { .article-link {
@ -1040,10 +1050,15 @@ footer {
} }
.article-link-title { .article-link-title {
display: flex;
align-items: center;
gap: 0.5rem;
font-weight: 600; font-weight: 600;
color: var(--text-primary); color: var(--text-primary);
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
font-size: 1em; font-size: 1em;
text-transform: uppercase;
letter-spacing: 0.05em;
} }
.article-link-meta { .article-link-meta {
@ -1089,6 +1104,23 @@ footer {
border-bottom: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color);
} }
.article-meta {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
margin-top: 1rem;
align-items: center;
font-size: 0.95em;
}
.article-meta .article-date,
.article-meta .article-author {
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--text-secondary);
}
.article-image { .article-image {
margin: 1.5rem 0; margin: 1.5rem 0;
width: 100%; width: 100%;
@ -1736,6 +1768,17 @@ textarea:focus-visible {
background-color: var(--bg-secondary); background-color: var(--bg-secondary);
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
table-layout: fixed;
}
.ebooks-table th:nth-child(1),
.ebooks-table td:nth-child(1) {
width: 66.67%;
}
.ebooks-table th:nth-child(2),
.ebooks-table td:nth-child(2) {
width: 33.33%;
} }
.ebooks-table thead { .ebooks-table thead {
@ -1801,6 +1844,16 @@ textarea:focus-visible {
font-size: 0.9em; font-size: 0.9em;
} }
.btn-icon-only {
padding: 0.5rem;
aspect-ratio: 1;
justify-content: center;
}
.btn-icon-only .icon-inline {
margin-right: 0;
}
.ebooks-table td .text-center { .ebooks-table td .text-center {
text-align: center; text-align: center;
color: var(--text-secondary); color: var(--text-secondary);

7164
static/js/nostr.bundle.js

File diff suppressed because it is too large Load Diff

19
templates/articles.html

@ -22,14 +22,6 @@
<li> <li>
<a href="#" class="article-link" data-dtag="{{$item.DTag}}" data-index="{{$index}}"{{if eq $index 0}} data-active="true"{{end}}> <a href="#" class="article-link" data-dtag="{{$item.DTag}}" data-index="{{$index}}"{{if eq $index 0}} data-active="true"{{end}}>
<div class="article-link-title"><span class="icon-inline">{{icon "file-text"}}</span> {{$item.Title}}</div> <div class="article-link-title"><span class="icon-inline">{{icon "file-text"}}</span> {{$item.Title}}</div>
{{if $item.Time}}
<div class="article-link-meta">
<span class="article-date"><span class="icon-inline">{{icon "clock"}}</span> {{$item.Time}}</span>
{{if $item.Author}}
<span class="article-author">{{template "user-badge-simple" (dict "Pubkey" $item.Author "Profiles" $.Profiles)}}</span>
{{end}}
</div>
{{end}}
</a> </a>
</li> </li>
{{end}} {{end}}
@ -43,7 +35,16 @@
<article class="blog-article{{if eq $index 0}} active{{end}}" data-dtag="{{$item.DTag}}" id="article-{{$item.DTag}}"> <article class="blog-article{{if eq $index 0}} active{{end}}" data-dtag="{{$item.DTag}}" id="article-{{$item.DTag}}">
<header class="article-header"> <header class="article-header">
<h1 class="article-title">{{$item.Title}}</h1> <h1 class="article-title">{{$item.Title}}</h1>
<p class="article-subtitle">Longform article</p> {{if or $item.Time $item.Author}}
<div class="article-meta">
{{if $item.Time}}
<span class="article-date"><span class="icon-inline">{{icon "clock"}}</span> {{$item.Time}}</span>
{{end}}
{{if $item.Author}}
<span class="article-author"><span class="icon-inline">{{icon "user"}}</span> {{template "user-badge-simple" (dict "Pubkey" $item.Author "Profiles" $.Profiles)}}</span>
{{end}}
</div>
{{end}}
</header> </header>
{{if and $item.Image (ne $item.Image "")}} {{if and $item.Image (ne $item.Image "")}}
<div class="article-image"> <div class="article-image">

25
templates/blog.html

@ -8,18 +8,12 @@
<!-- Left Sidebar --> <!-- Left Sidebar -->
<aside class="blog-sidebar"> <aside class="blog-sidebar">
<div class="blog-header"> <div class="blog-header">
{{if .BlogIndexAuthor}}
<div class="blog-author-handle">{{template "user-badge-simple" (dict "Pubkey" .BlogIndexAuthor "Profiles" $.Profiles)}}</div>
{{end}}
{{if .BlogIndexImage}} {{if .BlogIndexImage}}
<div class="blog-image"> <div class="blog-image">
<img src="{{.BlogIndexImage}}" alt="{{.BlogIndexTitle}}" /> <img src="{{.BlogIndexImage}}" alt="{{.BlogIndexTitle}}" />
</div> </div>
{{end}} {{end}}
<h1 class="blog-title">{{if .BlogIndexTitle}}{{.BlogIndexTitle}}{{else}}Blog{{end}}</h1> <h1 class="blog-title">{{if .BlogIndexTitle}}{{.BlogIndexTitle}}{{else}}Blog{{end}}</h1>
{{if .BlogIndexAuthor}}
<p class="blog-byline">by {{template "user-badge-simple" (dict "Pubkey" .BlogIndexAuthor "Profiles" $.Profiles)}}</p>
{{end}}
{{if .BlogIndexSummary}} {{if .BlogIndexSummary}}
<p class="blog-description">{{.BlogIndexSummary}}</p> <p class="blog-description">{{.BlogIndexSummary}}</p>
{{end}} {{end}}
@ -35,14 +29,6 @@
<li> <li>
<a href="#" class="article-link" data-dtag="{{$item.DTag}}" data-index="{{$index}}"{{if eq $index 0}} data-active="true"{{end}}> <a href="#" class="article-link" data-dtag="{{$item.DTag}}" data-index="{{$index}}"{{if eq $index 0}} data-active="true"{{end}}>
<div class="article-link-title"><span class="icon-inline">{{icon "file-text"}}</span> {{$item.Title}}</div> <div class="article-link-title"><span class="icon-inline">{{icon "file-text"}}</span> {{$item.Title}}</div>
{{if $item.Time}}
<div class="article-link-meta">
<span class="article-date"><span class="icon-inline">{{icon "clock"}}</span> {{$item.Time}}</span>
{{if $item.Author}}
<span class="article-author">{{template "user-badge-simple" (dict "Pubkey" $item.Author "Profiles" $.Profiles)}}</span>
{{end}}
</div>
{{end}}
</a> </a>
</li> </li>
{{end}} {{end}}
@ -56,7 +42,16 @@
<article class="blog-article{{if eq $index 0}} active{{end}}" data-dtag="{{$item.DTag}}" id="article-{{$item.DTag}}"> <article class="blog-article{{if eq $index 0}} active{{end}}" data-dtag="{{$item.DTag}}" id="article-{{$item.DTag}}">
<header class="article-header"> <header class="article-header">
<h1 class="article-title">{{$item.Title}}</h1> <h1 class="article-title">{{$item.Title}}</h1>
<p class="article-subtitle">This entry originally appeared in this blog.</p> {{if or $item.Time $item.Author}}
<div class="article-meta">
{{if $item.Time}}
<span class="article-date"><span class="icon-inline">{{icon "clock"}}</span> {{$item.Time}}</span>
{{end}}
{{if $item.Author}}
<span class="article-author"><span class="icon-inline">{{icon "user"}}</span> {{template "user-badge-simple" (dict "Pubkey" $item.Author "Profiles" $.Profiles)}}</span>
{{end}}
</div>
{{end}}
</header> </header>
{{if and $item.Image (ne $item.Image "")}} {{if and $item.Image (ne $item.Image "")}}
<div class="article-image"> <div class="article-image">

5
templates/components.html

@ -1,7 +1,7 @@
{{/* Feed Component - Reusable feed sidebar */}} {{/* Feed Component - Reusable feed sidebar */}}
{{define "feed"}} {{define "feed"}}
<div class="feed-container"> <div class="feed-container">
<h3><span class="icon-inline">{{icon "rss"}}</span> Recent Notes <a href="https://aitherboard.imwald.eu/feed/relay/theforest.nostr1.com" target="_blank" rel="noopener noreferrer" class="feed-link-header"><span class="icon-inline">{{icon "external-link"}}</span> View Full Feed</a></h3> <h3><span class="icon-inline">{{icon "rss"}}</span> Recent Notes</h3>
<div class="feed-items"> <div class="feed-items">
{{range .FeedItems}} {{range .FeedItems}}
<article class="feed-item"> <article class="feed-item">
@ -10,9 +10,6 @@
<time class="feed-time" datetime="{{.TimeISO}}"><span class="icon-inline">{{icon "clock"}}</span> {{.Time}}</time> <time class="feed-time" datetime="{{.TimeISO}}"><span class="icon-inline">{{icon "clock"}}</span> {{.Time}}</time>
</header> </header>
<div class="feed-content">{{.Content}}</div> <div class="feed-content">{{.Content}}</div>
<footer class="feed-footer">
<a href="https://aitherboard.imwald.eu/event/{{.EventID}}" class="feed-link" target="_blank" rel="noopener noreferrer"><span class="icon-inline">{{icon "external-link"}}</span> View on Aitherboard</a>
</footer>
</article> </article>
{{else}} {{else}}
<p class="feed-empty"><span class="icon-inline">{{icon "inbox"}}</span> No recent notes available.</p> <p class="feed-empty"><span class="icon-inline">{{icon "inbox"}}</span> No recent notes available.</p>

11
templates/contact.html

@ -171,7 +171,7 @@
<script type="application/json" id="contact-relays-data">{{json .ContactRelays}}</script> <script type="application/json" id="contact-relays-data">{{json .ContactRelays}}</script>
<script src="https://cdn.jsdelivr.net/npm/nostr-tools@1.18.0/lib/nostr.bundle.js"></script> <script src="/static/js/nostr.bundle.js"></script>
<script> <script>
(function() { (function() {
const form = document.getElementById('contact-form'); const form = document.getElementById('contact-form');
@ -361,13 +361,15 @@
const tags = []; const tags = [];
// Add 'a' tag for repository announcement if available // Add 'a' tag for repository announcement if available
if (repoAnnouncement) { if (repoAnnouncement && repoAnnouncement.pubkey && repoAnnouncement.dTag) {
tags.push(['a', `30617:${repoAnnouncement.pubkey}:${repoAnnouncement.dTag}`]); tags.push(['a', `30617:${repoAnnouncement.pubkey}:${repoAnnouncement.dTag}`]);
tags.push(['p', repoAnnouncement.pubkey]); tags.push(['p', repoAnnouncement.pubkey]);
if (repoAnnouncement.maintainers && repoAnnouncement.maintainers.length > 0) { if (repoAnnouncement.maintainers && repoAnnouncement.maintainers.length > 0) {
repoAnnouncement.maintainers.forEach(maintainer => { repoAnnouncement.maintainers.forEach(maintainer => {
if (maintainer) {
tags.push(['p', maintainer]); tags.push(['p', maintainer]);
}
}); });
} }
} }
@ -392,7 +394,10 @@
}); });
// Add contact relays tag // Add contact relays tag
tags.push(['relays', ...contactRelays]); // Store relays as a JSON string in the tag value
if (contactRelays && contactRelays.length > 0) {
tags.push(['relays', JSON.stringify(contactRelays)]);
}
// Create unsigned event (kind 1 for contact messages) // Create unsigned event (kind 1 for contact messages)
const unsignedEvent = { const unsignedEvent = {

2
templates/ebooks.html

@ -25,7 +25,7 @@
<strong>{{.Title}}</strong> <strong>{{.Title}}</strong>
{{if .Summary}}<br><small class="text-muted">{{.Summary}}</small>{{end}} {{if .Summary}}<br><small class="text-muted">{{.Summary}}</small>{{end}}
<div style="margin-top: 0.75rem;"> <div style="margin-top: 0.75rem;">
<a href="https://alexandria.gitcitadel.eu/publication/naddr/{{.Naddr}}" target="_blank" rel="noopener noreferrer" class="btn btn-sm"><span class="icon-inline">{{icon "external-link"}}</span> View on Alexandria</a> <a href="https://alexandria.gitcitadel.eu/publication/naddr/{{.Naddr}}" target="_blank" rel="noopener noreferrer" class="btn btn-sm btn-icon-only" title="View on Alexandria"><span class="icon-inline">{{icon "external-link"}}</span></a>
</div> </div>
</td> </td>
<td>{{template "user-badge-simple" (dict "Pubkey" .Author "Profiles" $.Profiles)}}</td> <td>{{template "user-badge-simple" (dict "Pubkey" .Author "Profiles" $.Profiles)}}</td>

2
templates/feed.html

@ -6,7 +6,7 @@
</header> </header>
<div class="feed-about-blurb"> <div class="feed-about-blurb">
<h2>About TheForest Relay</h2> <h2>About The Forest 🌲 Relay</h2>
<p>The Forest is a Nostr relay operated by GitCitadel. It provides a reliable, fast, and open relay service for the Nostr protocol.</p> <p>The Forest is a Nostr relay operated by GitCitadel. It provides a reliable, fast, and open relay service for the Nostr protocol.</p>
<ul> <ul>
<li><strong>Relay URL : </strong> <a href="https://theforest.nostr1.com" target="_blank" rel="noopener noreferrer"><code>wss://theforest.nostr1.com</code></a></li> <li><strong>Relay URL : </strong> <a href="https://theforest.nostr1.com" target="_blank" rel="noopener noreferrer"><code>wss://theforest.nostr1.com</code></a></li>

2
templates/landing.html

@ -100,7 +100,7 @@
<div class="feature-card"> <div class="feature-card">
<h3><span class="icon-inline">{{icon "book"}}</span> E-Books</h3> <h3><span class="icon-inline">{{icon "book"}}</span> E-Books</h3>
<p>Discover and download e-books from the decentralized #Alexandria library.</p> <p>Discover and download e-books from the decentralized Alexandria library.</p>
<a href="/ebooks" class="btn"><span class="icon-inline">{{icon "arrow-right"}}</span> View E-Books</a> <a href="/ebooks" class="btn"><span class="icon-inline">{{icon "arrow-right"}}</span> View E-Books</a>
</div> </div>

1
templates/wiki.html

@ -14,7 +14,6 @@
<a href="/wiki/{{.DTag}}" class="wiki-link"><span class="icon-inline">{{icon "file-text"}}</span> {{.Title}}</a> <a href="/wiki/{{.DTag}}" class="wiki-link"><span class="icon-inline">{{icon "file-text"}}</span> {{.Title}}</a>
{{end}} {{end}}
</div> </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