From dd1b44132199aa72c2b699e1160fbe6b885f0ef6 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Wed, 3 Dec 2025 16:15:55 +0000 Subject: landing page: display relay inforamtion document --- src/http/landing.rs | 133 ++++++++++++++++++++++++++++++++++++++++++++++++- src/http/nip11.rs | 8 +-- templates/landing.html | 59 ++++++++++++++++++++-- 3 files changed, 189 insertions(+), 11 deletions(-) diff --git a/src/http/landing.rs b/src/http/landing.rs index c76a7e6..8ab4a68 100644 --- a/src/http/landing.rs +++ b/src/http/landing.rs @@ -398,7 +398,7 @@ fn generate_nip_cards(nip11: &RelayInformationDocument) -> String { if let Some(meta) = metadata.get(nip) { html.push_str(&format!( r#"
-
NIP-{:02} {}
+
NIP-{:02} {}
{}
"#, nip, meta.title, meta.description @@ -407,7 +407,7 @@ fn generate_nip_cards(nip11: &RelayInformationDocument) -> String { // Fallback for unknown NIPs - still show them html.push_str(&format!( r#"
-
NIP-{:02}
+
NIP-{:02}
"#, nip )); @@ -418,6 +418,129 @@ fn generate_nip_cards(nip11: &RelayInformationDocument) -> String { html } +/// Escape HTML special characters +fn escape_html(s: &str) -> String { + s.replace('&', "&") + .replace('<', "<") + .replace('>', ">") + .replace('"', """) + .replace('\'', "'") +} + +/// Generate table rows for NIP-11 relay information +fn generate_relay_info_rows(nip11: &RelayInformationDocument) -> String { + let mut html = String::new(); + + // Follow NIP-11 document order: + // name + html.push_str(&format!( + r#"name{}"#, + escape_html(&nip11.name) + )); + html.push('\n'); + + // description + html.push_str(&format!( + r#"description{}"#, + escape_html(&nip11.description) + )); + html.push('\n'); + + // pubkey (if present) + if let Some(ref pubkey) = nip11.pubkey { + html.push_str(&format!( + r#"pubkey{}"#, + escape_html(pubkey) + )); + html.push('\n'); + } + + // contact (if present) + if let Some(ref contact) = nip11.contact { + html.push_str(&format!( + r#"contact{}"#, + escape_html(contact) + )); + html.push('\n'); + } + + // supported_nips + let nips_str = nip11 + .supported_nips + .iter() + .map(|n| n.to_string()) + .collect::>() + .join(", "); + html.push_str(&format!( + r#"supported_nips{}"#, + nips_str + )); + html.push('\n'); + + // software + html.push_str(&format!( + r#"software{}"#, + escape_html(&nip11.software), + escape_html(&nip11.software) + )); + html.push('\n'); + + // version + html.push_str(&format!( + r#"version{}"#, + escape_html(&nip11.version) + )); + html.push('\n'); + + // icon (if present) + if let Some(ref icon) = nip11.icon { + html.push_str(&format!( + r#"icon{}"#, + escape_html(icon), + escape_html(icon) + )); + html.push('\n'); + } + + // GRASP-01 Extensions: + // supported_grasps + let grasps_str = nip11.supported_grasps.join(", "); + html.push_str(&format!( + r#"supported_grasps{}"#, + escape_html(&grasps_str) + )); + html.push('\n'); + + // repo_acceptance_criteria + html.push_str(&format!( + r#"repo_acceptance_criteria{}"#, + escape_html(&nip11.repo_acceptance_criteria) + )); + html.push('\n'); + + // curation (if present) + if let Some(ref curation) = nip11.curation { + html.push_str(&format!( + r#"curation{}"#, + escape_html(curation) + )); + html.push('\n'); + } + + html +} + +/// Generate the relay icon HTML for the header +fn generate_relay_icon_html(nip11: &RelayInformationDocument) -> String { + match &nip11.icon { + Some(icon_url) => format!( + r#"Relay Icon"#, + escape_html(icon_url) + ), + None => String::new(), + } +} + /// Generate the HTML landing page pub fn get_html(config: &Config) -> String { // Get NIP-11 document for supported NIPs and GRASPs @@ -430,6 +553,8 @@ pub fn get_html(config: &Config) -> String { let hero_tags = generate_hero_tags(&nip11); let grasp_cards = generate_grasp_cards(&nip11); let nip_cards = generate_nip_cards(&nip11); + let relay_info_rows = generate_relay_info_rows(&nip11); + let relay_icon = generate_relay_icon_html(&nip11); format!( include_str!("../../templates/landing.html"), @@ -437,12 +562,16 @@ pub fn get_html(config: &Config) -> String { relay_name = config.relay_name(), relay_description = config.relay_description, version = get_version(), + nip11_version = escape_html(&nip11.version), curation = curation, + repo_acceptance_criteria = escape_html(&nip11.repo_acceptance_criteria), theme_toggle = get_theme_toggle_html(), theme_script = get_theme_script(), hero_tags = hero_tags, grasp_cards = grasp_cards, nip_cards = nip_cards, + relay_info_rows = relay_info_rows, + relay_icon = relay_icon, ) } diff --git a/src/http/nip11.rs b/src/http/nip11.rs index 2a37385..1ed80de 100644 --- a/src/http/nip11.rs +++ b/src/http/nip11.rs @@ -76,11 +76,7 @@ impl RelayInformationDocument { // GRASP-01 Extensions supported_grasps: vec!["GRASP-01".to_string()], - repo_acceptance_criteria: format!( - "Repositories must list this relay ({}) in both 'clone' and 'relays' tags of kind 30617 announcements. \ - All other events must reference accepted repositories or accepted events.", - config.domain - ), + repo_acceptance_criteria: "None".to_string(), curation: None, // Not a curated relay - only SPAM prevention via GRASP-01 policy } } @@ -118,7 +114,7 @@ mod tests { assert!(doc.supported_nips.contains(&34)); assert!(doc.supported_nips.contains(&77)); assert_eq!(doc.supported_grasps, vec!["GRASP-01"]); - assert!(doc.repo_acceptance_criteria.contains("relay.example.com")); + assert!(doc.repo_acceptance_criteria.contains("None")); assert!(doc.curation.is_none()); assert_eq!( doc.icon, diff --git a/templates/landing.html b/templates/landing.html index 6dc2833..41bb1e5 100644 --- a/templates/landing.html +++ b/templates/landing.html @@ -27,9 +27,13 @@ gap: 16px; margin-bottom: 24px; }} - .logo {{ + .relay-icon {{ width: 48px; height: 48px; + max-width: 48px; + max-height: 48px; + object-fit: contain; + border-radius: 8px; }} h1 {{ font-size: 2rem; @@ -123,6 +127,9 @@ font-weight: 500; margin-right: 8px; }} + .badge-nip {{ + background: var(--success); + }} code {{ padding: 2px 8px; }} .tag-container {{ margin: 16px 0; @@ -169,6 +176,37 @@ background: var(--brand-light); text-decoration: none; }} + /* Relay Information Document table styles */ + .info-table {{ + width: 100%; + border-collapse: collapse; + font-size: 0.875rem; + }} + .info-table th {{ + text-align: left; + padding: 8px 12px; + color: var(--text-muted); + font-weight: 500; + width: 140px; + vertical-align: top; + border-bottom: 1px solid var(--border); + }} + .info-table td {{ + padding: 8px 12px; + color: var(--text); + border-bottom: 1px solid var(--border); + word-break: break-all; + }} + .info-table tr:last-child th, + .info-table tr:last-child td {{ + border-bottom: none; + }} + .info-table a {{ + color: var(--brand-light); + }} + .info-table code {{ + font-size: 0.75rem; + }} .footer {{ margin-top: 64px; }} .footer-separator {{ margin: 0 0.5em; opacity: 0.5; }} @@ -178,6 +216,7 @@
+ {relay_icon}

{relay_name}

@@ -187,11 +226,16 @@ Browse Repositories on GitWorkshop.dev → -
+
Curation: {curation}
+
+ Repo Acceptance Criteria: + {repo_acceptance_criteria} +
+
Supports: {hero_tags} @@ -205,7 +249,7 @@
-

Grasp Server Powered by ngit-grasp

+

Grasp Server Powered by ngit-grasp ({nip11_version})

Git hosting distributed across relays using Nostr for authorization. Find out more...

@@ -234,6 +278,15 @@ {nip_cards}
+
+
Relay Information Document
+
+ + {relay_info_rows} +
+
+
+ -- cgit v1.2.3