Merge pull request #1 from dtonon/feature/design-profile-note

This commit is contained in:
fiatjaf_
2023-05-27 07:41:26 -03:00
committed by GitHub
20 changed files with 2049 additions and 26 deletions

4
.gitignore vendored
View File

@@ -1 +1,5 @@
njump
.air.toml
tmp/
node_modules/
.sass-cache/

5
go.mod
View File

@@ -7,7 +7,7 @@ require (
github.com/die-net/lrucache v0.0.0-20220628165024-20a71bc65bf1
github.com/lukevers/freetype-go v0.0.0-20150513150840-77e276735410
github.com/mailru/easyjson v0.7.7
github.com/nbd-wtf/go-nostr v0.18.1-0.20230509030905-52a493fd9666
github.com/nbd-wtf/go-nostr v0.18.4
github.com/pelletier/go-toml v1.9.5
golang.org/x/image v0.0.0-20190802002840-cff245a6509b
)
@@ -23,6 +23,9 @@ require (
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.2.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/tidwall/gjson v1.14.4 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
golang.org/x/exp v0.0.0-20221106115401-f9659909a136 // indirect
golang.org/x/sys v0.6.0 // indirect
)

14
go.sum
View File

@@ -69,8 +69,12 @@ github.com/lukevers/freetype-go v0.0.0-20150513150840-77e276735410 h1:ED5jVfC//X
github.com/lukevers/freetype-go v0.0.0-20150513150840-77e276735410/go.mod h1:cnsFc3HOpydgckvXF3xq4fvlLFOAuTh4VyJ118x8LQc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/nbd-wtf/go-nostr v0.18.1-0.20230509030905-52a493fd9666 h1:dWnwxgOl+dnVsKQLoNo5TaYJB7opG3LUIZ0YPl63iQI=
github.com/nbd-wtf/go-nostr v0.18.1-0.20230509030905-52a493fd9666/go.mod h1:fN8trCzHEtsf2954h8neqNERM/OXnCYI71nA4wWCobI=
github.com/nbd-wtf/go-nostr v0.18.4-0.20230514132335-123d3a6a9ab1 h1:rfkAcjBBR7j9aHRYIn+VjCO12lt8Bero5/cYPmO6Q6Q=
github.com/nbd-wtf/go-nostr v0.18.4-0.20230514132335-123d3a6a9ab1/go.mod h1:GPJOOK8US38kz+bfb9nWe873Xu0e6bXlThejOs1LTkc=
github.com/nbd-wtf/go-nostr v0.18.4-0.20230525112312-5c0f8bff83ea h1:L4eHtPd5bgj7Cl9j+TA5fZaQGVPt8SunZhqNtMbqzWo=
github.com/nbd-wtf/go-nostr v0.18.4-0.20230525112312-5c0f8bff83ea/go.mod h1:GPJOOK8US38kz+bfb9nWe873Xu0e6bXlThejOs1LTkc=
github.com/nbd-wtf/go-nostr v0.18.4 h1:P5qHEvvwS6DMaDMD82fP66M3kFBJnNQATczGH93rC0s=
github.com/nbd-wtf/go-nostr v0.18.4/go.mod h1:GPJOOK8US38kz+bfb9nWe873Xu0e6bXlThejOs1LTkc=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -92,6 +96,12 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.5 h1:s5PTfem8p8EbKQOctVV53k6jCJt3UX4IEJzwh+C324Q=
github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=

View File

@@ -6,3 +6,7 @@ deploy: build
rsync njump turgot:njump/njump-new
ssh turgot 'mv njump/njump-new njump/njump'
ssh root@turgot 'systemctl start njump'
refresh_build:
sass static/styles.scss static/styles.css
go build -o ./tmp/main .

View File

@@ -7,6 +7,9 @@ import (
)
func main() {
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
http.HandleFunc("/image/", generate)
http.HandleFunc("/proxy/", proxy)
http.HandleFunc("/", render)

212
package-lock.json generated Normal file
View File

@@ -0,0 +1,212 @@
{
"name": "njump",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"sass": "^1.62.1"
}
},
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"engines": {
"node": ">=8"
}
},
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dependencies": {
"fill-range": "^7.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/immutable": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz",
"integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg=="
},
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dependencies": {
"binary-extensions": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/sass": {
"version": "1.62.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.62.1.tgz",
"integrity": "sha512-NHpxIzN29MXvWiuswfc1W3I0N8SXBd8UR26WntmDlRYf0bSADnwnOjsyMZ3lMezSlArD33Vs3YFhp7dWvL770A==",
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
"source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
"sass": "sass.js"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
}
}
}

5
package.json Normal file
View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"sass": "^1.62.1"
}
}

View File

