add all the things

Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
William Casarin 2022-08-04 08:51:56 -07:00
parent b56cd0642f
commit c075d3dd0e
23 changed files with 765 additions and 1 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View file

@ -8,6 +8,10 @@ html { font-family: 'Inter', sans-serif; }
max-width: 800px;
}
.blog-container {
font-family: serif;
}
label {
white-space: nowrap;
}

BIN
img/bitcoin-p2p.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

BIN
img/digital-nomad.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

BIN
img/encrypted-message.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
img/undercover.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

View file

@ -8,9 +8,10 @@
<meta property="og:title" content="Damus">
<meta property="og:description" content="A new social network that you control">
<meta property="og:image" content="https://damus.io/img/damus.png">
<meta property="og:image" content="https://damus.io/img/logo.png">
<meta property="og:url" content="https://damus.io">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://damus.io/img/logo.png">
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/skeleton.css?v=2">

1
log/.envrc Normal file
View file

@ -0,0 +1 @@
export PATH=$PWD:$PATH

View file

@ -0,0 +1,16 @@
# The Damus Log - Powered by #nostr
Hey there, Welcome to the damus log! A blog powered by... nostr! What does this mean!? What is nostr? Let's find out!
nostr is what powers damus, an iOS nostr client we're working on. It's a fancy pants new internet protocol designed to be the email of social networks. Imagine if email was controlled by a single company. Everyone would have to use the same email client (probably something like gmail), and a single company would have complete control over all your data... everyone's data!
This isn't good, this is why the internet today was originally built on these decentralized protocols. Things like websites and email are all available on different platforms, clients and servers. This freedom to pick and choose prevents any single company to have complete control over our data.
nostr is an attempt to do the same for social networks themselves. It provides a censorship resistant, real-time database. Anyone can run a nostr relay and no single relay is in control of the data. It's quite ingenious if we say so ourselves.
We like it so much we've made our blog nostr-powered! The comments below are from the nostr network. You can comment on it from the damus client itself! If you're interested in trying it out, try out the testflight at the bottom of our homepage:
=> https://damus.io damus.io
Looking forward to seeing you on nostr!

View file

@ -0,0 +1,55 @@
<!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=17">
<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 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
does this mean!? What is nostr? Lets find out!</p>
<p>nostr is what powers damus, an iOS nostr client were working on.
Its a fancy pants new internet protocol designed to be the email of
social networks. Imagine if email was controlled by a single company.
Everyone would have to use the same email client (probably something
like gmail), and a single company would have complete control over all
your data… everyones data!</p>
<p>This isnt good, this is why the internet today was originally built
on these decentralized protocols. Things like websites and email are all
available on different platforms, clients and servers. This freedom to
pick and choose prevents any single company to have complete control
over our data.</p>
<p>nostr is an attempt to do the same for social networks themselves. It
provides a censorship resistant, real-time database. Anyone can run a
nostr relay and no single relay is in control of the data. Its quite
ingenious if we say so ourselves.</p>
<p>We like it so much weve made our blog nostr-powered! The comments
below are from the nostr network. You can comment on it from the damus
client itself! If youre interested in trying it out, try out the
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>
<div id="comments">
</div>
<script src="nostr.js?v=4" ></script>
<script src="comments.js?v=16" ></script>
<script>
const relay = comments_init("4e8b44bb43018f79bd3efcdcd71af43814cdf996e0c62adedda1ac33bf5e1371")
</script>
</div> <!-- container -->
</body>
</html>

17
log/Makefile Normal file
View file

@ -0,0 +1,17 @@
POSTS=$(wildcard *.gmi)
HTMLS=$(POSTS:.gmi=.html)
all: $(HTMLS)
clean: fake
rm -f $(HTMLS)
dist: all
rsync -avzP ./ charon:/www/damus.io/log/
%.html: %.gmi head.html tail.html
./gmi2md < $< | pandoc -f markdown -t html -o - | cat head.html - tail.html > $@
.PHONY: fake

69
log/comments.css Normal file
View file

@ -0,0 +1,69 @@
.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%;
}
}

177
log/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';
}
}

25
log/gmi2md Executable file
View file

