Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
William Casarin 2022-08-19 11:15:26 -07:00
parent c075d3dd0e
commit 9788baa322
18 changed files with 1007 additions and 23 deletions

View file

@ -12,6 +12,16 @@ html { font-family: 'Inter', sans-serif; }
font-family: serif;
}
a {
text-decoration: underline;
font-family: -system-ui, sans-serif;
color: white;
}
a:visited {
color: #eee;
}
label {
white-space: nowrap;
}

View file

@ -15,7 +15,7 @@
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/skeleton.css?v=2">
<link rel="stylesheet" href="css/custom.css?v=4">
<link rel="stylesheet" href="css/custom.css?v=5">
</head>
<body>
@ -66,8 +66,13 @@
<div>
<img style="width: 200px" src="img/app-store-coming-soon.svg" />
</div>
<div>
<div style="margin-top: 20px">
<p>
<a href="https://damus.io/log">The Damus Log</a>
</p>
<p>
<a href="https://testflight.apple.com/join/CLwjLxWl">Join the TestFlight Beta</a>
</p>
</div>
</section>

View file

@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>The Damus Log</title>
<link rel="stylesheet" href="log.css?v=17">
<link rel="stylesheet" href="log.css?v=29">
<link rel="stylesheet" href="comments.css?v=5">
</head>
<body>
@ -16,6 +16,7 @@
</span>
</section>
<div class="container">
<a href="https://damus.io/log" class="date">&lt; The Damus Log</a>
<h1 id="the-damus-log---powered-by-nostr">The Damus Log - Powered by
#nostr</h1>
<p>Hey there, Welcome to the damus log! A blog powered by… nostr! What
@ -42,13 +43,25 @@ testflight at the bottom of our homepage:</p>
<p><a href="https://damus.io">damus.io</a></p>
<p>Looking forward to seeing you on nostr!</p>
<h3>Comments</h3>
<h3><a id="comment-link" href="nostr:e:">Comments</a></h3>
<div id="comments">
</div>
<script src="nostr.js?v=4" ></script>
<script src="comments.js?v=16" ></script>
<script>
const relay = comments_init("4e8b44bb43018f79bd3efcdcd71af43814cdf996e0c62adedda1ac33bf5e1371")
const threads = {
"the-stuff-loads-better-release": "9941b55c2844f275b7b8714a1c39859088a425ce798f740ea8fea879f9098641",
"introducing-damus-log": "4e8b44bb43018f79bd3efcdcd71af43814cdf996e0c62adedda1ac33bf5e1371",
}
let relay
for (const key of Object.keys(threads)) {
if (window.location.href.includes(key)) {
const id = threads[key]
relay = comments_init(id)
document.querySelector("#comment-link").href = 'nostr:e:' + id
break
}
}
</script>
</div> <!-- container -->
</body>

View file

@ -0,0 +1,51 @@
# v0.1.3 - The "Stuff Loads Better" Release
It's that time again! A new damus release. This one fixes a bunch of annoying issues such as profiles not loading properly in some situations. We also do a much better job at caching profile pictures, so no more weird poppyness and wasting your cell data.
If you're not on the testflight already, you can get it here:
=> https://testflight.apple.com/join/CLwjLxWl Damus TestFlight
This was the last release before lightning support, so next version will be exciting!!
Anyways, here's the full changlog!
```
# Added
- Support kind 42 chat messages (ArcadeCity).
- Friend icons next to names on some views. Check is friend. Arrows are friend-of-friends
- Load chat view first if content contains #chat
- Cancel button on search box
- Added profile picture cache
- Multiline DM messages
# Changed
- #hashtags now use the `t` tag instead of `hashtag`
- Clicking a chatroom quote reply will now expand it instead of jumping to it
- Clicking on a note will now always scroll it to the bottom
- Check note ids and signatures on every note
- use bech32 ids everywhere
- Don't animate scroll in chat view
- Post button is not shown if the content is only whitespace
# Fixed
- Fixed thread loading issue when clicking on boosts
- Fixed various issues with chatroom view
- Fix bug where sometimes nested navigation views weren't dismissed when tapping the tab bar
- Fixed minor carousel spacing issue on homescreen
- You can now reference users, notes hashtags in DMs
- Profile pics are now loaded in the background
- Limit post sizes to max 32,000 as an upper bound sanity limit.
- Missing profiles are now loaded everywhere
- No longer parse hashtags in urls
- Logging out now resets your keypair and actually logs out
- Copying text in DMs will now copy the decrypted text
```
=> https://damus.io Damus TestFlight