@@ -4,19 +4,24 @@ import (
_ "embed"
"encoding/json"
"fmt"
"html"
"net/http"
"regexp"
"strings"
"text/template"
"time"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19"
)
//go:embed event.html
var eventHTML string
//go:embed static/raw.html
var rawHTML string
var tmpl = template.Must(template.New("event").Parse(eventHTML))
//go:embed static/profile.html
var profileHTML string
//go:embed static/note.html
var noteHTML string
func render(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.URL.Path, ":~", r.Header.Get("user-agent"))
@@ -48,10 +53,11 @@ func render(w http.ResponseWriter, r *http.Request) {
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")
author := event
if event.Kind != 0 {
typ = "event"
typ = "note"
author, _ = getEvent(r.Context(), npub)
if event.Kind >= 30000 && event.Kind < 40000 {
@@ -154,19 +160,24 @@ func render(w http.ResponseWriter, r *http.Request) {
description = prettyJsonOrRaw(event.Content)
}
content := prettyJsonOrRaw(event.Content)
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,
@@ -174,6 +185,25 @@ func render(w http.ResponseWriter, r *http.Request) {
"proxy": "https://" + hostname + "/proxy?src=",
"eventJSON": string(eventJSON),
}
templates := make(map[string]string)
templates["profile"] = profileHTML
templates["note"] = noteHTML
templates["address"] = rawHTML
var funcMap = template.FuncMap{
"BasicFormatting": BasicFormatting,
"SanitizeString": html.EscapeString,
}
tmpl, err := template.Must(template.New("event").
Funcs(funcMap).
Parse(templates[typ])).
ParseFiles("static/head.html", "static/top.html", "static/column_clients.html", "static/footer.html", "static/scripts.js")
if err != nil {
// Handle error
}
if err := tmpl.ExecuteTemplate(w, "event", params); err != nil {
http.Error(w, "error rendering: "+err.Error(), 500)
return

View File

@@ -0,0 +1,22 @@
<div class="column column_clients">
<div class="title open-list">
<span class="text">Open {{.type}} in</span>
<span class="open">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 20 20">
<path fill="#FAFAFA" fill-rule="evenodd" d="M3.808.355h2.85a2.85 2.85 0 0 1 2.85 2.85v2.85a2.85 2.85 0 0 1-2.85 2.85h-2.85a2.85 2.85 0 0 1-2.85-2.85v-2.85a2.85 2.85 0 0 1 2.85-2.85Zm2.85 6.65a.95.95 0 0 0 .95-.95v-2.85a.95.95 0 0 0-.95-.95h-2.85a.95.95 0 0 0-.95.95v2.85c0 .525.425.95.95.95h2.85Zm0 3.8h-2.85a2.85 2.85 0 0 0-2.85 2.85v2.85a2.85 2.85 0 0 0 2.85 2.85h2.85a2.85 2.85 0 0 0 2.85-2.85v-2.85a2.85 2.85 0 0 0-2.85-2.85Zm0 6.65a.95.95 0 0 0 .95-.95v-2.85a.95.95 0 0 0-.95-.95h-2.85a.95.95 0 0 0-.95.95v2.85c0 .525.425.95.95.95h2.85Zm10.45-6.65h-2.85a2.85 2.85 0 0 0-2.85 2.85v2.85a2.85 2.85 0 0 0 2.85 2.85h2.85a2.85 2.85 0 0 0 2.85-2.85v-2.85a2.85 2.85 0 0 0-2.85-2.85Zm0 6.65a.95.95 0 0 0 .95-.95v-2.85a.95.95 0 0 0-.95-.95h-2.85a.95.95 0 0 0-.95.95v2.85c0 .525.425.95.95.95h2.85Zm0-17.1h-2.85a2.85 2.85 0 0 0-2.85 2.85v2.85a2.85 2.85 0 0 0 2.85 2.85h2.85a2.85 2.85 0 0 0 2.85-2.85v-2.85a2.85 2.85 0 0 0-2.85-2.85Zm0 6.65a.95.95 0 0 0 .95-.95v-2.85a.95.95 0 0 0-.95-.95h-2.85a.95.95 0 0 0-.95.95v2.85c0 .525.425.95.95.95h2.85Z" clip-rule="evenodd"/>
</svg>
</span>
<span class="close">
<svg xmlns="http://www.w3.org/2000/svg" width="31" height="16" fill="currentColor" viewBox="0 0 31 16">
<path fill="#fff" d="M30.207 3.016 16.744 14.983a1.496 1.496 0 0 1-1.974 0L1.307 3.016A1.496 1.496 0 0 1 3.28.772l12.476 11.085L28.233.772a1.496 1.496 0 1 1 1.974 2.244Z"/>
</svg>
</span>
</div>
<div class="clients_wrapper">
{{range .clients}}
<div class="btn">
<a class="client" href="{{.url}}"><span>Open {{.type}} in</span> {{.name}}</a>
</div>
{{end}}
</div>
</div>

16
static/footer.html Normal file
View File

@@ -0,0 +1,16 @@
<div class="background"></div>
<div class="footer">
Powered by <a href="https://git.fiatjaf.com/njump">njump</a>
</div>
<svg width="0" height="0" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<clipPath id="svg-shape" clipPathUnits="objectBoundingBox">
<path
transform="scale(0.005, 0.005)"
d="M100,200c43.8,0,68.2,0,84.1-15.9C200,168.2,200,143.8,200,100s0-68.2-15.9-84.1C168.2,0,143.8,0,100,0S31.8,0,15.9,15.9C0,31.8,0,56.2,0,100s0,68.2,15.9,84.1C31.8,200,56.2,200,100,200z"
/>
</clipPath>
</defs>
</svg>

51
static/head.html Normal file
View File

@@ -0,0 +1,51 @@
<head>
{{if eq .type "profile"}}
<title>Nostr Public Key {{.npub}}</title>
<meta property="og:site_name" content="{{.npub | SanitizeString}}" />
<meta property="og:title" content="{{.title | SanitizeString}}" />
{{ if .metadata.Picture }}
<meta property="og:image" content="{{.metadata.Picture | SanitizeString}}" />
<meta property="twitter:image" content="{{.proxy}}{{.metadata.Picture | SanitizeString}}" />
{{end}} {{ if .metadata.About }}
<meta property="og:description" content="{{.metadata.About | SanitizeString}}" />
{{end}}
<meta property="twitter:card" content="summary" />
{{end}}
<!----------->
{{ if eq .type "event" }}
<title>Nostr Event {{.nevent}}</title>
<meta property="og:site_name" content="{{.authorLong | SanitizeString}}" />
<meta property="og:title" content="{{.title | SanitizeString}}" />
<meta name="twitter:title" content="{{.twitterTitle | SanitizeString}}" />
<!---->
{{ if .textImageURL }}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@nostrprotocol" />
<meta property="og:image" content="{{.textImageURL | SanitizeString}}" />
<meta name="twitter:image" content="{{.textImageURL | SanitizeString}}" />
{{ else }}
<!---->
<meta property="twitter:card" content="summary" />
{{ if .image }}
<meta property="og:image" content="{{.image | SanitizeString}}" />
<meta name="twitter:image" content="{{.proxy}}{{.image | SanitizeString}}" />
{{end}} {{ if .video }}
<meta property="og:video" content="{{.video | SanitizeString}}" />
<meta property="og:video:secure_url" content="{{.video | SanitizeString}}" />
<meta property="og:video:type" content="video/{{.videoType | SanitizeString}}" />
{{end}}
<!---->
{{end}}
<meta property="og:description" content="{{.description | SanitizeString}}" />
<meta name="twitter:description" content="{{.description | SanitizeString}}" />
{{end}}
<!----------->
{{ if eq .type "address" }}
<title>Nostr Address {{.naddr | SanitizeString }}</title>
{{end}}
<!----------->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/static/styles.css?v=20230527" />
</head>

81
static/note.html Normal file
View File

@@ -0,0 +1,81 @@
<!DOCTYPE html>
<html class="theme--default">
<meta charset="UTF-8">
{{template "head.html" .}}
<body class="note">
{{template "top.html" .}}
<div class="container_wrapper">
<div class="container">
<div class="column column_content">
<div class="profile_intro">
<a href="/{{.npub | SanitizeString}}">
<div class="pic-wrapper">
<img class="pic" src="{{.metadata.Picture | SanitizeString}}" />
</div>
<div class="info-wrapper">
<div class="name">
{{.metadata.Name | SanitizeString}} <span class="display">{{.metadata.DisplayName | SanitizeString}}</span>
</div>
<div class="npub">{{.npubShort | SanitizeString}}</div>
</div>
<div class="published_at">
{{.createdAt | SanitizeString}}
</div>
</a>
</div>
<div class="field separator"></div>
<div class="field content">
{{.content | SanitizeString | BasicFormatting }}
</div>
<div class="field separator"></div>
<div class="field">
<div class="label">Author Public key</div>
{{.npub}}
</div>
<div class="field last_update">
Last update:<br/>
{{.createdAt | SanitizeString}}
</div>
<div class="field advanced-switch-wrapper">
<input type="checkbox" id="advanced-switch" class="advanced-switch" />
<label for="advanced-switch">X</label>
<label for="advanced-switch">Show more details</label>
</div>
<div class="field advanced">
<div class="label">Nevent</div>
<div>{{.nevent | SanitizeString}}</div>
</div>
<div class="field advanced boxed">
<div class="label">Event JSON</div>
<div class="json">{{.eventJSON | SanitizeString}}</div>
</div>
<div class="field separator"></div>
</div>
{{template "column_clients.html" .}}
</div>
</div>
{{template "footer.html"}}
<script>
{{template "scripts.js"}}
</script>
</body>
</html>

89
static/profile.html Normal file
View File

@@ -0,0 +1,89 @@
<!DOCTYPE html>
<html class="theme--default">
<meta charset="UTF-8">
{{template "head.html" .}}
<body class="profile">
{{template "top.html" .}}
<div class="container_wrapper">
<div class="container">
<div class="column columnA">
<div class="info-wrapper">
{{.metadata.Name | SanitizeString}} <span class="display">{{.metadata.DisplayName | SanitizeString}}</span>
</div>
<div class="pic-wrapper">
<img class="pic" src="{{.metadata.Picture | SanitizeString}}" />
</div>
<div class="last_update">
Last update:<br/>
{{.createdAt | SanitizeString}}
</div>
</div>
<div class="column column_content">
<div class="field info-wrapper">
<div class="name">
{{.metadata.Name | SanitizeString}} <span class="display">{{.metadata.DisplayName | SanitizeString}}</span>
</div>
</div>
<div class="field separator long"></div>
<div class="field">
<a href="{{.metadata.Website | SanitizeString}}">{{.metadata.Website | SanitizeString}}</a>
</div>
<div class="field">
{{.metadata.About | SanitizeString}}
</div>
<div class="field separator"></div>
<div class="field">
<div class="label">Public key</div>
{{.npub | SanitizeString}}
</div>
<div class="field">
<div class="label">NIP-05</div>
{{.metadata.NIP05 | SanitizeString}}
</div>
<div class="field">
<div class="label">LN Address</div>
{{.metadata.LUD16 | SanitizeString}}
</div>
<div class="field last_update">
Last update:<br/>
{{.createdAt | SanitizeString}}
</div>
<div class="field advanced-switch-wrapper">
<input type="checkbox" id="advanced-switch" class="advanced-switch" />
<label for="advanced-switch">X</label>
<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>
<div class="field separator"></div>
</div>
{{template "column_clients.html" .}}
</div>
</div>
{{template "footer.html"}}
<script>
{{template "scripts.js"}}
</script>
</body>
</html>

97
static/scripts.js Normal file
View File

@@ -0,0 +1,97 @@
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].parentNode.setAttribute('count', count)
clients[i].parentNode.setAttribute('title', 'Used ' + count + ' times')
clients[i].addEventListener('click', () => {
localStorage.setItem(key, count + 1)
})
counts.push([count, name, url])
}
// 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))
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]
}
}
let jsons = document.querySelectorAll('.json')
for (let i = 0; i < jsons.length; i++) {
console.log(jsons[i].innerHTML)
jsons[i].innerHTML = syntaxHighlight(jsons[i].innerHTML)
}
const shareButton = document.querySelector('.open-list')
const clients_list = document.querySelector('.column_clients')
shareButton.addEventListener('click', function () {
clients_list.classList.toggle('up')
if (clients_list.classList.contains('up')) {
document.body.classList.add('lock')
} else {
document.body.classList.remove('lock')
}
})
function updateAdvanceSwitch() {
advanced_list.forEach(element => {
if (advanceSwitch.checked) {
element.classList.add('visible')
} else {
element.classList.remove('visible')
}
})
}
const advanceSwitch = document.querySelector('.advanced-switch')
const advanced_list = document.querySelectorAll('.advanced')
advanceSwitch.addEventListener('change', function () {
updateAdvanceSwitch()
})
updateAdvanceSwitch() // Check at the page load, some browsers keep the state in cache
var url = new URL(window.location.href)
var searchParams = new URLSearchParams(url.search)
if (searchParams.has('details') && searchParams.get('details') == 'yes') {
advanceSwitch.click()
}
function syntaxHighlight(json) {
json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
return json.replace(
/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
function (match) {
var cls = 'number'
if (/^"/.test(match)) {
if (/:$/.test(match)) {
cls = 'key'
} else {
cls = 'string'
}
} else if (/true|false/.test(match)) {
cls = 'boolean'
} else if (/null/.test(match)) {
cls = 'null'
}
return '<span class="' + cls + '">' + match + '</span>'
}
)
}