@ -0,0 +1,25 @@
#!/usr/bin/env sed -Ef
# gmi2md: Sed script to convert text/gemini to markdown.
# Based on v0.14.2 of the gemini spec.
#
# This script is dedicated to the public domain according to the terms of CC0:
# https://creativecommons.org/publicdomain/zero/1.0/
x
/^```/ {
x
/^```/ {
x
s/.*//
x
}
b
}
g
/^=>/ {
s/[][()]/\\&/g
s/^=>\s*([^[:space:]]+)\s*$/[\1](\1)/
s/^=>\s*([^[:space:]]+)\s+(.+)/[\2](\1)/
}

18
log/head.html Normal file
View file

@ -0,0 +1,18 @@
<!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=17">
<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">

1
log/index.html Symbolic link
View file

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

154
log/log.css Normal file
View file

@ -0,0 +1,154 @@
@import url('https://rsms.me/inter/inter.css');
.header {
display: flex;
margin: 50px 0 0 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;
}
}
p {
margin: 1em 0;
}
a {
color: #1a1a1a;
}
a:visited {
color: #1a1a1a;
}
img {
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
font-family: 'Inter', system-ui, sans-serif;
margin-top: 1.4em;
}
h5, h6 {
font-size: 1em;
font-style: italic;
}
h6 {
font-weight: normal;
}
ol, ul {
padding-left: 1.7em;
margin-top: 1em;
}
li > ol, li > ul {
margin-top: 0;
}
blockquote {
margin: 1em 0 1em 1.7em;
padding-left: 1em;
border-left: 2px solid #e6e6e6;
color: #606060;
}
code {
font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
font-size: 85%;
margin: 0;
}
pre {
margin: 1em 0;
overflow: auto;
}
pre code {
padding: 0;
overflow: visible;
}
.sourceCode {
background-color: transparent;
overflow: visible;
}
hr {
background-color: #1a1a1a;
border: none;
height: 1px;
margin: 1em 0;
}
table {
margin: 1em 0;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
display: block;
font-variant-numeric: lining-nums tabular-nums;
}
table caption {
margin-bottom: 0.75em;
}
tbody {
margin-top: 0.5em;
border-top: 1px solid #1a1a1a;
border-bottom: 1px solid #1a1a1a;
}
th {
border-top: 1px solid #1a1a1a;
padding: 0.25em 0.5em 0.25em 0.5em;
}
td {
padding: 0.125em 0.5em 0.25em 0.5em;
}
header {
margin-bottom: 4em;
text-align: center;
}
#TOC li {
list-style: none;
}
#TOC a:not(:hover) {
text-decoration: none;
}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
.display.math{display: block; text-align: center; margin: 0.5rem auto;}

73
log/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))
}
}
}

12
log/tail.html Normal file
View file

@ -0,0 +1,12 @@
<h3>Comments</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")
</script>
</div> <!-- container -->
</body>
</html>

10
log/template-head.html Normal file
View file

@ -0,0 +1,10 @@
<!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>
</head>
<body>

3
log/template-tail.html Normal file
View file

@ -0,0 +1,3 @@
</body>
</html>

17
web/index.html Normal file
View file

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Damus Web</title>
</head>
<body>
<h1>Damus Web</h1>
<div id="content">
</div>
<script src="index.js"></script>
</body>
</html>

111
web/index.js Normal file
View file

@ -0,0 +1,111 @@
async function damus_init()
{
const relay = await Relay("wss://relay.damus.io")
const now = (new Date().getTime()) / 1000
const el = document.querySelector("#content")
const model = {events: []}
el.innerHTML = render_initial_content()
model.el = el.querySelector("#home")
relay.subscribe("test_sub_id", {kinds: [1], limit: 20})
relay.event = (sub_id, ev) => {
insert_event_sorted(model.events, ev)
if (model.realtime)
render_home_view(model)
}
relay.eose = () => {
model.realtime = true
render_home_view(model)
}
return relay
}
function render_home_view(model) {
model.el.innerHTML = render_events(model.events)
}
function render_initial_content() {
return `<ul id="home"> </ul>`
}
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 render_events(evs) {
return evs.map(render_event).join("\n")
}
function render_event(ev) {
return `<li>${ev.content}</li>`
}
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_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]
console.log("sending", tosend)
this.ws.send(JSON.stringify(tosend))
}
function handle_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))
}
}
}
const relay = damus_init()