damus.io

damus.io website
git clone git://jb55.com/damus.io
Log | Files | Refs

commit 9788baa322b2c4dd7d5771c3fa17c8d3d937eaf9
parent c075d3dd0eed21b32b1c4373d5ab7b887216256e
Author: William Casarin <jb55@jb55.com>
Date:   Fri, 19 Aug 2022 11:15:26 -0700

latest

Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
Mcss/custom.css | 10++++++++++
Mindex.html | 11++++++++---
Mlog/2022-08-02-introducing-damus-log.html | 19++++++++++++++++---
Alog/2022-08-19-the-stuff-loads-better-release.gmi | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Alog/2022-08-19-the-stuff-loads-better-release.html | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlog/gmi2md | 2+-
Mlog/head.html | 3++-
Alog/img | 2++
Tlog/index.html | 0
Mlog/log.css | 27++++++++++++++++++++-------
Mlog/tail.html | 16++++++++++++++--
Aweb/Makefile | 4++++
Aweb/comments.js | 177+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aweb/damus.css | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aweb/damus.js | 183+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aweb/img/damus-nobg.svg | 186+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mweb/index.html | 21++++++++++++++++-----
Aweb/nostr.js | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
18 files changed, 973 insertions(+), 22 deletions(-)

diff --git a/css/custom.css b/css/custom.css @@ -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; } diff --git a/index.html b/index.html @@ -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> - <a href="https://testflight.apple.com/join/CLwjLxWl">Join the TestFlight Beta</a> + <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> diff --git a/log/2022-08-02-introducing-damus-log.html b/log/2022-08-02-introducing-damus-log.html @@ -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> diff --git a/log/2022-08-19-the-stuff-loads-better-release.gmi b/log/2022-08-19-the-stuff-loads-better-release.gmi @@ -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 + diff --git a/log/2022-08-19-the-stuff-loads-better-release.html b/log/2022-08-19-the-stuff-loads-better-release.html @@ -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>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.</p> +<p>If you’re 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, here’s 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> diff --git a/log/gmi2md b/log/gmi2md @@ -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. diff --git a/log/head.html b/log/head.html @@ -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> diff --git a/log/img b/log/img @@ -0,0 +1 @@ +../img/+ \ No newline at end of file diff --git a/log/index.html b/log/index.html diff --git a/log/log.css b/log/log.css @@ -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%; } diff --git a/log/tail.html b/log/tail.html @@ -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> diff --git a/web/Makefile b/web/Makefile @@ -0,0 +1,4 @@ + + +dist: + rsync -avzP ./ charon:/www/damus.io/web/ diff --git a/web/comments.js b/web/comments.js @@ -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'; + } +} diff --git a/web/damus.css b/web/damus.css @@ -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%; + } +} diff --git a/web/damus.js b/web/damus.js @@ -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'; + } +} diff --git a/web/img/damus-nobg.svg b/web/img/damus-nobg.svg @@ -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> diff --git a/web/index.html b/web/index.html @@ -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"> - </div> - <script src="index.js"></script> + <section class="header"> + <span class="logo"> + <img src="img/damus-nobg.svg"/> + </span> + </section> + <div class="container"> + <div id="posts"> + </div> + </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> diff --git a/web/nostr.js b/web/nostr.js @@ -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)) + } + } +} +