View file

@ -0,0 +1,88 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>The Damus Log</title>
<link rel="stylesheet" href="log.css?v=29">
<link rel="stylesheet" href="comments.css?v=5">
</head>
<body>
<section class="header">
<span class="logo">
<img src="/img/damus-nobg.svg"/>
</span>
</section>
<div class="container">
<a href="https://damus.io/log" class="date">&lt; The Damus Log</a>
<h1 id="v0.1.3---the-stuff-loads-better-release">v0.1.3 - The “Stuff
Loads Better” Release</h1>
<p>Its that time again! A new damus release. This one fixes a bunch of
annoying issues such as profiles not loading properly in some
situations. We also do a much better job at caching profile pictures, so
no more weird poppyness and wasting your cell data.</p>
<p>If youre not on the testflight already, you can get it here:</p>
<p><a href="https://testflight.apple.com/join/CLwjLxWl">Damus
TestFlight</a></p>
<p>This was the last release before lightning support, so next version
will be exciting!!</p>
<p>Anyways, heres the full changlog!</p>
<pre><code># Added
- Support kind 42 chat messages (ArcadeCity).
- Friend icons next to names on some views. Check is friend. Arrows are friend-of-friends
- Load chat view first if content contains #chat
- Cancel button on search box
- Added profile picture cache
- Multiline DM messages
# Changed
- #hashtags now use the `t` tag instead of `hashtag`
- Clicking a chatroom quote reply will now expand it instead of jumping to it
- Clicking on a note will now always scroll it to the bottom
- Check note ids and signatures on every note
- use bech32 ids everywhere
- Don&#39;t animate scroll in chat view
- Post button is not shown if the content is only whitespace
# Fixed
- Fixed thread loading issue when clicking on boosts
- Fixed various issues with chatroom view
- Fix bug where sometimes nested navigation views weren&#39;t dismissed when tapping the tab bar
- Fixed minor carousel spacing issue on homescreen
- You can now reference users, notes hashtags in DMs
- Profile pics are now loaded in the background
- Limit post sizes to max 32,000 as an upper bound sanity limit.
- Missing profiles are now loaded everywhere
- No longer parse hashtags in urls
- Logging out now resets your keypair and actually logs out
- Copying text in DMs will now copy the decrypted text</code></pre>
<p><a href="https://damus.io">Damus TestFlight</a></p>
<h3><a id="comment-link" href="nostr:e:">Comments</a></h3>
<div id="comments">
</div>
<script src="nostr.js?v=4" ></script>
<script src="comments.js?v=16" ></script>
<script>
const threads = {
"the-stuff-loads-better-release": "9941b55c2844f275b7b8714a1c39859088a425ce798f740ea8fea879f9098641",
"introducing-damus-log": "4e8b44bb43018f79bd3efcdcd71af43814cdf996e0c62adedda1ac33bf5e1371",
}
let relay
for (const key of Object.keys(threads)) {
if (window.location.href.includes(key)) {
const id = threads[key]
relay = comments_init(id)
document.querySelector("#comment-link").href = 'nostr:e:' + id
break
}
}
</script>
</div> <!-- container -->
</body>
</html>

View file

@ -1,4 +1,4 @@
#!/usr/bin/env sed -Ef
#!/usr/bin/env sedef
# gmi2md: Sed script to convert text/gemini to markdown.
# Based on v0.14.2 of the gemini spec.

View file

@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>The Damus Log</title>
<link rel="stylesheet" href="log.css?v=17">
<link rel="stylesheet" href="log.css?v=29">
<link rel="stylesheet" href="comments.css?v=5">
</head>
<body>
@ -16,3 +16,4 @@
</span>
</section>
<div class="container">
<a href="https://damus.io/log" class="date">&lt; The Damus Log</a>

1
log/img Symbolic link
View file

@ -0,0 +1 @@
../img/

View file

@ -1 +0,0 @@
2022-08-02-introducing-damus-log.html

