Implement template for not profile/note kinds, improve details

This commit is contained in:
Daniele Tonon
2023-05-28 22:58:03 +02:00
parent cfbf39e9f1
commit 13120d01df
11 changed files with 230 additions and 218 deletions

View File

@@ -47,26 +47,33 @@ func render(w http.ResponseWriter, r *http.Request) {
return
}
typ := "profile"
npub, _ := nip19.EncodePublicKey(event.PubKey)
nevent, _ := nip19.EncodeEvent(event.ID, []string{}, event.PubKey)
naddr := ""
createdAt := time.Unix(int64(event.CreatedAt), 0).Format("2006-01-02 15:04:05")
typ := ""
author := event
if event.Kind != 0 {
typ = "note"
author, _ = getEvent(r.Context(), npub)
if event.Kind >= 30000 && event.Kind < 40000 {
if event.Kind == 0 {
typ = "profile"
} else {
if event.Kind == 1 || event.Kind == 7 || event.Kind == 30023 {
typ = "note"
} else if event.Kind >= 30000 && event.Kind < 40000 {
typ = "address"
if d := event.Tags.GetFirst([]string{"d", ""}); d != nil {
naddr, _ = nip19.EncodeEntity(event.PubKey, event.Kind, d.Value(), []string{})
}
} else {
typ = "other"
}
author, _ = getEvent(r.Context(), npub)
}
kindDescription := kindNames[event.Kind]
kindNIP := kindNIPS[event.Kind]
imageMatch := regexp.MustCompile(`https:\/\/[^ ]*\.(gif|jpe?g|png|webp)`).FindStringSubmatch(event.Content)
var image string
if len(imageMatch) > 0 {
@@ -164,36 +171,39 @@ func render(w http.ResponseWriter, r *http.Request) {
eventJSON, _ := json.MarshalIndent(event, "", " ")
params := map[string]any{
"createdAt": createdAt,
"clients": generateClientList(code, event),
"type": typ,
"title": title,
"twitterTitle": twitterTitle,
"npub": npub,
"npubShort": npubShort,
"nevent": nevent,
"naddr": naddr,
"metadata": metadata,
"authorLong": authorLong,
"description": description,
"content": content,
"textImageURL": textImageURL,
"videoType": videoType,
"image": image,
"video": video,
"proxy": "https://" + hostname + "/njump/proxy?src=",
"eventJSON": string(eventJSON),
"createdAt": createdAt,
"clients": generateClientList(code, event),
"type": typ,
"title": title,
"twitterTitle": twitterTitle,
"npub": npub,
"npubShort": npubShort,
"nevent": nevent,
"naddr": naddr,
"metadata": metadata,
"authorLong": authorLong,
"description": description,
"content": content,
"textImageURL": textImageURL,
"videoType": videoType,
"image": image,
"video": video,
"proxy": "https://" + hostname + "/njump/proxy?src=",
"eventJSON": string(eventJSON),
"kindID": event.Kind,
"kindDescription": kindDescription,
"kindNIP": kindNIP,
}
// Use a mapping to expressly link the templates and share them between more kinds/types
template_mapping := make(map[string]string)
template_mapping["profile"] = "profile.html"
template_mapping["note"] = "note.html"
template_mapping["address"] = "raw.html"
template_mapping["address"] = "other.html"
// If a mapping is not found fallback to raw
if template_mapping[typ] == "" {
template_mapping[typ] = "raw.html"
template_mapping[typ] = "other.html"
}
funcMap := template.FuncMap{

View File

@@ -296,31 +296,31 @@ body {
.container .column_content .field.advanced.visible {
display: block;
}
.container .column_content .field.advanced.boxed {
.container .column_content .field.boxed {
padding: 0 1rem 1rem;
margin-left: -1rem;
margin-right: -1rem;
}
.theme--default .container .column_content .field.advanced.boxed {
.theme--default .container .column_content .field.boxed {
background: #f3f3f3;
}
.theme--dark .container .column_content .field.advanced.boxed {
.theme--dark .container .column_content .field.boxed {
background: #131313;
}
.container .column_content .field.advanced.boxed .label {
.container .column_content .field.boxed .label {
padding: 0.2rem 1rem;
margin: 0 -1rem;
}
.theme--default .container .column_content .field.advanced.boxed .label {
.theme--default .container .column_content .field.boxed .label {
color: #373737;
}
.theme--dark .container .column_content .field.advanced.boxed .label {
.theme--dark .container .column_content .field.boxed .label {
color: #9a9a9a;
}
.theme--default .container .column_content .field.advanced.boxed .label {
.theme--default .container .column_content .field.boxed .label {
background: #c9c9c9;
}
.theme--dark .container .column_content .field.advanced.boxed .label {
.theme--dark .container .column_content .field.boxed .label {
background: #191919;
}
.container .column_content .field.advanced-switch-wrapper {
@@ -592,23 +592,23 @@ body.profile .column_content {
}
}
body.note .column_content {
body.note .column_content, body.raw .column_content {
flex-basis: 75%;
max-width: 75%;
}
@media (max-width: 580px) {
body.note .column_content {
body.note .column_content, body.raw .column_content {
flex-basis: 100%;
max-width: 100%;
margin-right: 0;
}
}
body.note .column_content .profile_intro {
body.note .column_content .profile_intro, body.raw .column_content .profile_intro {
display: flex;
max-width: 100%;
align-items: center;
}
body.note .column_content .profile_intro a {
body.note .column_content .profile_intro a, body.raw .column_content .profile_intro a {
display: inherit;
width: 100%;
align-items: inherit;
@@ -617,49 +617,51 @@ body.note .column_content .profile_intro a {
flex-wrap: wrap;
}
@media (max-width: 580px) {
body.note .column_content .profile_intro a {
body.note .column_content .profile_intro a, body.raw .column_content .profile_intro a {
margin-top: 0rem;
margin-bottom: -0.5rem;
}
}
body.note .column_content .profile_intro .info-wrapper {
body.note .column_content .profile_intro .info-wrapper, body.raw .column_content .profile_intro .info-wrapper {
flex-grow: 1;
margin-bottom: 1rem;
}
@media (max-width: 580px) {
body.note .column_content .profile_intro .info-wrapper {
body.note .column_content .profile_intro .info-wrapper, body.raw .column_content .profile_intro .info-wrapper {
display: block;
}
}
@media (max-width: 580px) {
body.note .column_content .profile_intro .info-wrapper .name,
body.note .column_content .profile_intro .info-wrapper .npub {
body.note .column_content .profile_intro .info-wrapper .npub, body.raw .column_content .profile_intro .info-wrapper .name,
body.raw .column_content .profile_intro .info-wrapper .npub {
display: block-inline;
font-size: 0.9rem;
}
}
body.note .column_content .profile_intro .pic-wrapper {
body.note .column_content .profile_intro .pic-wrapper, body.raw .column_content .profile_intro .pic-wrapper {
flex-basis: 16%;
margin-right: 1rem;
}
@media (max-width: 580px) {
body.note .column_content .profile_intro .pic-wrapper {
body.note .column_content .profile_intro .pic-wrapper, body.raw .column_content .profile_intro .pic-wrapper {
margin-right: 0.5rem;
}
}
body.note .column_content .profile_intro .published_at {
body.note .column_content .profile_intro .published_at, body.raw .column_content .profile_intro .published_at {
width: 100%;
text-align: right;
align-self: end;
font-size: 0.8rem;
}
.theme--default body.note .column_content .profile_intro .published_at {
.theme--default body.note .column_content .profile_intro .published_at, .theme--default body.raw .column_content .profile_intro .published_at {
color: #9a9a9a;
}
.theme--dark body.note .column_content .profile_intro .published_at {
.theme--dark body.note .column_content .profile_intro .published_at, .theme--dark body.raw .column_content .profile_intro .published_at {
color: #f3f3f3;
}
@media (max-width: 580px) {
body.note .column_content .profile_intro .published_at {
body.note .column_content .profile_intro .published_at, body.raw .column_content .profile_intro .published_at {
padding-top: 0.5rem;
}
}

View File

@@ -328,22 +328,22 @@ a {
&.visible {
display: block;
}
&.boxed {
padding: 0 1rem 1rem;
margin-left: -1rem;
margin-right: -1rem;
}
&.boxed {
padding: 0 1rem 1rem;
margin-left: -1rem;
margin-right: -1rem;
@include themed() {
background: t($boxed-bg);
}
.label {
padding: 0.2rem 1rem;
margin: 0 -1rem;
@include themed() {
background: t($boxed-bg);
color: t($boxed-title);
}
.label {
padding: 0.2rem 1rem;
margin: 0 -1rem;
@include themed() {
color: t($boxed-title);
}
@include themed() {
background: t($boxed-bg-title);
}
@include themed() {
background: t($boxed-bg-title);
}
}
}
@@ -584,7 +584,7 @@ body.profile {
}
}
body.note {
body.note, body.raw {
.column_content {
flex-basis: 75%;
max-width: 75%;
@@ -611,6 +611,7 @@ body.note {
}
.info-wrapper {
flex-grow: 1;
margin-bottom: 1rem;
@media (max-width: 580px) {
display: block;
}

19
templates/details.html Normal file
View File

@@ -0,0 +1,19 @@
<div class="field {{if not (eq .type "other")}}advanced{{end}}">
<div class="label">Published at</div>
{{.createdAt | SanitizeString}}
</div>
<div class="field {{if not (eq .type "other")}}advanced{{end}}">
<div class="label">Kind type</div>
{{.kindID}} - <a href="https://github.com/nostr-protocol/nips/blob/master/{{.kindNIP | SanitizeString}}.md">{{.kindDescription | SanitizeString}}</a>
</div>
<div class="field {{if not (eq .type "other")}}advanced{{end}}">
<div class="label">Nevent</div>
<div>{{.nevent | SanitizeString}}</div>
</div>
<div class="field {{if not (eq .type "other")}}advanced{{end}} boxed">
<div class="label">Event JSON</div>
<div class="json">{{.eventJSON}}</div>
</div>

View File

@@ -56,7 +56,10 @@
<title>Nostr Address {{.naddr | SanitizeString }}</title>
{{end}}
<!----------->
{{ if eq .type "other" }}
<title>Nostr Event {{.kindID }} - {{.kindDescription | SanitizeString }}</title>
{{end}}
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/njump/static/styles.css?v=20230528" />
<link rel="stylesheet" href="/njump/static/styles.css?v=20230528b" />
</head>

View File

@@ -37,7 +37,7 @@
<div class="field">
<div class="label">Author Public key</div>
{{.npub}}
{{.npub | SanitizeString}}
</div>
<div class="field advanced-switch-wrapper">
@@ -50,15 +50,8 @@
<label for="advanced-switch">Show more details</label>
</div>
<div class="field advanced">
<div class="label">Nevent</div>
<div>{{.nevent | SanitizeString}}</div>
</div>
{{template "details.html" .}}
<div class="field advanced boxed">
<div class="label">Event JSON</div>
<div class="json">{{.eventJSON | SanitizeString}}</div>
</div>
<div class="field separator"></div>
</div>

41
templates/other.html Normal file
View File

@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html class="theme--default">
<meta charset="UTF-8" />
{{template "head.html" .}}
<body class="raw">
{{template "top.html" .}}
<div class="container_wrapper">
<div class="container">
<div class="column column_content">
<div class="profile_intro">
<div class="info-wrapper">
<div class="kind_desc">
{{.kindDescription | SanitizeString}}
</div>
</div>
</div>
<div class="field separator"></div>
<div class="field">
<div class="label">Author Public key</div>
<a href="/{{.npub | SanitizeString}}">{{.npub | SanitizeString}}</a>
</div>
{{template "details.html" .}}
<div class="field separator"></div>
</div>
</div>
</div>
{{template "footer.html"}}
<script>
{{template "scripts.js"}}
</script>
</body>
</html>

View File

@@ -69,15 +69,8 @@
<label for="advanced-switch">Show more details</label>
</div>
<div class="field advanced">
<div class="label">Metadata Event</div>
<div>{{.nevent | SanitizeString}}</div>
</div>
<div class="field advanced boxed">
<div class="label">Event JSON</div>
<div class="json">{{.eventJSON}}</div>
</div>
{{template "details.html" .}}
<div class="field separator"></div>
</div>

View File

@@ -1,101 +0,0 @@
<!DOCTYPE html>
<html>
<head>
{{if eq .type "profile"}}
<title>Nostr Public Key {{.npub}}</title>
<meta property="og:site_name" content="{{.npub}}" />
<meta property="og:title" content="{{.title}}" />
{{ if .metadata.Picture }}
<meta property="og:image" content="{{.metadata.Picture}}" />
<meta property="twitter:image" content="{{.proxy}}{{.metadata.Picture}}" />
{{end}} {{ if .metadata.About }}
<meta property="og:description" content="{{.metadata.About}}" />
{{end}}
<meta property="twitter:card" content="summary" />
{{end}}
<!----------->
{{ if eq .type "event" }}
<title>Nostr Event {{.nevent}}</title>
<meta property="og:site_name" content="{{.authorLong}}" />
<meta property="og:title" content="{{.title}}" />
<meta name="twitter:title" content="{{.twitterTitle}}" />
<!---->
{{ if .textImageURL }}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@nostrprotocol" />
<meta property="og:image" content="{{.textImageURL}}" />
<meta name="twitter:image" content="{{.textImageURL}}" />
{{ else }}
<!---->
<meta property="twitter:card" content="summary" />
{{ if .image }}
<meta property="og:image" content="{{.image}}" />
<meta name="twitter:image" content="{{.proxy}}{{.image}}" />
{{end}} {{ if .video }}
<meta property="og:video" content="{{.video}}" />
<meta property="og:video:secure_url" content="{{.video}}" />
<meta property="og:video:type" content="video/{{.videoType}}" />
{{end}}
<!---->
{{end}}
<meta property="og:description" content="{{.description}}" />
<meta name="twitter:description" content="{{.description}}" />
{{end}}
<!----------->
{{ if eq .type "address" }}
<title>Nostr Address {{.naddr}}</title>
{{end}}
<!----------->
<style>
body {
padding: 3%;
}
#event {
margin: 5% 0;
white-space: pre-wrap;
word-wrap: break-word;
word-break: break-all;
font-family: monospace;
font-size: 130%;
}
</style>
</head>
<body>
<div>
open {{.type}} in {{range .clients}}<br />
&nbsp;&nbsp;<span style="color: #bbb"></span>
<a class="client" href="{{.url}}">{{.name}}</a> {{end}}
</div>
<div id="event">{{.eventJSON}}</div>
<div style="display: flex; justify-content: flex-end">
<span>
powered by <a href="https://github.com/fiatjaf/njump">njump</a>
</span>
</div>
<script>
const type = '{{.type}}'
let counts = []
let clients = document.querySelectorAll('.client')
for (let i = 0; i < clients.length; i++) {
let name = clients[i].innerText
let url = clients[i].href
let key = 'nj:' + type + ':' + name
let count = parseInt(localStorage.getItem(key) || 0)
clients[i].previousElementSibling.innerText = count
clients[i].addEventListener('click', () => {
localStorage.setItem(key, count + 1)
})
counts.push([count, name, url])
}
counts.sort((a, b) => b[0] - a[0])
let tailsum = counts.slice(1).reduce((acc, c) => acc + c[0], 0)
if (location.hash !== '#noredirect') {
if (counts[0][0] - tailsum > 10) {
location.href = counts[0][2]
}
}
</script>
</body>
</html>

View File

@@ -16,20 +16,22 @@ for (let i = 0; i < clients.length; i++) {
// Reorder clients following the counter
let clients_wrapper = document.querySelector('.clients_wrapper')
const elements = Array.from(clients_wrapper.getElementsByClassName('btn'))
elements.sort((a, b) => {
const rankA = parseInt(a.getAttribute('count'))
const rankB = parseInt(b.getAttribute('count'))
return rankB - rankA
})
elements.forEach(element => clients_wrapper.appendChild(element))
if (clients_wrapper !== null) {
const elements = Array.from(clients_wrapper.getElementsByClassName('btn'))
elements.sort((a, b) => {
const rankA = parseInt(a.getAttribute('count'))
const rankB = parseInt(b.getAttribute('count'))
return rankB - rankA
})
elements.forEach(element => clients_wrapper.appendChild(element))
counts.sort((a, b) => b[0] - a[0])
let tailsum = counts.slice(1).reduce((acc, c) => acc + c[0], 0)
counts.sort((a, b) => b[0] - a[0])
let tailsum = counts.slice(1).reduce((acc, c) => acc + c[0], 0)
if (location.hash !== '#noredirect') {
if (counts[0][0] - tailsum > 10) {
location.href = counts[0][2]
if (location.hash !== '#noredirect') {
if (counts[0][0] - tailsum > 10) {
location.href = counts[0][2]
}
}
}

View File

@@ -12,28 +12,77 @@ import (
)
var kindNames = map[int]string{
0: "profile metadata",
1: "text note",
2: "relay recommendation",
3: "contact list",
4: "encrypted direct message",
5: "event deletion",
6: "repost",
7: "reaction",
8: "badge award",
40: "channel creation",
41: "channel metadata",
42: "channel message",
43: "channel hide message",
44: "channel mute user",
1984: "report",
9735: "zap",
9734: "zap request",
10002: "relay list",
30008: "profile badges",
30009: "badge definition",
30078: "app-specific data",
30023: "article",
0: "Metadata",
1: "Short Text Note",
2: "Recommend Relay",
3: "Contacts",
4: "Encrypted Direct Messages",
5: "Event Deletion",
6: "Reposts",
7: "Reaction",
8: "Badge Award",
40: "Channel Creation",
41: "Channel Metadata",
42: "Channel Message",
43: "Channel Hide Message",
44: "Channel Mute User",
1063: "File Metadata",
1984: "Reporting",
9734: "Zap Request",
9735: "Zap",
10000: "Mute List",
10001: "Pin List",
10002: "Relay List Metadata",
13194: "Wallet Info",
22242: "Client Authentication",
23194: "Wallet Request",
23195: "Wallet Response",
24133: "Nostr Connect",
30000: "Categorized People List",
30001: "Categorized Bookmark List",
30008: "Profile Badges",
30009: "Badge Definition",
30017: "Create or update a stall",
30018: "Create or update a product",
30023: "Long-form Content",
30078: "Application-specific Data",
}
var kindNIPS = map[int]string{
0: "01",
1: "01",
2: "01",
3: "02",
4: "04",
5: "09",
6: "18",
7: "25",
8: "58",
40: "28",
41: "28",
42: "28",
43: "28",
44: "28",
1063: "94",
1984: "56",
9734: "57",
9735: "57",
10000: "51",
10001: "51",
10002: "65",
13194: "47",
22242: "42",
23194: "47",
23195: "47",
24133: "46",
30000: "51",
30001: "51",
30008: "58",
30009: "58",
30017: "15",
30018: "15",
30023: "23",
30078: "78",
}
func generateClientList(code string, event *nostr.Event) []map[string]string {