diff --git a/.gitignore b/.gitignore index 26069bb..4a942e4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ node_modules/ static/tailwind-bundle.min.css yarn.lock package-lock.json +*_templ.go diff --git a/archive.templ b/archive.templ new file mode 100644 index 0000000..aafc5da --- /dev/null +++ b/archive.templ @@ -0,0 +1,43 @@ +package main + +templ archiveTemplate(params ArchivePageParams) { + + + + + { params.Title } + @headCommonTemplate(params.HeadParams) + + + @topTemplate() +
+
+
+
{ params.Title }
+
+
+
+

{ params.Title }

+
+
+ for _, v:= range params.Data { + + { v } + + } +
+
+ if params.PrevPage != 0 { + << Prev page + } + if params.NextPage != 0 { + Next page >> + } +
+
+
+
+ @footerTemplate() + + +} diff --git a/tailwind.css b/base.css similarity index 100% rename from tailwind.css rename to base.css diff --git a/clients.templ b/clients.templ new file mode 100644 index 0000000..d876890 --- /dev/null +++ b/clients.templ @@ -0,0 +1,147 @@ +package main + +templ clientsTemplate(clients []ClientReference) { + + + +} diff --git a/common.templ b/common.templ new file mode 100644 index 0000000..5d3677e --- /dev/null +++ b/common.templ @@ -0,0 +1,56 @@ +package main + +templ authorHeaderTemplate(metadata Metadata) { +
+ +
+ +
+
+
+ { metadata.Name } + + if metadata.Name != metadata.DisplayName { + / { metadata.DisplayName } + } +
+
+ { metadata.NpubShort() } +
+
+
+
+} + +templ lastNotesTemplate(lastNotes []EnhancedEvent) { + +} diff --git a/data.go b/data.go index 2ae8494..0cfe771 100644 --- a/data.go +++ b/data.go @@ -3,129 +3,20 @@ package main import ( "context" "fmt" - "html" "html/template" - "regexp" "strconv" "strings" "time" "github.com/nbd-wtf/go-nostr" - "github.com/nbd-wtf/go-nostr/nip10" "github.com/nbd-wtf/go-nostr/nip19" sdk "github.com/nbd-wtf/nostr-sdk" - "github.com/texttheater/golang-levenshtein/levenshtein" ) -type EnhancedEvent struct { - event *nostr.Event - relays []string -} - -func (ee EnhancedEvent) IsReply() bool { - return nip10.GetImmediateReply(ee.event.Tags) != nil -} - -func (ee EnhancedEvent) Reply() *nostr.Tag { - return nip10.GetImmediateReply(ee.event.Tags) -} - -func (ee EnhancedEvent) Preview() template.HTML { - lines := strings.Split(html.EscapeString(ee.event.Content), "\n") - var processedLines []string - for _, line := range lines { - if strings.TrimSpace(line) == "" { - continue - } - processedLine := shortenNostrURLs(line) - processedLines = append(processedLines, processedLine) - } - - return template.HTML(strings.Join(processedLines, "
")) -} - -func (ee EnhancedEvent) RssTitle() string { - regex := regexp.MustCompile(`(?i)`) - replacedString := regex.ReplaceAllString(string(ee.Preview()), " ") - words := strings.Fields(replacedString) - title := "" - for i, word := range words { - if len(title)+len(word)+1 <= 65 { // +1 for space - if title != "" { - title += " " - } - title += word - } else { - if i > 1 { // the first word len is > 65 - title = title + " ..." - } else { - title = "" - } - break - } - } - - content := ee.RssContent() - distance := levenshtein.DistanceForStrings([]rune(title), []rune(content), levenshtein.DefaultOptions) - similarityThreshold := 5 - if distance <= similarityThreshold { - return "" - } else { - return title - } -} - -func (ee EnhancedEvent) RssContent() string { - content := ee.event.Content - content = basicFormatting(html.EscapeString(content), true, false, false) - content = renderQuotesAsHTML(context.Background(), content, false) - if ee.IsReply() { - nevent, _ := nip19.EncodeEvent(ee.Reply().Value(), ee.relays, ee.event.PubKey) - neventShort := nevent[:8] + "…" + nevent[len(nevent)-4:] - content = "In reply to " + neventShort + "
_________________________

" + content - } - return content -} - -func (ee EnhancedEvent) Thumb() string { - imgRegex := regexp.MustCompile(`(https?://[^\s]+\.(?:png|jpe?g|gif|bmp|svg)(?:/[^\s]*)?)`) - matches := imgRegex.FindAllStringSubmatch(ee.event.Content, -1) - if len(matches) > 0 { - // The first match group captures the image URL - return matches[0][1] - } - return "" -} - -func (ee EnhancedEvent) Npub() string { - npub, _ := nip19.EncodePublicKey(ee.event.PubKey) - return npub -} - -func (ee EnhancedEvent) NpubShort() string { - npub := ee.Npub() - return npub[:8] + "…" + npub[len(npub)-4:] -} - -func (ee EnhancedEvent) Nevent() string { - nevent, _ := nip19.EncodeEvent(ee.event.ID, ee.relays, ee.event.PubKey) - return nevent -} - -func (ee EnhancedEvent) CreatedAtStr() string { - return time.Unix(int64(ee.event.CreatedAt), 0).Format("2006-01-02 15:04:05") -} - -func (ee EnhancedEvent) ModifiedAtStr() string { - return time.Unix(int64(ee.event.CreatedAt), 0).Format("2006-01-02T15:04:05Z07:00") -} - type Data struct { templateId TemplateID event *nostr.Event relays []string - npub string - npubShort string nprofile string nevent string neventNaked string @@ -151,47 +42,6 @@ type Data struct { kind1311Metadata *Kind1311Metadata } -type Kind1063Metadata struct { - Magnet string - Dim string - Size string - Summary string - Image string - URL string - AES256GCM string - M string - X string - I string - Blurhash string - Thumb string -} - -type Kind30311Metadata struct { - Title string - Summary string - Image string - Status string - Host sdk.ProfileMetadata - HostNpub string - Tags []string -} - -type Kind1311Metadata struct { - // ... -} - -func (fm Kind1063Metadata) IsVideo() bool { return strings.Split(fm.M, "/")[0] == "video" } -func (fm Kind1063Metadata) IsImage() bool { return strings.Split(fm.M, "/")[0] == "image" } -func (fm Kind1063Metadata) DisplayImage() string { - if fm.Image != "" { - return fm.Image - } else if fm.IsImage() { - return fm.URL - } else { - return "" - } -} - func grabData(ctx context.Context, code string, isProfileSitemap bool) (*Data, error) { // code can be a nevent, nprofile, npub or nip05 identifier, in which case we try to fetch the associated event event, relays, err := getEvent(ctx, code, nil) @@ -218,10 +68,10 @@ func grabData(ctx context.Context, code string, isProfileSitemap bool) (*Data, e relays: relays, } - data.npub, _ = nip19.EncodePublicKey(event.PubKey) - data.npubShort = data.npub[:8] + "…" + data.npub[len(data.npub)-4:] - data.authorLong = data.npub // hopefully will be replaced later - data.authorShort = data.npubShort // hopefully will be replaced later + npub, _ := nip19.EncodePublicKey(event.PubKey) + npubShort := npub[:8] + "…" + npub[len(npub)-4:] + data.authorLong = npub // hopefully will be replaced later + data.authorShort = npubShort // hopefully will be replaced later data.nevent, _ = nip19.EncodeEvent(event.ID, relaysForNip19, event.PubKey) data.neventNaked, _ = nip19.EncodeEvent(event.ID, nil, event.PubKey) data.naddr = "" diff --git a/details.templ b/details.templ new file mode 100644 index 0000000..42aa3bd --- /dev/null +++ b/details.templ @@ -0,0 +1,119 @@ +package main + +import ( + "strconv" +) + +templ detailsTemplate(params DetailsParams) { +
+ if params.Npub != "" { +
+
Author Public Key
+ { params.Npub } +
+ } + if params.FileMetadata != nil { + if params.FileMetadata.Summary != "" { +
+
Summary
+ { params.FileMetadata.Summary } +
+ } + if params.FileMetadata.Dim != "" { +
+
Dimension
+ { params.FileMetadata.Dim } +
+ } + if params.FileMetadata.Size != "" { +
+
Size
+ { params.FileMetadata.Size } bytes +
+ } + if params.FileMetadata.Magnet != "" { +
+
Magnet URL
+ { params.FileMetadata.Magnet } +
+ } + } + if len(params.SeenOn) != 0 { +
+
Seen on
+ for _, v := range params.SeenOn { + { v } + } +
+ } + + if params.HideDetails { +
+ + +
+ } +
+
+
Published at
+ { params.CreatedAt } +
+
+
Kind type
+ { strconv.Itoa(params.Kind) } + if params.KindNIP != "" { + { params.KindDescription } + } +
+ if params.Nevent != "" { +
+
Address Code
+ { params.Nevent } +
+ } +
+
+ Event JSON +
+
+ @templ.Raw(params.EventJSON) +
+
+ if params.Nprofile != "" { +
+
Author Profile Code
+ { params.Nprofile } +
+ } +
+} diff --git a/embedded_note.templ b/embedded_note.templ new file mode 100644 index 0000000..e11cafe --- /dev/null +++ b/embedded_note.templ @@ -0,0 +1,70 @@ +package main + +templ embeddedNoteTemplate(params EmbeddedNoteParams) { + + + + + + + + + +
+ +
+ @authorHeaderTemplate(params.Metadata) +
+
+ if params.Subject != "" { +

{ params.Subject }

+ } + +
{ params.Content }
+
+ { params.CreatedAt } +
+
+
+
+
+
+ This note has been published on Nostr and is embedded via Njump, + learn more +
+
+ + + + + + + + + + + + +} diff --git a/embedded_profile.templ b/embedded_profile.templ new file mode 100644 index 0000000..6a2c7aa --- /dev/null +++ b/embedded_profile.templ @@ -0,0 +1,126 @@ +package main + +templ embeddedProfileTemplate(params EmbeddedProfileParams) { + + + + + + + + + +
+ +
+
+
+
+
+ +
+
+
{ params.Metadata.Name }
+ if params.Metadata.Name != params.Metadata.DisplayName { +
+ { params.Metadata.DisplayName } +
+ } +
+
+
+ if params.Metadata.Website != "" || params.RenderedAuthorAboutText != "" { +
+ { params.RenderedAuthorAboutText } +
+
+
{ params.Metadata.Website }
+
+ { params.RenderedAuthorAboutText } +
+ } +
+
+
Public Key
+ { params.Npub } +
+
+ if params.Metadata.NIP05 != "" { +
NIP-05 Address
+ { params.Metadata.NIP05 } + } +
+
+ if params.Metadata.LUD16 != "" { +
NIP-57 Address
+ { params.Metadata.LUD16 } + } +
+ if len(params.AuthorRelays) > 0 { +
+
Publishing to
+ for index, element := range params.AuthorRelays { + { element } + } +
+ } +
+
+
+
+
+ This note has been published on Nostr and is embedded via Njump, + learn more +
+
+ + + + + + + + + + + + + + +} diff --git a/error.templ b/error.templ new file mode 100644 index 0000000..0f3f40b --- /dev/null +++ b/error.templ @@ -0,0 +1,35 @@ +package main + +templ errorTemplate(params ErrorPageParams) { + + + + + Error + @headCommonTemplate(params.HeadParams) + + + @topTemplate() +
+
+
+ @templ.Raw(params.Message) +
+
+ { params.Errors } +
+
+ Are you lost? + Go to the homepage +
+
+
+ @footerTemplate() + + +} diff --git a/file_metadata.templ b/file_metadata.templ new file mode 100644 index 0000000..7a6e3c9 --- /dev/null +++ b/file_metadata.templ @@ -0,0 +1,73 @@ +package main + +templ fileMetadataTemplate(params FileMetadataPageParams) { + + + + + File Metadata + @openGraphTemplate(params.OpenGraphParams) + @headCommonTemplate(params.HeadParams) + + + @topTemplate() +
+
+
+ @authorHeaderTemplate(params.Metadata) +
+ { params.CreatedAt } +
+
+ if params.ParentLink != "" { + in reply to + + @templ.Raw(params.ParentLink) + + } +
+
+
+ if params.Subject != "" { +

{ params.Subject }

+ } else { +

+ { params.Metadata.ShortName() } on Nostr: { params.TitleizedContent } +

+ } + + if params.FileMetadata.Image != "" { + { + } else if params.IsImage { + { + } else if params.IsVideo { + + } + Download file +
+ @detailsTemplate(params.DetailsParams) +
+
+ @clientsTemplate(params.Clients) +
+
+ @footerTemplate() + + +} diff --git a/footer.templ b/footer.templ new file mode 100644 index 0000000..d7ec31b --- /dev/null +++ b/footer.templ @@ -0,0 +1,22 @@ +package main + +templ footerTemplate() { +
+ + + + + + + + +} diff --git a/go.mod b/go.mod index cb33af3..73bd35b 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,8 @@ module github.com/fiatjaf/njump go 1.21.4 require ( - github.com/PuerkitoBio/goquery v1.5.0 + github.com/PuerkitoBio/goquery v1.8.1 + github.com/a-h/templ v0.2.513 github.com/dgraph-io/badger/v4 v4.2.0 github.com/fiatjaf/eventstore v0.3.6 github.com/fiatjaf/khatru v0.2.1 @@ -32,7 +33,7 @@ require ( require ( github.com/andybalholm/brotli v1.0.5 // indirect - github.com/andybalholm/cascadia v1.0.0 // indirect + github.com/andybalholm/cascadia v1.3.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/btcsuite/btcd/btcutil v1.1.3 // indirect @@ -57,8 +58,8 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.17.2 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect diff --git a/go.sum b/go.sum index 1da781b..dfa038d 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,14 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk= -github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= +github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= +github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= +github.com/a-h/templ v0.2.513 h1:ZmwGAOx4NYllnHy+FTpusc4+c5msoMpPIYX0Oy3dNqw= +github.com/a-h/templ v0.2.513/go.mod h1:9gZxTLtRzM3gQxO8jr09Na0v8/jfliS97S9W5SScanM= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o= -github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= +github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= @@ -122,8 +124,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= @@ -146,10 +148,13 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/microcosm-cc/bluemonday v1.0.24 h1:NGQoPtwGVcbGkKfvyYk1yRqknzBuoMiUrO6R7uFTPlw= github.com/microcosm-cc/bluemonday v1.0.24/go.mod h1:ArQySAMps0790cHSkdPEJ7bGkF2VePWH773hsJNSHf8= github.com/nbd-wtf/emoji v0.0.3 h1:YtkT7MVPXvqU1SQjvC/CShlWexnREzqNCxmhUnL00CA= @@ -223,12 +228,14 @@ github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4= golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= @@ -239,12 +246,11 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -254,6 +260,10 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -262,6 +272,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -273,15 +284,28 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -292,6 +316,7 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/head_common.templ b/head_common.templ new file mode 100644 index 0000000..2cc8a58 --- /dev/null +++ b/head_common.templ @@ -0,0 +1,70 @@ +package main + +templ headCommonTemplate(params HeadParams) { + + if params.Oembed != "" { + + + } + if params.IsProfile { + + + + } else { + + + + } + + if params.TailwindDebugStuff != "" { + @templ.Raw(params.TailwindDebugStuff) + } else { + + } + + + if params.NaddrNaked != "" { + + } else { + + } + +} diff --git a/homepage.templ b/homepage.templ new file mode 100644 index 0000000..3d986aa --- /dev/null +++ b/homepage.templ @@ -0,0 +1,247 @@ +package main + +templ homepageTemplate(params HomePageParams) { + + + + + njump - the nostr static gateway + + @head_commonTemplate(params.HeadCommonParams) + + + @topTemplate() +
+
+
+

What is njump?

+

+ njump is a HTTP + + Nostr + + gateway that allows you to browse profiles, notes and relays; it is + an easy way to preview a resource and then open it with your + preferred client. The typical use of njump is to share a + resource outside the Nostr world, where the + nostr: schema is not (yet) working. +

+

+ njump currently lives under { params.Host }, you can reach it + appending a Nostr + + NIP-19 + + entity (npub, nevent, naddr, + etc) after the domain: + + { params.Host }/<nip-19-entity> + . +

+

+ For example, here's + + a user profile + , + + a note + + and a + + long blog post + + . +

+

+ Try it now, jump to some Nostr content +

+
+
+
+
{ params.Host }/
+ + +
+
+ +
+

+ There are several reasons to choose njump when sharing Nostr + content outside of Nostr: +

+

Clean, fast and solid

+

+ Pages by njump are extremely lightweight and fast to load + because there isn't any client side javascript involved; they are + minimalistic with the right attention to typography, focusing the + content without unnecessary details. Furthermore they are cached, so + when sharing a page you can expect the other part will load it + without any glitch in a fraction of second: the perfect tool to + onboard new users! +

+

Good preview

+

+ njump renders everything on the server-side, so it is able to + generate useful rich previews that work on Telegram, Discord, + Twitter and other places. +

+

+ When opening the URL directly in a browser, visitors will find + referenced content like images, video and links to referenced Nostr + events displayed in a simple but effective way. It shows the note + parent, allowing the visitor to follow it up and it has custom CSS for + printing or exporting to PDF. +

+

Cooperative (jump-out)

+

+ njump is not interested capturing users at all, on the + contrary it invites them to "jump" to the Nostr resource by picking + from a list of web clients or with a nostr: prefix for native + clients. It even remembers the most used one for each visitor and + puts it on the top for fast clicking or tap. +

+

+ NIP-89 + support coming! +

+

+ Search friendly (jump-in) +

+

+ This is crucial: njump pages are static so search engines can + index them, this means that njump can help others to discover + great content on Nostr, jump in and join us! njump is the + only nostr resource that has this explicit goal, if you care that a + good note can be found online use njump to share it, this way + you also help Nostr flourish. +

+

Share NIP-05 profiles

+

+ Now you can share your own profile with a pretty + + NIP-05 + + inspired permalink: + + { params.Host }/<nip-05> + + , for example: + https://{ params.Host }/nvk.org + or + + https://{ params.Host }/mike@mikedilger.com + + . +

+

+ A profile shows the basic metadata infos, the used "outbox" relays + and the last notes. +

+

+ Share on Twitter and Telegram +

+

+ Now you can quickly and effortlessly share Nostr notes on Twitter + and Telegram, and maybe on many other "social platforms": just drop + a link, and njump will render the note text using the preview image + as a canvas, to maximize the sharing experience and utility. +
+ On Telegram we have also the Instant View to access long content + in-app! +

+

Relays view

+

+ You can have a view of the last content posted to a relay using + + { params.Host }/r/<relay-host> + + , for example: + + https://{ params.Host }/r/nostr.wine + +

+

+ Some basic infos ( + + NIP-11 + + ) are available; We hope operators will start to make them more + personal and informative so users can have a way to evaluate if/when + to join a relay. +

+

Website widgets

+
+ You can embed notes, long form contents and profiles in a web page + with a simple script: +
+ + <script src="https://{ params.Host }/embed/<nip-19-entity>" + /> + +
+
+ +
+
+ +
+
+
+

Inspector tool

+

+ You know, we are all programmers, including our moms, so for every + njump page you can toggle the "Show more details" switch and + inspect the full event JSON. Without installing other tools (like + nak) + this is probably the fastest way to access that. +

+
+
+
+ @footerTemplate() + + +} diff --git a/justfile b/justfile index 6aa5871..4ee888c 100644 --- a/justfile +++ b/justfile @@ -1,7 +1,34 @@ export PATH := "./node_modules/.bin:" + env_var('PATH') dev: - TAILWIND_DEBUG=true SKIP_LANGUAGE_MODEL=true go build -o /tmp/njump && /tmp/njump + fd --no-ignore-vcs 'go|templ|base.css' | entr -r bash -c 'TAILWIND_DEBUG=true SKIP_LANGUAGE_MODEL=true && templ generate && go build -o /tmp/njump && /tmp/njump' + +build: templ tailwind + go build -o ./njump + +deploy: templ tailwind + sed -i.bak "s#/tailwind-bundle.min.css#/tailwind-bundle.min.css?$(date +'%Y%m%d%H%M')#g" templates/head_common.html + GOOS=linux GOARCH=amd64 go build -o ./njump + mv -f templates/head_common.html.bak templates/head_common.html + rsync --progress njump njump:njump/njump-new + ssh njump 'systemctl stop njump' + ssh njump 'mv njump/njump-new njump/njump' + ssh njump 'systemctl start njump' + +debug-build: templ tailwind + go build -tags=nocache -o ./tmp/main . + +templ: + templ generate + +prettier: + prettier -w templates/*.html + +tailwind: + tailwind -i base.css -o static/tailwind-bundle.min.css --minify + +test: + go test -tags=nocache check-samples: #!/usr/bin/env xonsh @@ -21,27 +48,3 @@ check-samples: for code in samples: $(chromium @(base_url + '/' + code)) $(chromium @(base_url + '/njump/image/' + code)) - -build: tailwind - go build -o ./njump - -deploy: tailwind - sed -i.bak "s#/tailwind-bundle.min.css#/tailwind-bundle.min.css?$(date +'%Y%m%d%H%M')#g" templates/head_common.html - GOOS=linux GOARCH=amd64 go build -o ./njump - mv -f templates/head_common.html.bak templates/head_common.html - rsync --progress njump njump:njump/njump-new - ssh njump 'systemctl stop njump' - ssh njump 'mv njump/njump-new njump/njump' - ssh njump 'systemctl start njump' - -debug-build: tailwind - go build -tags=nocache -o ./tmp/main . - -prettier: - prettier -w templates/*.html - -tailwind: - tailwind -i tailwind.css -o static/tailwind-bundle.min.css --minify - -test: - go test -tags=nocache diff --git a/live_event.templ b/live_event.templ new file mode 100644 index 0000000..33b947a --- /dev/null +++ b/live_event.templ @@ -0,0 +1,81 @@ +package main + +templ liveEventTemplate(params LiveEventMessagePageParams) { + + + + + Stream: { params.LiveEvent.Title } by { params.LiveEvent.Host.Name } + @openGraphTemplate(params.OpenGraphParams) + @headCommonTemplate(params.HeadParams) + + + @topTemplate() +
+
+
+ @authorHeaderTemplate(params.Metadata) +
+ { params.CreatedAt } +
+
+ if params.ParentLink != "" { + in reply to + + @templ.Raw(params.ParentLink) + + } +
+
+
+

+ { params.LiveEvent.Title } + switch params.LiveEvent.Status { + case "ended": + Ended + case "live": + Live now! + } +

+
+ if params.LiveEvent.HostNpub != "" { + Streaming hosted by + + { params.LiveEvent.Host.Name } + + } +
+ +
+ for _, v := range params.LiveEvent.Tags { + { v } + } +
+ if params.LiveEvent.Summary != "" { +
{ params.LiveEvent.Summary }
+ } + if params.LiveEvent.Image != "" { + { + } +
+ @detailsTemplate(params.DetailsParams) +
+
+ @clientsTemplate(params.Clients) +
+
+ @footerTemplate() + + +} diff --git a/live_event_message.templ b/live_event_message.templ new file mode 100644 index 0000000..ca2bbae --- /dev/null +++ b/live_event_message.templ @@ -0,0 +1,52 @@ +package main + +templ liveEventMessageTemplate(params LiveEventMessagePageParams) { + + + + + { params.TitleizedContent } + @openGraphTemplate(params.OpenGraphParams) + @headCommonTemplate(params.HeadParams) + + + @topTemplate() +
+
+
+ @authorHeaderTemplate(params.Metadata) +
+ { params.CreatedAt } +
+
+ if params.ParentLink != "" { + messaging during the live event + + @templ.Raw(params.ParentLink) + + } +
+
+
+ if params.Subject != "" { +

{ params.Subject }

+ } else { +

+ { params.Metadata.ShortName() } on Nostr: { params.TitleizedContent } +

+ } + + @templ.Raw(params.Content) +
+ @detailsTemplate(params.DetailsParams) +
+
+ @clientsTemplate(params.Clients) +
+
+ @footerTemplate() + + +} diff --git a/main.go b/main.go index 0b45e24..d72220f 100644 --- a/main.go +++ b/main.go @@ -30,9 +30,6 @@ type Settings struct { //go:embed static/* var static embed.FS -//go:embed templates/* -var templates embed.FS - var ( s Settings log = zerolog.New(os.Stderr).Output(zerolog.ConsoleWriter{Out: os.Stdout}).With().Timestamp().Logger() diff --git a/note.templ b/note.templ new file mode 100644 index 0000000..cbb140e --- /dev/null +++ b/note.templ @@ -0,0 +1,52 @@ +package main + +templ noteTemplate(params NotePageParams) { + + + + + { params.TitleizedContent } + @openGraphTemplate(params.OpenGraphParams) + @headCommonTemplate(params.HeadParams) + + + @topTemplate() +
+
+
+ @authorHeaderTemplate(params.Metadata) +
+ { params.CreatedAt } +
+
+ if params.ParentLink != "" { + in reply to + + @templ.Raw(params.ParentLink) + + } +
+
+
+ if params.Subject != "" { +

{ params.Subject }

+ } else { +

+ { params.Metadata.ShortName() } on Nostr: { params.TitleizedContent } +

+ } + +
+ @templ.Raw(params.Content) +
+
+ @detailsTemplate(params.DetailsParams) +
+
+ @clientsTemplate(params.Clients) +
+
+ @footerTemplate() + + +} diff --git a/opengraph.templ b/opengraph.templ new file mode 100644 index 0000000..e2f6aaa --- /dev/null +++ b/opengraph.templ @@ -0,0 +1,44 @@ +package main + +templ openGraphTemplate(params OpenGraphParams) { + if params.SingleTitle != "" { + + + } else { + + + + } + + if params.BigImage != "" { + + + + + } else { + + + if params.Image != "" { + + + } + + if params.Video != "" { + + + + } + } + + if params.Text != "" { + + + } +} + +templ bigImagePrerender(bigImage string) { + +} diff --git a/other.templ b/other.templ new file mode 100644 index 0000000..4414037 --- /dev/null +++ b/other.templ @@ -0,0 +1,37 @@ +package main + +import "strconv" + +templ otherTemplate(params OtherPageParams) { + + + + + Nostr Event { strconv.Itoa(params.Kind) } - { params.KindDescription } + @headCommonTemplate(params.HeadParams) + + + @topTemplate() +
+
+
+
+
{ params.KindDescription }
+
+ if params.Alt != "" { +
+
+ { params.Alt } +
+ } + @detailsTemplate(params .DetailsParams) +
+
+
+
+ @footerTemplate() + + +} diff --git a/pages.go b/pages.go index cb58add..e9b6a13 100644 --- a/pages.go +++ b/pages.go @@ -8,31 +8,9 @@ import ( "strings" "github.com/nbd-wtf/go-nostr/nip11" - sdk "github.com/nbd-wtf/nostr-sdk" - "github.com/tylermmorton/tmpl" ) -type TemplateID int - -const ( - Note TemplateID = iota - Profile - LongForm - TelegramInstantView - FileMetadata - LiveEvent - LiveEventMessage - Other -) - -var ( - //go:embed templates/opengraph.html - tmplOpenGraph string - OpenGraphTemplate = tmpl.MustCompile(&OpenGraphPartial{}) -) - -//tmpl:bind head_common.html -type OpenGraphPartial struct { +type OpenGraphParams struct { SingleTitle string // x (we will always render just the singletitle if we have that) Superscript string @@ -49,44 +27,7 @@ type OpenGraphPartial struct { Text string } -func (*OpenGraphPartial) TemplateText() string { return tmplOpenGraph } - -var ( - //go:embed templates/head_common.html - tmplHeadCommon string - HeadCommonTemplate = tmpl.MustCompile(&HeadCommonPartial{}) -) - -//tmpl:bind head_common.html -type HeadCommonPartial struct { - IsProfile bool - TailwindDebugStuff template.HTML - NaddrNaked string - NeventNaked string - Oembed string -} - -func (*HeadCommonPartial) TemplateText() string { return tmplHeadCommon } - -var ( - //go:embed templates/top.html - tmplTop string - TopTemplate = tmpl.MustCompile(&TopPartial{}) -) - -//tmpl:bind top.html -type TopPartial struct{} - -func (*TopPartial) TemplateText() string { return tmplTop } - -var ( - //go:embed templates/details.html - tmplDetails string - DetailsTemplate = tmpl.MustCompile(&DetailsPartial{}) -) - -//tmpl:bind details.html -type DetailsPartial struct { +type DetailsParams struct { HideDetails bool CreatedAt string EventJSON template.HTML @@ -103,41 +44,15 @@ type DetailsPartial struct { LiveEvent *Kind30311Metadata } -func (*DetailsPartial) TemplateText() string { return tmplDetails } - -var ( - //go:embed templates/clients.html - tmplClients string - ClientsTemplate = tmpl.MustCompile(&ClientsPartial{}) -) - -//tmpl:bind clients.html -type ClientsPartial struct { - Clients []ClientReference +type HeadParams struct { + IsProfile bool + TailwindDebugStuff template.HTML + NaddrNaked string + NeventNaked string + Oembed string } -func (*ClientsPartial) TemplateText() string { return tmplClients } - -var ( - //go:embed templates/footer.html - tmplFooter string - FooterTemplate = tmpl.MustCompile(&FooterPartial{}) -) - -//tmpl:bind footer.html -type FooterPartial struct { - BigImage string -} - -func (*FooterPartial) TemplateText() string { return tmplFooter } - -var ( - //go:embed templates/telegram_instant_view.html - tmplTelegramInstantView string - TelegramInstantViewTemplate = tmpl.MustCompile(&TelegramInstantViewPage{}) -) - -type TelegramInstantViewPage struct { +type TelegramInstantViewParams struct { Video string VideoType string Image string @@ -145,42 +60,22 @@ type TelegramInstantViewPage struct { Content template.HTML Description string Subject string - Metadata sdk.ProfileMetadata + Metadata Metadata AuthorLong string CreatedAt string ParentLink template.HTML } -func (*TelegramInstantViewPage) TemplateText() string { return tmplTelegramInstantView } - -var ( - //go:embed templates/homepage.html - tmplHomePage string - HomePageTemplate = tmpl.MustCompile(&HomePage{}) -) - -type HomePage struct { - HeadCommonPartial `tmpl:"head_common"` - TopPartial `tmpl:"top"` - FooterPartial `tmpl:"footer"` +type HomePageParams struct { + HeadParams Host string Npubs []string LastNotes []string } -func (*HomePage) TemplateText() string { return tmplHomePage } - -var ( - //go:embed templates/archive.html - tmplArchive string - ArchiveTemplate = tmpl.MustCompile(&ArchivePage{}) -) - -type ArchivePage struct { - HeadCommonPartial `tmpl:"head_common"` - TopPartial `tmpl:"top"` - FooterPartial `tmpl:"footer"` +type ArchivePageParams struct { + HeadParams Title string PathPrefix string @@ -191,64 +86,36 @@ type ArchivePage struct { PrevPage int } -func (*ArchivePage) TemplateText() string { return tmplArchive } - -var ( - //go:embed templates/other.html - tmplOther string - OtherTemplate = tmpl.MustCompile(&OtherPage{}) -) - -type OtherPage struct { - HeadCommonPartial `tmpl:"head_common"` - TopPartial `tmpl:"top"` - DetailsPartial `tmpl:"details"` - FooterPartial `tmpl:"footer"` +type OtherPageParams struct { + HeadParams + DetailsParams Kind int KindDescription string Alt string } -func (*OtherPage) TemplateText() string { return tmplOther } - -var ( - //go:embed templates/note.html - tmplNote string - NoteTemplate = tmpl.MustCompile(&NotePage{}) -) - -type NotePage struct { - OpenGraphPartial `tmpl:"opengraph"` - HeadCommonPartial `tmpl:"head_common"` - TopPartial `tmpl:"top"` - DetailsPartial `tmpl:"details"` - ClientsPartial `tmpl:"clients"` - FooterPartial `tmpl:"footer"` +type NotePageParams struct { + OpenGraphParams + HeadParams + DetailsParams Content template.HTML CreatedAt string - Metadata sdk.ProfileMetadata + Metadata Metadata Npub string NpubShort string ParentLink template.HTML SeenOn []string Subject string TitleizedContent string + Clients []ClientReference } -func (*NotePage) TemplateText() string { return tmplNote } - -var ( - //go:embed templates/embedded_note.html - tmplEmbeddedNote string - EmbeddedNoteTemplate = tmpl.MustCompile(&EmbeddedNotePage{}) -) - -type EmbeddedNotePage struct { +type EmbeddedNoteParams struct { Content template.HTML CreatedAt string - Metadata sdk.ProfileMetadata + Metadata Metadata Npub string NpubShort string SeenOn []string @@ -256,27 +123,16 @@ type EmbeddedNotePage struct { Url string } -func (*EmbeddedNotePage) TemplateText() string { return tmplEmbeddedNote } - -var ( - //go:embed templates/profile.html - tmplProfile string - ProfileTemplate = tmpl.MustCompile(&ProfilePage{}) -) - -type ProfilePage struct { - HeadCommonPartial `tmpl:"head_common"` - TopPartial `tmpl:"top"` - DetailsPartial `tmpl:"details"` - ClientsPartial `tmpl:"clients"` - FooterPartial `tmpl:"footer"` +type ProfilePageParams struct { + HeadParams + DetailsParams AuthorRelays []string Content string CreatedAt string Domain string LastNotes []EnhancedEvent - Metadata sdk.ProfileMetadata + Metadata Metadata NormalizedAuthorWebsiteURL string RenderedAuthorAboutText template.HTML Nevent string @@ -285,34 +141,15 @@ type ProfilePage struct { IsReply string Proxy string Title string + Clients []ClientReference } -func (*ProfilePage) TemplateText() string { return tmplProfile } - -var ( - //go:embed templates/_last_notes.html - tmplLastNotes string - LastNotesTemplate = tmpl.MustCompile(&LastNotesPage{}) -) - -type LastNotesPage struct { - LastNotes []EnhancedEvent -} - -func (*LastNotesPage) TemplateText() string { return tmplLastNotes } - -var ( - //go:embed templates/embedded_profile.html - tmplEmbeddedProfile string - EmbeddedProfileTemplate = tmpl.MustCompile(&EmbeddedProfilePage{}) -) - -type EmbeddedProfilePage struct { +type EmbeddedProfileParams struct { AuthorRelays []string Content string CreatedAt string Domain string - Metadata sdk.ProfileMetadata + Metadata Metadata NormalizedAuthorWebsiteURL string RenderedAuthorAboutText template.HTML Nevent string @@ -322,25 +159,14 @@ type EmbeddedProfilePage struct { Title string } -func (*EmbeddedProfilePage) TemplateText() string { return tmplEmbeddedProfile } - -var ( - //go:embed templates/file_metadata.html - tmplFileMetadata string - FileMetadataTemplate = tmpl.MustCompile(&FileMetadataPage{}) -) - -type FileMetadataPage struct { - OpenGraphPartial `tmpl:"opengraph"` - HeadCommonPartial `tmpl:"head_common"` - TopPartial `tmpl:"top"` - DetailsPartial `tmpl:"details"` - ClientsPartial `tmpl:"clients"` - FooterPartial `tmpl:"footer"` +type FileMetadataPageParams struct { + OpenGraphParams + HeadParams + DetailsParams Content template.HTML CreatedAt string - Metadata sdk.ProfileMetadata + Metadata Metadata Npub string NpubShort string ParentLink template.HTML @@ -353,27 +179,18 @@ type FileMetadataPage struct { FileMetadata Kind1063Metadata IsImage bool IsVideo bool + + Clients []ClientReference } -func (*FileMetadataPage) TemplateText() string { return tmplFileMetadata } - -var ( - //go:embed templates/live_event.html - tmplLiveEvent string - LiveEventTemplate = tmpl.MustCompile(&LiveEventPage{}) -) - -type LiveEventPage struct { - OpenGraphPartial `tmpl:"opengraph"` - HeadCommonPartial `tmpl:"head_common"` - TopPartial `tmpl:"top"` - DetailsPartial `tmpl:"details"` - ClientsPartial `tmpl:"clients"` - FooterPartial `tmpl:"footer"` +type LiveEventPageParams struct { + OpenGraphParams + HeadParams + DetailsParams Content template.HTML CreatedAt string - Metadata sdk.ProfileMetadata + Metadata Metadata Npub string NpubShort string ParentLink template.HTML @@ -384,27 +201,18 @@ type LiveEventPage struct { Alt string LiveEvent Kind30311Metadata + + Clients []ClientReference } -func (*LiveEventPage) TemplateText() string { return tmplLiveEvent } - -var ( - //go:embed templates/live_event_message.html - tmplLiveEventMessage string - LiveEventMessageTemplate = tmpl.MustCompile(&LiveEventMessagePage{}) -) - -type LiveEventMessagePage struct { - OpenGraphPartial `tmpl:"opengraph"` - HeadCommonPartial `tmpl:"head_common"` - TopPartial `tmpl:"top"` - DetailsPartial `tmpl:"details"` - ClientsPartial `tmpl:"clients"` - FooterPartial `tmpl:"footer"` +type LiveEventMessagePageParams struct { + OpenGraphParams + HeadParams + DetailsParams Content template.HTML CreatedAt string - Metadata sdk.ProfileMetadata + Metadata Metadata Npub string NpubShort string ParentLink template.HTML @@ -415,114 +223,41 @@ type LiveEventMessagePage struct { Alt string LiveEventMessage Kind1311Metadata + + Clients []ClientReference } -func (*LiveEventMessagePage) TemplateText() string { return tmplLiveEventMessage } - -var ( - //go:embed templates/relay.html - tmplRelay string - RelayTemplate = tmpl.MustCompile(&RelayPage{}) -) - -type RelayPage struct { - HeadCommonPartial `tmpl:"head_common"` - TopPartial `tmpl:"top"` - ClientsPartial `tmpl:"clients"` - FooterPartial `tmpl:"footer"` +type RelayPageParams struct { + HeadParams Info *nip11.RelayInformationDocument Hostname string Proxy string LastNotes []EnhancedEvent ModifiedAt string + Clients []ClientReference } -func (*RelayPage) TemplateText() string { return tmplRelay } - -var ( - //go:embed templates/sitemap.xml - tmplSitemap string - SitemapTemplate = tmpl.MustCompile(&SitemapPage{}) -) - -type SitemapPage struct { - Host string - ModifiedAt string - - // for the profile sitemap - Npub string - - // for the relay sitemap - RelayHostname string - Info *nip11.RelayInformationDocument - - // for the profile and relay sitemaps - LastNotes []EnhancedEvent - - // for the archive sitemap - PathPrefix string - Data []string +type ErrorPageParams struct { + HeadParams + Message string + Errors string } -func (*SitemapPage) TemplateText() string { return tmplSitemap } - -var ( - //go:embed templates/rss.xml - tmplRSS string - RSSTemplate = tmpl.MustCompile(&RSSPage{}) -) - -type RSSPage struct { - Host string - ModifiedAt string - Title string - - // for the profile RSS - Npub string - Metadata sdk.ProfileMetadata - - // for the relay RSS - RelayHostname string - Info *nip11.RelayInformationDocument - - // for the profile and relay RSSs - LastNotes []EnhancedEvent - - // for the archive RSS - PathPrefix string - Data []string -} - -func (*RSSPage) TemplateText() string { return tmplRSS } - -var ( - //go:embed templates/error.html - tmplError string - ErrorTemplate = tmpl.MustCompile(&ErrorPage{}) -) - -type ErrorPage struct { - HeadCommonPartial `tmpl:"head_common"` - TopPartial `tmpl:"top"` - FooterPartial `tmpl:"footer"` - Message template.HTML - Errors string -} - -func (e *ErrorPage) TemplateText() string { +func (e *ErrorPageParams) MessageHTML() string { if e.Message != "" { - return tmplError + return "<error omitted>" } switch { case strings.Contains(e.Errors, "invalid checksum"): - e.Message = "It looks like you entered an invalid event code.
Check if you copied it fully, a good idea is compare the first and the last characters." + return "It looks like you entered an invalid event code.
Check if you copied it fully, a good idea is compare the first and the last characters." case strings.Contains(e.Errors, "couldn't find this"): - e.Message = "Can't find the event in the relays. Try getting an `nevent1` code with relay hints." - case strings.Contains(e.Errors, "invalid bech32 string length"), strings.Contains(e.Errors, "invalid separator"): - e.Message = "You have typed a wrong event code, we need a URL path that starts with /npub1, /nprofile1, /nevent1, /naddr1, or something like /name@domain.com (or maybe just /domain.com) or an event id as hex (like /aef8b32af...)" + return "Can't find the event in the relays. Try getting an `nevent1` code with relay hints." + case strings.Contains(e.Errors, "invalid bech32 string length"), + strings.Contains(e.Errors, "invalid separator"), + strings.Contains(e.Errors, "not part of charset"): + return "You have typed a wrong event code, we need a URL path that starts with /npub1, /nprofile1, /nevent1, /naddr1, or something like /name@domain.com (or maybe just /domain.com) or an event id as hex (like /aef8b32af...)" default: - e.Message = "I can't give any suggestions to solve the problem.
Please tag daniele and fiatjaf and complain!" + return "I can't give any suggestions to solve the problem.
Please tag daniele and fiatjaf and complain!" } - return tmplError } diff --git a/profile.templ b/profile.templ new file mode 100644 index 0000000..809b324 --- /dev/null +++ b/profile.templ @@ -0,0 +1,156 @@ +package main + +import "fmt" + +templ profileTemplate(params ProfilePageParams) { + + + + + { params.Metadata.Name } / { params.Metadata.DisplayName } is on Nostr + + + + if params.Metadata.Picture != "" { + + + } + if params.Metadata.About != "" { + + } + + + + + @headCommonTemplate(params.HeadParams) + + + @topTemplate() +
+
+
+ +
+ +
+
+
+ +
+ if params.Metadata.Website != "" { + + } + if params.RenderedAuthorAboutText != "" { +
+ @templ.Raw(params.RenderedAuthorAboutText) +
+ } + if params.Metadata.Website != "" || params.RenderedAuthorAboutText != "" { +
+ } +
+
Public Key
+ { params.Npub } +
+
+ if params.Metadata.NIP05 != "" { +
NIP-05 Address
+ { params.Metadata.NIP05 } + } +
+
+ if params.Metadata.LUD16 != "" { +
NIP-57 Address
+ { params.Metadata.LUD16 } + } +
+
+
Profile Code
+ { params.Nprofile } +
+ if len(params.AuthorRelays) != 0 { +
+
Publishing to
+ for _, relay := range params.AuthorRelays { + + { relay } + + } +
+ } + @detailsTemplate(params.DetailsParams) +
+ if len(params.LastNotes) != 0 { + + } +
+
+
+ @clientsTemplate(params.Clients) +
+
+ @footerTemplate() + + +} diff --git a/relay.templ b/relay.templ new file mode 100644 index 0000000..334ca79 --- /dev/null +++ b/relay.templ @@ -0,0 +1,145 @@ +package main + +templ relayTemplate(params RelayPageParams) { + + + + + Nostr Relay { params.Hostname } - { params.Info.Name } + + + + if params.Info.Icon != "" { + + + } + if params.Info.Description != "" { + + + } + + + + @headCommonTemplate(params.HeadParams) + ) + + + @topTemplate() +
+
+
+ +
+ +
+
+
+ +
+ +
+ { params.Info.Description } +
+
+ if params.Info.PubKey != "" { +
+
Public Key
+ { params.Info.PubKey } +
+ } + + if params.Info.Contact != "" { +
+
Contact
+ { params.Info.Contact } +
+ } +
+ +
+ @clientsTemplate(params.Clients) +
+
+ @footerTemplate() + + +} diff --git a/tailwind.config.js b/tailwind.config.js index 1381854..534d6b5 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,5 +1,5 @@ module.exports = { - content: ['./templates/*.html', './*.go'], + content: ['./*.go', './*.templ'], darkMode: ['class', '.theme--dark'], theme: { extend: { diff --git a/telegram_instant_view.templ b/telegram_instant_view.templ new file mode 100644 index 0000000..9058c02 --- /dev/null +++ b/telegram_instant_view.templ @@ -0,0 +1,51 @@ +package main + +templ telegramInstantViewTemplate(params TelegramInstantViewParams) { + + + + + + + + if params.Description != "" { + + } + + if params.Image != "" { + + } + + if params.Video != "" { + + + + } + + + + +
+

+ if params.Subject != "" { + { params.Subject } + } else { + { params.Metadata.ShortName } on Nostr: + } +

+ if params.ParentLink != "" { + + } + + if params.Summary != "" { + + } + + { params.Content } + if params.Subject != "" { + + } +
+} diff --git a/template_types.go b/template_types.go new file mode 100644 index 0000000..d41edab --- /dev/null +++ b/template_types.go @@ -0,0 +1,174 @@ +package main + +import ( + "context" + "html" + "html/template" + "regexp" + "strings" + "time" + + "github.com/nbd-wtf/go-nostr" + "github.com/nbd-wtf/go-nostr/nip10" + "github.com/nbd-wtf/go-nostr/nip19" + sdk "github.com/nbd-wtf/nostr-sdk" + "github.com/texttheater/golang-levenshtein/levenshtein" +) + +type Metadata struct { + sdk.ProfileMetadata +} + +func (m Metadata) Npub() string { + npub, _ := nip19.EncodePublicKey(m.PubKey) + return npub +} + +func (m Metadata) NpubShort() string { + npub := m.Npub() + return npub[:8] + "…" + npub[len(npub)-4:] +} + +type EnhancedEvent struct { + event *nostr.Event + relays []string +} + +func (ee EnhancedEvent) IsReply() bool { + return nip10.GetImmediateReply(ee.event.Tags) != nil +} + +func (ee EnhancedEvent) Reply() *nostr.Tag { + return nip10.GetImmediateReply(ee.event.Tags) +} + +func (ee EnhancedEvent) Preview() template.HTML { + lines := strings.Split(html.EscapeString(ee.event.Content), "\n") + var processedLines []string + for _, line := range lines { + if strings.TrimSpace(line) == "" { + continue + } + processedLine := shortenNostrURLs(line) + processedLines = append(processedLines, processedLine) + } + + return template.HTML(strings.Join(processedLines, "
")) +} + +func (ee EnhancedEvent) RssTitle() string { + regex := regexp.MustCompile(`(?i)`) + replacedString := regex.ReplaceAllString(string(ee.Preview()), " ") + words := strings.Fields(replacedString) + title := "" + for i, word := range words { + if len(title)+len(word)+1 <= 65 { // +1 for space + if title != "" { + title += " " + } + title += word + } else { + if i > 1 { // the first word len is > 65 + title = title + " ..." + } else { + title = "" + } + break + } + } + + content := ee.RssContent() + distance := levenshtein.DistanceForStrings([]rune(title), []rune(content), levenshtein.DefaultOptions) + similarityThreshold := 5 + if distance <= similarityThreshold { + return "" + } else { + return title + } +} + +func (ee EnhancedEvent) RssContent() string { + content := ee.event.Content + content = basicFormatting(html.EscapeString(content), true, false, false) + content = renderQuotesAsHTML(context.Background(), content, false) + if ee.IsReply() { + nevent, _ := nip19.EncodeEvent(ee.Reply().Value(), ee.relays, ee.event.PubKey) + neventShort := nevent[:8] + "…" + nevent[len(nevent)-4:] + content = "In reply to " + neventShort + "
_________________________

" + content + } + return content +} + +func (ee EnhancedEvent) Thumb() string { + imgRegex := regexp.MustCompile(`(https?://[^\s]+\.(?:png|jpe?g|gif|bmp|svg)(?:/[^\s]*)?)`) + matches := imgRegex.FindAllStringSubmatch(ee.event.Content, -1) + if len(matches) > 0 { + // The first match group captures the image URL + return matches[0][1] + } + return "" +} + +func (ee EnhancedEvent) Npub() string { + npub, _ := nip19.EncodePublicKey(ee.event.PubKey) + return npub +} + +func (ee EnhancedEvent) NpubShort() string { + npub := ee.Npub() + return npub[:8] + "…" + npub[len(npub)-4:] +} + +func (ee EnhancedEvent) Nevent() string { + nevent, _ := nip19.EncodeEvent(ee.event.ID, ee.relays, ee.event.PubKey) + return nevent +} + +func (ee EnhancedEvent) CreatedAtStr() string { + return time.Unix(int64(ee.event.CreatedAt), 0).Format("2006-01-02 15:04:05") +} + +func (ee EnhancedEvent) ModifiedAtStr() string { + return time.Unix(int64(ee.event.CreatedAt), 0).Format("2006-01-02T15:04:05Z07:00") +} + +type Kind1063Metadata struct { + Magnet string + Dim string + Size string + Summary string + Image string + URL string + AES256GCM string + M string + X string + I string + Blurhash string + Thumb string +} + +type Kind30311Metadata struct { + Title string + Summary string + Image string + Status string + Host sdk.ProfileMetadata + HostNpub string + Tags []string +} + +type Kind1311Metadata struct { + // ... +} + +func (fm Kind1063Metadata) IsVideo() bool { return strings.Split(fm.M, "/")[0] == "video" } +func (fm Kind1063Metadata) IsImage() bool { return strings.Split(fm.M, "/")[0] == "image" } +func (fm Kind1063Metadata) DisplayImage() string { + if fm.Image != "" { + return fm.Image + } else if fm.IsImage() { + return fm.URL + } else { + return "" + } +} diff --git a/templates/_last_notes.html b/templates/_last_notes.html deleted file mode 100644 index 6407ffa..0000000 --- a/templates/_last_notes.html +++ /dev/null @@ -1,32 +0,0 @@ -{{if not (eq 0 (len .LastNotes))}} - -{{end}} diff --git a/templates/archive.html b/templates/archive.html deleted file mode 100644 index a411575..0000000 --- a/templates/archive.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - {{.Title}} - - {{template "head_common" .HeadCommonPartial}} - - - - {{template "top" .}} - -
-
-
-
{{.Title}}
-
- -
-
-

{{.Title}}

-
- -
- {{range $element := .Data }} - - {{$element}} - - {{end}} -
- -
- {{if not (eq .PrevPage 0)}} - << Prev page - {{end}} {{if not (eq .NextPage 0)}} - Next page >> -
- {{end}} -
-
-
- - {{template "footer" .}} - - diff --git a/templates/clients.html b/templates/clients.html deleted file mode 100644 index 51a6ba2..0000000 --- a/templates/clients.html +++ /dev/null @@ -1,155 +0,0 @@ - - - - - diff --git a/templates/details.html b/templates/details.html deleted file mode 100644 index c4bd13a..0000000 --- a/templates/details.html +++ /dev/null @@ -1,151 +0,0 @@ - -
- -{{ if not (eq "" .Npub) }} -
-
Author Public Key
- {{.Npub}} -
-{{ end }} - - - -{{ if not (eq nil .FileMetadata) }} - -{{ if not (eq "" .FileMetadata.Summary) }} -
-
Summary
- {{.FileMetadata.Summary}} -
-{{ end }} - -{{ if not (eq "" .FileMetadata.Dim) }} -
-
Dimension
- {{.FileMetadata.Dim}} -
-{{ end }} - -{{ if not (eq "" .FileMetadata.Size) }} -
-
Size
- {{.FileMetadata.Size}} bytes -
-{{ end }} - -{{ if not (eq "" .FileMetadata.Magnet) }} -
-
Magnet URL
- {{.FileMetadata.Magnet}} -
-{{ end }} - -{{ end }} - - - -{{ if not (eq 0 (len .SeenOn)) }} -
-
Seen on
- {{ range .SeenOn }}{{.}} - {{ end }} -
-{{ end }} - - -{{ if .HideDetails }} -
- - - -
-{{ end }} - -
-
-
Published at
- {{.CreatedAt}} -
- -
-
Kind type
- {{.Kind}} - {{ if not (eq .KindNIP "")}} - - {{.KindDescription}} - {{ end }} -
- - {{ if not (eq "" .Nevent) }} -
-
Address Code
- {{.Nevent}} -
- {{ end }} - - - -
-
- Event JSON -
-
- {{- .EventJSON}} -
-
- - {{ if not (eq "" .Nprofile) }} -
-
Author Profile Code
- {{.Nprofile}} -
- {{ end }} -
diff --git a/templates/embedded_note.html b/templates/embedded_note.html deleted file mode 100644 index a8f8c99..0000000 --- a/templates/embedded_note.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/templates/embedded_profile.html b/templates/embedded_profile.html deleted file mode 100644 index f88a607..0000000 --- a/templates/embedded_profile.html +++ /dev/null @@ -1,144 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/templates/error.html b/templates/error.html deleted file mode 100644 index 12b44e3..0000000 --- a/templates/error.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - Error - {{template "head_common" .HeadCommonPartial}} - - - - {{template "top" .}} - -
-
-
{{.Message}}
-
- {{.Errors}} -
-
- Are you lost? - Go to the homepage -
-
-
- - {{template "footer" .}} - - diff --git a/templates/file_metadata.html b/templates/file_metadata.html deleted file mode 100644 index e1a88fd..0000000 --- a/templates/file_metadata.html +++ /dev/null @@ -1,104 +0,0 @@ - - - - - File Metadata - {{template "opengraph" .OpenGraphPartial}} - - {{template "head_common" .HeadCommonPartial}} - - - - {{template "top" .}} - -
-
-
-
- -
- -
-
-
- {{.Metadata.Name}} - - {{if not (eq .Metadata.Name .Metadata.DisplayName)}} - / {{.Metadata.DisplayName}} - {{end}} -
-
- {{.NpubShort}} -
-
-
-
-
- {{.CreatedAt}} -
- -
- {{ if not (eq "" .ParentLink) }} in reply to - {{ .ParentLink }} {{ end }} -
- -
- -
- {{ if (not (eq "" .Subject))}} -

{{.Subject}}

- {{ else }} -

- {{.Metadata.ShortName}} on Nostr: {{.TitleizedContent}} -

- {{ end }} - - - {{ if (not (eq "" .FileMetadata.Image))}} - {{ .Alt }} - {{ else if .IsImage }} - {{ .Alt }} - {{ else if .IsVideo }} - - {{ end }} - - Download file -
- - {{template "details" .DetailsPartial}} - -
-
- - {{template "clients" .ClientsPartial}} -
-
- - {{template "footer" .}} - - diff --git a/templates/footer.html b/templates/footer.html deleted file mode 100644 index 0da0b9f..0000000 --- a/templates/footer.html +++ /dev/null @@ -1,24 +0,0 @@ -
- - - - - - - - - - - - diff --git a/templates/head_common.html b/templates/head_common.html deleted file mode 100644 index cab2659..0000000 --- a/templates/head_common.html +++ /dev/null @@ -1,76 +0,0 @@ - - -{{ if not (eq "" .Oembed) }} - - -{{ end }} - - - -{{if .IsProfile}} - - - -{{else}} - - - -{{ end }} - -{{ if not (eq "" .TailwindDebugStuff) }} {{ .TailwindDebugStuff }} {{ else }} - -{{ end }} - - - -{{ if not (eq "" .NaddrNaked) }} - -{{ else }} - -{{ end }} - - diff --git a/templates/homepage.html b/templates/homepage.html deleted file mode 100644 index 827426f..0000000 --- a/templates/homepage.html +++ /dev/null @@ -1,253 +0,0 @@ - - - - - njump - the nostr static gateway - - - {{template "head_common" .HeadCommonPartial}} - - - - {{template "top" .}} - -
-
-
-

What is njump?

- -

- njump is a HTTP - Nostr - gateway that allows you to browse profiles, notes and relays; it is - an easy way to preview a resource and then open it with your - preferred client. The typical use of njump is to share a - resource outside the Nostr world, where the - nostr: schema is not (yet) working. -

-

- njump currently lives under {{ .Host }}, you can reach it - appending a Nostr - NIP-19 - entity (npub, nevent, naddr, - etc) after the domain: - {{ .Host }}/<nip-19-entity>. -

-

- For example, here's - a user profile, - a note - and a - long blog post. -

- -

- Try it now, jump to some Nostr content -

- -
-
-
-
{{ .Host }}/
- - -
-
-
- or pick - some random content -
-
- -

- There are several reasons to choose njump when sharing Nostr - content outside of Nostr: -

- -

Clean, fast and solid

-

- Pages by njump are extremely lightweight and fast to load - because there isn't any client side javascript involved; they are - minimalistic with the right attention to typography, focusing the - content without unnecessary details. Furthermore they are cached, so - when sharing a page you can expect the other part will load it - without any glitch in a fraction of second: the perfect tool to - onboard new users! -

- -

Good preview

-

- njump renders everything on the server-side, so it is able to - generate useful rich previews that work on Telegram, Discord, - Twitter and other places. -

-

- When opening the URL directly in a browser, visitors will find - referenced content like images, video and links to referenced Nostr - events displayed in a simple but effective way. It shows the note - parent, allowing the visitor to follow it up and it has custom CSS - for printing or exporting to PDF. -

- -

Cooperative (jump-out)

-

- njump is not interested capturing users at all, on the - contrary it invites them to "jump" to the Nostr resource by picking - from a list of web clients or with a nostr: for native - clients. It even remembers the most used one for each visitor and - puts it on the top for fast clicking or tap. -

-

- NIP-89 - support coming! -

- -

- Search friendly (jump-in) -

-

- This is crucial: njump pages are static so search engines can - index them, this means that njump can help others to discover - great content on Nostr, jump in and join us! njump is the - only nostr resource that has this explicit goal, if you care that a - good note can be found online use njump to share it, this way - you also help Nostr flourish. -

- -

Share NIP-05 profiles

-

- Now you can share your own profile with a pretty - NIP-05 - inspired permalink: - {{ .Host }}/<nip-5>, for example: - https://{{ .Host }}/nvk.org - or - https://{{ .Host }}/mike@mikedilger.com. -

-

- A profile shows the basic metadata infos, the used "outbox" relays - and the last notes. -

- -

- Share on Twitter and Telegram -

-

- Now you can quickly and effortlessly share Nostr notes on Twitter - and Telegram, and maybe on many other "social platforms": just drop - a link, and njump will render the note text using the preview image - as a canvas, to maximize the sharing experience and utility.
- On Telegram we have also the Instant View to access long content - in-app! -

- -

Relays view

-

- You can have a view of the last content posted to a relay using - {{ .Host }}/r/<relay-host>, for example: - https://{{ .Host }}/r/nostr.wine -

-

- Some basic infos (NIP-11) are available; We hope operators will start to make them more - personal and informative so users can have a way to evaluate if/when - to join a relay. -

- -

Website widgets

-
- You can embed notes, long form contents and profiles in a web page - with a simple script:
- <script src="https://{{ .Host }}/embed/<nip-19-entity>" - /> -
-
- -
-
- -
-
-
- -

Inspector tool

-

- You know, we are all programmers, including our moms, so for every - njump page you can toggle the "Show more details" switch and - inspect the full event JSON. Without installing other tools (like - nak) - this is probably the fastest way to access that. -

-
-
-
- - {{template "footer" .}} - - diff --git a/templates/live_event.html b/templates/live_event.html deleted file mode 100644 index d70ec91..0000000 --- a/templates/live_event.html +++ /dev/null @@ -1,119 +0,0 @@ - - - - - Stream: {{.LiveEvent.Title}} by {{ .LiveEvent.Host.Name }} - {{template "opengraph" .OpenGraphPartial}} - - {{template "head_common" .HeadCommonPartial}} - - - - {{template "top" .}} - -
-
-
-
- -
- -
-
-
- {{.Metadata.Name}} - - {{if not (eq .Metadata.Name .Metadata.DisplayName)}} - / {{.Metadata.DisplayName}} - {{end}} -
-
- {{.NpubShort}} -
-
-
-
-
- {{.CreatedAt}} -
- -
- {{ if not (eq "" .ParentLink) }} in reply to - {{ .ParentLink }} {{ end }} -
- -
- -
-

- {{.LiveEvent.Title}}{{ if (eq "ended" - .LiveEvent.Status)}} - Ended - {{ else if (eq "live" .LiveEvent.Status)}} - Live now! - {{ end }} -

-
- {{ if not (eq "" .LiveEvent.HostNpub) }} Streaming hosted by - {{ .LiveEvent.Host.Name }} - {{ end }} -
- -
- {{ range .LiveEvent.Tags }} - {{ . }} - {{ end }} -
- {{ if (not (eq "" .LiveEvent.Summary))}} -
{{ .LiveEvent.Summary }}
- {{ end }} {{ if (not (eq "" .LiveEvent.Image))}} - {{ .Alt }} - {{ end }} -
- - {{template "details" .DetailsPartial}} - -
-
- - {{template "clients" .ClientsPartial}} -
-
- - {{template "footer" .}} - - diff --git a/templates/live_event_message.html b/templates/live_event_message.html deleted file mode 100644 index 16c0ae1..0000000 --- a/templates/live_event_message.html +++ /dev/null @@ -1,85 +0,0 @@ - - - - - {{.TitleizedContent}} - - {{template "opengraph" .OpenGraphPartial}} - - {{template "head_common" .HeadCommonPartial}} - - - - {{template "top" .}} - -
-
-
-
- -
- -
-
-
- {{.Metadata.Name}} - - {{if not (eq .Metadata.Name .Metadata.DisplayName)}} - / {{.Metadata.DisplayName}} - {{end}} -
-
- {{.NpubShort}} -
-
-
-
-
- {{.CreatedAt}} -
- -
- {{ if not (eq "" .ParentLink) }} messaging during the live event - {{ .ParentLink }} {{ end }} -
- -
- -
- {{ if (not (eq "" .Subject))}} -

{{.Subject}}

- {{ else }} -

- {{.Metadata.ShortName}} on Nostr: {{.TitleizedContent}} -

- {{ end }} - - {{ .Content }} -
- - {{template "details" .DetailsPartial}} - -
-
- - {{template "clients" .ClientsPartial}} -
-
- - {{template "footer" .}} - - diff --git a/templates/note.html b/templates/note.html deleted file mode 100644 index af7b5a4..0000000 --- a/templates/note.html +++ /dev/null @@ -1,85 +0,0 @@ - - - - - {{.TitleizedContent}} - - {{template "opengraph" .OpenGraphPartial}} - - {{template "head_common" .HeadCommonPartial}} - - - - {{template "top" .}} - -
-
-
-
- -
- -
-
-
- {{.Metadata.Name}} - - {{if not (eq .Metadata.Name .Metadata.DisplayName)}} - / {{.Metadata.DisplayName}} - {{end}} -
-
- {{.NpubShort}} -
-
-
-
-
- {{.CreatedAt}} -
- -
- {{ if not (eq "" .ParentLink) }} in reply to - {{ .ParentLink }} {{ end }} -
- -
- -
- {{ if (not (eq "" .Subject))}} -

{{.Subject}}

- {{ else }} -

- {{.Metadata.ShortName}} on Nostr: {{.TitleizedContent}} -

- {{ end }} - -
{{ .Content }}
-
- - {{template "details" .DetailsPartial}} - -
-
- - {{template "clients" .ClientsPartial}} -
-
- - {{template "footer" .OpenGraphPartial}} - - diff --git a/templates/opengraph.html b/templates/opengraph.html deleted file mode 100644 index 95dfcf9..0000000 --- a/templates/opengraph.html +++ /dev/null @@ -1,39 +0,0 @@ -{{ if not (eq "" .SingleTitle)}} - - -{{ else }} - - - -{{ end }} - - -{{ if not (eq "" .BigImage) }} - - - - -{{ else }} - - -{{ if not (eq "" .Image) }} - - -{{ end }} - -{{ if not (eq "" .Video) }} - - - -{{ end }} - -{{ end }} - - -{{ if not (eq "" .Text) }} - - -{{ end }} diff --git a/templates/other.html b/templates/other.html deleted file mode 100644 index 6755a02..0000000 --- a/templates/other.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - Nostr Event {{.Kind}} - {{.KindDescription }} - - - {{template "head_common" .HeadCommonPartial}} - - - - {{template "top" .}} - -
-
-
-
-
{{.KindDescription}}
-
- - {{ if not (eq "" .Alt) }} -
- -
- {{.Alt}} -
- {{ end }} - - - {{template "details" .DetailsPartial}} - -
-
-
-
- - {{template "footer" .}} - - diff --git a/templates/profile.html b/templates/profile.html deleted file mode 100644 index dfe0ac4..0000000 --- a/templates/profile.html +++ /dev/null @@ -1,175 +0,0 @@ - - - - - {{.Metadata.Name}} / {{.Metadata.DisplayName}} is on nostr - - - - {{ if not (eq "" .Metadata.Picture) }} - - - {{end}} {{ if not (eq "" .Metadata.About) }} - - {{end}} - - - - - - - - {{template "head_common" .HeadCommonPartial}} - - - - {{template "top" .}} - -
-
-
- -
- -
-
- -
- - -
- {{ if not (eq "" .Metadata.Website) }} - - {{ end }} {{ if not (eq "" .RenderedAuthorAboutText) }} -
- {{.RenderedAuthorAboutText}} -
- {{ end }} {{ if or (not (eq "" .Metadata.Website)) (not (eq "" - .RenderedAuthorAboutText)) }} -
- {{ end }} -
-
Public Key
- {{.Npub}} -
-
- {{ if not (eq "" .Metadata.NIP05) }} -
NIP-05 Address
- {{.Metadata.NIP05}} {{ end }} -
-
- {{ if not (eq "" .Metadata.LUD16) }} -
NIP-57 Address
- {{.Metadata.LUD16}} {{ end }} -
-
-
Profile Code
- {{.Nprofile}} -
- - {{ if not (eq 0 (len .AuthorRelays)) }} -
-
Publishing to
- {{range $index, $element := .AuthorRelays}} - {{$element}} - {{end}} -
- {{ end }} - - - - {{template "details" .DetailsPartial}} - -
- {{if not (eq 0 (len .LastNotes))}} - - {{end}} -
- -
-
- - {{template "clients" .ClientsPartial}} -
-
- - {{template "footer" .}} - - diff --git a/templates/relay.html b/templates/relay.html deleted file mode 100644 index 12c22a2..0000000 --- a/templates/relay.html +++ /dev/null @@ -1,157 +0,0 @@ - - - - - Nostr Relay {{.Hostname}} - {{.Info.Name}} - - - - {{ if not (eq "" .Info.Icon) }} - - - {{end}} {{ if not (eq "" .Info.Description) }} - - - {{end}} - - - - - - - {{template "head_common" .HeadCommonPartial}} - - - - {{template "top" .}} - -
-
-
- -
- -
-
- -
- - -
- -
- {{.Info.Description}} -
-
- - {{ if not (eq "" .Info.PubKey) }} -
-
Public Key
- {{.Info.PubKey}} -
- {{ end }} - - {{ if not (eq "" .Info.Contact) }} -
-
Contact
- {{.Info.Contact}} -
- {{ end }} - -
- -
- - {{template "clients" .ClientsPartial}} -
-
- - {{template "footer" .}} - - diff --git a/templates/telegram_instant_view.html b/templates/telegram_instant_view.html deleted file mode 100644 index b73ecb2..0000000 --- a/templates/telegram_instant_view.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - -{{ if not (eq "" .Description) }} - -{{ end }} - -{{ if not (eq "" .Image) }} - -{{ end }} - -{{ if not (eq "" .Video) }} - - - -{{ end }} - - - - - - -
-

- {{ if not (eq "" .Subject) }} {{.Subject}} {{ else }} - {{.Metadata.ShortName}} on Nostr: {{ end - }} -

- - {{ if not (eq "" .ParentLink) }} - - {{ end }} - - {{ if not (eq "" .Summary) }} - - {{ end }} - - - {{.Content}} {{ if not (eq "" .Subject) }} - - {{ end }} -
diff --git a/templates/top.html b/templates/top.html deleted file mode 100644 index 6d8bb7b..0000000 --- a/templates/top.html +++ /dev/null @@ -1,45 +0,0 @@ -
- What is Nostr? -
- - -
-
- - diff --git a/top.templ b/top.templ new file mode 100644 index 0000000..e30f8ca --- /dev/null +++ b/top.templ @@ -0,0 +1,47 @@ +package main + +templ topTemplate() { +
+ What is Nostr? +
+ + +
+
+ +} diff --git a/xml-pages.go b/xml-pages.go new file mode 100644 index 0000000..2f22542 --- /dev/null +++ b/xml-pages.go @@ -0,0 +1,65 @@ +package main + +import ( + _ "embed" + + "github.com/nbd-wtf/go-nostr/nip11" + sdk "github.com/nbd-wtf/nostr-sdk" + "github.com/tylermmorton/tmpl" +) + +var ( + //go:embed xml/sitemap.xml + tmplSitemap string + SitemapTemplate = tmpl.MustCompile(&SitemapPage{}) +) + +type SitemapPage struct { + Host string + ModifiedAt string + + // for the profile sitemap + Npub string + + // for the relay sitemap + RelayHostname string + Info *nip11.RelayInformationDocument + + // for the profile and relay sitemaps + LastNotes []EnhancedEvent + + // for the archive sitemap + PathPrefix string + Data []string +} + +func (*SitemapPage) TemplateText() string { return tmplSitemap } + +var ( + //go:embed xml/rss.xml + tmplRSS string + RSSTemplate = tmpl.MustCompile(&RSSPage{}) +) + +type RSSPage struct { + Host string + ModifiedAt string + Title string + + // for the profile RSS + Npub string + Metadata sdk.ProfileMetadata + + // for the relay RSS + RelayHostname string + Info *nip11.RelayInformationDocument + + // for the profile and relay RSSs + LastNotes []EnhancedEvent + + // for the archive RSS + PathPrefix string + Data []string +} + +func (*RSSPage) TemplateText() string { return tmplRSS } diff --git a/templates/rss.xml b/xml/rss.xml similarity index 100% rename from templates/rss.xml rename to xml/rss.xml diff --git a/templates/sitemap.xml b/xml/sitemap.xml similarity index 100% rename from templates/sitemap.xml rename to xml/sitemap.xml