Add note rendering with basic formatting (images and links)

This commit is contained in:
Daniele Tonon
2023-05-25 20:55:22 +02:00
parent d01e6c5d04
commit 41685aa8ea
7 changed files with 578 additions and 129 deletions

263
note.html Normal file
View File

@@ -0,0 +1,263 @@
<!DOCTYPE html>
<html>
<meta charset="UTF-8">
<head>
{{if eq .type "profile"}}
<title>Nostr Public Key {{.npub}}</title>
<meta property="og:site_name" content="{{.npub}}" />
<meta property="og:title" content="{{.title}}" />
{{ if .metadata.Picture }}
<meta property="og:image" content="{{.metadata.Picture}}" />
<meta property="twitter:image" content="{{.proxy}}{{.metadata.Picture}}" />
{{end}} {{ if .metadata.About }}
<meta property="og:description" content="{{.metadata.About}}" />
{{end}}
<meta property="twitter:card" content="summary" />
{{end}}
<!----------->
{{ if eq .type "event" }}
<title>Nostr Event {{.nevent}}</title>
<meta property="og:site_name" content="{{.authorLong}}" />
<meta property="og:title" content="{{.title}}" />
<meta name="twitter:title" content="{{.twitterTitle}}" />
<!---->
{{ if .textImageURL }}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@nostrprotocol" />
<meta property="og:image" content="{{.textImageURL}}" />
<meta name="twitter:image" content="{{.textImageURL}}" />
{{ else }}
<!---->
<meta property="twitter:card" content="summary" />
{{ if .image }}
<meta property="og:image" content="{{.image}}" />
<meta name="twitter:image" content="{{.proxy}}{{.image}}" />
{{end}} {{ if .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}}
<meta property="og:description" content="{{.description}}" />
<meta name="twitter:description" content="{{.description}}" />
{{end}}
<!----------->
{{ if eq .type "address" }}
<title>Nostr Address {{.naddr}}</title>
{{end}}
<!----------->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/static/styles.css" />
</head>
<body>
<div class="top">
<a href="https://nostr.com" class="nostr_link">What is nostr?</a>
</div>
<div class="container_wrapper">
<div class="container note">
<div class="column column_content">
<div class="profile_intro">
<div class="pic-wrapper">
<a href="/{{.npub}}"><img class="pic" src="{{ .metadata.Picture }}" /></a>
</div>
<div class="info-wrapper">
<div class="name">
{{.metadata.Name}} <span class="display">{{.metadata.DisplayName}}</span>
</div>
<div class="npub">{{.npubShort}}</div>
</div>
<div class="published_at">
{{.createdAt}}
</div>
</div>
<div class="field separator"></div>
<div class="field content">
{{.description | BasicFormatting }}
</div>
<div class="field separator"></div>
<div class="field">
<div class="label">Author Public key</div>
{{.npub}}
</div>
<div class="field">
<div class="label">Nevent</div>
<div>{{.nevent}}</div>
</div>
<div class="field last_update">
Last update:<br/>
{{.createdAt}}
</div>
<div class="field advanced-switch-wrapper">
<input type="checkbox" id="advanced-switch" class="advanced-switch" />
<label for="advanced-switch">X</label>
<label for="advanced-switch">Show more details</label>
</div>
<div class="field advanced">
<div class="label">Event JSON</div>
<div class="json">{{.eventJSON}}</div>
</div>
<div class="field separator"></div>
</div>
<div class="column column_clients">
<div class="title open-list">
<span class="text">Open {{.type}} in</span>
<span class="open">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 20 20">
<path fill="#FAFAFA" fill-rule="evenodd" d="M3.808.355h2.85a2.85 2.85 0 0 1 2.85 2.85v2.85a2.85 2.85 0 0 1-2.85 2.85h-2.85a2.85 2.85 0 0 1-2.85-2.85v-2.85a2.85 2.85 0 0 1 2.85-2.85Zm2.85 6.65a.95.95 0 0 0 .95-.95v-2.85a.95.95 0 0 0-.95-.95h-2.85a.95.95 0 0 0-.95.95v2.85c0 .525.425.95.95.95h2.85Zm0 3.8h-2.85a2.85 2.85 0 0 0-2.85 2.85v2.85a2.85 2.85 0 0 0 2.85 2.85h2.85a2.85 2.85 0 0 0 2.85-2.85v-2.85a2.85 2.85 0 0 0-2.85-2.85Zm0 6.65a.95.95 0 0 0 .95-.95v-2.85a.95.95 0 0 0-.95-.95h-2.85a.95.95 0 0 0-.95.95v2.85c0 .525.425.95.95.95h2.85Zm10.45-6.65h-2.85a2.85 2.85 0 0 0-2.85 2.85v2.85a2.85 2.85 0 0 0 2.85 2.85h2.85a2.85 2.85 0 0 0 2.85-2.85v-2.85a2.85 2.85 0 0 0-2.85-2.85Zm0 6.65a.95.95 0 0 0 .95-.95v-2.85a.95.95 0 0 0-.95-.95h-2.85a.95.95 0 0 0-.95.95v2.85c0 .525.425.95.95.95h2.85Zm0-17.1h-2.85a2.85 2.85 0 0 0-2.85 2.85v2.85a2.85 2.85 0 0 0 2.85 2.85h2.85a2.85 2.85 0 0 0 2.85-2.85v-2.85a2.85 2.85 0 0 0-2.85-2.85Zm0 6.65a.95.95 0 0 0 .95-.95v-2.85a.95.95 0 0 0-.95-.95h-2.85a.95.95 0 0 0-.95.95v2.85c0 .525.425.95.95.95h2.85Z" clip-rule="evenodd"/>
</svg>
</span>
<span class="close">
<svg xmlns="http://www.w3.org/2000/svg" width="31" height="16" fill="currentColor" viewBox="0 0 31 16">
<path fill="#fff" d="M30.207 3.016 16.744 14.983a1.496 1.496 0 0 1-1.974 0L1.307 3.016A1.496 1.496 0 0 1 3.28.772l12.476 11.085L28.233.772a1.496 1.496 0 1 1 1.974 2.244Z"/>
</svg>
</span>
</div>
<div class="clients_wrapper">
{{range .clients}}
<div class="btn">
<a class="client" href="{{.url}}"><span>Open {{.type}} in</span> {{.name}}</a>
</div>
{{end}}
</div>
</div>
</div>
</div>
<div class="background"></div>
<div class="footer">
Powered by <a href="https://git.fiatjaf.com/njump">njump</a>
</div>
<script>
const type = '{{.type}}'
let counts = []
let clients = document.querySelectorAll('.client')
for (let i = 0; i < clients.length; i++) {
let name = clients[i].innerText
let url = clients[i].href
let key = 'nj:' + type + ':' + name
let count = parseInt(localStorage.getItem(key) || 0)
clients[i].parentNode.setAttribute("count", count)
clients[i].parentNode.setAttribute("title", "Used " + count + " times")
clients[i].addEventListener('click', () => {
localStorage.setItem(key, count + 1)
})
counts.push([count, name, url])
}
// Reorder clients following the counter
let clients_wrapper = document.querySelector('.clients_wrapper')
const elements = Array.from(clients_wrapper.getElementsByClassName("btn"));
elements.sort((a, b) => {
const rankA = parseInt(a.getAttribute("count"));
const rankB = parseInt(b.getAttribute("count"));
return rankB - rankA;
});
elements.forEach((element) => clients_wrapper.appendChild(element));
counts.sort((a, b) => b[0] - a[0])
let tailsum = counts.slice(1).reduce((acc, c) => acc + c[0], 0)
if (location.hash !== '#noredirect') {
if (counts[0][0] - tailsum > 10) {
location.href = counts[0][2]
}
}
</script>
<script>
let jsons = document.querySelectorAll('.json')
for (let i = 0; i < jsons.length; i++) {
console.log(jsons[i].innerHTML);
jsons[i].innerHTML = syntaxHighlight(jsons[i].innerHTML);
}
const shareButton = document.querySelector('.open-list');
const clients_list = document.querySelector('.column_clients');
shareButton.addEventListener('click', function() {
clients_list.classList.toggle('up');
if (clients_list.classList.contains('up')) {
document.body.classList.add('lock');
} else {
document.body.classList.remove('lock');
}
});
function updateAdvanceSwitch() {
advanced_list.forEach(element => {
if (advanceSwitch.checked) {
element.classList.add('visible');
} else {
element.classList.remove('visible');
}
});
}
const advanceSwitch = document.querySelector('.advanced-switch');
const advanced_list = document.querySelectorAll('.advanced');
advanceSwitch.addEventListener('change', function() {
updateAdvanceSwitch()
});
updateAdvanceSwitch(); // Check at the page load, some browsers keep the state in cache
var url = new URL(window.location.href);
var searchParams = new URLSearchParams(url.search);
if (searchParams.has('details') && searchParams.get('details') == 'yes') {
advanceSwitch.click();
}
function syntaxHighlight(json) {
json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
var cls = 'number';
if (/^"/.test(match)) {
if (/:$/.test(match)) {
cls = 'key';
} else {
cls = 'string';
}
} else if (/true|false/.test(match)) {
cls = 'boolean';
} else if (/null/.test(match)) {
cls = 'null';
}
return '<span class="' + cls + '">' + match + '</span>';
});
}
</script>
</body>
<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>
</html>

