mirror of
https://github.com/aljazceru/njump.git
synced 2025-12-17 22:34:25 +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
|
static/tailwind-bundle.min.css
|
||||||
yarn.lock
|
yarn.lock
|
||||||
package-lock.json
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
|
||||||
"html/template"
|
"html/template"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
"github.com/nbd-wtf/go-nostr/nip10"
|
|
||||||
"github.com/nbd-wtf/go-nostr/nip19"
|
"github.com/nbd-wtf/go-nostr/nip19"
|
||||||
sdk "github.com/nbd-wtf/nostr-sdk"
|
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 {
|
type Data struct {
|
||||||
templateId TemplateID
|
templateId TemplateID
|
||||||
event *nostr.Event
|
event *nostr.Event
|
||||||
relays []string
|
relays []string
|
||||||
npub string
|
|
||||||
npubShort string
|
|
||||||
nprofile string
|
nprofile string
|
||||||
nevent string
|
nevent string
|
||||||
neventNaked string
|
neventNaked string
|
||||||
@@ -151,47 +42,6 @@ type Data struct {
|
|||||||
kind1311Metadata *Kind1311Metadata
|
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) {
|
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
|
// 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)
|
event, relays, err := getEvent(ctx, code, nil)
|
||||||
@@ -218,10 +68,10 @@ func grabData(ctx context.Context, code string, isProfileSitemap bool) (*Data, e
|
|||||||
relays: relays,
|
relays: relays,
|
||||||
}
|
}
|
||||||
|
|
||||||
data.npub, _ = nip19.EncodePublicKey(event.PubKey)
|
npub, _ := nip19.EncodePublicKey(event.PubKey)
|
||||||
data.npubShort = data.npub[:8] + "…" + data.npub[len(data.npub)-4:]
|
npubShort := npub[:8] + "…" + npub[len(npub)-4:]
|
||||||
data.authorLong = data.npub // hopefully will be replaced later
|
data.authorLong = npub // hopefully will be replaced later
|
||||||
data.authorShort = data.npubShort // hopefully will be replaced later
|
data.authorShort = npubShort // hopefully will be replaced later
|
||||||
data.nevent, _ = nip19.EncodeEvent(event.ID, relaysForNip19, event.PubKey)
|
data.nevent, _ = nip19.EncodeEvent(event.ID, relaysForNip19, event.PubKey)
|
||||||
data.neventNaked, _ = nip19.EncodeEvent(event.ID, nil, event.PubKey)
|
data.neventNaked, _ = nip19.EncodeEvent(event.ID, nil, event.PubKey)
|
||||||
data.naddr = ""
|
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
|
go 1.21.4
|
||||||
|
|
||||||
require (
|
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/dgraph-io/badger/v4 v4.2.0
|
||||||
github.com/fiatjaf/eventstore v0.3.6
|
github.com/fiatjaf/eventstore v0.3.6
|
||||||
github.com/fiatjaf/khatru v0.2.1
|
github.com/fiatjaf/khatru v0.2.1
|
||||||
@@ -32,7 +33,7 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
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/aymerick/douceur v0.2.0 // indirect
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
|
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
|
||||||
github.com/btcsuite/btcd/btcutil v1.1.3 // 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/josharian/intern v1.0.0 // indirect
|
||||||
github.com/klauspost/compress v1.17.2 // indirect
|
github.com/klauspost/compress v1.17.2 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/puzpuzpuz/xsync/v2 v2.5.1 // 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=
|
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/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.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
|
||||||
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
|
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/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 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
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.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
|
||||||
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
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 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
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=
|
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.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.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.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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
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/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 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
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/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 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
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-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.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 h1:NGQoPtwGVcbGkKfvyYk1yRqknzBuoMiUrO6R7uFTPlw=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.24/go.mod h1:ArQySAMps0790cHSkdPEJ7bGkF2VePWH773hsJNSHf8=
|
github.com/microcosm-cc/bluemonday v1.0.24/go.mod h1:ArQySAMps0790cHSkdPEJ7bGkF2VePWH773hsJNSHf8=
|
||||||
github.com/nbd-wtf/emoji v0.0.3 h1:YtkT7MVPXvqU1SQjvC/CShlWexnREzqNCxmhUnL00CA=
|
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/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.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/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 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
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-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-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-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-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-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 h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4=
|
||||||
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
|
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/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.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.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-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-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-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-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-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-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/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-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-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-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 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
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=
|
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-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-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-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-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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-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-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-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-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-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.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
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/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.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.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.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 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
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=
|
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-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-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.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-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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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')
|
export PATH := "./node_modules/.bin:" + env_var('PATH')
|
||||||
|
|
||||||
dev:
|
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:
|
check-samples:
|
||||||
#!/usr/bin/env xonsh
|
#!/usr/bin/env xonsh
|
||||||
@@ -21,27 +48,3 @@ check-samples:
|
|||||||
for code in samples:
|
for code in samples:
|
||||||
$(chromium @(base_url + '/' + code))
|
$(chromium @(base_url + '/' + code))
|
||||||
$(chromium @(base_url + '/njump/image/' + 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/*
|
//go:embed static/*
|
||||||
var static embed.FS
|
var static embed.FS
|
||||||
|
|
||||||
//go:embed templates/*
|
|
||||||
var templates embed.FS
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
s Settings
|
s Settings
|
||||||
log = zerolog.New(os.Stderr).Output(zerolog.ConsoleWriter{Out: os.Stdout}).With().Timestamp().Logger()
|
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"
|
"strings"
|
||||||
|
|
||||||
"github.com/nbd-wtf/go-nostr/nip11"
|
"github.com/nbd-wtf/go-nostr/nip11"
|
||||||
sdk "github.com/nbd-wtf/nostr-sdk"
|
|
||||||
"github.com/tylermmorton/tmpl"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type TemplateID int
|
type OpenGraphParams struct {
|
||||||
|
|
||||||
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 {
|
|
||||||
SingleTitle string
|
SingleTitle string
|
||||||
// x (we will always render just the singletitle if we have that)
|
// x (we will always render just the singletitle if we have that)
|
||||||
Superscript string
|
Superscript string
|
||||||
@@ -49,44 +27,7 @@ type OpenGraphPartial struct {
|
|||||||
Text string
|
Text string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*OpenGraphPartial) TemplateText() string { return tmplOpenGraph }
|
type DetailsParams struct {
|
||||||
|
|
||||||
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 {
|
|
||||||
HideDetails bool
|
HideDetails bool
|
||||||
CreatedAt string
|
CreatedAt string
|
||||||
EventJSON template.HTML
|
EventJSON template.HTML
|
||||||
@@ -103,41 +44,15 @@ type DetailsPartial struct {
|
|||||||
LiveEvent *Kind30311Metadata
|
LiveEvent *Kind30311Metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*DetailsPartial) TemplateText() string { return tmplDetails }
|
type HeadParams struct {
|
||||||
|
IsProfile bool
|
||||||
var (
|
TailwindDebugStuff template.HTML
|
||||||
//go:embed templates/clients.html
|
NaddrNaked string
|
||||||
tmplClients string
|
NeventNaked string
|
||||||
ClientsTemplate = tmpl.MustCompile(&ClientsPartial{})
|
Oembed string
|
||||||
)
|
|
||||||
|
|
||||||
//tmpl:bind clients.html
|
|
||||||
type ClientsPartial struct {
|
|
||||||
Clients []ClientReference
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*ClientsPartial) TemplateText() string { return tmplClients }
|
type TelegramInstantViewParams struct {
|
||||||
|
|
||||||
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 {
|
|
||||||
Video string
|
Video string
|
||||||
VideoType string
|
VideoType string
|
||||||
Image string
|
Image string
|
||||||
@@ -145,42 +60,22 @@ type TelegramInstantViewPage struct {
|
|||||||
Content template.HTML
|
Content template.HTML
|
||||||
Description string
|
Description string
|
||||||
Subject string
|
Subject string
|
||||||
Metadata sdk.ProfileMetadata
|
Metadata Metadata
|
||||||
AuthorLong string
|
AuthorLong string
|
||||||
CreatedAt string
|
CreatedAt string
|
||||||
ParentLink template.HTML
|
ParentLink template.HTML
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*TelegramInstantViewPage) TemplateText() string { return tmplTelegramInstantView }
|
type HomePageParams struct {
|
||||||
|
HeadParams
|
||||||
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"`
|
|
||||||
|
|
||||||
Host string
|
Host string
|
||||||
Npubs []string
|
Npubs []string
|
||||||
LastNotes []string
|
LastNotes []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*HomePage) TemplateText() string { return tmplHomePage }
|
type ArchivePageParams struct {
|
||||||
|
HeadParams
|
||||||
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"`
|
|
||||||
|
|
||||||
Title string
|
Title string
|
||||||
PathPrefix string
|
PathPrefix string
|
||||||
@@ -191,64 +86,36 @@ type ArchivePage struct {
|
|||||||
PrevPage int
|
PrevPage int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*ArchivePage) TemplateText() string { return tmplArchive }
|
type OtherPageParams struct {
|
||||||
|
HeadParams
|
||||||
var (
|
DetailsParams
|
||||||
//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"`
|
|
||||||
|
|
||||||
Kind int
|
Kind int
|
||||||
KindDescription string
|
KindDescription string
|
||||||
Alt string
|
Alt string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*OtherPage) TemplateText() string { return tmplOther }
|
type NotePageParams struct {
|
||||||
|
OpenGraphParams
|
||||||
var (
|
HeadParams
|
||||||
//go:embed templates/note.html
|
DetailsParams
|
||||||
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"`
|
|
||||||
|
|
||||||
Content template.HTML
|
Content template.HTML
|
||||||
CreatedAt string
|
CreatedAt string
|
||||||
Metadata sdk.ProfileMetadata
|
Metadata Metadata
|
||||||
Npub string
|
Npub string
|
||||||
NpubShort string
|
NpubShort string
|
||||||
ParentLink template.HTML
|
ParentLink template.HTML
|
||||||
SeenOn []string
|
SeenOn []string
|
||||||
Subject string
|
Subject string
|
||||||
TitleizedContent string
|
TitleizedContent string
|
||||||
|
Clients []ClientReference
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*NotePage) TemplateText() string { return tmplNote }
|
type EmbeddedNoteParams struct {
|
||||||
|
|
||||||
var (
|
|
||||||
//go:embed templates/embedded_note.html
|
|
||||||
tmplEmbeddedNote string
|
|
||||||
EmbeddedNoteTemplate = tmpl.MustCompile(&EmbeddedNotePage{})
|
|
||||||
)
|
|
||||||
|
|
||||||
type EmbeddedNotePage struct {
|
|
||||||
Content template.HTML
|
Content template.HTML
|
||||||
CreatedAt string
|
CreatedAt string
|
||||||
Metadata sdk.ProfileMetadata
|
Metadata Metadata
|
||||||
Npub string
|
Npub string
|
||||||
NpubShort string
|
NpubShort string
|
||||||
SeenOn []string
|
SeenOn []string
|
||||||
@@ -256,27 +123,16 @@ type EmbeddedNotePage struct {
|
|||||||
Url string
|
Url string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*EmbeddedNotePage) TemplateText() string { return tmplEmbeddedNote }
|
type ProfilePageParams struct {
|
||||||
|
HeadParams
|
||||||
var (
|
DetailsParams
|
||||||
//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"`
|
|
||||||
|
|
||||||
AuthorRelays []string
|
AuthorRelays []string
|
||||||
Content string
|
Content string
|
||||||
CreatedAt string
|
CreatedAt string
|
||||||
Domain string
|
Domain string
|
||||||
LastNotes []EnhancedEvent
|
LastNotes []EnhancedEvent
|
||||||
Metadata sdk.ProfileMetadata
|
Metadata Metadata
|
||||||
NormalizedAuthorWebsiteURL string
|
NormalizedAuthorWebsiteURL string
|
||||||
RenderedAuthorAboutText template.HTML
|
RenderedAuthorAboutText template.HTML
|
||||||
Nevent string
|
Nevent string
|
||||||
@@ -285,34 +141,15 @@ type ProfilePage struct {
|
|||||||
IsReply string
|
IsReply string
|
||||||
Proxy string
|
Proxy string
|
||||||
Title string
|
Title string
|
||||||
|
Clients []ClientReference
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*ProfilePage) TemplateText() string { return tmplProfile }
|
type EmbeddedProfileParams struct {
|
||||||
|
|
||||||
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 {
|
|
||||||
AuthorRelays []string
|
AuthorRelays []string
|
||||||
Content string
|
Content string
|
||||||
CreatedAt string
|
CreatedAt string
|
||||||
Domain string
|
Domain string
|
||||||
Metadata sdk.ProfileMetadata
|
Metadata Metadata
|
||||||
NormalizedAuthorWebsiteURL string
|
NormalizedAuthorWebsiteURL string
|
||||||
RenderedAuthorAboutText template.HTML
|
RenderedAuthorAboutText template.HTML
|
||||||
Nevent string
|
Nevent string
|
||||||
@@ -322,25 +159,14 @@ type EmbeddedProfilePage struct {
|
|||||||
Title string
|
Title string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*EmbeddedProfilePage) TemplateText() string { return tmplEmbeddedProfile }
|
type FileMetadataPageParams struct {
|
||||||
|
OpenGraphParams
|
||||||
var (
|
HeadParams
|
||||||
//go:embed templates/file_metadata.html
|
DetailsParams
|
||||||
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"`
|
|
||||||
|
|
||||||
Content template.HTML
|
Content template.HTML
|
||||||
CreatedAt string
|
CreatedAt string
|
||||||
Metadata sdk.ProfileMetadata
|
Metadata Metadata
|
||||||
Npub string
|
Npub string
|
||||||
NpubShort string
|
NpubShort string
|
||||||
ParentLink template.HTML
|
ParentLink template.HTML
|
||||||
@@ -353,27 +179,18 @@ type FileMetadataPage struct {
|
|||||||
FileMetadata Kind1063Metadata
|
FileMetadata Kind1063Metadata
|
||||||
IsImage bool
|
IsImage bool
|
||||||
IsVideo bool
|
IsVideo bool
|
||||||
|
|
||||||
|
Clients []ClientReference
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*FileMetadataPage) TemplateText() string { return tmplFileMetadata }
|
type LiveEventPageParams struct {
|
||||||
|
OpenGraphParams
|
||||||
var (
|
HeadParams
|
||||||
//go:embed templates/live_event.html
|
DetailsParams
|
||||||
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"`
|
|
||||||
|
|
||||||
Content template.HTML
|
Content template.HTML
|
||||||
CreatedAt string
|
CreatedAt string
|
||||||
Metadata sdk.ProfileMetadata
|
Metadata Metadata
|
||||||
Npub string
|
Npub string
|
||||||
NpubShort string
|
NpubShort string
|
||||||
ParentLink template.HTML
|
ParentLink template.HTML
|
||||||
@@ -384,27 +201,18 @@ type LiveEventPage struct {
|
|||||||
Alt string
|
Alt string
|
||||||
|
|
||||||
LiveEvent Kind30311Metadata
|
LiveEvent Kind30311Metadata
|
||||||
|
|
||||||
|
Clients []ClientReference
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*LiveEventPage) TemplateText() string { return tmplLiveEvent }
|
type LiveEventMessagePageParams struct {
|
||||||
|
OpenGraphParams
|
||||||
var (
|
HeadParams
|
||||||
//go:embed templates/live_event_message.html
|
DetailsParams
|
||||||
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"`
|
|
||||||
|
|
||||||
Content template.HTML
|
Content template.HTML
|
||||||
CreatedAt string
|
CreatedAt string
|
||||||
Metadata sdk.ProfileMetadata
|
Metadata Metadata
|
||||||
Npub string
|
Npub string
|
||||||
NpubShort string
|
NpubShort string
|
||||||
ParentLink template.HTML
|
ParentLink template.HTML
|
||||||
@@ -415,114 +223,41 @@ type LiveEventMessagePage struct {
|
|||||||
Alt string
|
Alt string
|
||||||
|
|
||||||
LiveEventMessage Kind1311Metadata
|
LiveEventMessage Kind1311Metadata
|
||||||
|
|
||||||
|
Clients []ClientReference
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*LiveEventMessagePage) TemplateText() string { return tmplLiveEventMessage }
|
type RelayPageParams struct {
|
||||||
|
HeadParams
|
||||||
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"`
|
|
||||||
|
|
||||||
Info *nip11.RelayInformationDocument
|
Info *nip11.RelayInformationDocument
|
||||||
Hostname string
|
Hostname string
|
||||||
Proxy string
|
Proxy string
|
||||||
LastNotes []EnhancedEvent
|
LastNotes []EnhancedEvent
|
||||||
ModifiedAt string
|
ModifiedAt string
|
||||||
|
Clients []ClientReference
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*RelayPage) TemplateText() string { return tmplRelay }
|
type ErrorPageParams struct {
|
||||||
|
HeadParams
|
||||||
var (
|
Message string
|
||||||
//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
|
|
||||||
Errors string
|
Errors string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ErrorPage) TemplateText() string {
|
func (e *ErrorPageParams) MessageHTML() string {
|
||||||
if e.Message != "" {
|
if e.Message != "" {
|
||||||
return tmplError
|
return "<error omitted>"
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case strings.Contains(e.Errors, "invalid checksum"):
|
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"):
|
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."
|
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"):
|
case strings.Contains(e.Errors, "invalid bech32 string length"),
|
||||||
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...)"
|
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:
|
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 = {
|
module.exports = {
|
||||||
content: ['./templates/*.html', './*.go'],
|
content: ['./*.go', './*.templ'],
|
||||||
darkMode: ['class', '.theme--dark'],
|
darkMode: ['class', '.theme--dark'],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
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