mirror of
https://github.com/aljazceru/njump.git
synced 2026-01-31 03:34:37 +01:00
Support embedding notes in external web pages
SQASH
This commit is contained in:
1
main.go
1
main.go
@@ -105,6 +105,7 @@ func main() {
|
||||
mux.HandleFunc("/e/", redirectFromESlash)
|
||||
mux.HandleFunc("/p/", redirectFromPSlash)
|
||||
mux.HandleFunc("/favicon.ico", redirectToFavicon)
|
||||
mux.HandleFunc("/embed/", renderEmbedjs)
|
||||
mux.HandleFunc("/", renderEvent)
|
||||
|
||||
log.Print("listening at http://0.0.0.0:" + s.Port)
|
||||
|
||||
19
pages.go
19
pages.go
@@ -236,6 +236,25 @@ type NotePage struct {
|
||||
|
||||
func (*NotePage) TemplateText() string { return tmplNote }
|
||||
|
||||
var (
|
||||
//go:embed templates/embedded_note.html
|
||||
tmplEmbeddedNote string
|
||||
EmbeddedNoteTemplate = tmpl.MustCompile(&EmbeddedNotePage{})
|
||||
)
|
||||
|
||||
type EmbeddedNotePage struct {
|
||||
Content template.HTML
|
||||
CreatedAt string
|
||||
Metadata sdk.ProfileMetadata
|
||||
Npub string
|
||||
NpubShort string
|
||||
SeenOn []string
|
||||
Subject string
|
||||
Url string
|
||||
}
|
||||
|
||||
func (*EmbeddedNotePage) TemplateText() string { return tmplEmbeddedNote }
|
||||
|
||||
var (
|
||||
//go:embed templates/profile.html
|
||||
tmplProfile string
|
||||
|
||||
63
render_embedded.go
Normal file
63
render_embedded.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
"html/template"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func renderEmbedded(w http.ResponseWriter, r *http.Request, code string) {
|
||||
fmt.Println(r.URL.Path, "@.", r.Header.Get("user-agent"))
|
||||
|
||||
data, err := grabData(r.Context(), code, false)
|
||||
if err != nil {
|
||||
w.Header().Set("Cache-Control", "max-age=60")
|
||||
errorPage := &ErrorPage{
|
||||
Errors: err.Error(),
|
||||
}
|
||||
errorPage.TemplateText()
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
ErrorTemplate.Render(w, errorPage)
|
||||
return
|
||||
}
|
||||
|
||||
var subject string
|
||||
for _, tag := range data.event.Tags {
|
||||
if tag[0] == "subject" || tag[0] == "title" {
|
||||
subject = tag[1]
|
||||
}
|
||||
}
|
||||
|
||||
if data.event.Kind == 30023 || data.event.Kind == 30024 {
|
||||
data.content = mdToHTML(data.content, data.templateId == TelegramInstantView)
|
||||
} else {
|
||||
// first we run basicFormatting, which turns URLs into their appropriate HTML tags
|
||||
data.content = basicFormatting(html.EscapeString(data.content), true, false)
|
||||
// then we render quotes as HTML, which will also apply basicFormatting to all the internal quotes
|
||||
data.content = renderQuotesAsHTML(r.Context(), data.content, data.templateId == TelegramInstantView)
|
||||
// we must do this because inside <blockquotes> we must treat <img>s differently when telegram_instant_view
|
||||
}
|
||||
|
||||
switch data.templateId {
|
||||
case Note:
|
||||
err = EmbeddedNoteTemplate.Render(w, &EmbeddedNotePage{
|
||||
Content: template.HTML(data.content),
|
||||
CreatedAt: data.createdAt,
|
||||
Metadata: data.metadata,
|
||||
Npub: data.npub,
|
||||
NpubShort: data.npubShort,
|
||||
Subject: subject,
|
||||
Url: code,
|
||||
})
|
||||
|
||||
default:
|
||||
log.Error().Int("templateId", int(data.templateId)).Msg("no way to render")
|
||||
http.Error(w, "tried to render an unsupported template at render_event.go", 500)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error rendering tmpl")
|
||||
}
|
||||
return
|
||||
}
|
||||
12
render_embedjs.go
Normal file
12
render_embedjs.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func renderEmbedjs(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/javascript")
|
||||
http.ServeFile(w, r, "templates/embed.js")
|
||||
return
|
||||
}
|
||||
@@ -66,6 +66,13 @@ func renderEvent(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the embed parameter is set to "yes"
|
||||
embedParam := r.URL.Query().Get("embed")
|
||||
if embedParam == "yes" {
|
||||
renderEmbedded(w, r, code)
|
||||
return
|
||||
}
|
||||
|
||||
// get data for this event
|
||||
data, err := grabData(r.Context(), code, false)
|
||||
if err != nil {
|
||||
|
||||
43
templates/embed.js
Normal file
43
templates/embed.js
Normal file
@@ -0,0 +1,43 @@
|
||||
(function() {
|
||||
var scriptElement = document.currentScript;
|
||||
|
||||
// Extract the event ID from the script's src attribute
|
||||
var scriptSrc = scriptElement.src;
|
||||
var host = new URL(scriptSrc).origin;
|
||||
|
||||
// Extract the event parameter from the script's src attribute
|
||||
var eventParam = scriptSrc.substring(scriptSrc.lastIndexOf('/') + 1);
|
||||
|
||||
var width = scriptElement.getAttribute('width') || '100%';
|
||||
var height = scriptElement.getAttribute('height') || 'auto';
|
||||
var iframe = document.createElement('iframe');
|
||||
iframe.src = host + '/' + eventParam + '?embed=yes';
|
||||
|
||||
// Basic styles
|
||||
iframe.style.width = width;
|
||||
iframe.style.height = height;
|
||||
iframe.style.border = '2px solid #807a7a';
|
||||
iframe.style.borderRadius = '10px';
|
||||
|
||||
// Add a class to easily permit overwriting the styles
|
||||
iframe.classList.add("nostr-embedded")
|
||||
|
||||
scriptElement.parentNode.insertBefore(iframe, scriptElement.nextSibling);
|
||||
|
||||
// Listen for messages from the iframe
|
||||
window.addEventListener('message', function(event) {
|
||||
// Check if the 'height' attribute is explicitly set
|
||||
if (!scriptElement.hasAttribute('height')) {
|
||||
// Calculate the maximum height based on 50% of the viewport height
|
||||
var maxViewportHeight = window.innerHeight * 0.5;
|
||||
|
||||
// Adjust the height of the iframe based on the received content height
|
||||
var receivedHeight = Math.min(event.data.height, maxViewportHeight);
|
||||
iframe.style.height = receivedHeight + 'px';
|
||||
|
||||
if (receivedHeight < event.data.height) {
|
||||
iframe.contentWindow.postMessage({showGradient: true}, '*');
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
118
templates/embedded_note.html
Normal file
118
templates/embedded_note.html
Normal file
@@ -0,0 +1,118 @@
|
||||
<!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" />
|
||||
<script src="https://unpkg.com/hyperscript.org@0.9.12"></script>
|
||||
<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"
|
||||
>
|
||||
<div
|
||||
class="mx-auto px-4 pb-4 pt-4 sm:flex sm:items-center sm:justify-center sm:px-0"
|
||||
>
|
||||
<a href="/{{ .Url }}" target="_new">
|
||||
<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">
|
||||
<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 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 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>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<svg width="0" height="0" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<clipPath id="svg-shape" clipPathUnits="objectBoundingBox">
|
||||
<path
|
||||
transform="scale(0.005, 0.005)"
|
||||
d="M100,200c43.8,0,68.2,0,84.1-15.9C200,168.2,200,143.8,200,100s0-68.2-15.9-84.1C168.2,0,143.8,0,100,0S31.8,0,15.9,15.9C0,31.8,0,56.2,0,100s0,68.2,15.9,84.1C31.8,200,56.2,200,100,200z"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<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')
|
||||
}
|
||||
})
|
||||
</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"
|
||||
></div>
|
||||
|
||||
<img
|
||||
src="/njump/static/logo.png"
|
||||
class="pointer-events-none fixed bottom-2 right-2 w-[100px]"
|
||||
/>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user