35
log/index.html Normal file
View file

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>The Damus Log</title>
<link rel="stylesheet" href="log.css?v=28">
<link rel="stylesheet" href="comments.css?v=5">
</head>
<body>
<section class="header">
<span class="logo">
<img src="/img/damus-nobg.svg"/>
</span>
</section>
<div class="container">
<h1>The Damus Log</h1>
<ul>
<li><a href="2022-08-19-the-stuff-loads-better-release.html">v0.1.3 - The "Stuff Loads Better" Release</a><span class="date">2022-08-19</span></li>
<li><a href="2022-08-02-introducing-damus-log.html">Introducing The Damus Log</a><span class="date">2022-08-02</span></li>
</ul>
<h3><a href="nostr:e:2ed9b99190f0acf8f5cf768d4edd4be004a1262c6d296f341333e5e94b5ec423">Comments</a></h3>
<div id="comments">
</div>
<script src="nostr.js?v=4" ></script>
<script src="comments.js?v=16" ></script>
<script>
const relay = comments_init("2ed9b99190f0acf8f5cf768d4edd4be004a1262c6d296f341333e5e94b5ec423")
</script>
</div> <!-- container -->
</body>
</html>

View file

@ -12,17 +12,36 @@
letter-spacing: -0.05em;
}
.date {
font-size: 0.7em;
margin-left: 10px;
color: #eee;
}
.logo img {
padding-right: 18px;
width: 60px;
}
a {
font-family: -system-ui, sans-serif;
color: white;
}
a:visited {
color: #eee;
}
body {
color: white;
min-height: 800px;
}
html {
line-height: 1.5;
font-size: 20px;
font-family: "Georgia", sans-serif;
color: white;
background: linear-gradient(45deg, rgba(28,85,255,1) 0%, rgba(127,53,171,1) 59%, rgba(255,11,214,1) 100%);
}
.container {
@ -56,12 +75,6 @@ html {
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}

View file

@ -1,11 +1,23 @@
<h3>Comments</h3>
<h3><a id="comment-link" href="nostr:e:">Comments</a></h3>
<div id="comments">
</div>
<script src="nostr.js?v=4" ></script>
<script src="comments.js?v=16" ></script>
<script>
const relay = comments_init("4e8b44bb43018f79bd3efcdcd71af43814cdf996e0c62adedda1ac33bf5e1371")
const threads = {
"the-stuff-loads-better-release": "9941b55c2844f275b7b8714a1c39859088a425ce798f740ea8fea879f9098641",
"introducing-damus-log": "4e8b44bb43018f79bd3efcdcd71af43814cdf996e0c62adedda1ac33bf5e1371",
}
let relay
for (const key of Object.keys(threads)) {
if (window.location.href.includes(key)) {
const id = threads[key]
relay = comments_init(id)
document.querySelector("#comment-link").href = 'nostr:e:' + id
break
}
}
</script>
</div> <!-- container -->
</body>

4
web/Makefile Normal file
View file

@ -0,0 +1,4 @@
dist:
rsync -avzP ./ charon:/www/damus.io/web/

177
web/comments.js Normal file
View file

@ -0,0 +1,177 @@
function uuidv4() {
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}
async function comments_init(thread)
{
const relay = await Relay("wss://relay.damus.io")
const now = (new Date().getTime()) / 1000
const model = {events: [], profiles: {}}
const comments_id = uuidv4()
const profiles_id = uuidv4()
model.pool = relay
model.el = document.querySelector("#comments")
relay.subscribe(comments_id, {kinds: [1], "#e": [thread]})
relay.event = (sub_id, ev) => {
if (sub_id === comments_id) {
if (ev.content !== "")
insert_event_sorted(model.events, ev)
if (model.realtime)
render_home_view(model)
} else if (sub_id === profiles_id) {
try {
model.profiles[ev.pubkey] = JSON.parse(ev.content)
} catch {
console.log("failed to parse", ev.content)
}
}
}
relay.eose = async (sub_id) => {
if (sub_id === comments_id) {
handle_comments_loaded(profiles_id, model)
} else if (sub_id === profiles_id) {
handle_profiles_loaded(profiles_id, model)
}
}
return relay
}
function handle_profiles_loaded(profiles_id, model) {
// stop asking for profiles
model.pool.unsubscribe(profiles_id)
model.realtime = true
render_home_view(model)
}
// load profiles after comment notes are loaded
function handle_comments_loaded(profiles_id, model)
{
const pubkeys = model.events.reduce((s, ev) => {
s.add(ev.pubkey)
return s
}, new Set())
const authors = Array.from(pubkeys)
// load profiles
model.pool.subscribe(profiles_id, {kinds: [0], authors: authors})
}
function render_home_view(model) {
model.el.innerHTML = render_events(model)
}
function render_events(model) {
const render = render_event.bind(null, model)
return model.events.map(render).join("\n")
}
function render_event(model, ev) {
const profile = model.profiles[ev.pubkey] || {
name: "anon",
display_name: "Anonymous",
}
const delta = time_delta(new Date().getTime(), ev.created_at*1000)
return `
<div class="comment">
<div class="info">
${render_name(ev.pubkey, profile)}
<span>${delta}</span>
</div>
<img class="pfp" src="${get_picture(ev.pubkey, profile)}">
<p>
${format_content(ev.content)}
</p>
</div>
`
}
function convert_quote_blocks(content)
{
const split = content.split("\n")
let blockin = false
return split.reduce((str, line) => {
if (line !== "" && line[0] === '>') {
if (!blockin) {
str += "<span class='quote'>"
blockin = true
}
str += sanitize(line.slice(1))
} else {
if (blockin) {
blockin = false
str += "</span>"
}
str += sanitize(line)
}
return str + "<br/>"
}, "")
}
function format_content(content)
{
return convert_quote_blocks(content)
}
function sanitize(content)
{
if (!content)
return ""
return content.replaceAll("<","&lt;").replaceAll(">","&gt;")
}
function get_picture(pk, profile)
{
return sanitize(profile.picture) || "https://robohash.org/" + pk
}
function render_name(pk, profile={})
{
const display_name = profile.display_name || profile.user
const username = profile.name || "anon"
const name = display_name || username
return `<div class="username">${sanitize(name)}</div>`
}
function time_delta(current, previous) {
var msPerMinute = 60 * 1000;
var msPerHour = msPerMinute * 60;
var msPerDay = msPerHour * 24;
var msPerMonth = msPerDay * 30;
var msPerYear = msPerDay * 365;
var elapsed = current - previous;
if (elapsed < msPerMinute) {
return Math.round(elapsed/1000) + ' seconds ago';
}
else if (elapsed < msPerHour) {
return Math.round(elapsed/msPerMinute) + ' minutes ago';
}
else if (elapsed < msPerDay ) {
return Math.round(elapsed/msPerHour ) + ' hours ago';
}
else if (elapsed < msPerMonth) {
return Math.round(elapsed/msPerDay) + ' days ago';
}
else if (elapsed < msPerYear) {
return Math.round(elapsed/msPerMonth) + ' months ago';
}
else {
return Math.round(elapsed/msPerYear ) + ' years ago';
}
}

122
web/damus.css Normal file
View file

@ -0,0 +1,122 @@
.header {
display: flex;
margin: 30px 0 30px 0;
flex-direction: column;
align-items: center;
}
.logo {
margin-bottom: 0;
letter-spacing: -0.05em;
}
.logo img {
padding-right: 18px;
width: 60px;
}
html {
line-height: 1.5;
font-size: 20px;
font-family: "Georgia", sans-serif;
color: white;
background: linear-gradient(45deg, rgba(28,85,255,1) 0%, rgba(127,53,171,1) 59%, rgba(255,11,214,1) 100%);
}
.container {
margin: 0 auto;
max-width: 36em;
hyphens: auto;
word-wrap: break-word;
text-rendering: optimizeLegibility;
font-kerning: normal;
}
@media (max-width: 600px) {
.container {
font-size: 0.9em;
padding: 1em;
}
}
@media print {
.container {
background-color: transparent;
color: black;
font-size: 12pt;
}
p, h2, h3 {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
}
.pfp {
width: 60px;
height: 60px;
margin: 0 15px 0 15px;
border-radius: 50%;
}
.comment {
display: flex;
font-family: system-ui, sans;
margin-bottom: 20px;
flex-wrap: wrap;
align-items: center;
}
.comment p {
background-color: rgba(255.0,255.0,255.0,0.1);
padding: 10px;
border-radius: 8px;
margin: 0;
width: 55%;
}
.comment .info {
text-align: right;
width: 18%;
line-height: 0.8em;
}
.quote {
border-left: 2px solid white;
margin-left: 10px;
padding: 10px;
background-color: rgba(255.0,255.0,255.0,0.1);
display: block;
}
.comment .info span {
font-size: 11px;
color: rgba(255.0,255.0,255.0,0.7);
}
@media (max-width: 800px){
/* Reverse the order of elements in the user comments,
so that the avatar and info appear after the text. */
.comment .info {
order: 2;
width: 50%;
text-align: left;
}
.pfp {
order: 1;
margin: 0 15px 0 0;
}
.comment {
padding: 10px;
border-radius: 8px;
background-color: rgba(255.0,255.0,255.0,0.1);
}
.comment p {
order: 3;
margin-top: 10px;
width: 100%;
}
}

183
web/damus.js Normal file
View file

@ -0,0 +1,183 @@
function uuidv4() {
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}
async function damus_web_init(thread)
{
const relay = await Relay("wss://relay.damus.io")
const now = (new Date().getTime()) / 1000
const model = {events: [], profiles: {}}
const comments_id = uuidv4()
const profiles_id = uuidv4()
model.pool = relay
model.el = document.querySelector("#posts")
relay.subscribe(comments_id, {kinds: [1], limit: 100})
relay.event = (sub_id, ev) => {
if (sub_id === comments_id) {
if (ev.content !== "")
insert_event_sorted(model.events, ev)
if (model.realtime)
render_home_view(model)
} else if (sub_id === profiles_id) {
try {
model.profiles[ev.pubkey] = JSON.parse(ev.content)
} catch {
console.log("failed to parse", ev.content)
}
}
}
relay.eose = async (sub_id) => {
if (sub_id === comments_id) {
handle_comments_loaded(profiles_id, model)
} else if (sub_id === profiles_id) {
handle_profiles_loaded(profiles_id, model)
}
}
return relay
}
function handle_profiles_loaded(profiles_id, model) {
// stop asking for profiles
model.pool.unsubscribe(profiles_id)
model.realtime = true
render_home_view(model)
}
// load profiles after comment notes are loaded
function handle_comments_loaded(profiles_id, model)
{
const pubkeys = model.events.reduce((s, ev) => {
s.add(ev.pubkey)
return s
}, new Set())
const authors = Array.from(pubkeys)
// load profiles
model.pool.subscribe(profiles_id, {kinds: [0], authors: authors})
}
function render_home_view(model) {
model.el.innerHTML = render_events(model)
}
function render_events(model) {
const render = render_event.bind(null, model)
return model.events.map(render).join("\n")
}
function render_event(model, ev) {
const profile = model.profiles[ev.pubkey] || {
name: "anon",
display_name: "Anonymous",
}
const delta = time_delta(new Date().getTime(), ev.created_at*1000)
const pk = ev.pubkey
return `
<div class="comment">
<div class="info">
${render_name(ev.pubkey, profile)}
<span>${delta}</span>
</div>
<img class="pfp" onerror="this.onerror=null;this.src='${robohash(pk)}';" src="${get_picture(pk, profile)}">
<p>
${format_content(ev.content)}
</p>
</div>
`
}
function convert_quote_blocks(content)
{
const split = content.split("\n")
let blockin = false
return split.reduce((str, line) => {
if (line !== "" && line[0] === '>') {
if (!blockin) {
str += "<span class='quote'>"
blockin = true
}
str += sanitize(line.slice(1))
} else {
if (blockin) {
blockin = false
str += "</span>"
}
str += sanitize(line)
}
return str + "<br/>"
}, "")
}
function format_content(content)
{
return convert_quote_blocks(content)
}
function sanitize(content)
{
if (!content)
return ""
return content.replaceAll("<","&lt;").replaceAll(">","&gt;")
}
function robohash(pk) {
return "https://robohash.org/" + pk
}
function get_picture(pk, profile)
{
return sanitize(profile.picture) || robohash(pk)
}
function render_name(pk, profile={})
{
const display_name = profile.display_name || profile.user
const username = profile.name || "anon"
const name = display_name || username
return `<div class="username">${sanitize(name)}</div>`
}
function time_delta(current, previous) {
var msPerMinute = 60 * 1000;
var msPerHour = msPerMinute * 60;
var msPerDay = msPerHour * 24;
var msPerMonth = msPerDay * 30;
var msPerYear = msPerDay * 365;
var elapsed = current - previous;
if (elapsed < msPerMinute) {
return Math.round(elapsed/1000) + ' seconds ago';
}
else if (elapsed < msPerHour) {
return Math.round(elapsed/msPerMinute) + ' minutes ago';
}
else if (elapsed < msPerDay ) {
return Math.round(elapsed/msPerHour ) + ' hours ago';
}
else if (elapsed < msPerMonth) {
return Math.round(elapsed/msPerDay) + ' days ago';
}
else if (elapsed < msPerYear) {
return Math.round(elapsed/msPerMonth) + ' months ago';
}
else {
return Math.round(elapsed/msPerYear ) + ' years ago';
}
}

186
web/img/damus-nobg.svg Normal file
View file

@ -0,0 +1,186 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="146.15311mm"
height="184.664mm"
viewBox="0 0 146.15311 184.66401"
version="1.1"
id="svg5"
inkscape:version="1.2-alpha (0bd5040e, 2022-02-05)"
sodipodi:docname="damus-nobg.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:blackoutopacity="0.0"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="0.5946522"
inkscape:cx="73.992831"
inkscape:cy="206.8436"
inkscape:window-width="1435"
inkscape:window-height="844"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="0"
inkscape:current-layer="layer2" />
<defs
id="defs2">
<linearGradient
inkscape:collect="always"
id="linearGradient39361">
<stop
style="stop-color:#0de8ff;stop-opacity:0.78082192;"
offset="0"
id="stop39357" />
<stop
style="stop-color:#d600fc;stop-opacity:0.95433789;"
offset="1"
id="stop39359" />
</linearGradient>
<inkscape:path-effect
effect="bspline"
id="path-effect255"
is_visible="true"
lpeversion="1"
weight="33.333333"
steps="2"
helper_size="0"
apply_no_weight="true"
apply_with_weight="true"
only_selected="false" />
<linearGradient
inkscape:collect="always"
id="linearGradient2119">
<stop
style="stop-color:#1c55ff;stop-opacity:1;"
offset="0"
id="stop2115" />
<stop
style="stop-color:#7f35ab;stop-opacity:1;"
offset="0.5"
id="stop2123" />
<stop
style="stop-color:#ff0bd6;stop-opacity:1;"
offset="1"
id="stop2117" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2119"
id="linearGradient2121"
x1="10.067794"
y1="248.81357"
x2="246.56145"
y2="7.1864405"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient39361"
id="linearGradient39367"
x1="62.104473"
y1="128.78963"
x2="208.25758"
y2="128.78963"
gradientUnits="userSpaceOnUse" />
</defs>
<g
inkscape:label="Background"
inkscape:groupmode="layer"
id="layer1"
sodipodi:insensitive="true"
style="display:none"
transform="translate(-62.104473,-36.457485)">
<rect
style="fill:url(#linearGradient2121);fill-opacity:1;stroke-width:0.264583"
id="rect61"
width="256"
height="256"
x="-5.3875166e-08"
y="-1.0775033e-07"
ry="0"
inkscape:label="Gradient"
sodipodi:insensitive="true" />
</g>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Logo"
sodipodi:insensitive="true"
transform="translate(-62.104473,-36.457485)">
<path
style="fill:url(#linearGradient39367);fill-opacity:1;stroke:#ffffff;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 101.1429,213.87373 C 67.104473,239.1681 67.104473,42.67112 67.104473,42.67112 135.18122,57.58146 203.25844,72.491904 203.25758,105.24181 c -8.6e-4,32.74991 -68.07625,83.33755 -102.11468,108.63192 z"
id="path253"
sodipodi:insensitive="true" />
</g>
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="Poly"
sodipodi:insensitive="true"
transform="translate(-62.104473,-36.457485)">
<path
style="fill:#ffffff;fill-opacity:0.325424;stroke:#ffffff;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 67.32839,76.766948 112.00424,99.41949 100.04873,52.226693 Z"
id="path4648" />
<path
style="fill:#ffffff;fill-opacity:0.274576;stroke:#ffffff;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 111.45696,98.998695 107.00758,142.60261 70.077729,105.67276 Z"
id="path9299" />
<path
style="fill:#ffffff;fill-opacity:0.379661;stroke:#ffffff;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 111.01202,99.221164 29.14343,-37.15232 25.80641,39.377006 z"
id="path9301" />
<path
style="fill:#ffffff;fill-opacity:0.447458;stroke:#ffffff;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 111.45696,99.443631 57.17452,55.172309 -2.89209,-53.17009 z"
id="path9368" />
<path
style="fill:#ffffff;fill-opacity:0.20678;stroke:#ffffff;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 106.78511,142.38015 62.06884,12.68073 -57.17452,-55.617249 z"
id="path9370" />
<path
style="fill:#ffffff;fill-opacity:0.244068;stroke:#ffffff;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 106.78511,142.38015 -28.47603,32.9254 62.51378,7.56395 z"
id="path9372" />
<path
style="fill:#ffffff;fill-opacity:0.216949;stroke:#ffffff;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 165.96186,101.44585 195.7727,125.02756 182.64703,78.754017 Z"
id="path9374" />
</g>
<g
inkscape:groupmode="layer"
id="layer4"
inkscape:label="Vertices"
transform="translate(-62.104473,-36.457485)">
<circle
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path27764"
cx="106.86934"
cy="142.38014"
r="2.0022209" />
<circle
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="circle28773"
cx="111.54119"
cy="99.221161"
r="2.0022209" />
<circle
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="circle29091"
cx="165.90784"
cy="101.36163"
r="2.0022209" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.5 KiB

View file

@ -1,4 +1,3 @@
<!DOCTYPE html>
<html lang="en">
<head>
@ -6,12 +5,24 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Damus Web</title>
<link rel="stylesheet" href="damus.css?v=2">
</head>
<body>
<h1>Damus Web</h1>
<div id="content">
<section class="header">
<span class="logo">
<img src="img/damus-nobg.svg"/>
</span>
</section>
<div class="container">
<div id="posts">
</div>
<script src="index.js"></script>
</div>
<script src="nostr.js?v=1"></script>
<script src="damus.js?v=2"></script>
<script>
const relay = damus_web_init("4e8b44bb43018f79bd3efcdcd71af43814cdf996e0c62adedda1ac33bf5e1371")
</script>
</body>
</html>

73
web/nostr.js Normal file
View file

@ -0,0 +1,73 @@
function insert_event_sorted(evs, new_ev) {
for (let i = 0; i < evs.length; i++) {
const ev = evs[i]
if (new_ev.id === ev.id) {
return false
}
if (new_ev.created_at > ev.created_at) {
evs.splice(i, 0, new_ev)
return true
}
}
evs.push(new_ev)
return true
}
function Relay(relay, opts={})
{
if (!(this instanceof Relay))
return new Relay(relay, opts)
this.relay = relay
this.opts = opts
const me = this
return new Promise((resolve, reject) => {
const ws = me.ws = new WebSocket(relay);
let resolved = false
ws.onmessage = (m) => { handle_nostr_message(me, m) }
ws.onclose = () => { me.close && me.close() }
ws.onerror = () => { me.error && me.error() }
ws.onopen = () => {
if (resolved) {
me.open.bind(me)
return
}
resolved = true
resolve(me)
}
})
}
Relay.prototype.subscribe = function relay_subscribe(sub_id, ...filters) {
const tosend = ["REQ", sub_id, ...filters]
this.ws.send(JSON.stringify(tosend))
}
Relay.prototype.unsubscribe = function relay_unsubscribe(sub_id) {
const tosend = ["CLOSE", sub_id]
this.ws.send(JSON.stringify(tosend))
}
function handle_nostr_message(relay, msg)
{
const data = JSON.parse(msg.data)
if (data.length >= 2) {
switch (data[0]) {
case "EVENT":
if (data.length < 3)
return
return relay.event && relay.event(data[1], data[2])
case "EOSE":
return relay.eose && relay.eose(data[1])
case "NOTICE":
return relay.note && relay.note(...data.slice(1))
}
}
}