657
static/styles.css Normal file
View File

@@ -0,0 +1,657 @@
html {
font-family: Helvetica, sans-serif;
font-size: 19px;
font-weight: 300;
}
@media (max-width: 580px) {
html {
font-size: 18px;
}
}
body {
margin: 0;
margin-bottom: 4rem;
}
.theme--default body {
color: #373737;
}
.theme--dark body {
color: #FAFAFA;
}
@media (max-width: 580px) {
body.lock {
overflow: hidden;
}
}
.theme--default a {
color: #373737;
}
.theme--dark a {
color: #FAFAFA;
}
.background {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
overflow: hidden;
z-index: -1;
}
.theme--default .background {
background: #FFFFFF;
}
.theme--dark .background {
background: #1e1e1e;
}
.background::after {
content: "";
position: absolute;
width: 140%;
height: 100%;
transform: rotate(-20deg);
transform-origin: bottom;
bottom: -40%;
}
.theme--default .background::after {
background: #FFFFFF;
}
.theme--dark .background::after {
background: #181818;
}
@media (max-width: 580px) {
.background::after {
width: 200%;
bottom: -40%;
}
}
.theme-toggle {
position: fixed;
top: 1rem;
right: 1rem;
width: 1rem;
height: 1rem;
border-radius: 50%;
cursor: pointer;
}
.theme--default .theme-toggle {
color: #F3F3F3;
}
.theme--dark .theme-toggle {
color: #9a9a9a;
}
@media (max-width: 580px) {
.theme-toggle {
position: relative;
float: right;
top: 0;
right: 0;
}
}
.sun {
display: none;
}
.moon {
display: block;
}
.theme--dark .sun {
display: block;
}
.theme--dark .moon {
display: none;
}
.top {
display: flex;
justify-content: center;
align-items: center;
margin: 1rem 0;
justify-content: left;
}
@media (max-width: 580px) {
.top {
display: block;
width: 90%;
margin: 1rem auto 1rem auto;
font-size: 0.8rem;
}
}
.theme--default .top span {
color: #E32A6D;
}
.theme--dark .top span {
color: #E32A6D;
}
.pic-wrapper {
max-width: 100%;
overflow: hidden;
}
@media (max-width: 580px) {
.pic-wrapper {
flex-basis: 40%;
}
}
.pic-wrapper img.pic {
max-width: 100%;
width: 100%;
height: auto;
-webkit-clip-path: url(#svg-shape);
-moz-clip-path: url(#svg-shape);
-o-clip-path: url(#svg-shape);
-ms-clip-path: url(#svg-shape);
clip-path: url(#svg-shape);
}
.container_wrapper {
display: flex;
justify-content: center;
align-items: center;
}
@media (max-width: 580px) {
.container_wrapper {
display: block;
padding: 0 1rem;
margin: 0 auto;
}
}
.nostr_link {
flex-basis: 15%;
text-align: right;
text-decoration: none;
}
.container {
display: flex;
width: 70%;
justify-content: space-between;
gap: 4.8vw;
}
@media (max-width: 580px) {
.container {
display: block;
width: 100%;
}
}
.container .columnA {
flex-basis: 25%;
margin-top: 2rem;
}
@media (max-width: 580px) {
.container .columnA {
display: flex;
align-items: center;
margin-top: 0rem;
}
}
.container .columnA .info-wrapper {
display: none;
}
@media (max-width: 580px) {
.container .columnA .info-wrapper {
display: block;
flex-basis: 64%;
max-width: 64%;
overflow: hidden;
font-size: 1.6rem;
}
.container .columnA .info-wrapper .display {
display: block;
font-size: 1.2rem;
}
.theme--default .container .columnA .info-wrapper .display {
color: #C9C9C9;
}
.theme--dark .container .columnA .info-wrapper .display {
color: #C9C9C9;
}
}
.container .columnA .last_update {
font-size: 0.8em;
margin-top: 0.5rem;
text-align: center;
}
.theme--default .container .columnA .last_update {
color: #C9C9C9;
}
.theme--dark .container .columnA .last_update {
color: #C9C9C9;
}
@media (max-width: 580px) {
.container .columnA .last_update {
display: none;
}
}
.container .column_content {
flex-grow: 0;
flex-shrink: 0;
word-wrap: break-word;
margin-right: 1vw;
}
.container .column_content .info-wrapper {
font-size: 1.6rem;
}
.theme--default .container .column_content .info-wrapper .display {
color: #C9C9C9;
}
.theme--dark .container .column_content .info-wrapper .display {
color: #C9C9C9;
}
.container .column_content .info-wrapper .npub {
font-size: 1rem;
}
.theme--default .container .column_content .info-wrapper .npub {
color: #C9C9C9;
}
.theme--dark .container .column_content .info-wrapper .npub {
color: #C9C9C9;
}
@media (max-width: 580px) {
.container .column_content .info-wrapper {
display: none;
}
}
.container .column_content .separator {
height: 6px;
width: 30%;
margin-left: -0.6rem;
}
.theme--default .container .column_content .separator {
background: #F3F3F3;
}
.theme--dark .container .column_content .separator {
background: #232323;
}
.container .column_content .separator.long {
width: 50%;
}
@media (max-width: 580px) {
.container .column_content .separator {
margin-left: -1rem;
}
}
.container .column_content .field {
margin-bottom: 1.5rem;
}
.container .column_content .field .label {
font-size: 0.8rem;
}
.theme--default .container .column_content .field .label {
color: #E32A6D;
}
.theme--dark .container .column_content .field .label {
color: #E32A6D;
}
.container .column_content .field.advanced {
display: none;
}
.container .column_content .field.advanced.visible {
display: block;
}
.container .column_content .field.advanced.boxed {
padding: 0 1rem 1rem;
margin-left: -1rem;
margin-right: -1rem;
}
.theme--default .container .column_content .field.advanced.boxed {
background: #F3F3F3;
}
.theme--dark .container .column_content .field.advanced.boxed {
background: #131313;
}
.container .column_content .field.advanced.boxed .label {
padding: 0.2rem 1rem;
margin: 0 -1rem;
}
.theme--default .container .column_content .field.advanced.boxed .label {
color: #373737;
}
.theme--dark .container .column_content .field.advanced.boxed .label {
color: #9a9a9a;
}
.theme--default .container .column_content .field.advanced.boxed .label {
background: #C9C9C9;
}
.theme--dark .container .column_content .field.advanced.boxed .label {
background: #191919;
}
.container .column_content .field.advanced-switch-wrapper {
display: flex;
align-items: center;
}
.container .column_content .field.advanced-switch-wrapper input[type=checkbox] {
height: 0;
width: 0;
visibility: hidden;
display: none;
}
.container .column_content .field.advanced-switch-wrapper label:first-of-type {
cursor: pointer;
text-indent: -9999px;
width: 2.6rem;
height: 1.2rem;
background: #3d3d3d;
display: inline-block;
border-radius: 100px;
position: relative;
margin-right: 0.5rem;
}
@media (max-width: 580px) {
.container .column_content .field.advanced-switch-wrapper label:first-of-type {
width: 3rem;
height: 1.4rem;
}
}
.container .column_content .field.advanced-switch-wrapper label:first-of-type:after {
content: "";
position: absolute;
top: 2px;
left: 2px;
width: 1rem;
height: 1rem;
background: #fff;
border-radius: 1rem;
transition: 0.2s;
}
@media (max-width: 580px) {
.container .column_content .field.advanced-switch-wrapper label:first-of-type:after {
width: 1.2rem;
height: 1.2rem;
}
}
.theme--default .container .column_content .field.advanced-switch-wrapper input:checked + label {
background: #E32A6D;
}
.theme--dark .container .column_content .field.advanced-switch-wrapper input:checked + label {
background: #E32A6D;
}
.container .column_content .field.advanced-switch-wrapper input:checked + label:first-of-type:after {
left: calc(100% - 2px);
transform: translateX(-100%);
}
.container .column_content .field.advanced-switch-wrapper label:first-of-type:active:after {
width: 2rem;
}
.container .column_content .field.content {
line-height: 1.4rem;
}
.container .column_content .field.content img {
max-width: 100%;
margin: 1rem 0;
}
.container .column_content .field .json, .container .column_content .field .data {
white-space: pre-wrap;
word-break: break-all;
margin-top: 1rem;
}
.container .column_content .field .json .key, .container .column_content .field .data .key {
display: inline-block;
margin-top: 0.5rem;
}
.theme--default .container .column_content .field .json .key, .theme--default .container .column_content .field .data .key {
color: #E32A6D;
}
.theme--dark .container .column_content .field .json .key, .theme--dark .container .column_content .field .data .key {
color: #E32A6D;
}
.theme--default .container .column_content .field .json .string, .theme--default .container .column_content .field .data .string {
color: #373737;
}
.theme--dark .container .column_content .field .json .string, .theme--dark .container .column_content .field .data .string {
color: #FAFAFA;
}
.container .column_content .field .json .number, .container .column_content .field .data .number {
color: darkorange;
}
.theme--default .container .column_content .field .json .boolean, .theme--default .container .column_content .field .data .boolean {
color: #373737;
}
.theme--dark .container .column_content .field .json .boolean, .theme--dark .container .column_content .field .data .boolean {
color: #FAFAFA;
}
.theme--default .container .column_content .field .json .null, .theme--default .container .column_content .field .data .null {
color: #373737;
}
.theme--dark .container .column_content .field .json .null, .theme--dark .container .column_content .field .data .null {
color: #FAFAFA;
}
.container .column_content .field.last_update {
display: none;
}
@media (max-width: 580px) {
.container .column_content .field.last_update {
display: block;
font-size: 0.8em;
}
.theme--default .container .column_content .field.last_update {
color: #C9C9C9;
}
.theme--dark .container .column_content .field.last_update {
color: #C9C9C9;
}
}
.container .column_clients {
flex-basis: 25%;
margin-top: 2rem;
}
@media (max-width: 580px) {
.container .column_clients {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
transition: all 500ms ease-in-out;
}
.container .column_clients.up .btn {
display: block;
}
.container .column_clients.up .title span.open {
display: none;
}
}
.container .column_clients .title {
font-size: 0.8rem;
margin-bottom: 1rem;
text-align: center;
}
.container .column_clients .title span.open, .container .column_clients .title span.close {
display: none;
}
@media (max-width: 580px) {
.container .column_clients .title {
position: absolute;
top: 0;
right: 0;
width: 2.6rem;
height: 2.6rem;
border-left: 1px solid #bc1150;
}
.container .column_clients .title span.text {
display: none;
}
.container .column_clients .title span.open, .container .column_clients .title span.close {
display: inline;
}
.container .column_clients .title span.open svg, .container .column_clients .title span.close svg {
width: 50%;
height: 50%;
margin: 28% auto auto auto;
display: block;
}
}
.container .column_clients .btn {
display: flex;
align-items: center;
margin-bottom: 0.8rem;
}
@media (max-width: 580px) {
.container .column_clients .btn {
display: none;
margin-bottom: 0;
}
}
.container .column_clients .btn a {
flex-basis: 100%;
padding: 0.4rem;
text-align: center;
font-size: 0.9rem;
color: #FFFFFF;
background-color: #3d3d3d;
border-bottom: 1px solid #131313;
text-decoration: none;
border-radius: 8px;
}
@media (max-width: 580px) {
.container .column_clients .btn a {
display: block;
padding: 0.8rem;
border-radius: 0px;
font-weight: 400;
text-align: left;
}
}
.container .column_clients .btn a span {
display: none;
}
@media (max-width: 580px) {
.container .column_clients .btn a span {
display: inline;
color: #FFFFFF;
}
}
.container .column_clients .btn a:hover {
background: #373737;
}
@media (max-width: 580px) {
.container .column_clients .btn:first-of-type {
display: block;
}
}
.container .column_clients .btn:first-of-type a {
border-bottom: none;
}
.theme--default .container .column_clients .btn:first-of-type a {
background: #E32A6D;
}
.theme--dark .container .column_clients .btn:first-of-type a {
background: #E32A6D;
}
@media (max-width: 580px) {
.container .column_clients .btn:first-of-type a {
border-radius: 8px 8px 0 0;
}
}
.theme--default .container .column_clients .btn:first-of-type a:hover {
background: #bc1150;
}
.theme--dark .container .column_clients .btn:first-of-type a:hover {
background: #bc1150;
}
.container .column_clients .btn span {
flex-basis: 20%;
margin-left: 0.4rem;
color: #FFFFFF;
}
@media (max-width: 580px) {
.container .column_clients .btn span {
display: none;
}
}
body.profile .column_content {
flex-basis: 50%;
max-width: 50%;
}
@media (max-width: 580px) {
body.profile .column_content {
flex-basis: 100%;
max-width: 100%;
margin-right: 0;
}
}
body.note .column_content {
flex-basis: 70%;
max-width: 70%;
}
@media (max-width: 580px) {
body.note .column_content {
flex-basis: 100%;
max-width: 100%;
margin-right: 0;
}
}
body.note .column_content .profile_intro {
display: flex;
max-width: 100%;
align-items: center;
}
body.note .column_content .profile_intro a {
display: inherit;
align-items: inherit;
margin: 1rem 0 1rem 0;
text-decoration: none;
flex-wrap: wrap;
}
@media (max-width: 580px) {
body.note .column_content .profile_intro a {
margin-top: 0rem;
margin-bottom: -0.5rem;
}
}
body.note .column_content .profile_intro .info-wrapper {
flex-basis: 80%;
}
@media (max-width: 580px) {
body.note .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 {
display: block-inline;
font-size: 0.9rem;
}
}
body.note .column_content .profile_intro .pic-wrapper {
flex-basis: 16%;
margin-right: 1rem;
}
@media (max-width: 580px) {
body.note .column_content .profile_intro .pic-wrapper {
margin-right: 0.5rem;
}
}
body.note .column_content .profile_intro .published_at {
flex-grow: 1;
text-align: right;
align-self: end;
font-size: 0.8rem;
}
.theme--default body.note .column_content .profile_intro .published_at {
color: #9a9a9a;
}
.theme--dark body.note .column_content .profile_intro .published_at {
color: #F3F3F3;
}
@media (max-width: 580px) {
body.note .column_content .profile_intro .published_at {
padding-top: 0.5rem;
}
}
.footer {
font-size: 0.8rem;
text-align: center;
}
/*# sourceMappingURL=styles.css.map */

1
static/styles.css.map Normal file
View File

@@ -0,0 +1 @@
{"version":3,"sourceRoot":"","sources":["styles.scss"],"names":[],"mappings":";AAoFA;EACC;EACA;EACA;;AACA;EAJD;IAKE;;;;AAGF;EAIC;EACA;;AA9BG;EA2BA;;AA3BA;EA2BA;;AAKF;EADD;IAEE;;;;AAjCC;EAuCA;;AAvCA;EAuCA;;;AAGJ;EACC;EACA;EACA;EACA;EACA;EAIA;EACA;;AApDG;EAiDF;;AAjDE;EAiDF;;AAID;EACC;EACA;EAIA;EACA;EACA;EACA;EACA;;AA/DE;EAyDD;;AAzDC;EAyDD;;AAOD;EAXD;IAYE;IACA;;;;AAKH;EACC;EACA;EACA;EACA;EACA;EAKA;EACA;;AAlFG;EA8EF;;AA9EE;EA8EF;;AAKD;EAZD;IAaE;IACE;IACA;IACA;;;;AAIJ;EACC;;;AAED;EACC;;;AAIA;EACC;;AAED;EACC;;;AAIF;EACC;EACA;EACA;EACA;EACA;;AACA;EAND;IAOE;IACA;IACA;IACA;;;AArHE;EAyHD;;AAzHC;EAyHD;;;AAKH;EACC;EACA;;AACA;EAHD;IAIE;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAIF;EACC;EACA;EACA;;AACC;EAJF;IAKE;IACE;IACF;;;;AAGF;EACC;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;;AAEC;EANF;IAOE;IACE;;;AAGH;EACC;EACA;;AACA;EAHD;IAIE;IACA;IACA;;;AAED;EACC;;AACA;EAFD;IAGE;IACA;IACA;IACA;IACA;;EACA;IACC;IACA;;EA7LD;IA+LE;;EA/LF;IA+LE;;;AAKJ;EACC;EACA;EACA;;AAvMC;EAyMA;;AAzMA;EAyMA;;AAED;EAPD;IAQE;;;AAIH;EAEC;EACA;EACA;EACA;;AAEA;EACC;;AAxNC;EA2NC;;AA3ND;EA2NC;;AAGF;EACC;;AA/NA;EAiOC;;AAjOD;EAiOC;;AAGF;EAbD;IAcE;;;AAGF;EACC;EACA;EACA;;AA3OC;EA6OA;;AA7OA;EA6OA;;AAED;EACC;;AAED;EAVD;IAWE;;;AAGF;EACC;;AACA;EACC;;AAzPA;EA2PC;;AA3PD;EA2PC;;AAGF;EACC;;AACA;EACC;;AAED;EACC;EACA;EACA;;AAtQD;EAwQE;;AAxQF;EAwQE;;AAED;EACC;EACA;;AA5QF;EA8QG;;AA9QH;EA8QG;;AA9QH;EAiRG;;AAjRH;EAiRG;;AAKJ;EACC;EACA;;AAEA;EACC;EACA;EACA;EACA;;AAGD;EACC;EACA;EACA;EACA;EACA,YAnWS;EAoWT;EACA;EACA;EACA;;AACA;EAVD;IAWE;IACA;;;AAIF;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AACA;EAVD;IAWE;IACA;;;AA7TF;EAmUE;;AAnUF;EAmUE;;AAIF;EACC;EACA;;AAGD;EACC;;AAIF;EACC;;AACA;EACC;EACA;;AAGF;EACC;EACA;EACA;;AACA;EACC;EACA;;AA9VD;EAgWE;;AAhWF;EAgWE;;AAhWF;EAqWI;;AArWJ;EAqWI;;AAGJ;EAAU;;AAxWV;EA2WI;;AA3WJ;EA2WI;;AA3WJ;EAgXI;;AAhXJ;EAgXI;;AAIL;EACC;;AACA;EAFD;IAGE;IACA;;EAxXD;IA0XE;;EA1XF;IA0XE;;;AAQL;EACC;EACA;;AACA;EAHD;IAIE;IAEA;IACA;IACA;IACA;;EAEC;IACC;;EAED;IACC;;;AAIH;EACC;EACA;EACA;;AACA;EACC;;AAED;EAPD;IAQE;IACA;IACA;IACA;IACA;IACA;;EACA;IACC;;EAED;IACC;;EACA;IACC;IACA;IACA;IACA;;;AAKJ;EACC;EACA;EACA;;AACA;EAJD;IAKE;IACA;;;AAED;EACC;EACA;EACA;EACA;EACA,OAhgBU;EAigBV,kBA5fU;EA6fV;EACA;EACA;;AACA;EAVD;IAWE;IACA;IACA;IACA;IACA;;;AAED;EACC;;AACA;EAFD;IAGE;IACA,OAhhBQ;;;AAmhBV;EACC,YA9gBS;;AAkhBV;EADD;IAEE;;;AAED;EAIC;;AA7dD;EA2dG;;AA3dH;EA2dG;;AAGF;EALD;IAME;;;AA/dF;EAmeG;;AAneH;EAmeG;;AAKJ;EACC;EACA;EACA,OA7iBU;;AA8iBV;EAJD;IAKE;;;;AASJ;EACC;EACA;;AACA;EAHD;IAIE;IACA;IACA;;;;AAMF;EACC;EACA;;AACA;EAHD;IAIE;IACA;IACA;;;AAED;EACC;EACA;EACA;;AACA;EACC;EACA;EACA;EACA;EACA;;AACA;EAND;IAOE;IACA;;;AAGF;EACC;;AACA;EAFD;IAGE;;;AAGA;EADD;IAEE;IACA;;;AAIH;EACC;EACA;;AACA;EAHD;IAIE;;;AAGF;EACC;EACA;EACA;EACA;;AAhjBA;EAkjBC;;AAljBD;EAkjBC;;AAED;EARD;IASE;;;;AAOL;EACC;EACA","file":"styles.css"}

643
static/styles.scss Normal file
View File

@@ -0,0 +1,643 @@
$color-base1: #FFFFFF;
$color-base2: #FAFAFA;
$color-base3: #F3F3F3;
$color-base4: #C9C9C9;
$color-base5: #9a9a9a;
$color-base6: #3d3d3d;
$color-base7: #373737;
$color-accent1: #E32A6D;
$color-accent2: #bc1150;
$themes: (
default: (
base1: $color-base1,
base2: $color-base2,
base3: $color-base3,
base4: $color-base4,
base5: $color-base5,
base6: $color-base6,
base7: $color-base7,
accent1: $color-accent1,
accent2: $color-accent2,
bg-up: $color-base1,
bg-down: $color-base1,
boxed-title: $color-base7,
boxed-bg-title: $color-base4,
boxed-bg: $color-base3,
separator: $color-base3,
),
dark: (
base1: $color-base7,
base2: $color-base6,
base3: $color-base5,
base4: $color-base4,
base5: $color-base3,
base6: $color-base2,
base7: $color-base2,
accent1: $color-accent1,
accent2: $color-accent2,
bg-up: darken($color-base6, 12%),
bg-down: darken($color-base7, 12%),
boxed-title: $color-base5,
boxed-bg-title: darken($color-base6, 14%),
boxed-bg: darken($color-base7, 14%),
separator: darken($color-base7, 8%),
),
);
$base1: 'base1';
$base2: 'base2';
$base3: 'base3';
$base4: 'base4';
$base5: 'base5';
$base6: 'base6';
$base7: 'base7';
$accent1: 'accent1';
$accent2: 'accent2';
$bg-up: 'bg-up';
$bg-down: 'bg-down';
$boxed-title: 'boxed-title';
$boxed-bg-title: 'boxed-bg-title';
$boxed-bg: 'boxed-bg';
$separator: 'separator';
$theme-map: null;
@mixin themed() {
@each $theme, $map in $themes {
.theme--#{$theme} & {
$theme-map: () !global;
@each $key, $submap in $map {
$value: map-get(map-get($themes, $theme), '#{$key}');
$theme-map: map-merge($theme-map, ($key: $value)) !global;
}
@content;
$theme-map: null !global;
}
}
}
@function t($key) {
@return map-get($theme-map, $key);
}
/*# sourceMappingURL=styles.css.map */
html {
font-family: Helvetica, sans-serif;
font-size: 19px;
font-weight: 300;
@media (max-width: 580px) {
font-size: 18px;
}
}
body {
@include themed() {
color: t($base7);
}
margin: 0;
margin-bottom: 4rem;
&.lock {
@media (max-width: 580px) {
overflow: hidden;
}
}
}
a {
@include themed() {
color: t($base7);
}
}
.background {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
@include themed() {
background: t($bg-up);
}
overflow: hidden;
z-index: -1;
&::after {
content: "";
position: absolute;
@include themed() {
background: t($bg-down);
}
width: 140%;
height: 100%;
transform: rotate(-20deg);
transform-origin: bottom;
bottom: -40%;
@media (max-width: 580px) {
width: 200%;
bottom: -40%;
}
}
}
.theme-toggle {
position: fixed;
top: 1rem;
right: 1rem;
width: 1rem;
height: 1rem;
@include themed() {
color: t($base3);
// background: t($base3);
}
border-radius: 50%;
cursor: pointer;
@media (max-width: 580px) {
position: relative;
float: right;
top: 0;
right: 0;
}
}
.sun {
display: none
}
.moon {
display: block
}
.theme--dark {
.sun {
display: block
}
.moon {
display: none
}
}
.top {
display: flex;
justify-content: center;
align-items: center;
margin: 1rem 0;
justify-content: left;
@media (max-width: 580px) {
display: block;
width: 90%;
margin: 1rem auto 1rem auto;
font-size: 0.8rem;
}
span {
@include themed() {
color: t($accent1);
}
}
}
.pic-wrapper {
max-width: 100%;
overflow: hidden;
@media (max-width: 580px) {
flex-basis: 40%;
}
img.pic {
max-width: 100%;
width: 100%;
height: auto;
-webkit-clip-path: url(#svg-shape);
-moz-clip-path: url(#svg-shape);
-o-clip-path: url(#svg-shape);
-ms-clip-path: url(#svg-shape);
clip-path: url(#svg-shape);
}
}
.container_wrapper {
display: flex;
justify-content: center;
align-items: center;
@media (max-width: 580px) {
display: block;
padding: 0 1rem;
margin: 0 auto;
}
}
.nostr_link {
flex-basis: 15%;
text-align: right;
text-decoration: none;
}
.container {
display: flex;
width: 70%;
justify-content: space-between;
gap: 4.8vw;
@media (max-width: 580px) {
display: block;
width: 100%;
}
.columnA {
flex-basis: 25%;
margin-top: 2rem;
@media (max-width: 580px) {
display: flex;
align-items: center;
margin-top: 0rem;
}
.info-wrapper {
display: none;
@media (max-width: 580px) {
display: block;
flex-basis: 64%;
max-width: 64%;
overflow: hidden;
font-size: 1.6rem;
.display {
display: block;
font-size: 1.2rem;
@include themed() {
color: t($base4);
}
}
}
}
.last_update {
font-size: 0.8em;
margin-top: 0.5rem;
text-align: center;
@include themed() {
color: t($base4);
}
@media (max-width: 580px) {
display: none;
}
}
}
.column_content {
flex-grow: 0;
flex-shrink: 0;
word-wrap: break-word;
margin-right: 1vw;
.info-wrapper {
font-size: 1.6rem;
.display {
@include themed() {
color: t($base4);
}
}
.npub {
font-size: 1rem;
@include themed() {
color: t($base4);
}
}
@media (max-width: 580px) {
display: none;
}
}
.separator {
height: 6px;
width: 30%;
margin-left: -0.6rem;
@include themed() {
background: t($separator);
}
&.long {
width: 50%;
}
@media (max-width: 580px) {
margin-left: -1rem;
}
}
.field {
margin-bottom: 1.5rem;
.label {
font-size: 0.8rem;
@include themed() {
color: t($accent1);
}
}
&.advanced {
display: none;
&.visible {
display: block;
}
&.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() {
color: t($boxed-title);
}
@include themed() {
background: t($boxed-bg-title);
}
}
}
}
&.advanced-switch-wrapper {
display: flex;
align-items: center;
input[type=checkbox]{
height: 0;
width: 0;
visibility: hidden;
display: none;
}
label:first-of-type {
cursor: pointer;
text-indent: -9999px;
width: 2.6rem;
height: 1.2rem;
background: $color-base6;
display: inline-block;
border-radius: 100px;
position: relative;
margin-right: 0.5rem;
@media (max-width: 580px) {
width: 3rem;
height: 1.4rem;
}
}
label:first-of-type:after {
content: '';
position: absolute;
top: 2px;
left: 2px;
width: 1rem;
height: 1rem;
background: #fff;
border-radius: 1rem;
transition: 0.2s;
@media (max-width: 580px) {
width: 1.2rem;
height: 1.2rem;
}
}
input:checked + label {
@include themed() {
background: t($accent1);
}
}
input:checked + label:first-of-type:after {
left: calc(100% - 2px);
transform: translateX(-100%);
}
label:first-of-type:active:after {
width: 2rem;
}
}
&.content{
line-height: 1.4rem;
img {
max-width: 100%;
margin: 1rem 0;
}
}
.json, .data {
white-space: pre-wrap;
word-break: break-all;
margin-top: 1rem;
.key {
display: inline-block;
margin-top: 0.5rem;
@include themed() {
color: t($accent1);
}
}
.string {
@include themed() {
color: t($base7);
}
}
.number { color: darkorange; }
.boolean {
@include themed() {
color: t($base7);
}
}
.null {
@include themed() {
color: t($base7);
}
}
}
&.last_update {
display: none;
@media (max-width: 580px) {
display: block;
font-size: 0.8em;
@include themed() {
color: t($base4);
}
}
}
}
}
.column_clients {
flex-basis: 25%;
margin-top: 2rem;
@media (max-width: 580px) {
position: fixed;
// top: calc(100vh - 4.6rem);
bottom: 0;
left: 0;
width: 100%;
transition: all 500ms ease-in-out;
&.up {
.btn {
display: block;
}
.title span.open {
display: none;
}
}
}
.title {
font-size: 0.8rem;
margin-bottom: 1rem;
text-align: center;
span.open, span.close {
display: none;
}
@media (max-width: 580px) {
position: absolute;
top: 0;
right: 0;
width: 2.6rem;
height: 2.6rem;
border-left: 1px solid #bc1150;
span.text {
display: none;
}
span.open, span.close {
display: inline;
svg {
width: 50%;
height: 50%;
margin: 28% auto auto auto;
display: block;
}
}
}
}
.btn {
display: flex;
align-items: center;
margin-bottom: 0.8rem;
@media (max-width: 580px) {
display: none;
margin-bottom: 0;
}
a {
flex-basis: 100%;
padding: 0.4rem;
text-align: center;
font-size: 0.9rem;
color: $color-base1;
background-color: $color-base6;
border-bottom: 1px solid darken($color-base7, 14%);
text-decoration: none;
border-radius: 8px;
@media (max-width: 580px) {
display: block;
padding: 0.8rem;
border-radius: 0px;
font-weight: 400;
text-align: left;
}
span {
display: none;
@media (max-width: 580px) {
display: inline;
color: $color-base1;
}
}
&:hover {
background: $color-base7;
}
}
&:first-of-type {
@media (max-width: 580px) {
display: block;
}
a {
@include themed() {
background: t($accent1);
}
border-bottom: none;
@media (max-width: 580px) {
border-radius: 8px 8px 0 0;
}
&:hover {
@include themed() {
background: t($accent2);
}
}
}
}
span {
flex-basis: 20%;
margin-left: 0.4rem;
color: $color-base1;
@media (max-width: 580px) {
display: none;
}
}
}
}
}
body.profile {
.column_content {
flex-basis: 50%;
max-width: 50%;
@media (max-width: 580px) {
flex-basis: 100%;
max-width: 100%;
margin-right: 0;
}
}
}
body.note {
.column_content {
flex-basis: 70%;
max-width: 70%;
@media (max-width: 580px) {
flex-basis: 100%;
max-width: 100%;
margin-right: 0;
}
.profile_intro {
display: flex;
max-width: 100%;
align-items: center;
a {
display: inherit;
align-items: inherit;
margin: 1rem 0 1rem 0;
text-decoration: none;
flex-wrap: wrap;
@media (max-width: 580px) {
margin-top: -0rem;
margin-bottom: -0.5rem;
}
}
.info-wrapper {
flex-basis: 80%;
@media (max-width: 580px) {
display: block;
}
.name, .npub {
@media (max-width: 580px) {
display: block-inline;
font-size: 0.9rem;
}
}
}
.pic-wrapper {
flex-basis: 16%;
margin-right: 1rem;
@media (max-width: 580px) {
margin-right: 0.5rem;
}
}
.published_at {
flex-grow: 1;
text-align: right;
align-self: end;
font-size: 0.8rem;
@include themed() {
color: t($base5);
}
@media (max-width: 580px) {
padding-top: 0.5rem;
}
}
}
}
}
.footer {
font-size: 0.8rem;
text-align: center;
}

43
static/top.html Normal file
View File

@@ -0,0 +1,43 @@
<div class="top">
<a href="https://nostr.com" class="nostr_link">What is <span>nostr</span>?</a>
<div class="theme-toggle">
<svg aria-hidden="true" data-prefix="fas" class="moon" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M283.211 512c78.962 0 151.079-35.925 198.857-94.792 7.068-8.708-.639-21.43-11.562-19.35-124.203 23.654-238.262-71.576-238.262-196.954 0-72.222 38.662-138.635 101.498-174.394 9.686-5.512 7.25-20.197-3.756-22.23A258.156 258.156 0 0 0 283.211 0c-141.309 0-256 114.511-256 256 0 141.309 114.511 256 256 256z"></path></svg>
<svg aria-hidden="true" data-prefix="fas" class="sun" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 160c-52.9 0-96 43.1-96 96s43.1 96 96 96 96-43.1 96-96-43.1-96-96-96zm246.4 80.5l-94.7-47.3 33.5-100.4c4.5-13.6-8.4-26.5-21.9-21.9l-100.4 33.5-47.4-94.8c-6.4-12.8-24.6-12.8-31 0l-47.3 94.7L92.7 70.8c-13.6-4.5-26.5 8.4-21.9 21.9l33.5 100.4-94.7 47.4c-12.8 6.4-12.8 24.6 0 31l94.7 47.3-33.5 100.5c-4.5 13.6 8.4 26.5 21.9 21.9l100.4-33.5 47.3 94.7c6.4 12.8 24.6 12.8 31 0l47.3-94.7 100.4 33.5c13.6 4.5 26.5-8.4 21.9-21.9l-33.5-100.4 94.7-47.3c13-6.5 13-24.7.2-31.1zm-155.9 106c-49.9 49.9-131.1 49.9-181 0-49.9-49.9-49.9-131.1 0-181 49.9-49.9 131.1-49.9 181 0 49.9 49.9 49.9 131.1 0 181z"></path></svg>
</div>
</div>
<script>
function toggleTheme() {
const htmlElement = document.documentElement;
const isDarkMode = htmlElement.classList.contains('theme--dark');
// Toggle theme classes on the html element
htmlElement.classList.toggle('theme--dark', !isDarkMode);
htmlElement.classList.toggle('theme--default', isDarkMode);
// Save preference in a cookie that expires at the end of the session
const expiryDate = new Date();
document.cookie = `themePreference=${isDarkMode ? 'light' : 'dark'};`;
}
// Check if the OS prefers dark mode
const prefersDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
// Load preference from the cookie if it exists
const themePreference = document.cookie.replace(/(?:(?:^|.*;\s*)themePreference\s*\=\s*([^;]*).*$)|^.*$/, '$1');
const isDarkMode = themePreference === 'dark' || (themePreference === '' && prefersDarkMode);
// Apply theme based on the preference
const htmlElement = document.documentElement;
htmlElement.classList.toggle('theme--dark', isDarkMode);
htmlElement.classList.toggle('theme--default', !isDarkMode);
// Add click event listener to the theme toggle button
const themeToggleButton = document.querySelector('.theme-toggle');
themeToggleButton.addEventListener('click', toggleTheme);
// const prefersDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
// if (prefersDarkMode) {
// document.documentElement.classList.add('theme--dark');
// }
</script>

View File

@@ -2,7 +2,9 @@ package main
import (
"encoding/json"
"fmt"
"net/http"
"regexp"
"strings"
"github.com/nbd-wtf/go-nostr"
@@ -38,28 +40,28 @@ func generateClientList(code string, event *nostr.Event) []map[string]string {
if strings.HasPrefix(code, "nevent") || strings.HasPrefix(code, "note") {
return []map[string]string{
{"name": "native client", "url": "nostr:" + code},
{"name": "snort", "url": "https://snort.social/e/" + code},
{"name": "coracle", "url": "https://coracle.social/" + code},
{"name": "satellite", "url": "https://satellite.earth/thread/" + event.ID},
{"name": "iris", "url": "https://iris.to/" + code},
{"name": "yosup", "url": "https://yosup.app/thread/" + event.ID},
{"name": "nostr.band", "url": "https://nostr.band/" + code},
{"name": "primal", "url": "https://primal.net/thread/" + event.ID},
{"name": "nostribe", "url": "https://www.nostribe.com/post/" + event.ID},
{"name": "nostrid", "url": "https://web.nostrid.app/note/" + event.ID},
{"name": "Snort", "url": "https://Snort.social/e/" + code},
{"name": "Coracle", "url": "https://coracle.social/" + code},
{"name": "Satellite", "url": "https://satellite.earth/thread/" + event.ID},
{"name": "Iris", "url": "https://iris.to/" + code},
{"name": "Yosup", "url": "https://yosup.app/thread/" + event.ID},
{"name": "Nostr.band", "url": "https://nostr.band/" + code},
{"name": "Primal", "url": "https://primal.net/thread/" + event.ID},
{"name": "Nostribe", "url": "https://www.nostribe.com/post/" + event.ID},
{"name": "Nostrid", "url": "https://web.nostrid.app/note/" + event.ID},
}
} else if strings.HasPrefix(code, "npub") || strings.HasPrefix(code, "nprofile") {
return []map[string]string{
{"name": "native client", "url": "nostr:" + code},
{"name": "snort", "url": "https://snort.social/p/" + code},
{"name": "coracle", "url": "https://coracle.social/" + code},
{"name": "satellite", "url": "https://satellite.earth/@" + code},
{"name": "iris", "url": "https://iris.to/" + code},
{"name": "yosup", "url": "https://yosup.app/profile/" + event.PubKey},
{"name": "nostr.band", "url": "https://nostr.band/" + code},
{"name": "primal", "url": "https://primal.net/profile/" + event.PubKey},
{"name": "nostribe", "url": "https://www.nostribe.com/profile/" + event.PubKey},
{"name": "nostrid", "url": "https://web.nostrid.app/account/" + event.PubKey},
{"name": "Snort", "url": "https://snort.social/p/" + code},
{"name": "Coracle", "url": "https://coracle.social/" + code},
{"name": "Satellite", "url": "https://satellite.earth/@" + code},
{"name": "Iris", "url": "https://iris.to/" + code},
{"name": "Yosup", "url": "https://yosup.app/profile/" + event.PubKey},
{"name": "Nostr.band", "url": "https://nostr.band/" + code},
{"name": "Primal", "url": "https://primal.net/profile/" + event.PubKey},
{"name": "Nostribe", "url": "https://www.nostribe.com/profile/" + event.PubKey},
{"name": "Nostrid", "url": "https://web.nostrid.app/account/" + event.PubKey},
}
} else if strings.HasPrefix(code, "naddr") {
return []map[string]string{
@@ -114,3 +116,53 @@ func getPreviewStyle(r *http.Request) string {
return "unknown"
}
}
func BasicFormatting(input string) string {
lines := strings.Split(input, "\n")
var processedLines []string
for _, line := range lines {
processedLine := ReplaceURLsWithTags(line)
processedLines = append(processedLines, processedLine)
}
return strings.Join(processedLines, "<br/>")
}
func ReplaceURLsWithTags(line string) string {
// Match and replace image URLs with <img> tags
imageExtensions := []string{".jpg", ".jpeg", ".png", ".webp", ".gif"}
for _, extension := range imageExtensions {
regexPattern := fmt.Sprintf(`\s*(https?://\S+%s)\s*`, extension)
regex := regexp.MustCompile(regexPattern)
matches := regex.FindAllString(line, -1)
for _, match := range matches {
imgTag := fmt.Sprintf(`<img src="%s" alt="">`, strings.ReplaceAll(match, "\n", ""))
line = strings.ReplaceAll(line, match, imgTag)
return line
}
}
// Match and replace npup1, nprofile1, note1, nevent1, etc
nostrRegexPattern := `\s*nostr:((npub|note|nevent|nprofile)1[a-z0-9]+)\s*`
nostrRegex := regexp.MustCompile(nostrRegexPattern)
line = nostrRegex.ReplaceAllStringFunc(line, func(match string) string {
submatch := nostrRegex.FindStringSubmatch(match)
if len(submatch) < 2 {
return match
}
capturedGroup := submatch[1]
first6 := capturedGroup[:6]
last6 := capturedGroup[len(capturedGroup)-6:]
replacement := fmt.Sprintf(`<a href="/%s">%s</a>`, capturedGroup, first6+"…"+last6)
return replacement
})
// Match and replace other URLs with <a> tags
otherRegexPattern := `\S*(https?://\S+)\S*`
otherRegex := regexp.MustCompile(otherRegexPattern)
line = otherRegex.ReplaceAllString(line, `<a href="$1">$1</a>`)
return line
}