clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

feat/standalone-relay
tinyrelay / tinyclient / social.html
social.html 10.9K
1{{define "social"}}<section id="social">2<header id="social-head"><h1>Social</h1><p>Notes, stories and conversations, newest first.</p></header>3{{if .Error}}{{template "alert" .Error}}{{else}}4{{if .Actor}}{{template "socialComposer" .}}{{else}}<p data-social-welcome><a href="{{signinURL .Base .Path .Query}}">Sign in</a> to join the conversation.</p>{{end}}5<nav id="social-views" aria-label="Social feed"><a href="{{socialQuery .Query "kind" ""}}"{{if eq (.Query.Get "kind") ""}} aria-current="page"{{end}}>All posts</a><a href="{{socialQuery .Query "kind" "notes"}}"{{if eq (.Query.Get "kind") "notes"}} aria-current="page"{{end}}>Notes</a><a href="{{socialQuery .Query "kind" "articles"}}"{{if eq (.Query.Get "kind") "articles"}} aria-current="page"{{end}}>Articles</a></nav>6{{if .Feed}}<ol id="social-feed">{{range .Feed}}{{template "socialPost" (socialContext . $)}}{{end}}</ol>7{{else}}<div id="social-empty"><h2>{{if .Query.Get "q"}}No matching posts{{else}}A place to share what’s on your mind{{end}}</h2><p>{{if .Query.Get "q"}}Try another search or clear the filters.{{else}}Post a quick note or take your time with an article. Conversations grow from here.{{end}}</p></div>{{end}}8{{with index (asMap .Event) "next_cursor"}}<p id="social-pages"><a href="{{socialQuery $.Query "cursor" (str .)}}">Older posts →</a></p>{{end}}9{{end}}<noscript><p>Reading works without JavaScript. Publishing needs JavaScript and a connected signer.</p></noscript>10</section>{{end}}1112{{define "social-thread"}}{{$post := socialContext (index (asMap .Event) "post") .}}<section id="social-thread">13<p data-social-back><a href="/social">← Social</a></p>14{{if .Error}}{{template "alert" .Error}}{{else}}15{{if index $post "parent_url"}}<p data-thread-context><a href="{{index $post "parent_url"}}">↑ Parent post</a>{{if ne (index $post "parent_url") (index $post "root_url")}}{{with index $post "root_url"}} <a href="{{.}}">View full conversation</a>{{end}}{{end}}</p>{{end}}16<ol id="social-thread-post">{{template "socialPost" $post}}</ol>17<div id="social-comments"><h2>Conversation{{with index $post "reply_count"}} <small>{{.}}</small>{{end}}</h2>18{{if .Actor}}{{template "socialCommentComposer" $post}}{{else}}<p><a href="{{signinURL .Base .Path .Query}}">Sign in</a> to reply.</p>{{end}}19{{with index (asMap .Event) "comments"}}<ol>{{range .}}{{template "socialComment" (socialContext . $)}}{{end}}</ol>{{else}}<p data-social-empty-replies>No replies yet. Start the conversation.</p>{{end}}20{{with index (asMap .Event) "next_cursor"}}<p><a href="{{socialThreadPage $.Path $.Query (str .)}}">Older replies →</a></p>{{end}}21</div>{{end}}</section>{{end}}2223{{define "socialPost"}}{{$p := asMap .}}<li data-social-post data-kind="{{str (index $p "kind")}}">24{{template "socialByline" $p}}25{{if eq (str (index $p "kind")) "30023"}}26<article data-post-body="article">27{{if index $p "detail"}}<h1>{{or (str (index $p "title")) "Untitled article"}}</h1>{{else}}<h2><a href="{{socialURL $p}}">{{or (str (index $p "title")) "Untitled article"}}</a></h2>{{end}}28{{with index $p "summary"}}<p data-post-summary>{{str .}}</p>{{else}}{{if not (index $p "detail")}}<p data-post-summary>{{socialPreview $p}}</p>{{end}}{{end}}29{{with socialMediaURL (str (index $p "image"))}}<a href="{{socialURL $p}}" data-post-media><img src="{{.}}" alt="{{or (str (index $p "title")) "Article cover"}}" loading="lazy" referrerpolicy="no-referrer"></a>{{end}}30{{if index $p "detail"}}<div data-article-content>{{socialBody $p}}</div>{{end}}31{{with index $p "read_minutes"}}<small data-read-time>{{.}} min read{{if not (index $p "detail")}} <a href="{{socialURL $p}}">Read article →</a>{{end}}</small>{{end}}32</article>33{{else}}<article data-post-body="note">{{socialBody $p}}</article>{{end}}34{{template "socialActions" $p}}35</li>{{end}}3637{{define "socialByline"}}{{$p := asMap .}}<header data-social-byline><span data-social-avatar><nostr-avatar pubkey="{{index $p "pubkey"}}" aria-hidden="true">{{with socialMediaURL (str (index $p "author_picture"))}}<img src="{{.}}" alt="" loading="lazy" width="36" height="36" referrerpolicy="no-referrer">{{else}}<img src="{{avatarURL (str (index $p "pubkey"))}}" alt="" width="36" height="36" loading="lazy">{{end}}</nostr-avatar></span><span><a href="/social?author={{str (index $p "pubkey")}}"><b>{{if index $p "author_name"}}{{str (index $p "author_name")}}{{else}}{{template "nostrName" (str (index $p "pubkey"))}}{{end}}</b></a> <a href="{{socialURL $p}}"><time datetime="{{datetime (index $p "created_at")}}">{{when (index $p "created_at")}}</time></a>{{if eq (str (index $p "kind")) "30023"}} <small>Article</small>{{end}}{{with index $p "parent_url"}}<small data-social-parent>↳ <a href="{{.}}">Replying{{if index $p "parent_author"}} to {{template "nostrName" (str (index $p "parent_author"))}}{{else}} to a post{{end}}</a></small>{{end}}</span></header>{{end}}3839{{define "socialActions"}}{{$p := asMap .}}<footer data-social-actions>40<a href="{{socialURL $p}}#reply">{{if index $p "reply_count"}}{{index $p "reply_count"}} {{if eq (str (index $p "reply_count")) "1"}}reply{{else}}replies{{end}}{{else}}Reply{{end}}</a>41{{if index $p "viewer"}}<social-reaction actor="{{index $p "viewer"}}" event="{{index $p "id"}}" pubkey="{{index $p "pubkey"}}" kind="{{index $p "kind"}}" address="{{index $p "address"}}" relay="{{index $p "relay"}}" content="+"><button type="button" aria-label="Like this post" aria-pressed="{{socialReacted $p "+"}}"{{if socialReacted $p "+"}} disabled{{end}}>♥ {{socialReactionCount $p "+"}}</button></social-reaction>{{else}}<span aria-label="Likes">♥ {{socialReactionCount $p "+"}}</span>{{end}}42{{range index $p "reactions"}}{{$r := asMap .}}{{if ne (str (index $r "content")) "+"}}<span title="Reaction">{{with socialMediaURL (str (index $r "image"))}}<img data-reaction-emoji src="{{.}}" alt="{{index $r "content"}}" width="20" height="20" loading="lazy" referrerpolicy="no-referrer">{{else}}{{str (index $r "content")}}{{end}} {{index $r "count"}}</span>{{end}}{{end}}43{{if index $p "viewer"}}<details data-social-react><summary>React</summary>{{range strs "👍" "❤️" "😂" "🎉" "🤔"}}<social-reaction actor="{{index $p "viewer"}}" event="{{index $p "id"}}" pubkey="{{index $p "pubkey"}}" kind="{{index $p "kind"}}" address="{{index $p "address"}}" relay="{{index $p "relay"}}" content="{{.}}"><button type="button" aria-label="React {{.}}" aria-pressed="{{socialReacted $p .}}"{{if socialReacted $p .}} disabled{{end}}>{{.}}</button></social-reaction>{{end}}</details>{{end}}44{{if index $p "detail"}}<share-link url="{{socialURL $p}}"></share-link>{{else}}<a href="{{socialURL $p}}">Open</a>{{end}}45</footer>{{end}}4647{{define "socialComposer"}}{{$article := eq (.Query.Get "compose") "article"}}48<nav id="social-write" aria-label="Write"><a href="{{socialQuery .Query "compose" ""}}"{{if not $article}} aria-current="page"{{end}}>Write a note</a><a href="{{socialQuery .Query "compose" "article"}}"{{if $article}} aria-current="page"{{end}}>Write an article</a></nav>49<social-compose mode="{{if $article}}article{{else}}note{{end}}" actor="{{.Actor}}" relay="{{wsURL .URL}}" refresh="/social">50{{if $article}}<label>Title <input name="title" required maxlength="200" placeholder="Give your story a title"></label><label>Subtitle <input name="summary" maxlength="500" placeholder="A short introduction"></label>{{end}}51<label>{{if $article}}Article{{else}}Note{{end}} <textarea name="content" rows="{{if $article}}14{{else}}3{{end}}" maxlength="65000" placeholder="{{if $article}}Tell your story. Markdown is supported.{{else}}What’s on your mind?{{end}}"></textarea></label>52{{if $article}}<label>Cover image URL <input name="cover" type="url" placeholder="https://"></label>{{end}}53{{template "socialMediaFields" .}}54<details data-post-options><summary>Topics</summary><label>Tags <input name="tags" placeholder="nature, design, notes"></label></details>55<p data-compose-actions><button type="submit">{{if $article}}Publish article{{else}}Post note{{end}}</button> <button type="button" data-preview>Preview</button> <small>Draft saved in this browser.</small></p><div data-preview-output hidden></div>56</social-compose>{{end}}5758{{define "socialMediaFields"}}<details data-post-options><summary>Attach media</summary><label>Images, video or audio <input type="file" name="media-file" accept="image/*,video/*,audio/*" multiple></label><label>Or paste a media URL <input name="media" type="url" placeholder="https://"></label><label>Description <input name="alt" maxlength="1000" placeholder="Describe the media for readers"></label><small>Attachments are published with your post.</small></details>{{end}}5960{{define "socialCommentComposer"}}{{$p := asMap .}}<social-compose id="reply" mode="comment" actor="{{index $p "viewer"}}" relay="{{index $p "relay"}}" target="{{index $p "id"}}" target-kind="{{index $p "kind"}}" target-pubkey="{{index $p "pubkey"}}" target-tags="{{json (index $p "tags")}}" address="{{or (str (index $p "root_address")) (str (index $p "address"))}}" root="{{or (str (index $p "root_id")) (str (index $p "id"))}}" root-pubkey="{{str (index $p "root_author")}}" root-kind="{{or (str (index $p "root_kind")) (str (index $p "kind"))}}" refresh="{{socialURL $p}}">61<label>Reply <textarea name="content" rows="3" maxlength="20000" placeholder="Add to the conversation"></textarea></label>{{template "socialMediaFields" .}}<p data-compose-actions><button type="submit">Post reply</button> <button type="button" data-preview>Preview</button></p><div data-preview-output hidden></div>62</social-compose>{{end}}6364{{define "socialComment"}}{{$c := asMap .}}<li data-social-comment{{if index $c "nested"}} data-nested-reply{{end}}>{{template "socialByline" $c}}<div data-comment-body>{{socialBody $c}}</div>{{template "socialActions" $c}}</li>{{end}}6566{{define "panel-social"}}{{$stats := asMap (index (asMap .Event) "stats")}}<section><h4>{{if eq .Tab "social"}}In this feed{{else}}Conversation{{end}}</h4>{{if eq .Tab "social"}}<table><tr><th>notes</th><td>{{or (index $stats "notes") 0}}</td></tr><tr><th>articles</th><td>{{or (index $stats "articles") 0}}</td></tr><tr><th>replies</th><td>{{or (index $stats "comments") 0}}</td></tr></table><p><small>Newest first. Replies and reactions stay with their posts.</small></p>{{else}}<p>Follow the discussion or add a reply of your own.</p><p><a href="/social">All posts</a></p>{{end}}</section>67{{if eq .Tab "social"}}<section><h4>Find a post</h4><form action="/social" method="get"><input type="hidden" name="kind" value="{{.Query.Get "kind"}}"><label>Search <input name="q" type="search" value="{{.Query.Get "q"}}" placeholder="Words or a title"></label><label>Author <input name="author" value="{{.Query.Get "author"}}" placeholder="Public key"></label><button>Search</button> <a href="/social">Clear</a></form></section>{{end}}68<section><h4>Follow the feed</h4><p><a href="/social.xml">Atom</a> | <a href="/social.json">JSON Feed</a></p></section>69{{if not .Actor}}<section><h4>Join in</h4><p><a href="{{signinURL .Base .Path .Query}}">Sign in</a> to post, reply and react.</p></section>{{end}}{{end}}