mirror of
https://github.com/aljazceru/njump.git
synced 2025-12-17 06:14:22 +01:00
ported all templates to templ syntax.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ node_modules/
|
||||
static/tailwind-bundle.min.css
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
*_templ.go
|
||||
|
||||
43
archive.templ
Normal file
43
archive.templ
Normal file
@@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
templ archiveTemplate(params ArchivePageParams) {
|
||||
<!DOCTYPE html>
|
||||
<html class="theme--default text-lg font-light print:text-base sm:text-xl">
|
||||
<meta charset="UTF-8"/>
|
||||
<head>
|
||||
<title>{ params.Title }</title>
|
||||
@headCommonTemplate(params.HeadParams)
|
||||
</head>
|
||||
<body class="mb-16 bg-white text-gray-600 dark:bg-neutral-900 dark:text-neutral-50 print:text-black">
|
||||
@topTemplate()
|
||||
<div class="mx-auto block px-4 sm:flex sm:items-center sm:justify-center sm:px-0">
|
||||
<div class="flex w-full max-w-screen-2xl justify-between gap-10 overflow-visible px-4 print:w-full sm:w-11/12 md:w-10/12 lg:w-9/12 lg:gap-48vw">
|
||||
<div class="relative top-auto flex basis-1/4 items-center self-start sm:sticky sm:top-8 sm:mt-8 sm:items-start">
|
||||
<div class="text-2xl">{ params.Title }</div>
|
||||
</div>
|
||||
<div class="w-full break-words break-all print:w-full sm:w-1/2">
|
||||
<div class="mb-6 leading-5">
|
||||
<h1 class="text-xl">{ params.Title }</h1>
|
||||
</div>
|
||||
<div class="mb-6 leading-5">
|
||||
for _, v:= range params.Data {
|
||||
<a class="block" href="/{params.PathPrefix}{v}">
|
||||
{ v }
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
if params.PrevPage != 0 {
|
||||
<a href="/{params.PaginationUrl}/{params.PrevPage}"><< Prev page</a>
|
||||
}
|
||||
if params.NextPage != 0 {
|
||||
<a href="/{params.PaginationUrl}/{params.NextPage}">Next page >></a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@footerTemplate()
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
147
clients.templ
Normal file
147
clients.templ
Normal file
@@ -0,0 +1,147 @@
|
||||
package main
|
||||
|
||||
templ clientsTemplate(clients []ClientReference) {
|
||||
<aside class="fixed bottom-0 left-0 top-auto mt-4 w-full basis-3/12 self-start transition-all duration-500 print:hidden sm:sticky sm:bottom-auto sm:left-auto sm:top-8 sm:w-auto">
|
||||
<div class="absolute right-0 top-0 z-10 mb-4 h-10 w-10 text-center text-sm sm:relative sm:h-auto sm:w-auto">
|
||||
<span class="hidden sm:block">Open in</span>
|
||||
<div
|
||||
_="on click
|
||||
toggle .hidden on #open
|
||||
toggle .hidden on #close
|
||||
toggle .hidden on #gradient
|
||||
toggle .overflow-hidden on <body />
|
||||
toggle .hidden on <.client:not(:first-child) />
|
||||
toggle .top_client_sticky on <.clients_wrapper > :first-child />
|
||||
"
|
||||
>
|
||||
<div id="open" class="inline sm:hidden">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
class="m-auto mt-[28%] block h-1/2 w-1/2"
|
||||
>
|
||||
<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"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div id="close" class="mt-4 hidden sm:hidden">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="31"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 31 16"
|
||||
class="m-auto mt-[28%] block h-1/2 w-1/2"
|
||||
>
|
||||
<path
|
||||
fill="#fafafa"
|
||||
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"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="clients_wrapper | overflow-y-auto rounded-t-lg max-h-[55vh] sm:max-h-max"
|
||||
_="on load
|
||||
wait 50ms
|
||||
get my children
|
||||
get filterAndSort(it) then repeat for c in it call me.appendChild(c) end
|
||||
get first in me
|
||||
then tell it
|
||||
remove .hidden
|
||||
add .top_client
|
||||
"
|
||||
>
|
||||
for _, client := range clients {
|
||||
<div
|
||||
data-platform={ client.Platform }
|
||||
class="client | hidden w-full items-center border-b border-zinc-800 bg-zinc-700 first-of-type:rounded-t-lg first-of-type:border-0 first-of-type:bg-strongpink hover:bg-zinc-800 sm:mb-3 sm:flex sm:rounded-lg sm:border-0 sm:first-of-type:rounded-lg"
|
||||
_="on load get localStorage['nj:{.ID}'] or 0 then set @count to it then set @title to `used ${it} times`
|
||||
on click increment localStorage['nj:{client.ID}']"
|
||||
>
|
||||
<a
|
||||
class="client block basis-full px-3 py-3 text-left text-[17px] font-normal leading-4 text-white no-underline sm:inline sm:py-1.5 sm:text-center sm:font-light"
|
||||
href="{client.URL}"
|
||||
>
|
||||
<span class="ml-1.5 pr-2 inline basis-1/5 text-neutral-400 sm:hidden">Open in</span>
|
||||
{ client.Name }
|
||||
<span
|
||||
class="type | float-right mr-4 text-xs uppercase text-neutral-400 sm:hidden"
|
||||
>{ client.Platform }</span>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
<div data-platform="dummy" class="client | hidden h-8 bg-zinc-700"></div>
|
||||
</div>
|
||||
<div
|
||||
id="gradient"
|
||||
class="fixed bottom-0 hidden h-10 w-full bg-gradient-to-t from-zinc-800 to-transparent pointer-events-auto"
|
||||
></div>
|
||||
</aside>
|
||||
<style>
|
||||
.top_client_sticky {
|
||||
position: absolute;
|
||||
}
|
||||
.top_client span {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.top_client a span.type {
|
||||
display: none;
|
||||
}
|
||||
.clients_wrapper div {
|
||||
&:nth-child(2) {
|
||||
margin-top: 2.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
function getPlatform() {
|
||||
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
|
||||
|
||||
if (/android/i.test(userAgent)) {
|
||||
return 'android';
|
||||
} else if (/iPad|iPhone|iPod/i.test(userAgent) && !window.MSStream) {
|
||||
return 'ios';
|
||||
} else {
|
||||
return 'web';
|
||||
}
|
||||
}
|
||||
|
||||
function filterAndSort(children) {
|
||||
const platform = getPlatform();
|
||||
|
||||
const filteredElements = Array.from(children).filter((element) => {
|
||||
const platformAttr = element.getAttribute('data-platform');
|
||||
return platform === platformAttr || platformAttr === 'web' || platformAttr === 'native' || platformAttr === 'dummy';
|
||||
});
|
||||
|
||||
Array.from(children).forEach((element) => {
|
||||
if (!filteredElements.includes(element)) {
|
||||
element.remove();
|
||||
}
|
||||
});
|
||||
|
||||
const sortedElements = filteredElements.sort(
|
||||
(a, b) =>
|
||||
parseInt(b.getAttribute('count')) - parseInt(a.getAttribute('count'))
|
||||
);
|
||||
|
||||
// Assuming you want to re-insert the sorted elements into their parent container
|
||||
const parent = children[0].parentNode;
|
||||
parent.innerHTML = ''; // Clear the parent container
|
||||
sortedElements.forEach((element) => {
|
||||
parent.appendChild(element);
|
||||
});
|
||||
|
||||
return sortedElements;
|
||||
}
|
||||
</script>
|
||||
}
|
||||
56
common.templ
Normal file
56
common.templ
Normal file
@@ -0,0 +1,56 @@
|
||||
package main
|
||||
|
||||
templ authorHeaderTemplate(metadata Metadata) {
|
||||
<header class="mb-4 max-w-full">
|
||||
<a class="flex flex-wrap items-center" href={ templ.URL("/" + metadata.Npub()) }>
|
||||
<div class="print:basis-1-12 imgclip mr-2 max-w-full basis-1/6 overflow-hidden sm:mr-4">
|
||||
<img class="block h-auto w-full" src={ metadata.Picture }/>
|
||||
</div>
|
||||
<div class="block print:text-base sm:grow">
|
||||
<div class="leading-4 sm:text-2xl">
|
||||
{ metadata.Name }
|
||||
<!---->
|
||||
if metadata.Name != metadata.DisplayName {
|
||||
<span class="text-stone-400 sm:text-xl">/ { metadata.DisplayName } </span>
|
||||
}
|
||||
</div>
|
||||
<div class="text-sm leading-4 text-stone-400 sm:text-base">
|
||||
{ metadata.NpubShort() }
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</header>
|
||||
}
|
||||
|
||||
templ lastNotesTemplate(lastNotes []EnhancedEvent) {
|
||||
<aside>
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
<nav class="mb-6 leading-5">
|
||||
<h2 class="text-2xl text-strongpink">Last Notes</h2>
|
||||
for _, ee := range lastNotes {
|
||||
<a
|
||||
class="my-8 block no-underline hover:-ml-6 hover:border-l-05rem hover:border-solid hover:border-l-gray-100 hover:pl-4 dark:hover:border-l-zinc-700"
|
||||
href={ templ.URL("/" + ee.Nevent()) }
|
||||
>
|
||||
<div
|
||||
class="-ml-2.5 mb-1.5 flex flex-row flex-wrap border-b-4 border-solid border-b-gray-100 pb-1 pl-2.5 dark:border-b-neutral-800"
|
||||
>
|
||||
<div class="text-sm text-strongpink">{ ee.CreatedAtStr() }</div>
|
||||
if ee.IsReply() {
|
||||
<div class="ml-2 text-sm text-gray-300 dark:text-gray-400">- reply</div>
|
||||
}
|
||||
</div>
|
||||
<div
|
||||
class="mt-0.5 max-h-40 basis-full overflow-hidden hover:text-strongpink"
|
||||
_="on load if my scrollHeight > my offsetHeight add .gradient"
|
||||
dir="auto"
|
||||
>
|
||||
@templ.Raw(ee.Preview())
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
</nav>
|
||||
</aside>
|
||||
}
|
||||
158
data.go
158
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, "<br/>"))
|
||||
}
|
||||
|
||||
func (ee EnhancedEvent) RssTitle() string {
|
||||
regex := regexp.MustCompile(`(?i)<br\s?/?>`)
|
||||
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 <a href='/" + nevent + "'>" + neventShort + "</a><br/>_________________________<br/><br/>" + 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 = ""
|
||||
|
||||
119
details.templ
Normal file
119
details.templ
Normal file
@@ -0,0 +1,119 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
templ detailsTemplate(params DetailsParams) {
|
||||
<div class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"></div>
|
||||
if params.Npub != "" {
|
||||
<div class="mb-6 break-all leading-5">
|
||||
<div class="text-sm text-strongpink">Author Public Key</div>
|
||||
<span class="text-neutral-500 dark:text-neutral-300 text-[16px]">{ params.Npub }</span>
|
||||
</div>
|
||||
}
|
||||
if params.FileMetadata != nil {
|
||||
if params.FileMetadata.Summary != "" {
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Summary</div>
|
||||
<span class="text-neutral-500 dark:text-neutral-300 text-[16px]">{ params.FileMetadata.Summary }</span>
|
||||
</div>
|
||||
}
|
||||
if params.FileMetadata.Dim != "" {
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Dimension</div>
|
||||
<span class="text-neutral-500 dark:text-neutral-300 text-[16px]">{ params.FileMetadata.Dim }</span>
|
||||
</div>
|
||||
}
|
||||
if params.FileMetadata.Size != "" {
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Size</div>
|
||||
<span class="text-neutral-500 dark:text-neutral-300 text-[16px]">{ params.FileMetadata.Size } bytes</span>
|
||||
</div>
|
||||
}
|
||||
if params.FileMetadata.Magnet != "" {
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Magnet URL</div>
|
||||
<span class="text-neutral-500 dark:text-neutral-300 text-[16px]">{ params.FileMetadata.Magnet }</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
if len(params.SeenOn) != 0 {
|
||||
<div class="mb-6 leading-5 text-neutral-500 dark:text-neutral-300 text-[16px]">
|
||||
<div class="text-sm text-strongpink">Seen on</div>
|
||||
for _, v := range params.SeenOn {
|
||||
<a
|
||||
href={ templ.URL("/r/" + v) }
|
||||
class="underline-none pr-2 decoration-neutral-200 decoration-1 underline-offset-[6px] hover:underline"
|
||||
>{ v }</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<!-- details hidden behind a toggle -->
|
||||
if params.HideDetails {
|
||||
<div class="mb-6 flex items-center print:hidden">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="advanced-switch"
|
||||
class="hidden"
|
||||
_="on load make a URLSearchParams from location.search then get it.get('details') then if it is 'yes' set my.checked to true then trigger switch on me end
|
||||
on change or switch log my checked then if my checked is true
|
||||
remove .hidden from #hidden-fields
|
||||
tell the next <label /> from me
|
||||
add .bg-strongpink .after:translate-x-full
|
||||
remove .bg-gray-300 .dark:bg-zinc-800
|
||||
end
|
||||
otherwise
|
||||
add .hidden to #hidden-fields
|
||||
tell the next <label /> from me
|
||||
remove .bg-strongpink .after:translate-x-full
|
||||
add .bg-gray-300 .dark:bg-zinc-800
|
||||
end
|
||||
end
|
||||
"
|
||||
/>
|
||||
<label
|
||||
for="advanced-switch"
|
||||
class="cursor-pointer leading-4 underline text-neutral-500 dark:text-neutral-300 text-[16px] decoration-neutral-200 dark:decoration-neutral-500 decoration-1 underline-offset-[6px]"
|
||||
>Show more details</label>
|
||||
</div>
|
||||
}
|
||||
<div id="hidden-fields" class={ templ.KV("hidden", params.HideDetails) }>
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Published at</div>
|
||||
<span class="text-neutral-500 dark:text-neutral-300 text-[16px]">{ params.CreatedAt }</span>
|
||||
</div>
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Kind type</div>
|
||||
<span class="text-neutral-500 dark:text-neutral-300 text-[16px]">{ strconv.Itoa(params.Kind) }</span>
|
||||
if params.KindNIP != "" {
|
||||
<a
|
||||
href={ templ.URL("https://github.com/nostr-protocol/nips/blob/master/" + params.KindNIP + ".md") }
|
||||
class="underline decoration-neutral-200 dark:decoration-neutral-500 decoration-1 underline-offset-[6px] text-neutral-500 dark:text-neutral-300 text-[16px]"
|
||||
>{ params.KindDescription }</a>
|
||||
}
|
||||
</div>
|
||||
if params.Nevent != "" {
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Address Code</div>
|
||||
<span class="text-[16px] text-neutral-500 dark:text-neutral-300">{ params.Nevent }</span>
|
||||
</div>
|
||||
}
|
||||
<div class="-mx-4 my-8 bg-neutral-100 px-4 pb-4 leading-5 dark:bg-neutral-700">
|
||||
<div
|
||||
class="-mx-4 bg-neutral-300 px-4 py-1 text-neutral-100 dark:bg-neutral-800 dark:text-neutral-400"
|
||||
>
|
||||
Event JSON
|
||||
</div>
|
||||
<div class="mt-4 whitespace-pre-wrap break-all font-mono text-sm">
|
||||
@templ.Raw(params.EventJSON)
|
||||
</div>
|
||||
</div>
|
||||
if params.Nprofile != "" {
|
||||
<div class="mb-6 break-all leading-5">
|
||||
<div class="text-sm text-strongpink">Author Profile Code</div>
|
||||
<span class="text-neutral-500 dark:text-neutral-300 text-[16px]">{ params.Nprofile }</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
70
embedded_note.templ
Normal file
70
embedded_note.templ
Normal file
@@ -0,0 +1,70 @@
|
||||
package main
|
||||
|
||||
templ embeddedNoteTemplate(params EmbeddedNoteParams) {
|
||||
<!DOCTYPE html>
|
||||
<html class="theme--default text-lg font-light print:text-base sm:text-xl">
|
||||
<meta charset="UTF-8"/>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="/njump/static/tailwind-bundle.min.css"
|
||||
/>
|
||||
</head>
|
||||
<body class="relative bg-white text-gray-600 dark:bg-neutral-900 dark:text-neutral-50 print:text-black sm:items-center sm:justify-center">
|
||||
<style> ::-webkit-scrollbar { display: none; } </style>
|
||||
<div class="mx-auto w-full max-w-screen-2xl justify-between gap-10 overflow-visible px-4 pb-4 pt-4 print:w-full sm:w-11/12 md:w-10/12 lg:w-9/12">
|
||||
<a href={ "/" + params.Url } target="_new" class="no-underline">
|
||||
<div class="w-full break-words">
|
||||
@authorHeaderTemplate(params.Metadata)
|
||||
<div class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"></div>
|
||||
<article class="prose-cite:text-sm prose mb-6 leading-5 dark:prose-invert prose-headings:font-light prose-p:m-0 prose-p:mb-2 prose-blockquote:mx-0 prose-blockquote:my-8 prose-blockquote:mb-2 prose-blockquote:mt-2 prose-blockquote:border-l-05rem prose-blockquote:border-solid prose-blockquote:border-l-neutral-200 prose-blockquote:py-2 prose-blockquote:pl-4 prose-blockquote:pr-0 prose-blockquote:pt-0 prose-blockquote:font-light prose-blockquote:not-italic prose-ol:m-0 prose-ol:p-0 prose-ol:pl-4 prose-ul:m-0 prose-ul:p-0 prose-ul:pl-4 prose-li:mb-2 dark:prose-blockquote:border-l-neutral-700 sm:prose-a:text-justify">
|
||||
if params.Subject != "" {
|
||||
<h1 class="text-2xl">{ params.Subject }</h1>
|
||||
}
|
||||
<!-- main content -->
|
||||
<div dir="auto">{ params.Content }</div>
|
||||
<div class="mt-2 w-full text-right text-sm text-stone-400">
|
||||
{ params.CreatedAt }
|
||||
</div>
|
||||
</article>
|
||||
<div class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"></div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="text-sm leading-3 text-neutral-400">
|
||||
This note has been published on Nostr and is embedded via Njump,
|
||||
<a href="/" target="_new" class="underline">learn more</a>
|
||||
</div>
|
||||
</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"
|
||||
></path>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
<script>
|
||||
window.addEventListener('load', function () {
|
||||
var contentHeight = document.body.scrollHeight
|
||||
window.parent.postMessage({height: contentHeight}, '*')
|
||||
})
|
||||
|
||||
window.addEventListener('message', function (event) {
|
||||
if (event.data.showGradient) {
|
||||
var gradient = document.getElementById('bottom-gradient')
|
||||
gradient.classList.remove('hidden')
|
||||
}
|
||||
if (event.data.setDarkMode) {
|
||||
document.querySelector('html').classList.add('theme--dark')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<div id="bottom-gradient" class="pointer-events-none sticky bottom-0 left-0 hidden h-20 w-full bg-gradient-to-b from-transparent to-white dark:to-neutral-900"></div>
|
||||
<a href="/" target="_new" class="fixed bottom-2 right-2 w-[100px]"><img src="/njump/static/logo.png"/></a>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
126
embedded_profile.templ
Normal file
126
embedded_profile.templ
Normal file
@@ -0,0 +1,126 @@
|
||||
package main
|
||||
|
||||
templ embeddedProfileTemplate(params EmbeddedProfileParams) {
|
||||
<!DOCTYPE html>
|
||||
<html class="theme--default text-lg font-light print:text-base sm:text-xl">
|
||||
<meta charset="UTF-8"/>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="/njump/static/tailwind-bundle.min.css"
|
||||
/>
|
||||
</head>
|
||||
<body
|
||||
class="relative bg-white text-gray-600 dark:bg-neutral-900 dark:text-neutral-50 print:text-black"
|
||||
>
|
||||
<style> ::-webkit-scrollbar { display: none; } </style>
|
||||
<div
|
||||
class="mx-auto w-full max-w-screen-2xl justify-between gap-10 overflow-visible px-4 pb-4 pt-4 print:w-full sm:w-11/12 md:w-10/12 lg:w-9/12"
|
||||
>
|
||||
<a href="/{params.Npub}" target="_new" class="no-underline">
|
||||
<div class="w-full break-words">
|
||||
<div class="w-full break-words print:w-full">
|
||||
<header class="mb-4 max-w-full">
|
||||
<div class="flex flex-wrap items-center">
|
||||
<div
|
||||
class="print:basis-1-12 imgclip mr-2 max-w-full basis-1/6 overflow-hidden sm:mr-4 sm:basis-[10%]"
|
||||
>
|
||||
<img
|
||||
class="block h-auto w-full"
|
||||
src="{params.Metadata.Picture}"
|
||||
/>
|
||||
</div>
|
||||
<div class="block print:text-base">
|
||||
<div class="text-2xl">{ params.Metadata.Name }</div>
|
||||
if params.Metadata.Name != params.Metadata.DisplayName {
|
||||
<div class="leading-4 text-stone-400">
|
||||
{ params.Metadata.DisplayName }
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
if params.Metadata.Website != "" || params.RenderedAuthorAboutText != "" {
|
||||
<div dir="auto">
|
||||
{ params.RenderedAuthorAboutText }
|
||||
</div>
|
||||
<div class="-ml-4 mb-6 h-1.5 w-1/2 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"></div>
|
||||
<div class="mb-6 leading-5">{ params.Metadata.Website }</div>
|
||||
<div class="prose mb-6 leading-5 dark:prose-invert prose-headings:font-light sm:prose-a:text-justify">
|
||||
{ params.RenderedAuthorAboutText }
|
||||
</div>
|
||||
}
|
||||
<div class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"></div>
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Public Key</div>
|
||||
{ params.Npub }
|
||||
</div>
|
||||
<div class="mb-6 leading-5">
|
||||
if params.Metadata.NIP05 != "" {
|
||||
<div class="text-sm text-strongpink">NIP-05 Address</div>
|
||||
{ params.Metadata.NIP05 }
|
||||
}
|
||||
</div>
|
||||
<div class="mb-6 leading-5">
|
||||
if params.Metadata.LUD16 != "" {
|
||||
<div class="text-sm text-strongpink">NIP-57 Address</div>
|
||||
{ params.Metadata.LUD16 }
|
||||
}
|
||||
</div>
|
||||
if len(params.AuthorRelays) > 0 {
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Publishing to</div>
|
||||
for index, element := range params.AuthorRelays {
|
||||
<span class="mr-1 mt-2 inline-block max-w-full rounded-lg border border-slate-300 px-2 py-0.5">{ element }</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"></div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="text-sm leading-3 text-neutral-400">
|
||||
This note has been published on Nostr and is embedded via Njump,
|
||||
<a href="/" target="_new" class="underline">learn more</a>
|
||||
</div>
|
||||
</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"
|
||||
></path>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
<script>
|
||||
window.addEventListener('load', function () {
|
||||
var contentHeight = document.body.scrollHeight
|
||||
window.parent.postMessage({height: contentHeight}, '*')
|
||||
})
|
||||
|
||||
window.addEventListener('message', function (event) {
|
||||
if (event.data.showGradient) {
|
||||
var gradient = document.getElementById('bottom-gradient')
|
||||
gradient.classList.remove('hidden')
|
||||
}
|
||||
if (event.data.setDarkMode) {
|
||||
document.querySelector('html').classList.add('theme--dark')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<div
|
||||
id="bottom-gradient"
|
||||
class="pointer-events-none sticky bottom-0 left-0 hidden h-20 w-full bg-gradient-to-b from-transparent to-white dark:to-neutral-900"
|
||||
></div>
|
||||
<a href="/" target="_new" class="fixed bottom-2 right-2 w-[100px]">
|
||||
<img
|
||||
src="/njump/static/logo.png"
|
||||
/>
|
||||
</a>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
35
error.templ
Normal file
35
error.templ
Normal file
@@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
templ errorTemplate(params ErrorPageParams) {
|
||||
<!DOCTYPE html>
|
||||
<html class="theme--default text-lg font-light print:text-base sm:text-xl">
|
||||
<meta charset="UTF-8"/>
|
||||
<head>
|
||||
<title>Error</title>
|
||||
@headCommonTemplate(params.HeadParams)
|
||||
</head>
|
||||
<body
|
||||
class="mb-16 bg-white text-gray-600 dark:bg-neutral-900 dark:text-neutral-50 print:text-black"
|
||||
>
|
||||
@topTemplate()
|
||||
<div class="mx-auto mt-12 w-10/12 text-center lg:w-9/12">
|
||||
<div class="mx-auto w-4/5 sm:w-3/5">
|
||||
<div class="mt-4 text-2xl leading-6">
|
||||
@templ.Raw(params.Message)
|
||||
</div>
|
||||
<div class="my-8 italic text-neutral-400 dark:text-neutral-500">
|
||||
{ params.Errors }
|
||||
</div>
|
||||
<div>
|
||||
Are you lost?
|
||||
<a
|
||||
href="/"
|
||||
class="block leading-3 underline decoration-neutral-400 underline-offset-4"
|
||||
>Go to the homepage</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@footerTemplate()
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
73
file_metadata.templ
Normal file
73
file_metadata.templ
Normal file
@@ -0,0 +1,73 @@
|
||||
package main
|
||||
|
||||
templ fileMetadataTemplate(params FileMetadataPageParams) {
|
||||
<!DOCTYPE html>
|
||||
<html class="theme--default text-lg font-light print:text-base sm:text-xl">
|
||||
<meta charset="UTF-8"/>
|
||||
<head>
|
||||
<title>File Metadata</title>
|
||||
@openGraphTemplate(params.OpenGraphParams)
|
||||
@headCommonTemplate(params.HeadParams)
|
||||
</head>
|
||||
<body
|
||||
class="mb-16 bg-white text-gray-600 dark:bg-neutral-900 dark:text-neutral-50 print:text-black"
|
||||
>
|
||||
@topTemplate()
|
||||
<div class="mx-auto px-4 sm:flex sm:items-center sm:justify-center sm:px-0">
|
||||
<div
|
||||
class="w-full max-w-screen-2xl justify-between gap-10 overflow-visible print:w-full sm:flex sm:w-11/12 sm:px-4 md:w-10/12 lg:w-9/12 lg:gap-48vw"
|
||||
>
|
||||
<div class="w-full break-words print:w-full sm:w-3/4">
|
||||
@authorHeaderTemplate(params.Metadata)
|
||||
<div class="w-full text-right text-sm text-stone-400">
|
||||
{ params.CreatedAt }
|
||||
</div>
|
||||
<div class="w-full text-right text-sm text-stone-400">
|
||||
if params.ParentLink != "" {
|
||||
in reply to
|
||||
<span class="text-strongpink">
|
||||
@templ.Raw(params.ParentLink)
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<div class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"></div>
|
||||
<article
|
||||
class="prose-cite:text-sm prose mb-6 leading-5 dark:prose-invert prose-headings:font-light prose-p:m-0 prose-p:mb-2 prose-blockquote:mx-0 prose-blockquote:my-8 prose-blockquote:border-l-05rem prose-blockquote:border-solid prose-blockquote:border-l-gray-100 prose-blockquote:py-2 prose-blockquote:pl-4 prose-blockquote:pr-0 prose-ol:m-0 prose-ol:p-0 prose-ol:pl-4 prose-ul:m-0 prose-ul:p-0 prose-ul:pl-4 prose-li:mb-2 dark:prose-blockquote:border-l-zinc-800 sm:prose-a:text-justify"
|
||||
>
|
||||
if params.Subject != "" {
|
||||
<h1 class="text-2xl">{ params.Subject }</h1>
|
||||
} else {
|
||||
<h1 class="hidden">
|
||||
{ params.Metadata.ShortName() } on Nostr: { params.TitleizedContent }
|
||||
</h1>
|
||||
}
|
||||
<!-- main content -->
|
||||
if params.FileMetadata.Image != "" {
|
||||
<img src={ params.FileMetadata.Image } alt={ params.Alt }/>
|
||||
} else if params.IsImage {
|
||||
<img src={ params.FileMetadata.URL } alt={ params.Alt }/>
|
||||
} else if params.IsVideo {
|
||||
<video
|
||||
controls
|
||||
width="100%%"
|
||||
class="max-h-[90vh] bg-neutral-300 dark:bg-zinc-700"
|
||||
>
|
||||
<source src={ params.FileMetadata.URL } alt={ params.Alt }/>
|
||||
</video>
|
||||
}
|
||||
<a
|
||||
href={ templ.URL(params.FileMetadata.URL) }
|
||||
target="_new"
|
||||
class="not-prose mx-auto mb-3 block w-4/5 basis-full rounded-lg border-0 bg-strongpink px-4 py-2 text-center text-[17px] font-light text-white no-underline sm:w-2/6"
|
||||
>Download file</a>
|
||||
</article>
|
||||
@detailsTemplate(params.DetailsParams)
|
||||
<div class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"></div>
|
||||
</div>
|
||||
@clientsTemplate(params.Clients)
|
||||
</div>
|
||||
</div>
|
||||
@footerTemplate()
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
22
footer.templ
Normal file
22
footer.templ
Normal file
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
templ footerTemplate() {
|
||||
<div
|
||||
class="fixed -inset-x-24 inset-y-64 -z-10 h-full w-[200%] -rotate-12 overflow-hidden bg-gray-50 dark:bg-neutral-950 print:hidden"
|
||||
></div>
|
||||
<footer class="mb-4 mt-6 text-center text-sm text-gray-400">
|
||||
powered by
|
||||
<a class="text-gray-400 underline" href="/">njump</a> & open-sourced on
|
||||
<a class="text-gray-400 underline" href="https://github.com/fiatjaf/njump">github</a>
|
||||
</footer>
|
||||
<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"
|
||||
></path>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
}
|
||||
9
go.mod
9
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
|
||||
|
||||
45
go.sum
45
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=
|
||||
|
||||
70
head_common.templ
Normal file
70
head_common.templ
Normal file
@@ -0,0 +1,70 @@
|
||||
package main
|
||||
|
||||
templ headCommonTemplate(params HeadParams) {
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
if params.Oembed != "" {
|
||||
<link
|
||||
rel="alternate"
|
||||
type="application/json+oembed"
|
||||
href={ params.Oembed + "&format=json" }
|
||||
/>
|
||||
<link rel="alternate" type="text/xml+oembed" href={ params.Oembed + "&format=xml" }/>
|
||||
}
|
||||
if params.IsProfile {
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/njump/static/favicon/profile/apple-touch-icon.png?v=2"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/njump/static/favicon/profile/favicon-32x32.png?v=2"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/njump/static/favicon/profile/favicon-16x16.png?v=2"
|
||||
/>
|
||||
} else {
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/njump/static/favicon/event/apple-touch-icon.png?v=2"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/njump/static/favicon/event/favicon-32x32.png?v=2"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/njump/static/favicon/event/favicon-16x16.png?v=2"
|
||||
/>
|
||||
}
|
||||
<script src="https://unpkg.com/hyperscript.org@0.9.12"></script>
|
||||
if params.TailwindDebugStuff != "" {
|
||||
@templ.Raw(params.TailwindDebugStuff)
|
||||
} else {
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="/njump/static/tailwind-bundle.min.css"
|
||||
/>
|
||||
}
|
||||
<style> @media print { @page { margin: 2cm 3cm; } } </style>
|
||||
<meta name="theme-color" content="#e42a6d"/>
|
||||
if params.NaddrNaked != "" {
|
||||
<link rel="canonical" href={ "https://njump.me/" + params.NaddrNaked }/>
|
||||
} else {
|
||||
<link rel="canonical" href={ "https://njump.me/" + params.NeventNaked }/>
|
||||
}
|
||||
<script type="text/hyperscript">
|
||||
on load get [navigator.userAgent.includes('Safari'), navigator.userAgent.includes('Chrome')] then if it[0] is true and it[1] is false add .safari to <body /> end
|
||||
</script>
|
||||
}
|
||||
247
homepage.templ
Normal file
247
homepage.templ
Normal file
@@ -0,0 +1,247 @@
|
||||
package main
|
||||
|
||||
templ homepageTemplate(params HomePageParams) {
|
||||
<!DOCTYPE html>
|
||||
<html class="theme--default font-light">
|
||||
<meta charset="UTF-8"/>
|
||||
<head>
|
||||
<title>njump - the nostr static gateway</title>
|
||||
<meta name="description" content=""/>
|
||||
@head_commonTemplate(params.HeadCommonParams)
|
||||
</head>
|
||||
<body
|
||||
class="mb-16 bg-white text-gray-600 dark:bg-neutral-900 dark:text-neutral-50 print:text-black"
|
||||
>
|
||||
@topTemplate()
|
||||
<div class="mx-auto sm:mt-8 block px-4 sm:flex sm:items-center sm:justify-center sm:px-0">
|
||||
<div class="flex w-full max-w-screen-xl justify-between gap-10 overflow-visible px-1 print:w-full sm:w-9/12 xl:w-3/5">
|
||||
<div>
|
||||
<h2 class="text-2xl text-strongpink">What is njump?</h2>
|
||||
<p class="my-3 leading-5">
|
||||
<i>njump</i> is a HTTP
|
||||
<a class="underline" href="https://github.com/nostr-protocol/nostr">
|
||||
Nostr
|
||||
</a>
|
||||
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 <i>njump</i> is to share a
|
||||
resource outside the Nostr world, where the
|
||||
<code>nostr:</code> schema is not (yet) working.
|
||||
</p>
|
||||
<p class="my-3 leading-5">
|
||||
<i>njump</i> currently lives under { params.Host }, you can reach it
|
||||
appending a Nostr
|
||||
<a
|
||||
class="underline"
|
||||
href="https://github.com/nostr-protocol/nips/blob/master/19.md"
|
||||
>
|
||||
NIP-19
|
||||
</a>
|
||||
entity (<code>npub</code>, <code>nevent</code>, <code>naddr</code>,
|
||||
etc) after the domain:
|
||||
<span class="rounded bg-lavender px-1 dark:bg-garnet">
|
||||
{ params.Host }/<nip-19-entity>
|
||||
</span>.
|
||||
</p>
|
||||
<p class="my-3 leading-5">
|
||||
For example, here's
|
||||
<a
|
||||
class="underline"
|
||||
href="/npub1sn0wdenkukak0d9dfczzeacvhkrgz92ak56egt7vdgzn8pv2wfqqhrjdv9"
|
||||
>
|
||||
a user profile
|
||||
</a>,
|
||||
<a
|
||||
class="underline"
|
||||
href="/nevent1qqstnl4ddmhc0kzqpj7p543pvq9nvppc4laewc9x5ppucz7aagsa4dspzemhxue69uhhyetvv9ujumn0wd68ytnzv9hxgqgewaehxw309ac8junpd45kgtnxd9shg6npvchxxmmdqyv8wumn8ghj7un9d3shjtnndehhyapwwdhkx6tpdsds02v2"
|
||||
>
|
||||
a note
|
||||
</a>
|
||||
and a
|
||||
<a
|
||||
class="underline"
|
||||
href="/naddr1qqxnzd3cxqmrzv3exgmr2wfeqy08wumn8ghj7mn0wd68yttsw43zuam9d3kx7unyv4ezumn9wshszyrhwden5te0dehhxarj9ekk7mf0qy88wumn8ghj7mn0wvhxcmmv9uq3zamnwvaz7tmwdaehgu3wwa5kuef0qy2hwumn8ghj7un9d3shjtnwdaehgu3wvfnj7q3qdergggklka99wwrs92yz8wdjs952h2ux2ha2ed598ngwu9w7a6fsxpqqqp65wy2vhhv"
|
||||
>
|
||||
long blog post
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<h2 class="text-xl text-strongpink">
|
||||
Try it now, jump to some Nostr content
|
||||
</h2>
|
||||
<div
|
||||
class="my-3 mb-8 rounded-lg bg-zinc-100 p-4 pb-3 dark:bg-neutral-900 sm:p-6 sm:pb-4"
|
||||
>
|
||||
<form
|
||||
_="on submit halt the event's default then go to url `/${event.target.code.value}`"
|
||||
>
|
||||
<div
|
||||
class="flex flex-wrap items-center justify-center sm:flex-nowrap sm:justify-normal"
|
||||
>
|
||||
<div class="mb-1.5 text-xl sm:mb-0">{ params.Host }/</div>
|
||||
<input
|
||||
name="code"
|
||||
placeholder="paste a npub / nprofile / nevent / ..."
|
||||
autofocus
|
||||
class="ml-0 w-full basis-full rounded-lg border-0 bg-white p-2 text-base text-gray-700 placeholder:text-gray-300 focus:outline-0 dark:bg-zinc-900 dark:text-neutral-50 dark:placeholder:text-gray-400 sm:ml-1 sm:basis-11/12 sm:rounded-s-lg"
|
||||
/>
|
||||
<button
|
||||
class="ml-0 w-full basis-full rounded-lg border-0 bg-strongpink p-2 text-base uppercase text-white sm:-ml-4 sm:basis-2/12 sm:rounded-s-lg"
|
||||
>
|
||||
View
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="mt-3 text-center text-sm sm:mt-1 sm:text-left">
|
||||
or pick
|
||||
<a
|
||||
class="underline"
|
||||
href="/random"
|
||||
_="on click halt the event then fetch /random with method:'POST' then tell <input[name='code'] /> set @value to result"
|
||||
>
|
||||
some random content
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<p class="my-3 leading-5">
|
||||
There are several reasons to choose <i>njump</i> when sharing Nostr
|
||||
content outside of Nostr:
|
||||
</p>
|
||||
<h2 class="mt-7 text-2xl text-strongpink">Clean, fast and solid</h2>
|
||||
<p class="my-3 leading-5">
|
||||
Pages by <i>njump</i> 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!
|
||||
</p>
|
||||
<h2 class="mt-7 text-2xl text-strongpink">Good preview</h2>
|
||||
<p class="my-3 leading-5">
|
||||
<i>njump</i> renders everything on the server-side, so it is able to
|
||||
generate useful rich previews that work on Telegram, Discord,
|
||||
Twitter and other places.
|
||||
</p>
|
||||
<p class="my-3 leading-5">
|
||||
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.
|
||||
</p>
|
||||
<h2 class="mt-7 text-2xl text-strongpink">Cooperative (jump-out)</h2>
|
||||
<p class="my-3 leading-5">
|
||||
<i>njump</i> 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 <code>nostr:</code> 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.
|
||||
</p>
|
||||
<p class="my-3 leading-5">
|
||||
<a class="underline" href="https://github.com/nostr-protocol/nips/blob/master/89.md">NIP-89</a>
|
||||
support coming!
|
||||
</p>
|
||||
<h2 class="mt-7 text-2xl text-strongpink">
|
||||
Search friendly (jump-in)
|
||||
</h2>
|
||||
<p class="my-3 leading-5">
|
||||
This is crucial: <i>njump</i> pages are static so search engines can
|
||||
index them, this means that <i>njump</i> can help others to discover
|
||||
great content on Nostr, jump in and join us! <i>njump</i> is the
|
||||
only nostr resource that has this explicit goal, if you care that a
|
||||
good note can be found online use <i>njump</i> to share it, this way
|
||||
you also help Nostr flourish.
|
||||
</p>
|
||||
<h2 class="mt-7 text-2xl text-strongpink">Share NIP-05 profiles</h2>
|
||||
<p class="my-3 leading-5">
|
||||
Now you can share your own profile with a pretty
|
||||
<a
|
||||
class="underline"
|
||||
href="https://github.com/nostr-protocol/nips/blob/master/05.md"
|
||||
>
|
||||
NIP-05
|
||||
</a>
|
||||
inspired permalink:
|
||||
<span class="rounded bg-lavender px-1 dark:bg-garnet">
|
||||
{ params.Host }/<nip-05>
|
||||
</span>
|
||||
, for example:
|
||||
<a class="underline" href="/nvk.org">https://{ params.Host }/nvk.org</a>
|
||||
or
|
||||
<a class="underline" href="/mike@mikedilger.com">
|
||||
https://{ params.Host }/mike@mikedilger.com
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<p class="my-3 leading-5">
|
||||
A profile shows the basic metadata infos, the used "outbox" relays
|
||||
and the last notes.
|
||||
</p>
|
||||
<h2 class="mt-7 text-2xl text-strongpink">
|
||||
Share on Twitter and Telegram
|
||||
</h2>
|
||||
<p class="my-3 leading-5">
|
||||
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.
|
||||
<br/>
|
||||
On Telegram we have also the Instant View to access long content
|
||||
in-app!
|
||||
</p>
|
||||
<h2 class="mt-7 text-2xl text-strongpink">Relays view</h2>
|
||||
<p class="my-3 leading-5">
|
||||
You can have a view of the last content posted to a relay using
|
||||
<span class="rounded bg-lavender px-1 dark:bg-garnet">
|
||||
{ params.Host }/r/<relay-host>
|
||||
</span>
|
||||
, for example:
|
||||
<a class="underline" href="/r/nostr.wine">
|
||||
https://{ params.Host }/r/nostr.wine
|
||||
</a>
|
||||
</p>
|
||||
<p class="my-3 leading-5">
|
||||
Some basic infos (
|
||||
<a
|
||||
href="https://github.com/nostr-protocol/nips/blob/master/11.md"
|
||||
>
|
||||
NIP-11
|
||||
</a>
|
||||
) 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.
|
||||
</p>
|
||||
<h2 class="mt-7 text-2xl text-strongpink">Website widgets</h2>
|
||||
<div class="my-3 leading-5">
|
||||
You can embed notes, long form contents and profiles in a web page
|
||||
with a simple script:
|
||||
<br/>
|
||||
<span class="rounded bg-lavender px-1 dark:bg-garnet">
|
||||
<script src="https://{ params.Host }/embed/<nip-19-entity>"
|
||||
/>
|
||||
</span>
|
||||
<div class="mt-4 gap-8 sm:flex">
|
||||
<div class="mb-4 flex-auto sm:mb-0">
|
||||
<script src="/embed/npub1sn0wdenkukak0d9dfczzeacvhkrgz92ak56egt7vdgzn8pv2wfqqhrjdv9"></script>
|
||||
</div>
|
||||
<div class="flex-auto">
|
||||
<script src="/embed/naddr1qqxnzd3cxqmrzv3exgmr2wfeqy08wumn8ghj7mn0wd68yttsw43zuam9d3kx7unyv4ezumn9wshszyrhwden5te0dehhxarj9ekk7mf0qy88wumn8ghj7mn0wvhxcmmv9uq3zamnwvaz7tmwdaehgu3wwa5kuef0qy2hwumn8ghj7un9d3shjtnwdaehgu3wvfnj7q3qdergggklka99wwrs92yz8wdjs952h2ux2ha2ed598ngwu9w7a6fsxpqqqp65wy2vhhv"></script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="mt-7 text-2xl text-strongpink">Inspector tool</h2>
|
||||
<p class="my-3 leading-5">
|
||||
You know, we are all programmers, including our moms, so for every
|
||||
<i>njump</i> page you can toggle the "Show more details" switch and
|
||||
inspect the full event JSON. Without installing other tools (like
|
||||
<a class="underline" href="https://github.com/fiatjaf/nak">nak</a>)
|
||||
this is probably the fastest way to access that.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@footerTemplate()
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
53
justfile
53
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
|
||||
|
||||
81
live_event.templ
Normal file
81
live_event.templ
Normal file
@@ -0,0 +1,81 @@
|
||||
package main
|
||||
|
||||
templ liveEventTemplate(params LiveEventMessagePageParams) {
|
||||
<!DOCTYPE html>
|
||||
<html class="theme--default text-lg font-light print:text-base sm:text-xl">
|
||||
<meta charset="UTF-8"/>
|
||||
<head>
|
||||
<title>Stream: { params.LiveEvent.Title } by { params.LiveEvent.Host.Name }</title>
|
||||
@openGraphTemplate(params.OpenGraphParams)
|
||||
@headCommonTemplate(params.HeadParams)
|
||||
</head>
|
||||
<body class="mb-16 bg-white text-gray-600 dark:bg-neutral-900 dark:text-neutral-50 print:text-black">
|
||||
@topTemplate()
|
||||
<div class="mx-auto px-4 sm:flex sm:items-center sm:justify-center sm:px-0">
|
||||
<div class="w-full max-w-screen-2xl justify-between gap-10 overflow-visible print:w-full sm:flex sm:w-11/12 sm:px-4 md:w-10/12 lg:w-9/12 lg:gap-48vw">
|
||||
<div class="w-full break-words print:w-full sm:w-3/4">
|
||||
@authorHeaderTemplate(params.Metadata)
|
||||
<div class="w-full text-right text-sm text-stone-400">
|
||||
{ params.CreatedAt }
|
||||
</div>
|
||||
<div class="w-full text-right text-sm text-stone-400">
|
||||
if params.ParentLink != "" {
|
||||
in reply to
|
||||
<span class="text-strongpink">
|
||||
@templ.Raw(params.ParentLink)
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<div class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"></div>
|
||||
<article class="prose-cite:text-sm prose mb-6 max-w-full leading-5 dark:prose-invert prose-headings:font-light prose-p:m-0 prose-p:mb-2 prose-blockquote:mx-0 prose-blockquote:my-8 prose-blockquote:border-l-05rem prose-blockquote:border-solid prose-blockquote:border-l-gray-100 prose-blockquote:py-2 prose-blockquote:pl-4 prose-blockquote:pr-0 prose-ol:m-0 prose-ol:p-0 prose-ol:pl-4 prose-ul:m-0 prose-ul:p-0 prose-ul:pl-4 prose-li:mb-2 dark:prose-blockquote:border-l-zinc-800 sm:prose-a:text-justify">
|
||||
<h1 class="text-2xl">
|
||||
<span class="mr-2">{ params.LiveEvent.Title }</span>
|
||||
switch params.LiveEvent.Status {
|
||||
case "ended":
|
||||
<span class="whitespace-nowrap rounded bg-neutral-400 px-4 py-1 align-text-top text-base text-white dark:bg-neutral-700">Ended</span>
|
||||
case "live":
|
||||
<span class="whitespace-nowrap rounded bg-strongpink px-4 py-1 align-text-top text-base text-white">Live now!</span>
|
||||
}
|
||||
</h1>
|
||||
<div class="mb-4">
|
||||
if params.LiveEvent.HostNpub != "" {
|
||||
Streaming hosted by
|
||||
<a href="/{params.LiveEvent.HostNpub }">
|
||||
{ params.LiveEvent.Host.Name }
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<!-- main content -->
|
||||
<div class="mb-4">
|
||||
for _, v := range params.LiveEvent.Tags {
|
||||
<span
|
||||
class="mr-2 whitespace-nowrap rounded bg-neutral-200 px-2 dark:bg-neutral-700 dark:text-white"
|
||||
>{ v }</span>
|
||||
}
|
||||
</div>
|
||||
if params.LiveEvent.Summary != "" {
|
||||
<div>{ params.LiveEvent.Summary }</div>
|
||||
}
|
||||
if params.LiveEvent.Image != "" {
|
||||
<img
|
||||
src={ params.LiveEvent.Image }
|
||||
alt={ params.Alt }
|
||||
_="on load
|
||||
repeat until '{params.LiveEvent.Status }' == 'ended'
|
||||
set @src to '{params.LiveEvent.Image }'
|
||||
wait 5s
|
||||
end
|
||||
"
|
||||
/>
|
||||
}
|
||||
</article>
|
||||
@detailsTemplate(params.DetailsParams)
|
||||
<div class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"></div>
|
||||
</div>
|
||||
@clientsTemplate(params.Clients)
|
||||
</div>
|
||||
</div>
|
||||
@footerTemplate()
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
52
live_event_message.templ
Normal file
52
live_event_message.templ
Normal file
@@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
templ liveEventMessageTemplate(params LiveEventMessagePageParams) {
|
||||
<!DOCTYPE html>
|
||||
<html class="theme--default text-lg font-light print:text-base sm:text-xl">
|
||||
<meta charset="UTF-8"/>
|
||||
<head>
|
||||
<title>{ params.TitleizedContent }</title>
|
||||
@openGraphTemplate(params.OpenGraphParams)
|
||||
@headCommonTemplate(params.HeadParams)
|
||||
</head>
|
||||
<body class="mb-16 bg-white text-gray-600 dark:bg-neutral-900 dark:text-neutral-50 print:text-black">
|
||||
@topTemplate()
|
||||
<div class="mx-auto px-4 sm:flex sm:items-center sm:justify-center sm:px-0">
|
||||
<div class="w-full max-w-screen-2xl justify-between gap-10 overflow-visible print:w-full sm:flex sm:w-11/12 sm:px-4 md:w-10/12 lg:w-9/12 lg:gap-48vw">
|
||||
<div class="w-full break-words print:w-full sm:w-3/4">
|
||||
@authorHeaderTemplate(params.Metadata)
|
||||
<div class="w-full text-right text-sm text-stone-400">
|
||||
{ params.CreatedAt }
|
||||
</div>
|
||||
<div class="w-full text-right text-sm text-stone-400">
|
||||
if params.ParentLink != "" {
|
||||
messaging during the live event
|
||||
<span class="text-strongpink">
|
||||
@templ.Raw(params.ParentLink)
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<div class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"></div>
|
||||
<article
|
||||
class="prose-cite:text-sm prose mb-6 leading-5 dark:prose-invert prose-headings:font-light prose-p:m-0 prose-p:mb-2 prose-blockquote:mx-0 prose-blockquote:my-8 prose-blockquote:border-l-05rem prose-blockquote:border-solid prose-blockquote:border-l-gray-100 prose-blockquote:py-2 prose-blockquote:pl-4 prose-blockquote:pr-0 prose-ol:m-0 prose-ol:p-0 prose-ol:pl-4 prose-ul:m-0 prose-ul:p-0 prose-ul:pl-4 prose-li:mb-2 dark:prose-blockquote:border-l-zinc-800 sm:prose-a:text-justify"
|
||||
>
|
||||
if params.Subject != "" {
|
||||
<h1 class="text-2xl">{ params.Subject }</h1>
|
||||
} else {
|
||||
<h1 class="hidden">
|
||||
{ params.Metadata.ShortName() } on Nostr: { params.TitleizedContent }
|
||||
</h1>
|
||||
}
|
||||
<!-- main content -->
|
||||
@templ.Raw(params.Content)
|
||||
</article>
|
||||
@detailsTemplate(params.DetailsParams)
|
||||
<div class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"></div>
|
||||
</div>
|
||||
@clientsTemplate(params.Clients)
|
||||
</div>
|
||||
</div>
|
||||
@footerTemplate()
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
3
main.go
3
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()
|
||||
|
||||
52
note.templ
Normal file
52
note.templ
Normal file
@@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
templ noteTemplate(params NotePageParams) {
|
||||
<!DOCTYPE html>
|
||||
<html class="theme--default text-lg font-light print:text-base sm:text-xl">
|
||||
<meta charset="UTF-8"/>
|
||||
<head>
|
||||
<title>{ params.TitleizedContent }</title>
|
||||
@openGraphTemplate(params.OpenGraphParams)
|
||||
@headCommonTemplate(params.HeadParams)
|
||||
</head>
|
||||
<body class="mb-16 bg-white text-gray-600 dark:bg-neutral-900 dark:text-neutral-50 print:text-black">
|
||||
@topTemplate()
|
||||
<div class="mx-auto px-4 sm:flex sm:items-center sm:justify-center sm:px-0">
|
||||
<div class="w-full max-w-screen-2xl justify-between gap-10 overflow-visible print:w-full sm:flex sm:w-11/12 sm:px-4 md:w-10/12 lg:w-9/12 lg:gap-48vw">
|
||||
<div class="w-full break-words print:w-full sm:w-3/4">
|
||||
@authorHeaderTemplate(params.Metadata)
|
||||
<div class="w-full text-right text-sm text-stone-400">
|
||||
{ params.CreatedAt }
|
||||
</div>
|
||||
<div class="w-full text-right text-sm text-stone-400">
|
||||
if params.ParentLink != "" {
|
||||
in reply to
|
||||
<span class="text-strongpink">
|
||||
@templ.Raw(params.ParentLink)
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<div class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"></div>
|
||||
<article class="prose-cite:text-sm prose mb-6 leading-5 dark:prose-invert prose-headings:font-light prose-p:m-0 prose-p:mb-2 prose-blockquote:mx-0 prose-blockquote:my-8 prose-blockquote:mb-2 prose-blockquote:mt-2 prose-blockquote:border-l-05rem prose-blockquote:border-solid prose-blockquote:border-l-neutral-200 prose-blockquote:py-2 prose-blockquote:pl-4 prose-blockquote:pr-0 prose-blockquote:pt-0 prose-blockquote:font-light prose-blockquote:not-italic prose-ol:m-0 prose-ol:p-0 prose-ol:pl-4 prose-ul:m-0 prose-ul:p-0 prose-ul:pl-4 prose-li:mb-2 dark:prose-blockquote:border-l-neutral-700 sm:prose-a:text-justify">
|
||||
if params.Subject != "" {
|
||||
<h1 class="text-2xl">{ params.Subject }</h1>
|
||||
} else {
|
||||
<h1 class="hidden">
|
||||
{ params.Metadata.ShortName() } on Nostr: { params.TitleizedContent }
|
||||
</h1>
|
||||
}
|
||||
<!-- main content -->
|
||||
<div dir="auto">
|
||||
@templ.Raw(params.Content)
|
||||
</div>
|
||||
</article>
|
||||
@detailsTemplate(params.DetailsParams)
|
||||
<div class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"></div>
|
||||
</div>
|
||||
@clientsTemplate(params.Clients)
|
||||
</div>
|
||||
</div>
|
||||
@footerTemplate()
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
44
opengraph.templ
Normal file
44
opengraph.templ
Normal file
@@ -0,0 +1,44 @@
|
||||
package main
|
||||
|
||||
templ openGraphTemplate(params OpenGraphParams) {
|
||||
if params.SingleTitle != "" {
|
||||
<!-- we only display this on twitter as a single title -->
|
||||
<meta name="twitter:title" content={ params.SingleTitle }/>
|
||||
} else {
|
||||
<!-- these are not shown by twitter at all, so let's not even give them -->
|
||||
<meta property="og:site_name" content={ params.Superscript }/>
|
||||
<meta property="og:title" content={ params.Subscript }/>
|
||||
}
|
||||
<!-- this is used for when we want to take over the entire screen on twitter,
|
||||
mostly for the big "text-to-image" images -->
|
||||
if params.BigImage != "" {
|
||||
<meta name="twitter:card" content="summary_large_image"/>
|
||||
<meta name="twitter:site" content="@nostrprotocol"/>
|
||||
<meta property="og:image" content={ params.BigImage }/>
|
||||
<meta name="twitter:image" content={ params.BigImage }/>
|
||||
} else {
|
||||
<!-- otherwise we tell twitter to display it as a normal text-based embed.
|
||||
these distinctions don't seem to make any difference in other platforms,
|
||||
maybe telegram -->
|
||||
<meta name="twitter:card" content="summary"/>
|
||||
if params.Image != "" {
|
||||
<meta property="og:image" content={ params.Image }/>
|
||||
<meta name="twitter:image" content={ params.ProxiedImage }/>
|
||||
}
|
||||
<!---->
|
||||
if params.Video != "" {
|
||||
<meta property="og:video" content={ params.Video }/>
|
||||
<meta property="og:video:secure_url" content={ params.Video }/>
|
||||
<meta property="og:video:type" content="video/{params.VideoType}"/>
|
||||
}
|
||||
}
|
||||
<!-- now just display the short text if we have any (which we always should) -->
|
||||
if params.Text != "" {
|
||||
<meta property="og:description" content={ params.Text }/>
|
||||
<meta name="twitter:description" content={ params.Text }/>
|
||||
}
|
||||
}
|
||||
|
||||
templ bigImagePrerender(bigImage string) {
|
||||
<img src="{bigImage}" class="absolute left-[-999px] w-[100px]"/>
|
||||
}
|
||||
37
other.templ
Normal file
37
other.templ
Normal file
@@ -0,0 +1,37 @@
|
||||
package main
|
||||
|
||||
import "strconv"
|
||||
|
||||
templ otherTemplate(params OtherPageParams) {
|
||||
<!DOCTYPE html>
|
||||
<html class="theme--default text-lg font-light print:text-base sm:text-xl">
|
||||
<meta charset="UTF-8"/>
|
||||
<head>
|
||||
<title>Nostr Event { strconv.Itoa(params.Kind) } - { params.KindDescription }</title>
|
||||
@headCommonTemplate(params.HeadParams)
|
||||
</head>
|
||||
<body class="mb-16 bg-white text-gray-600 dark:bg-neutral-900 dark:text-neutral-50 print:text-black">
|
||||
@topTemplate()
|
||||
<div class="mx-auto block px-4 sm:flex sm:items-center sm:justify-center sm:px-0">
|
||||
<div class="flex w-full max-w-screen-2xl justify-between gap-10 overflow-visible px-4 print:w-full sm:w-11/12 md:w-10/12 lg:w-9/12 lg:gap-48vw">
|
||||
<div class="w-full break-words print:w-full md:w-10/12 lg:w-9/12">
|
||||
<header class="">
|
||||
<div class="mb-4 text-2xl">{ params.KindDescription }</div>
|
||||
</header>
|
||||
if params.Alt != "" {
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
<article class="prose-cite:text-sm prose mb-6 leading-5 dark:prose-invert">
|
||||
{ params.Alt }
|
||||
</article>
|
||||
}
|
||||
@detailsTemplate(params .DetailsParams)
|
||||
<div class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@footerTemplate()
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
401
pages.go
401
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
|
||||
}
|
||||
|
||||
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
|
||||
type ErrorPageParams struct {
|
||||
HeadParams
|
||||
Message string
|
||||
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.<br> 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.<br> 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.<br> Please tag <a href='/dtonon.com'>daniele</a> and <a href='/fiatjaf.com'>fiatjaf</a> and complain!"
|
||||
return "I can't give any suggestions to solve the problem.<br> Please tag <a href='/dtonon.com'>daniele</a> and <a href='/fiatjaf.com'>fiatjaf</a> and complain!"
|
||||
}
|
||||
return tmplError
|
||||
}
|
||||
|
||||
156
profile.templ
Normal file
156
profile.templ
Normal file
@@ -0,0 +1,156 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
templ profileTemplate(params ProfilePageParams) {
|
||||
<!DOCTYPE html>
|
||||
<html class="theme--default text-lg font-light print:text-base sm:text-xl">
|
||||
<meta charset="UTF-8"/>
|
||||
<head>
|
||||
<title>{ params.Metadata.Name } / { params.Metadata.DisplayName } is on Nostr</title>
|
||||
<meta
|
||||
name="description"
|
||||
content={ fmt.Sprintf("%s is %s's public key on Nostr", params.Npub, params.Metadata.ShortName()) }
|
||||
/>
|
||||
<meta property="og:title" content={ params.Title }/>
|
||||
<meta property="og:site_name" content={ params.Npub }/>
|
||||
if params.Metadata.Picture != "" {
|
||||
<meta property="og:image" content={ params.Metadata.Picture }/>
|
||||
<meta property="twitter:image" content={ params.Proxy + params.Metadata.Picture }/>
|
||||
}
|
||||
if params.Metadata.About != "" {
|
||||
<meta property="og:description" content={ params.Metadata.About }/>
|
||||
}
|
||||
<meta name="twitter:card" content="summary"/>
|
||||
<link rel="canonical" href={ "https://njump.me/" + params.Npub }/>
|
||||
<link
|
||||
rel="sitemap"
|
||||
type="application/xml"
|
||||
title={ "Sitemap for " + params.Npub }
|
||||
href={ "/" + params.Npub + ".xml" }
|
||||
/>
|
||||
<link
|
||||
rel="alternate"
|
||||
type="application/atom+xml"
|
||||
title="RSS"
|
||||
href={ "/" + params.Npub + ".rss" }
|
||||
/>
|
||||
@headCommonTemplate(params.HeadParams)
|
||||
</head>
|
||||
<body class="mb-16 bg-white text-gray-600 print:text-black dark:bg-neutral-900 dark:text-neutral-50">
|
||||
@topTemplate()
|
||||
<div class="mx-auto px-4 sm:flex sm:items-center sm:justify-center sm:px-0">
|
||||
<div class="w-full max-w-screen-2xl justify-between gap-10 overflow-visible print:w-full sm:flex sm:w-11/12 sm:px-4 md:w-10/12 lg:w-9/12 lg:gap-48vw">
|
||||
<header
|
||||
class="relative top-auto flex basis-1/4 items-center self-start sm:sticky sm:top-8 sm:mt-8 sm:block sm:items-start"
|
||||
>
|
||||
<div
|
||||
class="hidden basis-[64%] items-center overflow-hidden text-left"
|
||||
_="on load or scroll from window or resize from window get #profile_name then measure its top, height then if top is less than height / -2 or height is 0 remove .hidden otherwise add .hidden"
|
||||
>
|
||||
<div class="mb-3 sm:text-center">
|
||||
<div class="text-2xl">{ params.Metadata.Name }</div>
|
||||
if params.Metadata.Name != params.Metadata.DisplayName {
|
||||
<div class="text-base text-stone-400">
|
||||
{ params.Metadata.DisplayName }
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="imgclip max-w-[40%] basis-2/5 overflow-hidden sm:max-w-full sm:basis-auto"
|
||||
>
|
||||
<img class="block h-auto w-full" src={ params.Metadata.Picture }/>
|
||||
</div>
|
||||
</header>
|
||||
<div class="w-full break-words print:w-full sm:w-1/2">
|
||||
<header class="mb-6 hidden leading-5 sm:flex sm:items-center">
|
||||
<h1>
|
||||
<div id="profile_name" class="text-2xl">{ params.Metadata.Name }</div>
|
||||
if params.Metadata.Name != params.Metadata.DisplayName {
|
||||
<div class="text-xl text-stone-400">
|
||||
{ params.Metadata.DisplayName }
|
||||
</div>
|
||||
}
|
||||
</h1>
|
||||
</header>
|
||||
<div class="-ml-4 mb-6 h-1.5 w-1/2 bg-zinc-100 sm:-ml-2.5 dark:bg-zinc-700"></div>
|
||||
if params.Metadata.Website != "" {
|
||||
<div class="mb-6 leading-5">
|
||||
<a
|
||||
class="border-b-2 border-b-gray-300 pb-0.5 hover:text-strongpink"
|
||||
href={ templ.URL(params.NormalizedAuthorWebsiteURL) }
|
||||
>{ params.Metadata.Website }</a>
|
||||
</div>
|
||||
}
|
||||
if params.RenderedAuthorAboutText != "" {
|
||||
<div
|
||||
class="prose mb-6 leading-5 dark:prose-invert prose-headings:font-light sm:prose-a:text-justify"
|
||||
dir="auto"
|
||||
>
|
||||
@templ.Raw(params.RenderedAuthorAboutText)
|
||||
</div>
|
||||
}
|
||||
if params.Metadata.Website != "" || params.RenderedAuthorAboutText != "" {
|
||||
<div class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 sm:-ml-2.5 dark:bg-zinc-700"></div>
|
||||
}
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Public Key</div>
|
||||
{ params.Npub }
|
||||
</div>
|
||||
<div class="mb-6 leading-5">
|
||||
if params.Metadata.NIP05 != "" {
|
||||
<div class="text-sm text-strongpink">NIP-05 Address</div>
|
||||
{ params.Metadata.NIP05 }
|
||||
}
|
||||
</div>
|
||||
<div class="mb-6 leading-5">
|
||||
if params.Metadata.LUD16 != "" {
|
||||
<div class="text-sm text-strongpink">NIP-57 Address</div>
|
||||
{ params.Metadata.LUD16 }
|
||||
}
|
||||
</div>
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Profile Code</div>
|
||||
{ params.Nprofile }
|
||||
</div>
|
||||
if len(params.AuthorRelays) != 0 {
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Publishing to</div>
|
||||
for _, relay := range params.AuthorRelays {
|
||||
<a
|
||||
href={ templ.URL("/r/" + relay) }
|
||||
class="mr-1 mt-2 inline-block max-w-full rounded-lg border border-slate-300 px-2 py-0.5 hover:border hover:border-solid hover:border-strongpink hover:bg-strongpink hover:text-white"
|
||||
>
|
||||
{ relay }
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@detailsTemplate(params.DetailsParams)
|
||||
<div
|
||||
_={ "init fetch /profile-last-notes/" + params.Npub + " then put the result into me end" }
|
||||
>
|
||||
if len(params.LastNotes) != 0 {
|
||||
<aside>
|
||||
<div class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 sm:-ml-2.5 dark:bg-zinc-700"></div>
|
||||
<nav class="mb-6 leading-5">
|
||||
<h2 class="text-2xl text-strongpink">Last Notes</h2>
|
||||
for _, ee := range params.LastNotes {
|
||||
<a class="my-8 block no-underline hover:-ml-6 hover:border-l-05rem hover:border-solid hover:border-l-gray-100 hover:pl-4 dark:hover:border-l-zinc-700" href={ templ.URL("/" + ee.Nevent()) }>
|
||||
{ ee.Nevent() }
|
||||
</a>
|
||||
}
|
||||
</nav>
|
||||
</aside>
|
||||
}
|
||||
</div>
|
||||
<div class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 sm:-ml-2.5 dark:bg-zinc-700"></div>
|
||||
</div>
|
||||
@clientsTemplate(params.Clients)
|
||||
</div>
|
||||
</div>
|
||||
@footerTemplate()
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
145
relay.templ
Normal file
145
relay.templ
Normal file
@@ -0,0 +1,145 @@
|
||||
package main
|
||||
|
||||
templ relayTemplate(params RelayPageParams) {
|
||||
<!DOCTYPE html>
|
||||
<html class="theme--default text-lg font-light print:text-base sm:text-xl">
|
||||
<meta charset="UTF-8"/>
|
||||
<head>
|
||||
<title>Nostr Relay { params.Hostname } - { params.Info.Name }</title>
|
||||
<meta property="og:title" content={ params.Hostname + " - Nostr Relay" }/>
|
||||
<meta name="twitter:title" content={ params.Hostname + " - Nostr Relay" }/>
|
||||
<meta property="og:site_name" content={ params.Hostname + " - Nostr Relay" }/>
|
||||
if params.Info.Icon != "" {
|
||||
<meta property="og:image" content={ params.Info.Icon }/>
|
||||
<meta name="twitter:image" content={ params.Proxy + params.Info.Icon }/>
|
||||
}
|
||||
if params.Info.Description != "" {
|
||||
<meta property="og:description" content={ params.Info.Description }/>
|
||||
<meta name="twitter:description" content={ params.Info.Description }/>
|
||||
}
|
||||
<meta name="twitter:card" content="summary"/>
|
||||
<link
|
||||
rel="sitemap"
|
||||
type="application/xml"
|
||||
title={ "Sitemap for " + params.Hostname }
|
||||
href={ "/r/" + params.Hostname + ".xml" }
|
||||
/>
|
||||
<link
|
||||
rel="alternate"
|
||||
type="application/atom+xml"
|
||||
title="RSS"
|
||||
href={ "/r/" + params.Hostname + ".rss" }
|
||||
/>
|
||||
@headCommonTemplate(params.HeadParams)
|
||||
)
|
||||
</head>
|
||||
<body
|
||||
class="mb-16 bg-white text-gray-600 dark:bg-neutral-900 dark:text-neutral-50 print:text-black"
|
||||
>
|
||||
@topTemplate()
|
||||
<div class="mx-auto px-4 sm:flex sm:items-center sm:justify-center sm:px-0">
|
||||
<div
|
||||
class="w-full max-w-screen-2xl justify-between gap-10 overflow-visible px-4 print:w-full sm:flex md:w-10/12 lg:w-9/12 lg:gap-48vw"
|
||||
>
|
||||
<div
|
||||
class="relative top-auto flex basis-1/4 items-center self-start sm:sticky sm:top-8 sm:mt-8 sm:items-start"
|
||||
>
|
||||
<div
|
||||
class="hidden basis-2/3 items-center overflow-hidden text-left text-2xl sm:break-all"
|
||||
_="on load or scroll from window or resize from window get #relay_name then measure its top, height then if top is less than height / -2 or height is 0 add .flex then remove .hidden otherwise remove .flex then add .hidden"
|
||||
>
|
||||
{ params.Info.Name }
|
||||
</div>
|
||||
<div
|
||||
class="imgclip max-w-full basis-2/5 overflow-hidden sm:basis-auto"
|
||||
>
|
||||
<img class="block h-auto w-full" src={ params.Info.Icon }/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full break-words print:w-full sm:w-1/2">
|
||||
<header class="mb-6 hidden leading-5 sm:flex sm:items-center">
|
||||
<h1>
|
||||
<div id="relay_name" class="text-2xl">{ params.Info.Name }</div>
|
||||
</h1>
|
||||
</header>
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/2 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
<div class="mb-6 leading-5">
|
||||
<a
|
||||
class="border-b-2 border-b-gray-300 pb-0.5 hover:text-strongpink"
|
||||
href={ templ.URL("https://" + params.Hostname) }
|
||||
target="_blank"
|
||||
_="on mouseenter set my innerText to my.innerText.replace('wss://', 'https://')
|
||||
on mouseleave set my innerText to my.innerText.replace('https://', 'wss://')"
|
||||
>{ "wss://" + params.Hostname }</a>
|
||||
</div>
|
||||
<div
|
||||
class="prose mb-6 leading-5 dark:prose-invert prose-headings:font-light sm:prose-a:text-justify"
|
||||
dir="auto"
|
||||
>
|
||||
{ params.Info.Description }
|
||||
</div>
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/2 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
if params.Info.PubKey != "" {
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Public Key</div>
|
||||
{ params.Info.PubKey }
|
||||
</div>
|
||||
}
|
||||
<!---->
|
||||
if params.Info.Contact != "" {
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Contact</div>
|
||||
<a href={ templ.URL(params.Info.Contact) }>{ params.Info.Contact }</a>
|
||||
</div>
|
||||
}
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
<aside>
|
||||
<div class="mb-6 leading-5">
|
||||
<h2 class="text-2xl text-strongpink">Last Notes</h2>
|
||||
for _, ee := range params.LastNotes {
|
||||
<a
|
||||
class="my-8 block no-underline hover:-ml-6 hover:border-l-05rem hover:border-solid hover:border-l-gray-100 hover:pl-4 dark:hover:border-l-zinc-700"
|
||||
href={ templ.URL("/" + ee.Nevent()) }
|
||||
>
|
||||
<div
|
||||
class="-ml-2.5 mb-1.5 flex flex-row border-b-4 border-solid border-b-gray-100 pb-1 pl-2.5 dark:border-b-neutral-800"
|
||||
>
|
||||
<div class="text-sm text-strongpink">
|
||||
{ ee.CreatedAtStr() }
|
||||
</div>
|
||||
<br/>
|
||||
if ee.IsReply() {
|
||||
<div class="ml-2 text-xs text-gray-300 dark:text-gray-400">
|
||||
- reply
|
||||
</div>
|
||||
}
|
||||
<div class="ml-auto text-xs text-zinc-700 dark:text-neutral-50">
|
||||
by
|
||||
<a class="rounded bg-lavender px-1 hover:bg-strongpink hover:text-white dark:bg-garnet dark:hover:bg-strongpink" href={ templ.URL("/" + ee.Npub()) }>{ ee.NpubShort() }</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mt-0.5 max-h-40 basis-full overflow-hidden hover:text-strongpink"
|
||||
_="on load if my scrollHeight > my offsetHeight add .gradient"
|
||||
dir="auto"
|
||||
>
|
||||
@templ.Raw(ee.Preview())
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
@clientsTemplate(params.Clients)
|
||||
</div>
|
||||
</div>
|
||||
@footerTemplate()
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
content: ['./templates/*.html', './*.go'],
|
||||
content: ['./*.go', './*.templ'],
|
||||
darkMode: ['class', '.theme--dark'],
|
||||
theme: {
|
||||
extend: {
|
||||
|
||||
51
telegram_instant_view.templ
Normal file
51
telegram_instant_view.templ
Normal file
@@ -0,0 +1,51 @@
|
||||
package main
|
||||
|
||||
templ telegramInstantViewTemplate(params TelegramInstantViewParams) {
|
||||
<meta charset="UTF-8"/>
|
||||
<!-- check https://nikstar.me/post/instant-view/ for more information on how this was set up -->
|
||||
<!-- required stuff so telegram treats us like a medium.com article -->
|
||||
<meta property="al:android:app_name" content="Medium"/>
|
||||
<meta property="article:published_time" content={ params.CreatedAt }/>
|
||||
<!-- stuff that goes in the actual telegram message preview -->
|
||||
<meta property="og:site_name" content={ params.AuthorLong }/>
|
||||
if params.Description != "" {
|
||||
<meta property="og:description" content={ params.Description }/>
|
||||
}
|
||||
<!---->
|
||||
if params.Image != "" {
|
||||
<meta property="og:image" content={ params.Image }/>
|
||||
}
|
||||
<!---->
|
||||
if params.Video != "" {
|
||||
<meta property="og:video" content={ params.Video }/>
|
||||
<meta property="og:video:secure_url" content={ params.Video }/>
|
||||
<meta property="og:video:type" content={ "video/" + params.VideoType }/>
|
||||
}
|
||||
<!-- stuff that affects the content inside the preview window -->
|
||||
<meta name="author" content={ params.Metadata.ShortName }/>
|
||||
<meta name="telegram:channel" content="@nostr_protocol"/>
|
||||
<!-- basic content of the preview window -->
|
||||
<article>
|
||||
<h1>
|
||||
if params.Subject != "" {
|
||||
{ params.Subject }
|
||||
} else {
|
||||
<a href={ "/" + params.Metadata.Npub }>{ params.Metadata.ShortName }</a> on Nostr:
|
||||
}
|
||||
</h1>
|
||||
if params.ParentLink != "" {
|
||||
<aside>in reply to { params.ParentLink }</aside>
|
||||
}
|
||||
<!---->
|
||||
if params.Summary != "" {
|
||||
<aside>{ params.Summary }</aside>
|
||||
}
|
||||
<!---->
|
||||
{ params.Content }
|
||||
if params.Subject != "" {
|
||||
<aside>
|
||||
<a href={ "/" + params.Metadata.Npub }>{ params.Metadata.ShortName }</a>
|
||||
</aside>
|
||||
}
|
||||
</article>
|
||||
}
|
||||
174
template_types.go
Normal file
174
template_types.go
Normal file
@@ -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, "<br/>"))
|
||||
}
|
||||
|
||||
func (ee EnhancedEvent) RssTitle() string {
|
||||
regex := regexp.MustCompile(`(?i)<br\s?/?>`)
|
||||
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 <a href='/" + nevent + "'>" + neventShort + "</a><br/>_________________________<br/><br/>" + 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 ""
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
{{if not (eq 0 (len .LastNotes))}}
|
||||
<aside>
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
<nav class="mb-6 leading-5">
|
||||
<h2 class="text-2xl text-strongpink">Last Notes</h2>
|
||||
{{range $i, $ee := .LastNotes}}
|
||||
<a
|
||||
class="my-8 block no-underline hover:-ml-6 hover:border-l-05rem hover:border-solid hover:border-l-gray-100 hover:pl-4 dark:hover:border-l-zinc-700"
|
||||
href="/{{$ee.Nevent}}"
|
||||
>
|
||||
<div
|
||||
class="-ml-2.5 mb-1.5 flex flex-row flex-wrap border-b-4 border-solid border-b-gray-100 pb-1 pl-2.5 dark:border-b-neutral-800"
|
||||
>
|
||||
<div class="text-sm text-strongpink">{{$ee.CreatedAtStr}}</div>
|
||||
{{if $ee.IsReply}}
|
||||
<div class="ml-2 text-sm text-gray-300 dark:text-gray-400">- reply</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<div
|
||||
class="mt-0.5 max-h-40 basis-full overflow-hidden hover:text-strongpink"
|
||||
_="on load if my scrollHeight > my offsetHeight add .gradient"
|
||||
dir="auto"
|
||||
>
|
||||
{{$ee.Preview}}
|
||||
</div>
|
||||
</a>
|
||||
{{end}}
|
||||
</nav>
|
||||
</aside>
|
||||
{{end}}
|
||||
@@ -1,53 +0,0 @@
|
||||
<!doctype html>
|
||||
<html class="theme--default text-lg font-light print:text-base sm:text-xl">
|
||||
<meta charset="UTF-8" />
|
||||
<head>
|
||||
<title>{{.Title}}</title>
|
||||
|
||||
{{template "head_common" .HeadCommonPartial}}
|
||||
</head>
|
||||
|
||||
<body
|
||||
class="mb-16 bg-white text-gray-600 dark:bg-neutral-900 dark:text-neutral-50 print:text-black"
|
||||
>
|
||||
{{template "top" .}}
|
||||
|
||||
<div
|
||||
class="mx-auto block px-4 sm:flex sm:items-center sm:justify-center sm:px-0"
|
||||
>
|
||||
<div
|
||||
class="flex w-full max-w-screen-2xl justify-between gap-10 overflow-visible px-4 print:w-full sm:w-11/12 md:w-10/12 lg:w-9/12 lg:gap-48vw"
|
||||
>
|
||||
<div
|
||||
class="relative top-auto flex basis-1/4 items-center self-start sm:sticky sm:top-8 sm:mt-8 sm:items-start"
|
||||
>
|
||||
<div class="text-2xl">{{.Title}}</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full break-words break-all print:w-full sm:w-1/2">
|
||||
<div class="mb-6 leading-5">
|
||||
<h1 class="text-xl">{{.Title}}</h1>
|
||||
</div>
|
||||
|
||||
<div class="mb-6 leading-5">
|
||||
{{range $element := .Data }}
|
||||
<a class="block" href="/{{$.PathPrefix}}{{$element}}">
|
||||
{{$element}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between">
|
||||
{{if not (eq .PrevPage 0)}}
|
||||
<a href="/{{.PaginationUrl}}/{{.PrevPage}}"><< Prev page</a>
|
||||
{{end}} {{if not (eq .NextPage 0)}}
|
||||
<a href="/{{.PaginationUrl}}/{{.NextPage}}">Next page >></a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "footer" .}}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,155 +0,0 @@
|
||||
<aside
|
||||
class="fixed bottom-0 left-0 top-auto mt-4 w-full basis-3/12 self-start transition-all duration-500 print:hidden sm:sticky sm:bottom-auto sm:left-auto sm:top-8 sm:w-auto"
|
||||
>
|
||||
<div
|
||||
class="absolute right-0 top-0 z-10 mb-4 h-10 w-10 text-center text-sm sm:relative sm:h-auto sm:w-auto"
|
||||
>
|
||||
<span class="hidden sm:block">Open in</span>
|
||||
<div
|
||||
_="on click
|
||||
toggle .hidden on #open
|
||||
toggle .hidden on #close
|
||||
toggle .hidden on #gradient
|
||||
toggle .overflow-hidden on <body />
|
||||
toggle .hidden on <.client:not(:first-child) />
|
||||
toggle .top_client_sticky on <.clients_wrapper > :first-child />
|
||||
"
|
||||
>
|
||||
<div id="open" class="inline sm:hidden">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
class="m-auto mt-[28%] block h-1/2 w-1/2"
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
<div id="close" class="mt-4 hidden sm:hidden">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="31"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 31 16"
|
||||
class="m-auto mt-[28%] block h-1/2 w-1/2"
|
||||
>
|
||||
<path
|
||||
fill="#fafafa"
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="clients_wrapper | overflow-y-auto rounded-t-lg max-h-[55vh] sm:max-h-max"
|
||||
_="on load
|
||||
wait 50ms
|
||||
get my children
|
||||
get filterAndSort(it) then repeat for c in it call me.appendChild(c) end
|
||||
get first in me
|
||||
then tell it
|
||||
remove .hidden
|
||||
add .top_client
|
||||
"
|
||||
>
|
||||
{{range $index, $client := .Clients}}
|
||||
<div
|
||||
data-platform="{{$client.Platform}}"
|
||||
class="client | hidden w-full items-center border-b border-zinc-800 bg-zinc-700 first-of-type:rounded-t-lg first-of-type:border-0 first-of-type:bg-strongpink hover:bg-zinc-800 sm:mb-3 sm:flex sm:rounded-lg sm:border-0 sm:first-of-type:rounded-lg"
|
||||
_="on load get localStorage['nj:{{.ID}}'] or 0 then set @count to it then set @title to `used ${it} times`
|
||||
on click increment localStorage['nj:{{$client.ID}}']"
|
||||
>
|
||||
<a
|
||||
class="client block basis-full px-3 py-3 text-left text-[17px] font-normal leading-4 text-white no-underline sm:inline sm:py-1.5 sm:text-center sm:font-light"
|
||||
href="{{$client.URL}}"
|
||||
>
|
||||
<span
|
||||
class="ml-1.5 pr-2 inline basis-1/5 text-neutral-400 sm:hidden"
|
||||
>Open in</span
|
||||
>
|
||||
{{$client.Name}}
|
||||
<span
|
||||
class="type | float-right mr-4 text-xs uppercase text-neutral-400 sm:hidden"
|
||||
>{{$client.Platform}}</span
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
{{end}}
|
||||
<div data-platform="dummy" class="client | hidden h-8 bg-zinc-700" ></div>
|
||||
</div>
|
||||
<div
|
||||
id="gradient"
|
||||
class="fixed bottom-0 hidden h-10 w-full bg-gradient-to-t from-zinc-800 to-transparent pointer-events-auto"
|
||||
></div>
|
||||
</aside>
|
||||
|
||||
<style>
|
||||
.top_client_sticky {
|
||||
position: absolute;
|
||||
}
|
||||
.top_client span {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.top_client a span.type {
|
||||
display: none;
|
||||
}
|
||||
.clients_wrapper div {
|
||||
&:nth-child(2) {
|
||||
margin-top: 2.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function getPlatform() {
|
||||
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
|
||||
|
||||
if (/android/i.test(userAgent)) {
|
||||
return 'android';
|
||||
} else if (/iPad|iPhone|iPod/i.test(userAgent) && !window.MSStream) {
|
||||
return 'ios';
|
||||
} else {
|
||||
return 'web';
|
||||
}
|
||||
}
|
||||
|
||||
function filterAndSort(children) {
|
||||
const platform = getPlatform();
|
||||
|
||||
const filteredElements = Array.from(children).filter((element) => {
|
||||
const platformAttr = element.getAttribute('data-platform');
|
||||
return platform === platformAttr || platformAttr === 'web' || platformAttr === 'native' || platformAttr === 'dummy';
|
||||
});
|
||||
|
||||
Array.from(children).forEach((element) => {
|
||||
if (!filteredElements.includes(element)) {
|
||||
element.remove();
|
||||
}
|
||||
});
|
||||
|
||||
const sortedElements = filteredElements.sort(
|
||||
(a, b) =>
|
||||
parseInt(b.getAttribute('count')) - parseInt(a.getAttribute('count'))
|
||||
);
|
||||
|
||||
// Assuming you want to re-insert the sorted elements into their parent container
|
||||
const parent = children[0].parentNode;
|
||||
parent.innerHTML = ''; // Clear the parent container
|
||||
sortedElements.forEach((element) => {
|
||||
parent.appendChild(element);
|
||||
});
|
||||
|
||||
return sortedElements;
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
@@ -1,151 +0,0 @@
|
||||
<!-- always visible details -->
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
|
||||
{{ if not (eq "" .Npub) }}
|
||||
<div class="mb-6 break-all leading-5">
|
||||
<div class="text-sm text-strongpink">Author Public Key</div>
|
||||
<span class="text-neutral-500 dark:text-neutral-300 text-[16px]">{{.Npub}}</span>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<!------>
|
||||
|
||||
{{ if not (eq nil .FileMetadata) }}
|
||||
<!---->
|
||||
{{ if not (eq "" .FileMetadata.Summary) }}
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Summary</div>
|
||||
<span class="text-neutral-500 dark:text-neutral-300 text-[16px]"
|
||||
>{{.FileMetadata.Summary}}</span
|
||||
>
|
||||
</div>
|
||||
{{ end }}
|
||||
<!---->
|
||||
{{ if not (eq "" .FileMetadata.Dim) }}
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Dimension</div>
|
||||
<span class="text-neutral-500 dark:text-neutral-300 text-[16px]"
|
||||
>{{.FileMetadata.Dim}}</span
|
||||
>
|
||||
</div>
|
||||
{{ end }}
|
||||
<!---->
|
||||
{{ if not (eq "" .FileMetadata.Size) }}
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Size</div>
|
||||
<span class="text-neutral-500 dark:text-neutral-300 text-[16px]"
|
||||
>{{.FileMetadata.Size}} bytes</span
|
||||
>
|
||||
</div>
|
||||
{{ end }}
|
||||
<!---->
|
||||
{{ if not (eq "" .FileMetadata.Magnet) }}
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Magnet URL</div>
|
||||
<span class="text-neutral-500 dark:text-neutral-300 text-[16px]"
|
||||
>{{.FileMetadata.Magnet}}</span
|
||||
>
|
||||
</div>
|
||||
{{ end }}
|
||||
<!---->
|
||||
{{ end }}
|
||||
|
||||
<!------>
|
||||
|
||||
{{ if not (eq 0 (len .SeenOn)) }}
|
||||
<div class="mb-6 leading-5 text-neutral-500 dark:text-neutral-300 text-[16px]">
|
||||
<div class="text-sm text-strongpink">Seen on</div>
|
||||
{{ range .SeenOn }}<a
|
||||
href="/r/{{.}}"
|
||||
class="underline-none pr-2 decoration-neutral-200 decoration-1 underline-offset-[6px] hover:underline"
|
||||
>{{.}}</a
|
||||
>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<!-- details hidden behind a toggle -->
|
||||
{{ if .HideDetails }}
|
||||
<div class="mb-6 flex items-center print:hidden">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="advanced-switch"
|
||||
class="hidden"
|
||||
_="on load make a URLSearchParams from location.search then get it.get('details') then if it is 'yes' set my.checked to true then trigger switch on me end
|
||||
on change or switch log my checked then if my checked is true
|
||||
remove .hidden from #hidden-fields
|
||||
tell the next <label /> from me
|
||||
add .bg-strongpink .after:translate-x-full
|
||||
remove .bg-gray-300 .dark:bg-zinc-800
|
||||
end
|
||||
otherwise
|
||||
add .hidden to #hidden-fields
|
||||
tell the next <label /> from me
|
||||
remove .bg-strongpink .after:translate-x-full
|
||||
add .bg-gray-300 .dark:bg-zinc-800
|
||||
end
|
||||
end
|
||||
"
|
||||
/>
|
||||
<label
|
||||
for="advanced-switch"
|
||||
class="after:content-[""] relative mr-2 inline-block h-5 w-9 cursor-pointer rounded-full bg-gray-300 -indent-96 after:absolute after:inset-0.5 after:h-4 after:w-4 after:rounded-2xl after:bg-zinc-50 after:transition dark:bg-zinc-800 dark:after:bg-gray-700"
|
||||
> </label
|
||||
>
|
||||
<label
|
||||
for="advanced-switch"
|
||||
class="cursor-pointer text-sm leading-4 underline text-neutral-500 dark:text-neutral-300 text-[16px] decoration-neutral-200 dark:decoration-neutral-500 decoration-1 underline-offset-[6px]"
|
||||
>Show more details</label
|
||||
>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<div id="hidden-fields" class="{{if .HideDetails}}hidden{{end}}">
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Published at</div>
|
||||
<span class="text-neutral-500 dark:text-neutral-300 text-[16px]">{{.CreatedAt}}</span>
|
||||
</div>
|
||||
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Kind type</div>
|
||||
<span class="text-neutral-500 dark:text-neutral-300 text-[16px]">{{.Kind}}</span>
|
||||
{{ if not (eq .KindNIP "")}} -
|
||||
<a
|
||||
href="https://github.com/nostr-protocol/nips/blob/master/{{.KindNIP}}.md"
|
||||
class="text-neutral-500 underline decoration-neutral-200 dark:decoration-neutral-500 decoration-1 underline-offset-[6px] text-neutral-500 dark:text-neutral-300 text-[16px]"
|
||||
>{{.KindDescription}}</a
|
||||
>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
{{ if not (eq "" .Nevent) }}
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Address Code</div>
|
||||
<span class="text-[16px] text-neutral-500 dark:text-neutral-300"
|
||||
>{{.Nevent}}</span
|
||||
>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<!---->
|
||||
|
||||
<div class="-mx-4 my-8 bg-neutral-100 px-4 pb-4 leading-5 dark:bg-neutral-700">
|
||||
<div
|
||||
class="-mx-4 bg-neutral-300 px-4 py-1 text-neutral-100 dark:bg-neutral-800 dark:text-neutral-400"
|
||||
>
|
||||
Event JSON
|
||||
</div>
|
||||
<div class="mt-4 whitespace-pre-wrap break-all font-mono text-sm">
|
||||
{{- .EventJSON}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ if not (eq "" .Nprofile) }}
|
||||
<div class="mb-6 break-all leading-5">
|
||||
<div class="text-sm text-strongpink">Author Profile Code</div>
|
||||
<span class="text-neutral-500 dark:text-neutral-300 text-[16px]">{{.Nprofile}}</span>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
@@ -1,116 +0,0 @@
|
||||
<!doctype html>
|
||||
<html class="theme--default text-lg font-light print:text-base sm:text-xl">
|
||||
<meta charset="UTF-8" />
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="/njump/static/tailwind-bundle.min.css"
|
||||
/>
|
||||
</head>
|
||||
|
||||
<body
|
||||
class="relative bg-white text-gray-600 dark:bg-neutral-900 dark:text-neutral-50 print:text-black sm:items-center sm:justify-center"
|
||||
>
|
||||
<style>
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<div
|
||||
class="mx-auto w-full max-w-screen-2xl justify-between gap-10 overflow-visible px-4 pb-4 pt-4 print:w-full sm:w-11/12 md:w-10/12 lg:w-9/12"
|
||||
>
|
||||
<a href="/{{ .Url }}" target="_new" class="no-underline">
|
||||
<div class="w-full break-words">
|
||||
<header class="mb-4 max-w-full">
|
||||
<div class="flex flex-wrap items-center">
|
||||
<div
|
||||
class="print:basis-1-12 imgclip mr-2 max-w-full basis-[12%] overflow-hidden sm:mr-4 sm:basis-[8%]"
|
||||
>
|
||||
<img class="block h-auto w-full" src="{{.Metadata.Picture}}" />
|
||||
</div>
|
||||
<div class="block print:text-base sm:grow">
|
||||
<div class="leading-4">
|
||||
{{.Metadata.Name}}
|
||||
<!---->
|
||||
{{if not (eq .Metadata.Name .Metadata.DisplayName)}}
|
||||
<span class="text-stone-400"
|
||||
>/ {{.Metadata.DisplayName}}</span
|
||||
>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="text-sm leading-4 text-stone-400 sm:text-base">
|
||||
{{.NpubShort}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
|
||||
<article
|
||||
class="prose-cite:text-sm prose mb-6 leading-5 dark:prose-invert prose-headings:font-light prose-p:m-0 prose-p:mb-2 prose-blockquote:mx-0 prose-blockquote:my-8 prose-blockquote:mb-2 prose-blockquote:mt-2 prose-blockquote:border-l-05rem prose-blockquote:border-solid prose-blockquote:border-l-neutral-200 prose-blockquote:py-2 prose-blockquote:pl-4 prose-blockquote:pr-0 prose-blockquote:pt-0 prose-blockquote:font-light prose-blockquote:not-italic prose-ol:m-0 prose-ol:p-0 prose-ol:pl-4 prose-ul:m-0 prose-ul:p-0 prose-ul:pl-4 prose-li:mb-2 dark:prose-blockquote:border-l-neutral-700 sm:prose-a:text-justify"
|
||||
>
|
||||
{{ if (not (eq "" .Subject))}}
|
||||
<h1 class="text-2xl">{{.Subject}}</h1>
|
||||
{{ end }}
|
||||
<!-- main content -->
|
||||
<div dir="auto">{{ .Content }}</div>
|
||||
|
||||
<div class="mt-2 w-full text-right text-sm text-stone-400">
|
||||
{{.CreatedAt}}
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="text-sm leading-3 text-neutral-400">
|
||||
This note has been published on Nostr and is embedded via Njump,
|
||||
<a href="/" target="_new" class="underline">learn more</a>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
<script>
|
||||
window.addEventListener('load', function () {
|
||||
var contentHeight = document.body.scrollHeight
|
||||
window.parent.postMessage({height: contentHeight}, '*')
|
||||
})
|
||||
|
||||
window.addEventListener('message', function (event) {
|
||||
if (event.data.showGradient) {
|
||||
var gradient = document.getElementById('bottom-gradient')
|
||||
gradient.classList.remove('hidden')
|
||||
}
|
||||
if (event.data.setDarkMode) {
|
||||
document.querySelector('html').classList.add('theme--dark')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div
|
||||
id="bottom-gradient"
|
||||
class="pointer-events-none sticky bottom-0 left-0 hidden h-20 w-full bg-gradient-to-b from-transparent to-white dark:to-neutral-900"
|
||||
></div>
|
||||
|
||||
<a href="/" target="_new" class="fixed bottom-2 right-2 w-[100px]"
|
||||
><img src="/njump/static/logo.png"
|
||||
/></a>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,144 +0,0 @@
|
||||
<!doctype html>
|
||||
<html class="theme--default text-lg font-light print:text-base sm:text-xl">
|
||||
<meta charset="UTF-8" />
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="/njump/static/tailwind-bundle.min.css"
|
||||
/>
|
||||
</head>
|
||||
|
||||
<body
|
||||
class="relative bg-white text-gray-600 dark:bg-neutral-900 dark:text-neutral-50 print:text-black"
|
||||
>
|
||||
<style>
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div
|
||||
class="mx-auto w-full max-w-screen-2xl justify-between gap-10 overflow-visible px-4 pb-4 pt-4 print:w-full sm:w-11/12 md:w-10/12 lg:w-9/12"
|
||||
>
|
||||
<a href="/{{.Npub}}" target="_new" class="no-underline">
|
||||
<div class="w-full break-words">
|
||||
<div class="w-full break-words print:w-full">
|
||||
<header class="mb-4 max-w-full">
|
||||
<div class="flex flex-wrap items-center">
|
||||
<div
|
||||
class="print:basis-1-12 imgclip mr-2 max-w-full basis-1/6 overflow-hidden sm:mr-4 sm:basis-[10%]"
|
||||
>
|
||||
<img
|
||||
class="block h-auto w-full"
|
||||
src="{{.Metadata.Picture}}"
|
||||
/>
|
||||
</div>
|
||||
<div class="block print:text-base">
|
||||
<div class="text-2xl">{{.Metadata.Name}}</div>
|
||||
{{ if not (eq .Metadata.Name .Metadata.DisplayName) }}
|
||||
<div class="leading-4 text-stone-400">
|
||||
{{.Metadata.DisplayName}}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div dir="auto">
|
||||
{{ if or (not (eq "" .Metadata.Website)) (not (eq ""
|
||||
.RenderedAuthorAboutText)) }}
|
||||
</div>
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/2 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
<div class="mb-6 leading-5">{{.Metadata.Website}}</div>
|
||||
<div
|
||||
class="prose mb-6 leading-5 dark:prose-invert prose-headings:font-light sm:prose-a:text-justify"
|
||||
>
|
||||
{{.RenderedAuthorAboutText}}
|
||||
</div>
|
||||
{{ end }}
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Public Key</div>
|
||||
{{.Npub}}
|
||||
</div>
|
||||
<div class="mb-6 leading-5">
|
||||
{{ if not (eq "" .Metadata.NIP05) }}
|
||||
<div class="text-sm text-strongpink">NIP-05 Address</div>
|
||||
{{.Metadata.NIP05}} {{ end }}
|
||||
</div>
|
||||
<div class="mb-6 leading-5">
|
||||
{{ if not (eq "" .Metadata.LUD16) }}
|
||||
<div class="text-sm text-strongpink">NIP-57 Address</div>
|
||||
{{.Metadata.LUD16}} {{ end }}
|
||||
</div>
|
||||
<!-- <div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Profile Code</div>
|
||||
{{.Nprofile}}
|
||||
</div> -->
|
||||
|
||||
{{ if not (eq 0 (len .AuthorRelays)) }}
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Publishing to</div>
|
||||
{{range $index, $element := .AuthorRelays}}
|
||||
<span
|
||||
class="mr-1 mt-2 inline-block max-w-full rounded-lg border border-slate-300 px-2 py-0.5"
|
||||
>{{$element}}</span
|
||||
>
|
||||
{{end}}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="text-sm leading-3 text-neutral-400">
|
||||
This note has been published on Nostr and is embedded via Njump,
|
||||
<a href="/" target="_new" class="underline">learn more</a>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
<script>
|
||||
window.addEventListener('load', function () {
|
||||
var contentHeight = document.body.scrollHeight
|
||||
window.parent.postMessage({height: contentHeight}, '*')
|
||||
})
|
||||
|
||||
window.addEventListener('message', function (event) {
|
||||
if (event.data.showGradient) {
|
||||
var gradient = document.getElementById('bottom-gradient')
|
||||
gradient.classList.remove('hidden')
|
||||
}
|
||||
if (event.data.setDarkMode) {
|
||||
document.querySelector('html').classList.add('theme--dark')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div
|
||||
id="bottom-gradient"
|
||||
class="pointer-events-none sticky bottom-0 left-0 hidden h-20 w-full bg-gradient-to-b from-transparent to-white dark:to-neutral-900"
|
||||
></div>
|
||||
|
||||
<a href="/" target="_new" class="fixed bottom-2 right-2 w-[100px]"
|
||||
><img src="/njump/static/logo.png"
|
||||
/></a>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,33 +0,0 @@
|
||||
<!doctype html>
|
||||
<html class="theme--default text-lg font-light print:text-base sm:text-xl">
|
||||
<meta charset="UTF-8" />
|
||||
<head>
|
||||
<title>Error</title>
|
||||
{{template "head_common" .HeadCommonPartial}}
|
||||
</head>
|
||||
|
||||
<body
|
||||
class="mb-16 bg-white text-gray-600 dark:bg-neutral-900 dark:text-neutral-50 print:text-black"
|
||||
>
|
||||
{{template "top" .}}
|
||||
|
||||
<div class="mx-auto mt-12 w-10/12 text-center lg:w-9/12">
|
||||
<div class="mx-auto w-4/5 sm:w-3/5">
|
||||
<div class="mt-4 text-2xl leading-6">{{.Message}}</div>
|
||||
<div class="my-8 italic text-neutral-400 dark:text-neutral-500">
|
||||
{{.Errors}}
|
||||
</div>
|
||||
<div>
|
||||
Are you lost?
|
||||
<a
|
||||
href="/"
|
||||
class="block leading-3 underline decoration-neutral-400 underline-offset-4"
|
||||
>Go to the homepage</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "footer" .}}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,104 +0,0 @@
|
||||
<!doctype html>
|
||||
<html class="theme--default text-lg font-light print:text-base sm:text-xl">
|
||||
<meta charset="UTF-8" />
|
||||
<head>
|
||||
<title>File Metadata</title>
|
||||
{{template "opengraph" .OpenGraphPartial}}
|
||||
<!---->
|
||||
{{template "head_common" .HeadCommonPartial}}
|
||||
</head>
|
||||
|
||||
<body
|
||||
class="mb-16 bg-white text-gray-600 dark:bg-neutral-900 dark:text-neutral-50 print:text-black"
|
||||
>
|
||||
{{template "top" .}}
|
||||
|
||||
<div class="mx-auto px-4 sm:flex sm:items-center sm:justify-center sm:px-0">
|
||||
<div
|
||||
class="w-full max-w-screen-2xl justify-between gap-10 overflow-visible print:w-full sm:flex sm:w-11/12 sm:px-4 md:w-10/12 lg:w-9/12 lg:gap-48vw"
|
||||
>
|
||||
<div class="w-full break-words print:w-full sm:w-3/4">
|
||||
<header class="mb-4 max-w-full">
|
||||
<a class="flex flex-wrap items-center" href="/{{.Npub}}">
|
||||
<div
|
||||
class="print:basis-1-12 imgclip mr-2 max-w-full basis-1/6 overflow-hidden sm:mr-4"
|
||||
>
|
||||
<img class="block h-auto w-full" src="{{.Metadata.Picture}}" />
|
||||
</div>
|
||||
<div class="block print:text-base sm:grow">
|
||||
<div class="leading-4 sm:text-2xl">
|
||||
{{.Metadata.Name}}
|
||||
<!---->
|
||||
{{if not (eq .Metadata.Name .Metadata.DisplayName)}}
|
||||
<span class="text-stone-400 sm:text-xl"
|
||||
>/ {{.Metadata.DisplayName}}</span
|
||||
>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="text-sm leading-4 text-stone-400 sm:text-base">
|
||||
{{.NpubShort}}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</header>
|
||||
<div class="w-full text-right text-sm text-stone-400">
|
||||
{{.CreatedAt}}
|
||||
</div>
|
||||
|
||||
<div class="w-full text-right text-sm text-stone-400">
|
||||
{{ if not (eq "" .ParentLink) }} in reply to
|
||||
<span class="text-strongpink">{{ .ParentLink }}</span> {{ end }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
|
||||
<article
|
||||
class="prose-cite:text-sm prose prose mb-6 leading-5 dark:prose-invert prose-headings:font-light prose-p:m-0 prose-p:mb-2 prose-blockquote:mx-0 prose-blockquote:my-8 prose-blockquote:border-l-05rem prose-blockquote:border-solid prose-blockquote:border-l-gray-100 prose-blockquote:py-2 prose-blockquote:pl-4 prose-blockquote:pr-0 prose-ol:m-0 prose-ol:p-0 prose-ol:pl-4 prose-ul:m-0 prose-ul:p-0 prose-ul:pl-4 prose-li:mb-2 dark:prose-blockquote:border-l-zinc-800 sm:prose-a:text-justify"
|
||||
>
|
||||
{{ if (not (eq "" .Subject))}}
|
||||
<h1 class="text-2xl">{{.Subject}}</h1>
|
||||
{{ else }}
|
||||
<h1 class="hidden">
|
||||
{{.Metadata.ShortName}} on Nostr: {{.TitleizedContent}}
|
||||
</h1>
|
||||
{{ end }}
|
||||
<!-- main content -->
|
||||
|
||||
{{ if (not (eq "" .FileMetadata.Image))}}
|
||||
<img src="{{ .FileMetadata.Image }}" alt="{{ .Alt }}" />
|
||||
{{ else if .IsImage }}
|
||||
<img src="{{ .FileMetadata.URL }}" alt="{{ .Alt }}" />
|
||||
{{ else if .IsVideo }}
|
||||
<video
|
||||
controls
|
||||
width="100%%"
|
||||
class="max-h-[90vh] bg-neutral-300 dark:bg-zinc-700"
|
||||
>
|
||||
<source src="{{ .FileMetadata.URL }}" alt="{{ .Alt }}" />
|
||||
</video>
|
||||
{{ end }}
|
||||
|
||||
<a
|
||||
href="{{ .FileMetadata.URL }}"
|
||||
target="_new"
|
||||
class="not-prose mx-auto mb-3 block w-4/5 basis-full rounded-lg border-0 bg-strongpink px-4 py-2 text-center text-[17px] font-light text-white no-underline sm:w-2/6"
|
||||
>Download file</a
|
||||
>
|
||||
</article>
|
||||
|
||||
{{template "details" .DetailsPartial}}
|
||||
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
{{template "clients" .ClientsPartial}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "footer" .}}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,24 +0,0 @@
|
||||
<div
|
||||
class="fixed -inset-x-24 inset-y-64 -z-10 h-full w-[200%] -rotate-12 overflow-hidden bg-gray-50 dark:bg-neutral-950 print:hidden"
|
||||
></div>
|
||||
|
||||
<footer class="mb-4 mt-6 text-center text-sm text-gray-400">
|
||||
powered by
|
||||
<a class="text-gray-400 underline" href="/">njump</a> & open-sourced on
|
||||
<a class="text-gray-400 underline" href="https://github.com/fiatjaf/njump"
|
||||
>github</a
|
||||
>
|
||||
</footer>
|
||||
|
||||
<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>
|
||||
|
||||
<img src="{{.BigImage}}" class="absolute left-[-999px] w-[100px]" />
|
||||
@@ -1,76 +0,0 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
{{ if not (eq "" .Oembed) }}
|
||||
<link
|
||||
rel="alternate"
|
||||
type="application/json+oembed"
|
||||
href="{{.Oembed}}&format=json"
|
||||
/>
|
||||
<link rel="alternate" type="text/xml+oembed" href="{{.Oembed}}&format=xml" />
|
||||
{{ end }}
|
||||
|
||||
<!---->
|
||||
|
||||
{{if .IsProfile}}
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/njump/static/favicon/profile/apple-touch-icon.png?v=2"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/njump/static/favicon/profile/favicon-32x32.png?v=2"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/njump/static/favicon/profile/favicon-16x16.png?v=2"
|
||||
/>
|
||||
{{else}}
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/njump/static/favicon/event/apple-touch-icon.png?v=2"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/njump/static/favicon/event/favicon-32x32.png?v=2"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/njump/static/favicon/event/favicon-16x16.png?v=2"
|
||||
/>
|
||||
{{ end }}
|
||||
<script src="https://unpkg.com/hyperscript.org@0.9.12"></script>
|
||||
{{ if not (eq "" .TailwindDebugStuff) }} {{ .TailwindDebugStuff }} {{ else }}
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="/njump/static/tailwind-bundle.min.css"
|
||||
/>
|
||||
{{ end }}
|
||||
<style>
|
||||
@media print {
|
||||
@page {
|
||||
margin: 2cm 3cm;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<meta name="theme-color" content="#e42a6d" />
|
||||
|
||||
{{ if not (eq "" .NaddrNaked) }}
|
||||
<link rel="canonical" href="https://njump.me/{{.NaddrNaked}}" />
|
||||
{{ else }}
|
||||
<link rel="canonical" href="https://njump.me/{{.NeventNaked}}" />
|
||||
{{ end }}
|
||||
|
||||
<script type="text/hyperscript">
|
||||
on load get [navigator.userAgent.includes('Safari'), navigator.userAgent.includes('Chrome')] then if it[0] is true and it[1] is false add .safari to <body /> end
|
||||
</script>
|
||||
@@ -1,253 +0,0 @@
|
||||
<!doctype html>
|
||||
<html class="theme--default font-light">
|
||||
<meta charset="UTF-8" />
|
||||
<head>
|
||||
<title>njump - the nostr static gateway</title>
|
||||
<meta name="description" content="" />
|
||||
|
||||
{{template "head_common" .HeadCommonPartial}}
|
||||
</head>
|
||||
|
||||
<body
|
||||
class="mb-16 bg-white text-gray-600 dark:bg-neutral-900 dark:text-neutral-50 print:text-black"
|
||||
>
|
||||
{{template "top" .}}
|
||||
|
||||
<div
|
||||
class="mx-auto sm:mt-8 block px-4 sm:flex sm:items-center sm:justify-center sm:px-0"
|
||||
>
|
||||
<div
|
||||
class="flex w-full max-w-screen-xl justify-between gap-10 overflow-visible px-1 print:w-full sm:w-9/12 xl:w-3/5"
|
||||
>
|
||||
<div>
|
||||
<h2 class="text-2xl text-strongpink">What is njump?</h2>
|
||||
|
||||
<p class="my-3 leading-5">
|
||||
<i>njump</i> is a HTTP
|
||||
<a class="underline" href="https://github.com/nostr-protocol/nostr"
|
||||
>Nostr</a
|
||||
>
|
||||
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 <i>njump</i> is to share a
|
||||
resource outside the Nostr world, where the
|
||||
<code>nostr:</code> schema is not (yet) working.
|
||||
</p>
|
||||
<p class="my-3 leading-5">
|
||||
<i>njump</i> currently lives under {{ .Host }}, you can reach it
|
||||
appending a Nostr
|
||||
<a
|
||||
class="underline"
|
||||
href="https://github.com/nostr-protocol/nips/blob/master/19.md"
|
||||
>NIP-19</a
|
||||
>
|
||||
entity (<code>npub</code>, <code>nevent</code>, <code>naddr</code>,
|
||||
etc) after the domain:
|
||||
<span class="rounded bg-lavender px-1 dark:bg-garnet"
|
||||
>{{ .Host }}/<nip-19-entity></span
|
||||
>.
|
||||
</p>
|
||||
<p class="my-3 leading-5">
|
||||
For example, here's
|
||||
<a
|
||||
class="underline"
|
||||
href="/npub1sn0wdenkukak0d9dfczzeacvhkrgz92ak56egt7vdgzn8pv2wfqqhrjdv9"
|
||||
>a user profile</a
|
||||
>,
|
||||
<a
|
||||
class="underline"
|
||||
href="/nevent1qqstnl4ddmhc0kzqpj7p543pvq9nvppc4laewc9x5ppucz7aagsa4dspzemhxue69uhhyetvv9ujumn0wd68ytnzv9hxgqgewaehxw309ac8junpd45kgtnxd9shg6npvchxxmmdqyv8wumn8ghj7un9d3shjtnndehhyapwwdhkx6tpdsds02v2"
|
||||
>a note</a
|
||||
>
|
||||
and a
|
||||
<a
|
||||
class="underline"
|
||||
href="/naddr1qqxnzd3cxqmrzv3exgmr2wfeqy08wumn8ghj7mn0wd68yttsw43zuam9d3kx7unyv4ezumn9wshszyrhwden5te0dehhxarj9ekk7mf0qy88wumn8ghj7mn0wvhxcmmv9uq3zamnwvaz7tmwdaehgu3wwa5kuef0qy2hwumn8ghj7un9d3shjtnwdaehgu3wvfnj7q3qdergggklka99wwrs92yz8wdjs952h2ux2ha2ed598ngwu9w7a6fsxpqqqp65wy2vhhv"
|
||||
>long blog post</a
|
||||
>.
|
||||
</p>
|
||||
|
||||
<h2 class="text-xl text-strongpink">
|
||||
Try it now, jump to some Nostr content
|
||||
</h2>
|
||||
|
||||
<div
|
||||
class="my-3 mb-8 rounded-lg bg-zinc-100 p-4 pb-3 dark:bg-neutral-900 sm:p-6 sm:pb-4"
|
||||
>
|
||||
<form
|
||||
_="on submit halt the event's default then go to url `/${event.target.code.value}`"
|
||||
>
|
||||
<div
|
||||
class="flex flex-wrap items-center justify-center sm:flex-nowrap sm:justify-normal"
|
||||
>
|
||||
<div class="mb-1.5 text-xl sm:mb-0">{{ .Host }}/</div>
|
||||
<input
|
||||
name="code"
|
||||
placeholder="paste a npub / nprofile / nevent / ..."
|
||||
autofocus
|
||||
class="ml-0 w-full basis-full rounded-lg border-0 bg-white p-2 text-base text-gray-700 placeholder:text-gray-300 focus:outline-0 dark:bg-zinc-900 dark:text-neutral-50 dark:placeholder:text-gray-400 sm:ml-1 sm:basis-11/12 sm:rounded-s-lg"
|
||||
/>
|
||||
<button
|
||||
class="ml-0 w-full basis-full rounded-lg border-0 bg-strongpink p-2 text-base uppercase text-white sm:-ml-4 sm:basis-2/12 sm:rounded-s-lg"
|
||||
>
|
||||
View
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="mt-3 text-center text-sm sm:mt-1 sm:text-left">
|
||||
or pick
|
||||
<a
|
||||
class="underline"
|
||||
href="/random"
|
||||
_="on click halt the event then fetch /random with method:'POST' then tell <input[name='code'] /> set @value to result"
|
||||
>some random content</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="my-3 leading-5">
|
||||
There are several reasons to choose <i>njump</i> when sharing Nostr
|
||||
content outside of Nostr:
|
||||
</p>
|
||||
|
||||
<h2 class="mt-7 text-2xl text-strongpink">Clean, fast and solid</h2>
|
||||
<p class="my-3 leading-5">
|
||||
Pages by <i>njump</i> 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!
|
||||
</p>
|
||||
|
||||
<h2 class="mt-7 text-2xl text-strongpink">Good preview</h2>
|
||||
<p class="my-3 leading-5">
|
||||
<i>njump</i> renders everything on the server-side, so it is able to
|
||||
generate useful rich previews that work on Telegram, Discord,
|
||||
Twitter and other places.
|
||||
</p>
|
||||
<p class="my-3 leading-5">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<h2 class="mt-7 text-2xl text-strongpink">Cooperative (jump-out)</h2>
|
||||
<p class="my-3 leading-5">
|
||||
<i>njump</i> 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 <code>nostr:</code> for native
|
||||
clients. It even remembers the most used one for each visitor and
|
||||
puts it on the top for fast clicking or tap.
|
||||
</p>
|
||||
<p class="my-3 leading-5">
|
||||
<a
|
||||
class="underline"
|
||||
href="https://github.com/nostr-protocol/nips/blob/master/89.md"
|
||||
>NIP-89</a
|
||||
>
|
||||
support coming!
|
||||
</p>
|
||||
|
||||
<h2 class="mt-7 text-2xl text-strongpink">
|
||||
Search friendly (jump-in)
|
||||
</h2>
|
||||
<p class="my-3 leading-5">
|
||||
This is crucial: <i>njump</i> pages are static so search engines can
|
||||
index them, this means that <i>njump</i> can help others to discover
|
||||
great content on Nostr, jump in and join us! <i>njump</i> is the
|
||||
only nostr resource that has this explicit goal, if you care that a
|
||||
good note can be found online use <i>njump</i> to share it, this way
|
||||
you also help Nostr flourish.
|
||||
</p>
|
||||
|
||||
<h2 class="mt-7 text-2xl text-strongpink">Share NIP-05 profiles</h2>
|
||||
<p class="my-3 leading-5">
|
||||
Now you can share your own profile with a pretty
|
||||
<a
|
||||
class="underline"
|
||||
href="https://github.com/nostr-protocol/nips/blob/master/05.md"
|
||||
>NIP-05</a
|
||||
>
|
||||
inspired permalink:
|
||||
<span class="rounded bg-lavender px-1 dark:bg-garnet"
|
||||
>{{ .Host }}/<nip-5></span
|
||||
>, for example:
|
||||
<a class="underline" href="/nvk.org">https://{{ .Host }}/nvk.org</a>
|
||||
or
|
||||
<a class="underline" href="/mike@mikedilger.com"
|
||||
>https://{{ .Host }}/mike@mikedilger.com</a
|
||||
>.
|
||||
</p>
|
||||
<p class="my-3 leading-5">
|
||||
A profile shows the basic metadata infos, the used "outbox" relays
|
||||
and the last notes.
|
||||
</p>
|
||||
|
||||
<h2 class="mt-7 text-2xl text-strongpink">
|
||||
Share on Twitter and Telegram
|
||||
</h2>
|
||||
<p class="my-3 leading-5">
|
||||
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.<br />
|
||||
On Telegram we have also the Instant View to access long content
|
||||
in-app!
|
||||
</p>
|
||||
|
||||
<h2 class="mt-7 text-2xl text-strongpink">Relays view</h2>
|
||||
<p class="my-3 leading-5">
|
||||
You can have a view of the last content posted to a relay using
|
||||
<span class="rounded bg-lavender px-1 dark:bg-garnet"
|
||||
>{{ .Host }}/r/<relay-host></span
|
||||
>, for example:
|
||||
<a class="underline" href="/r/nostr.wine"
|
||||
>https://{{ .Host }}/r/nostr.wine</a
|
||||
>
|
||||
</p>
|
||||
<p class="my-3 leading-5">
|
||||
Some basic infos (<a
|
||||
href="https://github.com/nostr-protocol/nips/blob/master/11.md"
|
||||
>NIP-11</a
|
||||
>) 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.
|
||||
</p>
|
||||
|
||||
<h2 class="mt-7 text-2xl text-strongpink">Website widgets</h2>
|
||||
<div class="my-3 leading-5">
|
||||
You can embed notes, long form contents and profiles in a web page
|
||||
with a simple script:<br />
|
||||
<span class="rounded bg-lavender px-1 dark:bg-garnet"
|
||||
><script src="https://{{ .Host }}/embed/<nip-19-entity>"
|
||||
/></span
|
||||
>
|
||||
<div class="mt-4 gap-8 sm:flex">
|
||||
<div class="mb-4 flex-auto sm:mb-0">
|
||||
<script src="/embed/npub1sn0wdenkukak0d9dfczzeacvhkrgz92ak56egt7vdgzn8pv2wfqqhrjdv9"></script>
|
||||
</div>
|
||||
<div class="flex-auto">
|
||||
<script src="/embed/naddr1qqxnzd3cxqmrzv3exgmr2wfeqy08wumn8ghj7mn0wd68yttsw43zuam9d3kx7unyv4ezumn9wshszyrhwden5te0dehhxarj9ekk7mf0qy88wumn8ghj7mn0wvhxcmmv9uq3zamnwvaz7tmwdaehgu3wwa5kuef0qy2hwumn8ghj7un9d3shjtnwdaehgu3wvfnj7q3qdergggklka99wwrs92yz8wdjs952h2ux2ha2ed598ngwu9w7a6fsxpqqqp65wy2vhhv"></script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="mt-7 text-2xl text-strongpink">Inspector tool</h2>
|
||||
<p class="my-3 leading-5">
|
||||
You know, we are all programmers, including our moms, so for every
|
||||
<i>njump</i> page you can toggle the "Show more details" switch and
|
||||
inspect the full event JSON. Without installing other tools (like
|
||||
<a class="underline" href="https://github.com/fiatjaf/nak">nak</a>)
|
||||
this is probably the fastest way to access that.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "footer" .}}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,119 +0,0 @@
|
||||
<!doctype html>
|
||||
<html class="theme--default text-lg font-light print:text-base sm:text-xl">
|
||||
<meta charset="UTF-8" />
|
||||
<head>
|
||||
<title>Stream: {{.LiveEvent.Title}} by {{ .LiveEvent.Host.Name }}</title>
|
||||
{{template "opengraph" .OpenGraphPartial}}
|
||||
<!---->
|
||||
{{template "head_common" .HeadCommonPartial}}
|
||||
</head>
|
||||
|
||||
<body
|
||||
class="mb-16 bg-white text-gray-600 dark:bg-neutral-900 dark:text-neutral-50 print:text-black"
|
||||
>
|
||||
{{template "top" .}}
|
||||
|
||||
<div class="mx-auto px-4 sm:flex sm:items-center sm:justify-center sm:px-0">
|
||||
<div
|
||||
class="w-full max-w-screen-2xl justify-between gap-10 overflow-visible print:w-full sm:flex sm:w-11/12 sm:px-4 md:w-10/12 lg:w-9/12 lg:gap-48vw"
|
||||
>
|
||||
<div class="w-full break-words print:w-full sm:w-3/4">
|
||||
<header class="mb-4 max-w-full">
|
||||
<a class="flex flex-wrap items-center" href="/{{.Npub}}">
|
||||
<div
|
||||
class="print:basis-1-12 imgclip mr-2 max-w-full basis-1/6 overflow-hidden sm:mr-4"
|
||||
>
|
||||
<img class="block h-auto w-full" src="{{.Metadata.Picture}}" />
|
||||
</div>
|
||||
<div class="block print:text-base sm:grow">
|
||||
<div class="leading-4 sm:text-2xl">
|
||||
{{.Metadata.Name}}
|
||||
<!---->
|
||||
{{if not (eq .Metadata.Name .Metadata.DisplayName)}}
|
||||
<span class="text-stone-400 sm:text-xl"
|
||||
>/ {{.Metadata.DisplayName}}</span
|
||||
>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="text-sm leading-4 text-stone-400 sm:text-base">
|
||||
{{.NpubShort}}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</header>
|
||||
<div class="w-full text-right text-sm text-stone-400">
|
||||
{{.CreatedAt}}
|
||||
</div>
|
||||
|
||||
<div class="w-full text-right text-sm text-stone-400">
|
||||
{{ if not (eq "" .ParentLink) }} in reply to
|
||||
<span class="text-strongpink">{{ .ParentLink }}</span> {{ end }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
|
||||
<article
|
||||
class="prose-cite:text-sm prose mb-6 max-w-full leading-5 dark:prose-invert prose-headings:font-light prose-p:m-0 prose-p:mb-2 prose-blockquote:mx-0 prose-blockquote:my-8 prose-blockquote:border-l-05rem prose-blockquote:border-solid prose-blockquote:border-l-gray-100 prose-blockquote:py-2 prose-blockquote:pl-4 prose-blockquote:pr-0 prose-ol:m-0 prose-ol:p-0 prose-ol:pl-4 prose-ul:m-0 prose-ul:p-0 prose-ul:pl-4 prose-li:mb-2 dark:prose-blockquote:border-l-zinc-800 sm:prose-a:text-justify"
|
||||
>
|
||||
<h1 class="text-2xl">
|
||||
<span class="mr-2">{{.LiveEvent.Title}}</span>{{ if (eq "ended"
|
||||
.LiveEvent.Status)}}
|
||||
<span
|
||||
class="whitespace-nowrap rounded bg-neutral-400 px-4 py-1 align-text-top text-base text-white dark:bg-neutral-700"
|
||||
>Ended</span
|
||||
>
|
||||
{{ else if (eq "live" .LiveEvent.Status)}}
|
||||
<span
|
||||
class="whitespace-nowrap rounded bg-strongpink px-4 py-1 align-text-top text-base text-white"
|
||||
>Live now!</span
|
||||
>
|
||||
{{ end }}
|
||||
</h1>
|
||||
<div class="mb-4">
|
||||
{{ if not (eq "" .LiveEvent.HostNpub) }} Streaming hosted by
|
||||
<a href="/{{ .LiveEvent.HostNpub }}"
|
||||
>{{ .LiveEvent.Host.Name }}</a
|
||||
>
|
||||
{{ end }}
|
||||
</div>
|
||||
<!-- main content -->
|
||||
<div class="mb-4">
|
||||
{{ range .LiveEvent.Tags }}
|
||||
<span
|
||||
class="mr-2 whitespace-nowrap rounded bg-neutral-200 px-2 dark:bg-neutral-700 dark:text-white"
|
||||
>{{ . }}</span
|
||||
>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ if (not (eq "" .LiveEvent.Summary))}}
|
||||
<div>{{ .LiveEvent.Summary }}</div>
|
||||
{{ end }} {{ if (not (eq "" .LiveEvent.Image))}}
|
||||
<img
|
||||
src="{{ .LiveEvent.Image }}"
|
||||
alt="{{ .Alt }}"
|
||||
_="on load
|
||||
repeat until '{{ .LiveEvent.Status }}' == 'ended'
|
||||
set @src to '{{ .LiveEvent.Image }}'
|
||||
wait 5s
|
||||
end
|
||||
"
|
||||
/>
|
||||
{{ end }}
|
||||
</article>
|
||||
|
||||
{{template "details" .DetailsPartial}}
|
||||
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
{{template "clients" .ClientsPartial}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "footer" .}}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,85 +0,0 @@
|
||||
<!doctype html>
|
||||
<html class="theme--default text-lg font-light print:text-base sm:text-xl">
|
||||
<meta charset="UTF-8" />
|
||||
<head>
|
||||
<title>{{.TitleizedContent}}</title>
|
||||
|
||||
{{template "opengraph" .OpenGraphPartial}}
|
||||
<!---->
|
||||
{{template "head_common" .HeadCommonPartial}}
|
||||
</head>
|
||||
|
||||
<body
|
||||
class="mb-16 bg-white text-gray-600 dark:bg-neutral-900 dark:text-neutral-50 print:text-black"
|
||||
>
|
||||
{{template "top" .}}
|
||||
|
||||
<div class="mx-auto px-4 sm:flex sm:items-center sm:justify-center sm:px-0">
|
||||
<div
|
||||
class="w-full max-w-screen-2xl justify-between gap-10 overflow-visible print:w-full sm:flex sm:w-11/12 sm:px-4 md:w-10/12 lg:w-9/12 lg:gap-48vw"
|
||||
>
|
||||
<div class="w-full break-words print:w-full sm:w-3/4">
|
||||
<header class="mb-4 max-w-full">
|
||||
<a class="flex flex-wrap items-center" href="/{{.Npub}}">
|
||||
<div
|
||||
class="print:basis-1-12 imgclip mr-2 max-w-full basis-1/6 overflow-hidden sm:mr-4"
|
||||
>
|
||||
<img class="block h-auto w-full" src="{{.Metadata.Picture}}" />
|
||||
</div>
|
||||
<div class="block print:text-base sm:grow">
|
||||
<div class="leading-4 sm:text-2xl">
|
||||
{{.Metadata.Name}}
|
||||
<!---->
|
||||
{{if not (eq .Metadata.Name .Metadata.DisplayName)}}
|
||||
<span class="text-stone-400 sm:text-xl"
|
||||
>/ {{.Metadata.DisplayName}}</span
|
||||
>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="text-sm leading-4 text-stone-400 sm:text-base">
|
||||
{{.NpubShort}}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</header>
|
||||
<div class="w-full text-right text-sm text-stone-400">
|
||||
{{.CreatedAt}}
|
||||
</div>
|
||||
|
||||
<div class="w-full text-right text-sm text-stone-400">
|
||||
{{ if not (eq "" .ParentLink) }} messaging during the live event
|
||||
<span class="text-strongpink">{{ .ParentLink }}</span> {{ end }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
|
||||
<article
|
||||
class="prose-cite:text-sm prose mb-6 leading-5 dark:prose-invert prose-headings:font-light prose-p:m-0 prose-p:mb-2 prose-blockquote:mx-0 prose-blockquote:my-8 prose-blockquote:border-l-05rem prose-blockquote:border-solid prose-blockquote:border-l-gray-100 prose-blockquote:py-2 prose-blockquote:pl-4 prose-blockquote:pr-0 prose-ol:m-0 prose-ol:p-0 prose-ol:pl-4 prose-ul:m-0 prose-ul:p-0 prose-ul:pl-4 prose-li:mb-2 dark:prose-blockquote:border-l-zinc-800 sm:prose-a:text-justify"
|
||||
>
|
||||
{{ if (not (eq "" .Subject))}}
|
||||
<h1 class="text-2xl">{{.Subject}}</h1>
|
||||
{{ else }}
|
||||
<h1 class="hidden">
|
||||
{{.Metadata.ShortName}} on Nostr: {{.TitleizedContent}}
|
||||
</h1>
|
||||
{{ end }}
|
||||
<!-- main content -->
|
||||
{{ .Content }}
|
||||
</article>
|
||||
|
||||
{{template "details" .DetailsPartial}}
|
||||
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
{{template "clients" .ClientsPartial}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "footer" .}}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,85 +0,0 @@
|
||||
<!doctype html>
|
||||
<html class="theme--default text-lg font-light print:text-base sm:text-xl">
|
||||
<meta charset="UTF-8" />
|
||||
<head>
|
||||
<title>{{.TitleizedContent}}</title>
|
||||
|
||||
{{template "opengraph" .OpenGraphPartial}}
|
||||
<!---->
|
||||
{{template "head_common" .HeadCommonPartial}}
|
||||
</head>
|
||||
|
||||
<body
|
||||
class="mb-16 bg-white text-gray-600 dark:bg-neutral-900 dark:text-neutral-50 print:text-black"
|
||||
>
|
||||
{{template "top" .}}
|
||||
|
||||
<div class="mx-auto px-4 sm:flex sm:items-center sm:justify-center sm:px-0">
|
||||
<div
|
||||
class="w-full max-w-screen-2xl justify-between gap-10 overflow-visible print:w-full sm:flex sm:w-11/12 sm:px-4 md:w-10/12 lg:w-9/12 lg:gap-48vw"
|
||||
>
|
||||
<div class="w-full break-words print:w-full sm:w-3/4">
|
||||
<header class="mb-4 max-w-full">
|
||||
<a class="flex flex-wrap items-center" href="/{{.Npub}}">
|
||||
<div
|
||||
class="print:basis-1-12 imgclip mr-2 max-w-full basis-1/6 overflow-hidden sm:mr-4"
|
||||
>
|
||||
<img class="block h-auto w-full" src="{{.Metadata.Picture}}" />
|
||||
</div>
|
||||
<div class="block print:text-base sm:grow">
|
||||
<div class="leading-4 sm:text-2xl">
|
||||
{{.Metadata.Name}}
|
||||
<!---->
|
||||
{{if not (eq .Metadata.Name .Metadata.DisplayName)}}
|
||||
<span class="text-stone-400 sm:text-xl"
|
||||
>/ {{.Metadata.DisplayName}}</span
|
||||
>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="text-sm leading-4 text-stone-400 sm:text-base">
|
||||
{{.NpubShort}}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</header>
|
||||
<div class="w-full text-right text-sm text-stone-400">
|
||||
{{.CreatedAt}}
|
||||
</div>
|
||||
|
||||
<div class="w-full text-right text-sm text-stone-400">
|
||||
{{ if not (eq "" .ParentLink) }} in reply to
|
||||
<span class="text-strongpink">{{ .ParentLink }}</span> {{ end }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
|
||||
<article
|
||||
class="prose-cite:text-sm prose prose mb-6 leading-5 dark:prose-invert prose-headings:font-light prose-p:m-0 prose-p:mb-2 prose-blockquote:mx-0 prose-blockquote:my-8 prose-blockquote:mb-2 prose-blockquote:mt-2 prose-blockquote:border-l-05rem prose-blockquote:border-solid prose-blockquote:border-l-neutral-200 prose-blockquote:py-2 prose-blockquote:pl-4 prose-blockquote:pr-0 prose-blockquote:pt-0 prose-blockquote:font-light prose-blockquote:not-italic prose-ol:m-0 prose-ol:p-0 prose-ol:pl-4 prose-ul:m-0 prose-ul:p-0 prose-ul:pl-4 prose-li:mb-2 dark:prose-blockquote:border-l-neutral-700 sm:prose-a:text-justify"
|
||||
>
|
||||
{{ if (not (eq "" .Subject))}}
|
||||
<h1 class="text-2xl">{{.Subject}}</h1>
|
||||
{{ else }}
|
||||
<h1 class="hidden">
|
||||
{{.Metadata.ShortName}} on Nostr: {{.TitleizedContent}}
|
||||
</h1>
|
||||
{{ end }}
|
||||
<!-- main content -->
|
||||
<div dir="auto">{{ .Content }}</div>
|
||||
</article>
|
||||
|
||||
{{template "details" .DetailsPartial}}
|
||||
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
{{template "clients" .ClientsPartial}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "footer" .OpenGraphPartial}}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,39 +0,0 @@
|
||||
{{ if not (eq "" .SingleTitle)}}
|
||||
<!-- we only display this on twitter as a single title -->
|
||||
<meta name="twitter:title" content="{{.SingleTitle}}" />
|
||||
{{ else }}
|
||||
<!-- these are not shown by twitter at all, so let's not even give them -->
|
||||
<meta property="og:site_name" content="{{.Superscript}}" />
|
||||
<meta property="og:title" content="{{.Subscript}}" />
|
||||
{{ end }}
|
||||
|
||||
<!-- this is used for when we want to take over the entire screen on twitter,
|
||||
mostly for the big "text-to-image" images -->
|
||||
{{ if not (eq "" .BigImage) }}
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:site" content="@nostrprotocol" />
|
||||
<meta property="og:image" content="{{.BigImage}}" />
|
||||
<meta name="twitter:image" content="{{.BigImage}}" />
|
||||
{{ else }}
|
||||
<!-- otherwise we tell twitter to display it as a normal text-based embed.
|
||||
these distinctions don't seem to make any difference in other platforms,
|
||||
maybe telegram -->
|
||||
<meta name="twitter:card" content="summary" />
|
||||
{{ if not (eq "" .Image) }}
|
||||
<meta property="og:image" content="{{.Image}}" />
|
||||
<meta name="twitter:image" content="{{.ProxiedImage}}" />
|
||||
{{ end }}
|
||||
<!---->
|
||||
{{ if not (eq "" .Video) }}
|
||||
<meta property="og:video" content="{{.Video}}" />
|
||||
<meta property="og:video:secure_url" content="{{.Video}}" />
|
||||
<meta property="og:video:type" content="video/{{.VideoType}}" />
|
||||
{{ end }}
|
||||
<!-- end the BigImage /if above -->
|
||||
{{ end }}
|
||||
|
||||
<!-- now just display the short text if we have any (which we always should) -->
|
||||
{{ if not (eq "" .Text) }}
|
||||
<meta property="og:description" content="{{.Text}}" />
|
||||
<meta name="twitter:description" content="{{.Text}}" />
|
||||
{{ end }}
|
||||
@@ -1,51 +0,0 @@
|
||||
<!doctype html>
|
||||
<html class="theme--default text-lg font-light print:text-base sm:text-xl">
|
||||
<meta charset="UTF-8" />
|
||||
<head>
|
||||
<title>Nostr Event {{.Kind}} - {{.KindDescription }}</title>
|
||||
|
||||
<!---->
|
||||
{{template "head_common" .HeadCommonPartial}}
|
||||
</head>
|
||||
|
||||
<body
|
||||
class="mb-16 bg-white text-gray-600 dark:bg-neutral-900 dark:text-neutral-50 print:text-black"
|
||||
>
|
||||
{{template "top" .}}
|
||||
|
||||
<div
|
||||
class="mx-auto block px-4 sm:flex sm:items-center sm:justify-center sm:px-0"
|
||||
>
|
||||
<div
|
||||
class="flex w-full max-w-screen-2xl justify-between gap-10 overflow-visible px-4 print:w-full sm:w-11/12 md:w-10/12 lg:w-9/12 lg:gap-48vw"
|
||||
>
|
||||
<div class="w-full break-words print:w-full md:w-10/12 lg:w-9/12">
|
||||
<header class="">
|
||||
<div class="mb-4 text-2xl">{{.KindDescription}}</div>
|
||||
</header>
|
||||
|
||||
{{ if not (eq "" .Alt) }}
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
|
||||
<article
|
||||
class="prose-cite:text-sm prose prose mb-6 leading-5 dark:prose-invert"
|
||||
>
|
||||
{{.Alt}}
|
||||
</article>
|
||||
{{ end }}
|
||||
|
||||
<!---->
|
||||
{{template "details" .DetailsPartial}}
|
||||
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "footer" .}}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,175 +0,0 @@
|
||||
<!doctype html>
|
||||
<html class="theme--default text-lg font-light print:text-base sm:text-xl">
|
||||
<meta charset="UTF-8" />
|
||||
<head>
|
||||
<title>{{.Metadata.Name}} / {{.Metadata.DisplayName}} is on nostr</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="{{.Npub}} is {{.Metadata.Name}} / {{.Metadata.DisplayName}} public key on nostr"
|
||||
/>
|
||||
<meta property="og:title" content="{{.Title}}" />
|
||||
<meta property="og:site_name" content="{{.Npub}}" />
|
||||
{{ if not (eq "" .Metadata.Picture) }}
|
||||
<meta property="og:image" content="{{.Metadata.Picture}}" />
|
||||
<meta property="twitter:image" content="{{.Proxy}}{{.Metadata.Picture}}" />
|
||||
{{end}} {{ if not (eq "" .Metadata.About) }}
|
||||
<meta property="og:description" content="{{.Metadata.About}}" />
|
||||
{{end}}
|
||||
<meta name="twitter:card" content="summary" />
|
||||
|
||||
<link rel="canonical" href="https://njump.me/{{.Npub}}" />
|
||||
<link
|
||||
rel="sitemap"
|
||||
type="application/xml"
|
||||
title="Sitemap for {{.Npub}}"
|
||||
href="/{{.Npub}}.xml"
|
||||
/>
|
||||
|
||||
<link
|
||||
rel="alternate"
|
||||
type="application/atom+xml"
|
||||
title="RSS"
|
||||
href="/{{.Npub}}.rss"
|
||||
/>
|
||||
|
||||
{{template "head_common" .HeadCommonPartial}}
|
||||
</head>
|
||||
|
||||
<body
|
||||
class="mb-16 bg-white text-gray-600 print:text-black dark:bg-neutral-900 dark:text-neutral-50"
|
||||
>
|
||||
{{template "top" .}}
|
||||
|
||||
<div class="mx-auto px-4 sm:flex sm:items-center sm:justify-center sm:px-0">
|
||||
<div
|
||||
class="w-full max-w-screen-2xl justify-between gap-10 overflow-visible print:w-full sm:flex sm:w-11/12 sm:px-4 md:w-10/12 lg:w-9/12 lg:gap-48vw"
|
||||
>
|
||||
<header
|
||||
class="relative top-auto flex basis-1/4 items-center self-start sm:sticky sm:top-8 sm:mt-8 sm:block sm:items-start"
|
||||
>
|
||||
<div
|
||||
class="hidden basis-[64%] items-center overflow-hidden text-left"
|
||||
_="on load or scroll from window or resize from window get #profile_name then measure its top, height then if top is less than height / -2 or height is 0 remove .hidden otherwise add .hidden"
|
||||
>
|
||||
<div class="mb-3 sm:text-center">
|
||||
<div class="text-2xl">{{.Metadata.Name}}</div>
|
||||
{{if not (eq .Metadata.Name .Metadata.DisplayName)}}
|
||||
<div class="text-base text-stone-400">
|
||||
{{.Metadata.DisplayName}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="imgclip max-w-[40%] basis-2/5 overflow-hidden sm:max-w-full sm:basis-auto"
|
||||
>
|
||||
<img class="block h-auto w-full" src="{{.Metadata.Picture}}" />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="w-full break-words print:w-full sm:w-1/2">
|
||||
<header class="mb-6 hidden leading-5 sm:flex sm:items-center">
|
||||
<h1>
|
||||
<div id="profile_name" class="text-2xl">{{.Metadata.Name}}</div>
|
||||
{{if not (eq .Metadata.Name .Metadata.DisplayName)}}
|
||||
<div class="text-xl text-stone-400">
|
||||
{{.Metadata.DisplayName}}
|
||||
</div>
|
||||
{{end}}
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/2 bg-zinc-100 sm:-ml-2.5 dark:bg-zinc-700"
|
||||
></div>
|
||||
{{ if not (eq "" .Metadata.Website) }}
|
||||
<div class="mb-6 leading-5">
|
||||
<a
|
||||
class="border-b-2 border-b-gray-300 pb-0.5 hover:text-strongpink"
|
||||
href="{{.NormalizedAuthorWebsiteURL}}"
|
||||
>{{.Metadata.Website}}</a
|
||||
>
|
||||
</div>
|
||||
{{ end }} {{ if not (eq "" .RenderedAuthorAboutText) }}
|
||||
<div
|
||||
class="prose mb-6 leading-5 dark:prose-invert prose-headings:font-light sm:prose-a:text-justify"
|
||||
dir="auto"
|
||||
>
|
||||
{{.RenderedAuthorAboutText}}
|
||||
</div>
|
||||
{{ end }} {{ if or (not (eq "" .Metadata.Website)) (not (eq ""
|
||||
.RenderedAuthorAboutText)) }}
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 sm:-ml-2.5 dark:bg-zinc-700"
|
||||
></div>
|
||||
{{ end }}
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Public Key</div>
|
||||
{{.Npub}}
|
||||
</div>
|
||||
<div class="mb-6 leading-5">
|
||||
{{ if not (eq "" .Metadata.NIP05) }}
|
||||
<div class="text-sm text-strongpink">NIP-05 Address</div>
|
||||
{{.Metadata.NIP05}} {{ end }}
|
||||
</div>
|
||||
<div class="mb-6 leading-5">
|
||||
{{ if not (eq "" .Metadata.LUD16) }}
|
||||
<div class="text-sm text-strongpink">NIP-57 Address</div>
|
||||
{{.Metadata.LUD16}} {{ end }}
|
||||
</div>
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Profile Code</div>
|
||||
{{.Nprofile}}
|
||||
</div>
|
||||
|
||||
{{ if not (eq 0 (len .AuthorRelays)) }}
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Publishing to</div>
|
||||
{{range $index, $element := .AuthorRelays}}
|
||||
<a
|
||||
href="/r/{{$element}}"
|
||||
class="mr-1 mt-2 inline-block max-w-full rounded-lg border border-slate-300 px-2 py-0.5 hover:border hover:border-solid hover:border-strongpink hover:bg-strongpink hover:text-white"
|
||||
>{{$element}}</a
|
||||
>
|
||||
{{end}}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<!---->
|
||||
|
||||
{{template "details" .DetailsPartial}}
|
||||
|
||||
<div
|
||||
_="init fetch /profile-last-notes/{{.Npub}} then put the result into me end "
|
||||
>
|
||||
{{if not (eq 0 (len .LastNotes))}}
|
||||
<aside>
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 sm:-ml-2.5 dark:bg-zinc-700"
|
||||
></div>
|
||||
<nav class="mb-6 leading-5">
|
||||
<h2 class="text-2xl text-strongpink">Last Notes</h2>
|
||||
{{range $i, $ee := .LastNotes}}
|
||||
<a
|
||||
class="my-8 block no-underline hover:-ml-6 hover:border-l-05rem hover:border-solid hover:border-l-gray-100 hover:pl-4 dark:hover:border-l-zinc-700"
|
||||
href="/{{$ee.Nevent}}"
|
||||
>
|
||||
</a>
|
||||
{{end}}
|
||||
</nav>
|
||||
</aside>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 sm:-ml-2.5 dark:bg-zinc-700"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
{{template "clients" .ClientsPartial}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "footer" .}}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,157 +0,0 @@
|
||||
<!doctype html>
|
||||
<html class="theme--default text-lg font-light print:text-base sm:text-xl">
|
||||
<meta charset="UTF-8" />
|
||||
<head>
|
||||
<title>Nostr Relay {{.Hostname}} - {{.Info.Name}}</title>
|
||||
<meta property="og:title" content="{{.Hostname}} - nostr relay" />
|
||||
<meta name="twitter:title" content="{{.Hostname}} - nostr relay" />
|
||||
<meta property="og:site_name" content="{{.Hostname}} - nostr relay" />
|
||||
{{ if not (eq "" .Info.Icon) }}
|
||||
<meta property="og:image" content="{{.Info.Icon}}" />
|
||||
<meta name="twitter:image" content="{{.Proxy}}{{.Info.Icon}}" />
|
||||
{{end}} {{ if not (eq "" .Info.Description) }}
|
||||
<meta property="og:description" content="{{.Info.Description}}" />
|
||||
<meta name="twitter:description" content="{{.Info.Description}}" />
|
||||
{{end}}
|
||||
<meta name="twitter:card" content="summary" />
|
||||
|
||||
<link
|
||||
rel="sitemap"
|
||||
type="application/xml"
|
||||
title="Sitemap for {{.Hostname}}"
|
||||
href="/r/{{.Hostname}}.xml"
|
||||
/>
|
||||
|
||||
<link
|
||||
rel="alternate"
|
||||
type="application/atom+xml"
|
||||
title="RSS"
|
||||
href="/r/{{.Hostname}}.rss"
|
||||
/>
|
||||
|
||||
{{template "head_common" .HeadCommonPartial}}
|
||||
</head>
|
||||
|
||||
<body
|
||||
class="mb-16 bg-white text-gray-600 dark:bg-neutral-900 dark:text-neutral-50 print:text-black"
|
||||
>
|
||||
{{template "top" .}}
|
||||
|
||||
<div class="mx-auto px-4 sm:flex sm:items-center sm:justify-center sm:px-0">
|
||||
<div
|
||||
class="w-full max-w-screen-2xl justify-between gap-10 overflow-visible px-4 print:w-full sm:flex md:w-10/12 lg:w-9/12 lg:gap-48vw"
|
||||
>
|
||||
<div
|
||||
class="relative top-auto flex basis-1/4 items-center self-start sm:sticky sm:top-8 sm:mt-8 sm:items-start"
|
||||
>
|
||||
<div
|
||||
class="flex hidden basis-2/3 items-center overflow-hidden text-left text-2xl sm:break-all"
|
||||
_="on load or scroll from window or resize from window get #relay_name then measure its top, height then if top is less than height / -2 or height is 0 add .flex then remove .hidden otherwise remove .flex then add .hidden"
|
||||
>
|
||||
{{.Info.Name}}
|
||||
</div>
|
||||
<div
|
||||
class="imgclip max-w-full basis-2/5 overflow-hidden sm:basis-auto"
|
||||
>
|
||||
<img class="block h-auto w-full" src="{{.Info.Icon}}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full break-words print:w-full sm:w-1/2">
|
||||
<header class="mb-6 hidden leading-5 sm:flex sm:items-center">
|
||||
<h1>
|
||||
<div id="relay_name" class="text-2xl">{{.Info.Name}}</div>
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/2 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
<div class="mb-6 leading-5">
|
||||
<a
|
||||
class="border-b-2 border-b-gray-300 pb-0.5 hover:text-strongpink"
|
||||
href="https://{{.Hostname}}"
|
||||
target="_blank"
|
||||
_="on mouseenter set my innerText to my.innerText.replace('wss://', 'https://')
|
||||
on mouseleave set my innerText to my.innerText.replace('https://', 'wss://')"
|
||||
>wss://{{.Hostname}}</a
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="prose mb-6 leading-5 dark:prose-invert prose-headings:font-light sm:prose-a:text-justify"
|
||||
dir="auto"
|
||||
>
|
||||
{{.Info.Description}}
|
||||
</div>
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/2 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
|
||||
{{ if not (eq "" .Info.PubKey) }}
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Public Key</div>
|
||||
{{.Info.PubKey}}
|
||||
</div>
|
||||
{{ end }}
|
||||
<!---->
|
||||
{{ if not (eq "" .Info.Contact) }}
|
||||
<div class="mb-6 leading-5">
|
||||
<div class="text-sm text-strongpink">Contact</div>
|
||||
<a href="{{.Info.Contact}}">{{.Info.Contact}}</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<div
|
||||
class="-ml-4 mb-6 h-1.5 w-1/3 bg-zinc-100 dark:bg-zinc-700 sm:-ml-2.5"
|
||||
></div>
|
||||
<aside>
|
||||
<div class="mb-6 leading-5">
|
||||
<h2 class="text-2xl text-strongpink">Last Notes</h2>
|
||||
{{range $i, $ee := .LastNotes}}
|
||||
<a
|
||||
class="my-8 block no-underline hover:-ml-6 hover:border-l-05rem hover:border-solid hover:border-l-gray-100 hover:pl-4 dark:hover:border-l-zinc-700"
|
||||
href="/{{$ee.Nevent}}"
|
||||
>
|
||||
<div
|
||||
class="-ml-2.5 mb-1.5 flex flex-row border-b-4 border-solid border-b-gray-100 pb-1 pl-2.5 dark:border-b-neutral-800"
|
||||
>
|
||||
<div class="text-sm text-strongpink">
|
||||
{{$ee.CreatedAtStr}}
|
||||
</div>
|
||||
<br />
|
||||
{{if $ee.IsReply}}
|
||||
<div class="ml-2 text-xs text-gray-300 dark:text-gray-400">
|
||||
- reply
|
||||
</div>
|
||||
{{end}}
|
||||
<div
|
||||
class="ml-auto text-xs text-zinc-700 dark:text-neutral-50"
|
||||
>
|
||||
by
|
||||
<span
|
||||
class="rounded bg-lavender px-1 hover:bg-strongpink hover:text-white dark:bg-garnet dark:hover:bg-strongpink"
|
||||
href="/{{$ee.Npub}}"
|
||||
>{{$ee.NpubShort}}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mt-0.5 max-h-40 basis-full overflow-hidden hover:text-strongpink"
|
||||
_="on load if my scrollHeight > my offsetHeight add .gradient"
|
||||
dir="auto"
|
||||
>
|
||||
{{$ee.Preview}}
|
||||
</div>
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
{{template "clients" .ClientsPartial}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "footer" .}}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,50 +0,0 @@
|
||||
<meta charset="UTF-8" />
|
||||
<!-- check https://nikstar.me/post/instant-view/ for more information on how this was set up -->
|
||||
|
||||
<!-- required stuff so telegram treats us like a medium.com article -->
|
||||
<meta property="al:android:app_name" content="Medium" />
|
||||
<meta property="article:published_time" content="{{.CreatedAt}}" />
|
||||
|
||||
<!-- stuff that goes in the actual telegram message preview -->
|
||||
<meta property="og:site_name" content="{{.AuthorLong}}" />
|
||||
{{ if not (eq "" .Description) }}
|
||||
<meta property="og:description" content="{{.Description}}" />
|
||||
{{ end }}
|
||||
<!---->
|
||||
{{ if not (eq "" .Image) }}
|
||||
<meta property="og:image" content="{{.Image}}" />
|
||||
{{ end }}
|
||||
<!---->
|
||||
{{ if not (eq "" .Video) }}
|
||||
<meta property="og:video" content="{{.Video}}" />
|
||||
<meta property="og:video:secure_url" content="{{.Video}}" />
|
||||
<meta property="og:video:type" content="video/{{.VideoType}}" />
|
||||
{{ end }}
|
||||
|
||||
<!-- stuff that affects the content inside the preview window -->
|
||||
<meta name="author" content="{{.Metadata.ShortName}}" />
|
||||
<meta name="telegram:channel" content="@nostr_protocol" />
|
||||
|
||||
<!-- basic content of the preview window -->
|
||||
<article>
|
||||
<h1>
|
||||
{{ if not (eq "" .Subject) }} {{.Subject}} {{ else }}
|
||||
<a href="/{{.Metadata.Npub}}">{{.Metadata.ShortName}}</a> on Nostr: {{ end
|
||||
}}
|
||||
</h1>
|
||||
|
||||
{{ if not (eq "" .ParentLink) }}
|
||||
<aside>in reply to {{ .ParentLink }}</aside>
|
||||
{{ end }}
|
||||
<!---->
|
||||
{{ if not (eq "" .Summary) }}
|
||||
<aside>{{ .Summary }}</aside>
|
||||
{{ end }}
|
||||
|
||||
<!---->
|
||||
{{.Content}} {{ if not (eq "" .Subject) }}
|
||||
<aside>
|
||||
<a href="/{{.Metadata.Npub}}">{{.Metadata.ShortName}}</a>
|
||||
</aside>
|
||||
{{ end }}
|
||||
</article>
|
||||
@@ -1,45 +0,0 @@
|
||||
<header class="items-center p-4 pb-6 text-sm print:block sm:flex sm:text-base">
|
||||
<a
|
||||
href="https://nostr.com"
|
||||
class="text-md text-right text-sm print:hidden md:basis-1/6"
|
||||
target="_blank"
|
||||
>What is <span class="text-strongpink">Nostr</span>?</a
|
||||
>
|
||||
<div
|
||||
class="print:hidden; relative float-right h-4 w-4 cursor-pointer rounded-full text-gray-100 dark:text-gray-700 sm:fixed sm:right-4 sm:top-4 sm:float-none"
|
||||
_="on click tell <html /> toggle between .theme--dark and .theme--default then get your @class then get it[0].split('--')[1].split(' ')[0] then set localStorage.theme to it
|
||||
on load tell <html /> get localStorage.theme then if it is 'dark' add .theme--dark then remove .theme--default else if it is not 'default' then get window.matchMedia('(prefers-color-scheme: dark)').matches then if it is true add .theme--dark then remove .theme--default end"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-prefix="fas"
|
||||
class="block dark:hidden"
|
||||
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="hidden dark:block"
|
||||
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>
|
||||
</header>
|
||||
|
||||
<script type="text/hyperscript">
|
||||
on beforeprint from window tell <html /> remove .theme--dark add .theme--default
|
||||
on afterprint from window tell <html /> add .theme--dark remove .theme--default
|
||||
</script>
|
||||
47
top.templ
Normal file
47
top.templ
Normal file
@@ -0,0 +1,47 @@
|
||||
package main
|
||||
|
||||
templ topTemplate() {
|
||||
<header class="items-center p-4 pb-6 text-sm print:block sm:flex sm:text-base">
|
||||
<a
|
||||
href="https://nostr.com"
|
||||
class="text-md text-right text-sm print:hidden md:basis-1/6"
|
||||
target="_blank"
|
||||
>What is <span class="text-strongpink">Nostr</span>?</a>
|
||||
<div
|
||||
class="print:hidden; relative float-right h-4 w-4 cursor-pointer rounded-full text-gray-100 dark:text-gray-700 sm:fixed sm:right-4 sm:top-4 sm:float-none"
|
||||
_="on click tell <html /> toggle between .theme--dark and .theme--default then get your @class then get it[0].split('--')[1].split(' ')[0] then set localStorage.theme to it
|
||||
on load tell <html /> get localStorage.theme then if it is 'dark' add .theme--dark then remove .theme--default else if it is not 'default' then get window.matchMedia('(prefers-color-scheme: dark)').matches then if it is true add .theme--dark then remove .theme--default end"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-prefix="fas"
|
||||
class="block dark:hidden"
|
||||
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="hidden dark:block"
|
||||
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>
|
||||
</header>
|
||||
<script type="text/hyperscript">
|
||||
on beforeprint from window tell <html /> remove .theme--dark add .theme--default
|
||||
on afterprint from window tell <html /> add .theme--dark remove .theme--default
|
||||
</script>
|
||||
}
|
||||
65
xml-pages.go
Normal file
65
xml-pages.go
Normal file
@@ -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 }
|
||||
Reference in New Issue
Block a user