View File

@@ -62,7 +62,7 @@
<div class="container profile"> <div class="container profile">
<div class="column columnA"> <div class="column columnA">
<div class="name"> <div class="info-wrapper">
{{.metadata.Name}} <span class="display">{{.metadata.DisplayName}}</span> {{.metadata.Name}} <span class="display">{{.metadata.DisplayName}}</span>
</div> </div>
<div class="pic-wrapper"> <div class="pic-wrapper">
@@ -74,10 +74,12 @@
</div> </div>
</div> </div>
<div class="column columnB"> <div class="column column_content">
<div class="field name"> <div class="field info-wrapper">
<div class="name">
{{.metadata.Name}} <span class="display">{{.metadata.DisplayName}}</span> {{.metadata.Name}} <span class="display">{{.metadata.DisplayName}}</span>
</div> </div>
</div>
<div class="field separator long"></div> <div class="field separator long"></div>
<div class="field"> <div class="field">
<a href="{{.metadata.Website}}">{{.metadata.Website}}</a> <a href="{{.metadata.Website}}">{{.metadata.Website}}</a>
@@ -122,7 +124,7 @@
<div class="field separator"></div> <div class="field separator"></div>
</div> </div>
<div class="column columnC"> <div class="column column_clients">
<div class="title open-list"> <div class="title open-list">
<span class="text">Open {{.type}} in</span> <span class="text">Open {{.type}} in</span>
<span class="open"> <span class="open">
@@ -200,7 +202,7 @@
} }
const shareButton = document.querySelector('.open-list'); const shareButton = document.querySelector('.open-list');
const clients_list = document.querySelector('.columnC'); const clients_list = document.querySelector('.column_clients');
shareButton.addEventListener('click', function() { shareButton.addEventListener('click', function() {
clients_list.classList.toggle('up'); clients_list.classList.toggle('up');
if (clients_list.classList.contains('up')) { if (clients_list.classList.contains('up')) {

View File

@@ -19,7 +19,7 @@ var rawHTML string
//go:embed profile.html //go:embed profile.html
var profileHTML string var profileHTML string
//go:embed raw.html //go:embed note.html
var noteHTML string var noteHTML string
func render(w http.ResponseWriter, r *http.Request) { func render(w http.ResponseWriter, r *http.Request) {
@@ -161,6 +161,9 @@ func render(w http.ResponseWriter, r *http.Request) {
eventJSON, _ := json.MarshalIndent(event, "", " ") eventJSON, _ := json.MarshalIndent(event, "", " ")
// TODO: Sanitize content
description += "\n<script>alert('TODO: Sanitize the content!')</script>"
params := map[string]any{ params := map[string]any{
"createdAt": createdAt, "createdAt": createdAt,
"clients": generateClientList(code, event), "clients": generateClientList(code, event),
@@ -168,6 +171,7 @@ func render(w http.ResponseWriter, r *http.Request) {
"title": title, "title": title,
"twitterTitle": twitterTitle, "twitterTitle": twitterTitle,
"npub": npub, "npub": npub,
"npubShort": npubShort,
"nevent": nevent, "nevent": nevent,
"naddr": naddr, "naddr": naddr,
"metadata": metadata, "metadata": metadata,
@@ -183,9 +187,13 @@ func render(w http.ResponseWriter, r *http.Request) {
templates := make(map[string]string) templates := make(map[string]string)
templates["profile"] = profileHTML templates["profile"] = profileHTML
templates["note"] = rawHTML templates["note"] = noteHTML
templates["address"] = rawHTML templates["address"] = rawHTML
var tmpl = template.Must(template.New("event").Parse(templates[typ]))
var funcMap = template.FuncMap{
"BasicFormatting": BasicFormatting,
}
var tmpl = template.Must(template.New("event").Funcs(funcMap).Parse(templates[typ]))
if err := tmpl.ExecuteTemplate(w, "event", params); err != nil { if err := tmpl.ExecuteTemplate(w, "event", params); err != nil {
http.Error(w, "error rendering: "+err.Error(), 500) http.Error(w, "error rendering: "+err.Error(), 500)

View File

@@ -63,6 +63,26 @@ a {
} }
} }
.pic-wrapper {
max-width: 100%;
overflow: hidden;
}
@media (max-width: 580px) {
.pic-wrapper {
flex-basis: 40%;
}
}
.pic-wrapper img.pic {
max-width: 100%;
width: 100%;
height: auto;
-webkit-clip-path: url(#svg-shape);
-moz-clip-path: url(#svg-shape);
-o-clip-path: url(#svg-shape);
-ms-clip-path: url(#svg-shape);
clip-path: url(#svg-shape);
}
.container_wrapper { .container_wrapper {
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -82,146 +102,122 @@ a {
text-decoration: none; text-decoration: none;
} }
.container.profile { .container {
display: flex; display: flex;
width: 70%; width: 70%;
justify-content: space-between; justify-content: space-between;
gap: 4.8vw; gap: 4.8vw;
} }
@media (max-width: 580px) { @media (max-width: 580px) {
.container.profile { .container {
display: block; display: block;
width: 100%; width: 100%;
} }
} }
.container.profile .columnA { .container .columnA {
flex-basis: 25%; flex-basis: 25%;
margin-top: 2rem; margin-top: 2rem;
} }
@media (max-width: 580px) { @media (max-width: 580px) {
.container.profile .columnA { .container .columnA {
display: flex; display: flex;
align-items: center; align-items: center;
margin-top: 0rem; margin-top: 0rem;
} }
} }
.container.profile .columnA .name { .container .columnA .info-wrapper {
display: none; display: none;
} }
@media (max-width: 580px) { @media (max-width: 580px) {
.container.profile .columnA .name { .container .columnA .info-wrapper {
display: block; display: block;
flex-basis: 64%; flex-basis: 64%;
max-width: 64%; max-width: 64%;
overflow: hidden; overflow: hidden;
font-size: 1.6rem; font-size: 1.6rem;
} }
.container.profile .columnA .name .display { .container .columnA .info-wrapper .display {
display: block; display: block;
font-size: 1.2rem; font-size: 1.2rem;
color: #C9C9C9; color: #C9C9C9;
} }
} }
.container.profile .columnA .pic-wrapper { .container .columnA .last_update {
max-width: 100%;
overflow: hidden;
}
@media (max-width: 580px) {
.container.profile .columnA .pic-wrapper {
flex-basis: 40%;
}
}
.container.profile .columnA .pic-wrapper img.pic {
max-width: 100%;
width: 100%;
height: auto;
-webkit-clip-path: url(#svg-shape);
-moz-clip-path: url(#svg-shape);
-o-clip-path: url(#svg-shape);
-ms-clip-path: url(#svg-shape);
clip-path: url(#svg-shape);
}
.container.profile .columnA .last_update {
font-size: 0.8em; font-size: 0.8em;
margin-top: 0.5rem; margin-top: 0.5rem;
text-align: center; text-align: center;
color: #C9C9C9; color: #C9C9C9;
} }
@media (max-width: 580px) { @media (max-width: 580px) {
.container.profile .columnA .last_update { .container .columnA .last_update {
display: none; display: none;
} }
} }
.container.profile .columnB { .container .column_content {
flex-basis: 50%;
max-width: 50%;
flex-grow: 0; flex-grow: 0;
flex-shrink: 0; flex-shrink: 0;
word-wrap: break-word; word-wrap: break-word;
margin-right: 1vw; margin-right: 1vw;
} }
@media (max-width: 580px) { .container .column_content .info-wrapper {
.container.profile .columnB {
flex-basis: 100%;
max-width: 100%;
margin-right: 0;
}
}
.container.profile .columnB .name {
font-size: 1.6rem; font-size: 1.6rem;
} }
.container.profile .columnB .name .display { .container .column_content .info-wrapper .display {
color: #C9C9C9;
}
.container .column_content .info-wrapper .npub {
font-size: 1rem;
color: #C9C9C9; color: #C9C9C9;
} }
@media (max-width: 580px) { @media (max-width: 580px) {
.container.profile .columnB .name { .container .column_content .info-wrapper {
display: none; display: none;
} }
} }
.container.profile .columnB .separator { .container .column_content .separator {
height: 6px; height: 6px;
width: 30%; width: 30%;
margin-left: -0.6rem; margin-left: -0.6rem;
background-color: #F3F3F3; background-color: #F3F3F3;
} }
.container.profile .columnB .separator.long { .container .column_content .separator.long {
width: 50%; width: 50%;
} }
@media (max-width: 580px) { @media (max-width: 580px) {
.container.profile .columnB .separator { .container .column_content .separator {
margin-left: -1rem; margin-left: -1rem;
} }
} }
.container.profile .columnB .field { .container .column_content .field {
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
.container.profile .columnB .field .label { .container .column_content .field .label {
font-size: 0.8rem; font-size: 0.8rem;
color: #E32A6D; color: #E32A6D;
} }
.container.profile .columnB .field.advanced { .container .column_content .field.advanced {
display: none; display: none;
} }
.container.profile .columnB .field.advanced.visible { .container .column_content .field.advanced.visible {
display: block; display: block;
} }
.container.profile .columnB .field.advanced .label { .container .column_content .field.advanced .label {
padding: 0.2rem 1rem; padding: 0.2rem 1rem;
margin: 0 -1rem; margin: 0 -1rem;
color: #373737; color: #373737;
background-color: #C9C9C9; background-color: #C9C9C9;
} }
.container.profile .columnB .field.advanced-switch-wrapper { .container .column_content .field.advanced-switch-wrapper {
display: flex; display: flex;
align-items: center; align-items: center;
} }
.container.profile .columnB .field.advanced-switch-wrapper input[type=checkbox] { .container .column_content .field.advanced-switch-wrapper input[type=checkbox] {
height: 0; height: 0;
width: 0; width: 0;
visibility: hidden; visibility: hidden;
display: none; display: none;
} }
.container.profile .columnB .field.advanced-switch-wrapper label:first-of-type { .container .column_content .field.advanced-switch-wrapper label:first-of-type {
cursor: pointer; cursor: pointer;
text-indent: -9999px; text-indent: -9999px;
width: 2.6rem; width: 2.6rem;
@@ -233,12 +229,12 @@ a {
margin-right: 0.5rem; margin-right: 0.5rem;
} }
@media (max-width: 580px) { @media (max-width: 580px) {
.container.profile .columnB .field.advanced-switch-wrapper label:first-of-type { .container .column_content .field.advanced-switch-wrapper label:first-of-type {
width: 3rem; width: 3rem;
height: 1.4rem; height: 1.4rem;
} }
} }
.container.profile .columnB .field.advanced-switch-wrapper label:first-of-type:after { .container .column_content .field.advanced-switch-wrapper label:first-of-type:after {
content: ""; content: "";
position: absolute; position: absolute;
top: 2px; top: 2px;
@@ -250,83 +246,87 @@ a {
transition: 0.2s; transition: 0.2s;
} }
@media (max-width: 580px) { @media (max-width: 580px) {
.container.profile .columnB .field.advanced-switch-wrapper label:first-of-type:after { .container .column_content .field.advanced-switch-wrapper label:first-of-type:after {
width: 1.2rem; width: 1.2rem;
height: 1.2rem; height: 1.2rem;
} }
} }
.container.profile .columnB .field.advanced-switch-wrapper input:checked + label { .container .column_content .field.advanced-switch-wrapper input:checked + label {
background: #E32A6D; background: #E32A6D;
} }
.container.profile .columnB .field.advanced-switch-wrapper input:checked + label:first-of-type:after { .container .column_content .field.advanced-switch-wrapper input:checked + label:first-of-type:after {
left: calc(100% - 2px); left: calc(100% - 2px);
transform: translateX(-100%); transform: translateX(-100%);
} }
.container.profile .columnB .field.advanced-switch-wrapper label:first-of-type:active:after { .container .column_content .field.advanced-switch-wrapper label:first-of-type:active:after {
width: 2rem; width: 2rem;
} }
.container.profile .columnB .field .json, .container.profile .columnB .field .data { .container .column_content .field.content img {
max-width: 100%;
margin: 1rem 0;
}
.container .column_content .field .json, .container .column_content .field .data {
white-space: pre-wrap; white-space: pre-wrap;
word-break: break-all; word-break: break-all;
background-color: #F3F3F3; background-color: #F3F3F3;
padding: 1rem; padding: 1rem;
margin: 0 -1rem; margin: 0 -1rem;
} }
.container.profile .columnB .field .json .key, .container.profile .columnB .field .data .key { .container .column_content .field .json .key, .container .column_content .field .data .key {
display: inline-block; display: inline-block;
margin-top: 0.5rem; margin-top: 0.5rem;
color: #E32A6D; color: #E32A6D;
} }
.container.profile .columnB .field .json .string, .container.profile .columnB .field .data .string { .container .column_content .field .json .string, .container .column_content .field .data .string {
color: #373737; color: #373737;
} }
.container.profile .columnB .field .json .number, .container.profile .columnB .field .data .number { .container .column_content .field .json .number, .container .column_content .field .data .number {
color: darkorange; color: darkorange;
} }
.container.profile .columnB .field .json .boolean, .container.profile .columnB .field .data .boolean { .container .column_content .field .json .boolean, .container .column_content .field .data .boolean {
color: #373737; color: #373737;
} }
.container.profile .columnB .field .json .null, .container.profile .columnB .field .data .null { .container .column_content .field .json .null, .container .column_content .field .data .null {
color: #373737; color: #373737;
} }
.container.profile .columnB .field.last_update { .container .column_content .field.last_update {
display: none; display: none;
} }
@media (max-width: 580px) { @media (max-width: 580px) {
.container.profile .columnB .field.last_update { .container .column_content .field.last_update {
display: block; display: block;
font-size: 0.8em; font-size: 0.8em;
color: #C9C9C9; color: #C9C9C9;
} }
} }
.container.profile .columnC { .container .column_clients {
flex-basis: 25%; flex-basis: 25%;
margin-top: 2rem; margin-top: 2rem;
} }
@media (max-width: 580px) { @media (max-width: 580px) {
.container.profile .columnC { .container .column_clients {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
left: 0; left: 0;
width: 100%; width: 100%;
transition: all 500ms ease-in-out; transition: all 500ms ease-in-out;
} }
.container.profile .columnC.up .btn { .container .column_clients.up .btn {
display: block; display: block;
} }
.container.profile .columnC.up .title span.open { .container .column_clients.up .title span.open {
display: none; display: none;
} }
} }
.container.profile .columnC .title { .container .column_clients .title {
font-size: 0.8rem; font-size: 0.8rem;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.container.profile .columnC .title span.open, .container.profile .columnC .title span.close { .container .column_clients .title span.open, .container .column_clients .title span.close {
display: none; display: none;
} }
@media (max-width: 580px) { @media (max-width: 580px) {
.container.profile .columnC .title { .container .column_clients .title {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
@@ -334,31 +334,31 @@ a {
height: 2.6rem; height: 2.6rem;
border-left: 1px solid #bc1150; border-left: 1px solid #bc1150;
} }
.container.profile .columnC .title span.text { .container .column_clients .title span.text {
display: none; display: none;
} }
.container.profile .columnC .title span.open, .container.profile .columnC .title span.close { .container .column_clients .title span.open, .container .column_clients .title span.close {
display: inline; display: inline;
} }
.container.profile .columnC .title span.open svg, .container.profile .columnC .title span.close svg { .container .column_clients .title span.open svg, .container .column_clients .title span.close svg {
width: 50%; width: 50%;
height: 50%; height: 50%;
margin: 28% auto auto auto; margin: 28% auto auto auto;
display: block; display: block;
} }
} }
.container.profile .columnC .btn { .container .column_clients .btn {
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 0.8rem; margin-bottom: 0.8rem;
} }
@media (max-width: 580px) { @media (max-width: 580px) {
.container.profile .columnC .btn { .container .column_clients .btn {
display: none; display: none;
margin-bottom: 0; margin-bottom: 0;
} }
} }
.container.profile .columnC .btn a { .container .column_clients .btn a {
flex-basis: 80%; flex-basis: 80%;
padding: 0.4rem; padding: 0.4rem;
text-align: center; text-align: center;
@@ -370,7 +370,7 @@ a {
border-radius: 8px; border-radius: 8px;
} }
@media (max-width: 580px) { @media (max-width: 580px) {
.container.profile .columnC .btn a { .container .column_clients .btn a {
display: block; display: block;
padding: 0.8rem; padding: 0.8rem;
border-radius: 0px; border-radius: 0px;
@@ -378,45 +378,113 @@ a {
text-align: left; text-align: left;
} }
} }
.container.profile .columnC .btn a span { .container .column_clients .btn a span {
display: none; display: none;
} }
@media (max-width: 580px) { @media (max-width: 580px) {
.container.profile .columnC .btn a span { .container .column_clients .btn a span {
display: inline; display: inline;
color: #FFFFFF; color: #FFFFFF;
} }
} }
.container.profile .columnC .btn a:hover { .container .column_clients .btn a:hover {
background-color: #373737; background-color: #373737;
} }
@media (max-width: 580px) { @media (max-width: 580px) {
.container.profile .columnC .btn:first-of-type { .container .column_clients .btn:first-of-type {
display: block; display: block;
} }
} }
.container.profile .columnC .btn:first-of-type a { .container .column_clients .btn:first-of-type a {
background-color: #E32A6D; background-color: #E32A6D;
border-bottom: none; border-bottom: none;
} }
@media (max-width: 580px) { @media (max-width: 580px) {
.container.profile .columnC .btn:first-of-type a { .container .column_clients .btn:first-of-type a {
border-radius: 8px 8px 0 0; border-radius: 8px 8px 0 0;
} }
} }
.container.profile .columnC .btn:first-of-type a:hover { .container .column_clients .btn:first-of-type a:hover {
background-color: #bc1150; background-color: #bc1150;
} }
.container.profile .columnC .btn span { .container .column_clients .btn span {
flex-basis: 20%; flex-basis: 20%;
margin-left: 0.4rem; margin-left: 0.4rem;
color: #9a9a9a; color: #9a9a9a;
} }
@media (max-width: 580px) { @media (max-width: 580px) {
.container.profile .columnC .btn span { .container .column_clients .btn span {
display: none; display: none;
} }
} }
.container.profile .column_content {
flex-basis: 50%;
max-width: 50%;
}
@media (max-width: 580px) {
.container.profile .column_content {
flex-basis: 100%;
max-width: 100%;
margin-right: 0;
}
}
.container.note .column_content {
flex-basis: 70%;
max-width: 70%;
}
@media (max-width: 580px) {
.container.note .column_content {
flex-basis: 100%;
max-width: 100%;
margin-right: 0;
}
}
.container.note .column_content .profile_intro {
display: flex;
max-width: 100%;
align-items: center;
margin: 1rem 0 1rem 0;
}
@media (max-width: 580px) {
.container.note .column_content .profile_intro {
flex-wrap: wrap;
}
}
.container.note .column_content .profile_intro .info-wrapper {
display: block;
}
@media (max-width: 580px) {
.container.note .column_content .profile_intro .info-wrapper {
flex-basis: 80%;
}
}
@media (max-width: 580px) {
.container.note .column_content .profile_intro .info-wrapper .name, .container.note .column_content .profile_intro .info-wrapper .npub {
display: block-inline;
font-size: 0.9rem;
}
}
.container.note .column_content .profile_intro .pic-wrapper {
flex-basis: 16%;
margin-right: 1rem;
}
@media (max-width: 580px) {
.container.note .column_content .profile_intro .pic-wrapper {
margin-right: 0.5rem;
}
}
.container.note .column_content .profile_intro .published_at {
flex-grow: 1;
text-align: right;
align-self: end;
font-size: 0.8rem;
color: #9a9a9a;
}
@media (max-width: 580px) {
.container.note .column_content .profile_intro .published_at {
padding-top: 0.5rem;
}
}
.footer { .footer {
font-size: 0.8rem; font-size: 0.8rem;

View File

@@ -1 +1 @@
{"version":3,"sourceRoot":"","sources":["styles.scss"],"names":[],"mappings":";AAeA;EACC,aAXe;EAYf;EACA;;;AAED;EACC,OArBS;EAsBT;EACA;;AAEC;EADD;IAEE;;;;AAIH;EACC,OA/BS;;;AAiCV;EACC;EACA;EACA;EACA;EACA;EACA,kBAjCoB;EAkCpB;EACA;;AACA;EACC;EACA;EACA,kBAtCmB;EAuCnB;EACA;EACA;EACA;EACA;;AACA;EATD;IAUE;IACA;;;;AAIH;EACC;EACA;EACA;EACA;EACA;;AACA;EAND;IAOE;IACA;IACA;IACA;;;;AAGF;EACC;EACA;EACA;;AACC;EAJF;IAKE;IACE;IACF;;;;AAGF;EACC;EACA;EACA;;;AAED;EACC;EACA;EACA;EACA;;AAEC;EANF;IAOE;IACE;;;AAGH;EACC;EACA;;AACA;EAHD;IAIE;IACA;IACA;;;AAED;EACC;;AACA;EAFD;IAGE;IACA;IACA;IACA;IACA;;EACA;IACC;IACA;IACA,OAlHK;;;AAsHR;EACC;EACA;;AACA;EAHD;IAIE;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACC;EACA;EACA;EACA,OA3IO;;AA4IP;EALD;IAME;;;AAIH;EACC;EACA;EACA;EACA;EACA;EACA;;AACA;EAPD;IAQE;IACA;IACA;;;AAED;EACC;;AACA;EACC,OAhKM;;AAkKP;EALD;IAME;;;AAGF;EACC;EACA;EACA;EACA,kBAnKkB;;AAoKlB;EACC;;AAED;EARD;IASE;;;AAGF;EACC;;AACA;EACC;EACA,OArLM;;AAuLP;EACC;;AACA;EACC;;AAED;EACC;EACA;EACA,OAjMK;EAkML,kBAjMK;;AAoMP;EACC;EACA;;AAEA;EACC;EACA;EACA;EACA;;AAGD;EACC;EACA;EACA;EACA;EACA,YA5MgB;EA6MhB;EACA;EACA;EACA;;AACA;EAVD;IAWE;IACA;;;AAIF;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AACA;EAVD;IAWE;IACA;;;AAIF;EACC,YA/OK;;AAkPN;EACC;EACA;;AAGD;EACC;;AAIF;EACC;EACA;EACA,kBAzPiB;EA0PjB;EACA;;AACA;EACC;EACA;EACA,OArQK;;AAuQN;EAAU,OAzQJ;;AA0QN;EAAU;;AACV;EAAW,OA3QL;;AA4QN;EAAQ,OA5QF;;AA8QP;EACC;;AACA;EAFD;IAGE;IACA;IACA,OAlRK;;;AAuRT;EACC;EACA;;AACA;EAHD;IAIE;IAEA;IACA;IACA;IACA;;EAEC;IACC;;EAED;IACC;;;AAIH;EACC;EACA;;AACA;EACC;;AAED;EAND;IAOE;IACA;IACA;IACA;IACA;IACA;;EACA;IACC;;EAED;IACC;;EACA;IACC;IACA;IACA;IACA;;;AAKJ;EACC;EACA;EACA;;AACA;EAJD;IAKE;IACA;;;AAED;EACC;EACA;EACA;EACA;EACA,OAhVM;EAiVN,kBA3UiB;EA4UjB;EACA;EACA;;AACA;EAVD;IAWE;IACA;IACA;IACA;IACA;;;AAED;EACC;;AACA;EAFD;IAGE;IACA,OAhWI;;;AAmWN;EACC,kBA7VgB;;AAiWjB;EADD;IAEE;;;AAED;EACC,kBApWgB;EAqWhB;;AACA;EAHD;IAIE;;;AAED;EACC,kBAzWe;;AA6WlB;EACC;EACA;EACA,OAxXM;;AAyXN;EAJD;IAKE;;;;AAML;EACC;EACA","file":"styles.css"} {"version":3,"sourceRoot":"","sources":["styles.scss"],"names":[],"mappings":";AAeA;EACC,aAXe;EAYf;EACA;;;AAED;EACC,OArBS;EAsBT;EACA;;AAEC;EADD;IAEE;;;;AAIH;EACC,OA/BS;;;AAiCV;EACC;EACA;EACA;EACA;EACA;EACA,kBAjCoB;EAkCpB;EACA;;AACA;EACC;EACA;EACA,kBAtCmB;EAuCnB;EACA;EACA;EACA;EACA;;AACA;EATD;IAUE;IACA;;;;AAIH;EACC;EACA;EACA;EACA;EACA;;AACA;EAND;IAOE;IACA;IACA;IACA;;;;AAIF;EACC;EACA;;AACA;EAHD;IAIE;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAIF;EACC;EACA;EACA;;AACC;EAJF;IAKE;IACE;IACF;;;;AAGF;EACC;EACA;EACA;;;AAED;EACC;EACA;EACA;EACA;;AAEC;EANF;IAOE;IACE;;;AAGH;EACC;EACA;;AACA;EAHD;IAIE;IACA;IACA;;;AAED;EACC;;AACA;EAFD;IAGE;IACA;IACA;IACA;IACA;;EACA;IACC;IACA;IACA,OArIK;;;AAyIR;EACC;EACA;EACA;EACA,OA7IO;;AA8IP;EALD;IAME;;;AAIH;EAEC;EACA;EACA;EACA;;AAEA;EACC;;AACA;EACC,OA7JM;;AA+JP;EACC;EACA,OAjKM;;AAmKP;EATD;IAUE;;;AAGF;EACC;EACA;EACA;EACA,kBApKkB;;AAqKlB;EACC;;AAED;EARD;IASE;;;AAGF;EACC;;AACA;EACC;EACA,OAtLM;;AAwLP;EACC;;AACA;EACC;;AAED;EACC;EACA;EACA,OAlMK;EAmML,kBAlMK;;AAqMP;EACC;EACA;;AAEA;EACC;EACA;EACA;EACA;;AAGD;EACC;EACA;EACA;EACA;EACA,YA7MgB;EA8MhB;EACA;EACA;EACA;;AACA;EAVD;IAWE;IACA;;;AAIF;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AACA;EAVD;IAWE;IACA;;;AAIF;EACC,YAhPK;;AAmPN;EACC;EACA;;AAGD;EACC;;AAIF;EACC;EACA;;AAED;EACC;EACA;EACA,kBA9PiB;EA+PjB;EACA;;AACA;EACC;EACA;EACA,OA1QK;;AA4QN;EAAU,OA9QJ;;AA+QN;EAAU;;AACV;EAAW,OAhRL;;AAiRN;EAAQ,OAjRF;;AAmRP;EACC;;AACA;EAFD;IAGE;IACA;IACA,OAvRK;;;AA8RT;EACC;EACA;;AACA;EAHD;IAIE;IAEA;IACA;IACA;IACA;;EAEC;IACC;;EAED;IACC;;;AAIH;EACC;EACA;;AACA;EACC;;AAED;EAND;IAOE;IACA;IACA;IACA;IACA;IACA;;EACA;IACC;;EAED;IACC;;EACA;IACC;IACA;IACA;IACA;;;AAKJ;EACC;EACA;EACA;;AACA;EAJD;IAKE;IACA;;;AAED;EACC;EACA;EACA;EACA;EACA,OAvVM;EAwVN,kBAlViB;EAmVjB;EACA;EACA;;AACA;EAVD;IAWE;IACA;IACA;IACA;IACA;;;AAED;EACC;;AACA;EAFD;IAGE;IACA,OAvWI;;;AA0WN;EACC,kBApWgB;;AAwWjB;EADD;IAEE;;;AAED;EACC,kBA3WgB;EA4WhB;;AACA;EAHD;IAIE;;;AAED;EACC,kBAhXe;;AAoXlB;EACC;EACA;EACA,OA/XM;;AAgYN;EAJD;IAKE;;;AAOH;EACC;EACA;;AACA;EAHD;IAIE;IACA;IACA;;;AAMF;EACC;EACA;;AACA;EAHD;IAIE;IACA;IACA;;;AAED;EAgBC;EACA;EACA;EACA;;AAlBA;EADD;IAEE;;;AAED;EACC;;AACA;EAFD;IAGE;;;AAGA;EADD;IAEE;IACA;;;AAQH;EACC;EACA;;AACA;EAHD;IAIE;;;AAGF;EACC;EACA;EACA;EACA;EACA,OA5bK;;AA6bL;EAND;IAOE;;;;AASN;EACC;EACA","file":"styles.css"}

View File

@@ -68,6 +68,25 @@ a {
font-size: 0.8rem; font-size: 0.8rem;
} }
} }
.pic-wrapper {
max-width: 100%;
overflow: hidden;
@media (max-width: 580px) {
flex-basis: 40%;
}
img.pic {
max-width: 100%;
width: 100%;
height: auto;
-webkit-clip-path: url(#svg-shape);
-moz-clip-path: url(#svg-shape);
-o-clip-path: url(#svg-shape);
-ms-clip-path: url(#svg-shape);
clip-path: url(#svg-shape);
}
}
.container_wrapper { .container_wrapper {
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -83,7 +102,7 @@ a {
text-align: right; text-align: right;
text-decoration: none; text-decoration: none;
} }
.container.profile { .container {
display: flex; display: flex;
width: 70%; width: 70%;
justify-content: space-between; justify-content: space-between;
@@ -102,7 +121,7 @@ a {
align-items: center; align-items: center;
margin-top: 0rem; margin-top: 0rem;
} }
.name { .info-wrapper {
display: none; display: none;
@media (max-width: 580px) { @media (max-width: 580px) {
display: block; display: block;
@@ -117,23 +136,6 @@ a {
} }
} }
} }
.pic-wrapper {
max-width: 100%;
overflow: hidden;
@media (max-width: 580px) {
flex-basis: 40%;
}
img.pic {
max-width: 100%;
width: 100%;
height: auto;
-webkit-clip-path: url(#svg-shape);
-moz-clip-path: url(#svg-shape);
-o-clip-path: url(#svg-shape);
-ms-clip-path: url(#svg-shape);
clip-path: url(#svg-shape);
}
}
.last_update { .last_update {
font-size: 0.8em; font-size: 0.8em;
margin-top: 0.5rem; margin-top: 0.5rem;
@@ -144,23 +146,22 @@ a {
} }
} }
} }
.columnB { .column_content {
flex-basis: 50%;
max-width: 50%;
flex-grow: 0; flex-grow: 0;
flex-shrink: 0; flex-shrink: 0;
word-wrap: break-word; word-wrap: break-word;
margin-right: 1vw; margin-right: 1vw;
@media (max-width: 580px) {
flex-basis: 100%; .info-wrapper {
max-width: 100%;
margin-right: 0;
}
.name {
font-size: 1.6rem; font-size: 1.6rem;
.display { .display {
color: $color_2; color: $color_2;
} }
.npub {
font-size: 1rem;
color: $color_2;
}
@media (max-width: 580px) { @media (max-width: 580px) {
display: none; display: none;
} }
@@ -252,6 +253,10 @@ a {
} }
} }
&.content img {
max-width: 100%;
margin: 1rem 0;
}
.json, .data { .json, .data {
white-space: pre-wrap; white-space: pre-wrap;
word-break: break-all; word-break: break-all;
@@ -277,8 +282,10 @@ a {
} }
} }
} }
} }
.columnC {
.column_clients {
flex-basis: 25%; flex-basis: 25%;
margin-top: 2rem; margin-top: 2rem;
@media (max-width: 580px) { @media (max-width: 580px) {
@@ -385,7 +392,71 @@ a {
} }
} }
} }
&.profile {
.column_content {
flex-basis: 50%;
max-width: 50%;
@media (max-width: 580px) {
flex-basis: 100%;
max-width: 100%;
margin-right: 0;
} }
}
}
&.note {
.column_content {
flex-basis: 70%;
max-width: 70%;
@media (max-width: 580px) {
flex-basis: 100%;
max-width: 100%;
margin-right: 0;
}
.profile_intro {
@media (max-width: 580px) {
flex-wrap: wrap;
}
.info-wrapper {
display: block;
@media (max-width: 580px) {
flex-basis: 80%;
}
.name, .npub {
@media (max-width: 580px) {
display: block-inline;
font-size: 0.9rem;
}
}
}
display: flex;
max-width: 100%;
align-items: center;
margin: 1rem 0 1rem 0;
.pic-wrapper {
flex-basis: 16%;
margin-right: 1rem;
@media (max-width: 580px) {
margin-right: 0.5rem;
}
}
.published_at {
flex-grow: 1;
text-align: right;
align-self: end;
font-size: 0.8rem;
color: $color_5;
@media (max-width: 580px) {
padding-top: 0.5rem;
}
}
}
}
}
}
.footer { .footer {
font-size: 0.8rem; font-size: 0.8rem;
text-align: center; text-align: center;

View File

@@ -2,7 +2,9 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt"
"net/http" "net/http"
"regexp"
"strings" "strings"
"github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr"
@@ -114,3 +116,38 @@ func getPreviewStyle(r *http.Request) string {
return "unknown" return "unknown"
} }
} }
func BasicFormatting(input string) string {
lines := strings.Split(input, "\n")
var processedLines []string
for _, line := range lines {
processedLine := ReplaceURLsWithTags(line)
processedLines = append(processedLines, processedLine)
}
return strings.Join(processedLines, "<br/>")
}
func ReplaceURLsWithTags(line string) string {
// Match and replace image URLs with <img> tags
imageExtensions := []string{".jpg", ".jpeg", ".png", ".webp", ".gif"}
for _, extension := range imageExtensions {
regexPattern := fmt.Sprintf(`\s*(https?://\S+%s)\s*`, extension)
regex := regexp.MustCompile(regexPattern)
matches := regex.FindAllString(line, -1)
for _, match := range matches {
imgTag := fmt.Sprintf(`<img src="%s" alt="">`, strings.ReplaceAll(match, "\n", ""))
line = strings.ReplaceAll(line, match, imgTag)
return line
}
}
// Match and replace other URLs with <a> tags
otherRegexPattern := `\S*(https?://\S+)\S*`
otherRegex := regexp.MustCompile(otherRegexPattern)
line = otherRegex.ReplaceAllString(line, `<a href="$1">$1</a>`)
return line
}