v0.1.0
This commit is contained in:
parent
ad30d14a66
commit
04a379ae58
20 changed files with 2111 additions and 2475 deletions
40
README.md
40
README.md
|
|
@ -1,27 +1,25 @@
|
|||
# What is NostriChat?
|
||||
Nostri.chat is a chat widget you can easily embed in websites.
|
||||
# What is DiSseNT?
|
||||
|
||||
It uses Nostr as the underlying protocol, which permits a few pretty cool features.
|
||||
DiSseNT is my attempt at ressurecting a valuable [project](https://github.com/gab-ai-inc/gab-dissenter-extension/issues/117) to enable truely trustless and free commentary on the web. DiSseNT is a web app built on [NOSTR](https://github.com/nostr-protocol/nostr), and based on [Nostri.chat](https://github.com/pablof7z/nostr-chat-widget), that associates a set of NOSTR messages with a given URL.
|
||||
|
||||
## Operation Modes
|
||||
### Classic chat: 1-to-1 encrypted chats
|
||||
This mode implements the typical chat widget flow present in most websites. The visitor writes in the website and someone associated with the website responds.
|
||||
# How do I use it?
|
||||
1. Type or paste a url
|
||||
|
||||
No one else sees this communication
|
||||
# Roadmap
|
||||
- [x] basic posting, associated with a url
|
||||
- [x] display meta info
|
||||
- [ ] sort comments
|
||||
- [ ] zap comments
|
||||
- [ ] search comments
|
||||
- [ ] filter comments
|
||||
- [ ] chromium extension
|
||||
- [ ] dark mode
|
||||
- [ ] social media specific urls
|
||||
- [ ] relay picker
|
||||
- [ ] show dsnt'd urls
|
||||
|
||||
### Global chat: Topic/Website-based communication
|
||||
In this mode, the user engages in a conversation around a topic and everybody connected to the same relays can see the communication happening and interact with it.
|
||||
# Supporting the Roadmap
|
||||
|
||||
The communication can be scoped to one or multiple topics. (e.g. _#fasting_, _#bitcoin_, or your specific website).
|
||||
<a href='https://ko-fi.com/O4O1OZX1V' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi2.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
|
||||
|
||||
When a visitor interacts with this mode, the chat widget is populated with the prior conversations that have already occurred around this topic.
|
||||
|
||||
> Imagine visiting a website about #fasting, and you can immediately interact with anyone interested in that topic; you can ask questions and receive immediate responses from others
|
||||
|
||||
# Features
|
||||
- [x] NostrConnect key delegation
|
||||
- [x] Ephemeral keys
|
||||
- [x] Encrypted DMs mode
|
||||
- [x] Tag-scoped chats mode
|
||||
- [x] In-thread replies
|
||||
- [ ] Root-replies mode: similar to global (publicly available) but visitor doesn't see any past history and only sees in-thread replies to the OP
|
||||
<a href="https://liberapay.com/spencer.flagg/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a>
|
||||
347
package-lock.json
generated
347
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "dnst",
|
||||
"version": "0.3.14159",
|
||||
"name": "dsnt",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "dsnt",
|
||||
"version": "0.3.14159",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@nostr-connect/connect": "^0.2.3",
|
||||
"@nostr-dev-kit/ndk": "^0.3.32",
|
||||
|
|
@ -21,6 +21,7 @@
|
|||
"emoji-regex": "^10.2.1",
|
||||
"eventemitter3": "^5.0.0",
|
||||
"light-bolt11-decoder": "^3.0.0",
|
||||
"marked": "^9.0.0",
|
||||
"nostr": "^0.2.7",
|
||||
"nostr-dev-kit": "file:../../nostr/ndk/nostr-dev-kit",
|
||||
"nostr-tools": "^1.11.1",
|
||||
|
|
@ -50,10 +51,12 @@
|
|||
"rollup-plugin-svelte": "^7.1.5",
|
||||
"sirv-cli": "^2.0.2",
|
||||
"svelte": "^3.54.0",
|
||||
"svelte-routing": "^2.3.0",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^4.9.3",
|
||||
"vite": "^4.0.0"
|
||||
"vite": "^4.0.0",
|
||||
"vite-plugin-svelte-md": "^0.1.7"
|
||||
}
|
||||
},
|
||||
"../../nostr/ndk/nostr-dev-kit": {
|
||||
|
|
@ -3790,6 +3793,18 @@
|
|||
"resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz",
|
||||
"integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw=="
|
||||
},
|
||||
"node_modules/extend-shallow": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||
"integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-extendable": "^0.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
|
|
@ -4194,6 +4209,56 @@
|
|||
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
|
||||
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="
|
||||
},
|
||||
"node_modules/gray-matter": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
|
||||
"integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"js-yaml": "^3.13.1",
|
||||
"kind-of": "^6.0.2",
|
||||
"section-matter": "^1.0.0",
|
||||
"strip-bom-string": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/gray-matter/node_modules/argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/gray-matter/node_modules/esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"esparse": "bin/esparse.js",
|
||||
"esvalidate": "bin/esvalidate.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/gray-matter/node_modules/js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/hard-rejection": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz",
|
||||
|
|
@ -4539,6 +4604,15 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extendable": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
|
||||
"integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
|
|
@ -4880,6 +4954,15 @@
|
|||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
|
||||
},
|
||||
"node_modules/linkify-it": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz",
|
||||
"integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"uc.micro": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/livereload": {
|
||||
"version": "0.9.3",
|
||||
"resolved": "https://registry.npmjs.org/livereload/-/livereload-0.9.3.tgz",
|
||||
|
|
@ -5105,15 +5188,43 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-it": {
|
||||
"version": "13.0.1",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.1.tgz",
|
||||
"integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1",
|
||||
"entities": "~3.0.1",
|
||||
"linkify-it": "^4.0.1",
|
||||
"mdurl": "^1.0.1",
|
||||
"uc.micro": "^1.0.5"
|
||||
},
|
||||
"bin": {
|
||||
"markdown-it": "bin/markdown-it.js"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-it/node_modules/entities": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz",
|
||||
"integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
|
||||
"integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==",
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-9.0.0.tgz",
|
||||
"integrity": "sha512-37yoTpjU+TSXb9OBYY5n78z/CqXh76KiQj9xsKxEdztzU9fRLmbWO5YqKxgCVGKlNdexppnbKTkwB3RipVri8w==",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
"node": ">= 16"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
|
|
@ -5121,6 +5232,12 @@
|
|||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
|
||||
"integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow=="
|
||||
},
|
||||
"node_modules/mdurl": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
|
||||
"integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/memorystream": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
|
||||
|
|
@ -7379,6 +7496,19 @@
|
|||
"rimraf": "^2.5.2"
|
||||
}
|
||||
},
|
||||
"node_modules/section-matter": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
|
||||
"integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"extend-shallow": "^2.0.1",
|
||||
"kind-of": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/semiver": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/semiver/-/semiver-1.1.0.tgz",
|
||||
|
|
@ -7587,6 +7717,12 @@
|
|||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz",
|
||||
"integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w=="
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/stable": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz",
|
||||
|
|
@ -7725,6 +7861,15 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-bom-string": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
|
||||
"integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-indent": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
|
||||
|
|
@ -7958,6 +8103,12 @@
|
|||
"resolved": "https://registry.npmjs.org/svelte-qr/-/svelte-qr-1.0.0.tgz",
|
||||
"integrity": "sha512-7n/FPFhImPI68NCwChzYqzTbTpDhGCiFgGiCQY+IXS8sh0Xhzzd0wwQnN5n2BCJ0Uvti8s0RhKErwcw4Lp7RvQ=="
|
||||
},
|
||||
"node_modules/svelte-routing": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-routing/-/svelte-routing-2.3.0.tgz",
|
||||
"integrity": "sha512-M4KY7YrJ9txzn1ssLUa0dfkAxg7IuNpYMMspm/KoQKh/pHMGpCTAMn1q+gSxyUZNGDX1pq12fF2VRUq4+gBfxA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/svelte-scrollto": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-scrollto/-/svelte-scrollto-0.2.0.tgz",
|
||||
|
|
@ -8343,6 +8494,17 @@
|
|||
"typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x"
|
||||
}
|
||||
},
|
||||
"node_modules/typedoc/node_modules/marked": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
|
||||
"integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/typedoc/node_modules/minimatch": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
|
||||
|
|
@ -8369,6 +8531,12 @@
|
|||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uc.micro": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
|
||||
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/unbox-primitive": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
|
||||
|
|
@ -8528,6 +8696,23 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-svelte-md": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-svelte-md/-/vite-plugin-svelte-md-0.1.7.tgz",
|
||||
"integrity": "sha512-KtNqcuGyrr8EnTWxS+X9jCG6NnmONxYqoZJNr1VsLf+CKZrhykn+rpqxapcGr0g8KeDhYzrkkKASbQikCsQY4Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.0.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
"markdown-it": "^13.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ota-meshi"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^2.0.0 || ^3.0.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/rollup": {
|
||||
"version": "3.12.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.12.1.tgz",
|
||||
|
|
@ -11187,6 +11372,15 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"extend-shallow": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||
"integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-extendable": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
|
|
@ -11477,6 +11671,45 @@
|
|||
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
|
||||
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="
|
||||
},
|
||||
"gray-matter": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
|
||||
"integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"js-yaml": "^3.13.1",
|
||||
"kind-of": "^6.0.2",
|
||||
"section-matter": "^1.0.0",
|
||||
"strip-bom-string": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||
"dev": true
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"hard-rejection": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz",
|
||||
|
|
@ -11709,6 +11942,12 @@
|
|||
"has-tostringtag": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"is-extendable": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
|
||||
"integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
|
||||
"dev": true
|
||||
},
|
||||
"is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
|
|
@ -11950,6 +12189,15 @@
|
|||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
|
||||
},
|
||||
"linkify-it": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz",
|
||||
"integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"uc.micro": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"livereload": {
|
||||
"version": "0.9.3",
|
||||
"resolved": "https://registry.npmjs.org/livereload/-/livereload-0.9.3.tgz",
|
||||
|
|
@ -12118,16 +12366,43 @@
|
|||
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz",
|
||||
"integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ=="
|
||||
},
|
||||
"markdown-it": {
|
||||
"version": "13.0.1",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.1.tgz",
|
||||
"integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"argparse": "^2.0.1",
|
||||
"entities": "~3.0.1",
|
||||
"linkify-it": "^4.0.1",
|
||||
"mdurl": "^1.0.1",
|
||||
"uc.micro": "^1.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"entities": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz",
|
||||
"integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"marked": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
|
||||
"integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A=="
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-9.0.0.tgz",
|
||||
"integrity": "sha512-37yoTpjU+TSXb9OBYY5n78z/CqXh76KiQj9xsKxEdztzU9fRLmbWO5YqKxgCVGKlNdexppnbKTkwB3RipVri8w=="
|
||||
},
|
||||
"mdn-data": {
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
|
||||
"integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow=="
|
||||
},
|
||||
"mdurl": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
|
||||
"integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==",
|
||||
"dev": true
|
||||
},
|
||||
"memorystream": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
|
||||
|
|
@ -13685,6 +13960,16 @@
|
|||
"rimraf": "^2.5.2"
|
||||
}
|
||||
},
|
||||
"section-matter": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
|
||||
"integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"extend-shallow": "^2.0.1",
|
||||
"kind-of": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"semiver": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/semiver/-/semiver-1.1.0.tgz",
|
||||
|
|
@ -13850,6 +14135,12 @@
|
|||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz",
|
||||
"integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w=="
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||
"dev": true
|
||||
},
|
||||
"stable": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz",
|
||||
|
|
@ -13953,6 +14244,12 @@
|
|||
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
|
||||
"integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="
|
||||
},
|
||||
"strip-bom-string": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
|
||||
"integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
|
||||
"dev": true
|
||||
},
|
||||
"strip-indent": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
|
||||
|
|
@ -14082,6 +14379,12 @@
|
|||
"resolved": "https://registry.npmjs.org/svelte-qr/-/svelte-qr-1.0.0.tgz",
|
||||
"integrity": "sha512-7n/FPFhImPI68NCwChzYqzTbTpDhGCiFgGiCQY+IXS8sh0Xhzzd0wwQnN5n2BCJ0Uvti8s0RhKErwcw4Lp7RvQ=="
|
||||
},
|
||||
"svelte-routing": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-routing/-/svelte-routing-2.3.0.tgz",
|
||||
"integrity": "sha512-M4KY7YrJ9txzn1ssLUa0dfkAxg7IuNpYMMspm/KoQKh/pHMGpCTAMn1q+gSxyUZNGDX1pq12fF2VRUq4+gBfxA==",
|
||||
"dev": true
|
||||
},
|
||||
"svelte-scrollto": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-scrollto/-/svelte-scrollto-0.2.0.tgz",
|
||||
|
|
@ -14360,6 +14663,11 @@
|
|||
"shiki": "^0.14.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"marked": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
|
||||
"integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A=="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
|
||||
|
|
@ -14375,6 +14683,12 @@
|
|||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g=="
|
||||
},
|
||||
"uc.micro": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
|
||||
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==",
|
||||
"dev": true
|
||||
},
|
||||
"unbox-primitive": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
|
||||
|
|
@ -14473,6 +14787,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"vite-plugin-svelte-md": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-svelte-md/-/vite-plugin-svelte-md-0.1.7.tgz",
|
||||
"integrity": "sha512-KtNqcuGyrr8EnTWxS+X9jCG6NnmONxYqoZJNr1VsLf+CKZrhykn+rpqxapcGr0g8KeDhYzrkkKASbQikCsQY4Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "^5.0.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
"markdown-it": "^13.0.0"
|
||||
}
|
||||
},
|
||||
"vitefu": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.4.tgz",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "dsnt",
|
||||
"version": "0.3.14159",
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "svelte-kit sync && svelte-package",
|
||||
|
|
@ -18,10 +18,12 @@
|
|||
"rollup-plugin-svelte": "^7.1.5",
|
||||
"sirv-cli": "^2.0.2",
|
||||
"svelte": "^3.54.0",
|
||||
"svelte-routing": "^2.3.0",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^4.9.3",
|
||||
"vite": "^4.0.0"
|
||||
"vite": "^4.0.0",
|
||||
"vite-plugin-svelte-md": "^0.1.7"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
|
@ -38,6 +40,7 @@
|
|||
"emoji-regex": "^10.2.1",
|
||||
"eventemitter3": "^5.0.0",
|
||||
"light-bolt11-decoder": "^3.0.0",
|
||||
"marked": "^9.0.0",
|
||||
"nostr": "^0.2.7",
|
||||
"nostr-dev-kit": "file:../../nostr/ndk/nostr-dev-kit",
|
||||
"nostr-tools": "^1.11.1",
|
||||
|
|
|
|||
|
|
@ -1,377 +1,657 @@
|
|||
<script>
|
||||
import { chatAdapter, chatData, selectedMessage, zapsPerMessage } from './lib/store';
|
||||
import { onMount } from 'svelte';
|
||||
import NostrNote from './NostrNote.svelte';
|
||||
import * as animateScroll from "svelte-scrollto";
|
||||
import {
|
||||
chatAdapter,
|
||||
chatData,
|
||||
selectedMessage,
|
||||
zapsPerMessage,
|
||||
} from "./lib/store";
|
||||
import { onMount, onDestroy } from "svelte";
|
||||
import NostrNote from "./NostrNote.svelte";
|
||||
import * as animateScroll from "svelte-scrollto";
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
let events = [];
|
||||
let responseEvents = [];
|
||||
let responses = {};
|
||||
let profiles = {};
|
||||
let events = [];
|
||||
let responseEvents = [];
|
||||
let responses = {};
|
||||
let profiles = {};
|
||||
|
||||
export let websiteOwnerPubkey;
|
||||
export let chatConfiguration;
|
||||
let prevChatConfiguration;
|
||||
export let websiteOwnerPubkey;
|
||||
export let chatConfiguration;
|
||||
let prevChatConfiguration;
|
||||
|
||||
$: {
|
||||
if (chatConfiguration !== prevChatConfiguration && $chatAdapter) {
|
||||
$chatAdapter.setChatConfiguration(
|
||||
chatConfiguration.chatType,
|
||||
chatConfiguration.chatTags,
|
||||
chatConfiguration.chatReferenceTags,
|
||||
chatConfiguration.chatId);
|
||||
events = [];
|
||||
responses = {};
|
||||
rootNoteId = null;
|
||||
localStorage.removeItem('rootNoteId');
|
||||
export let mainElement;
|
||||
|
||||
// rootNoteId = localStorage.getItem('rootNoteId');
|
||||
// if (rootNoteId) {
|
||||
// $chatAdapter.subscribeToEventAndResponses(rootNoteId);
|
||||
// }
|
||||
$: {
|
||||
if (chatConfiguration !== prevChatConfiguration && $chatAdapter) {
|
||||
$chatAdapter.setChatConfiguration(
|
||||
chatConfiguration.chatType,
|
||||
chatConfiguration.chatTags,
|
||||
chatConfiguration.chatReferenceTags,
|
||||
chatConfiguration.chatId
|
||||
);
|
||||
events = [];
|
||||
responses = {};
|
||||
rootNoteId = null;
|
||||
localStorage.removeItem("rootNoteId");
|
||||
|
||||
// rootNoteId = localStorage.getItem('rootNoteId');
|
||||
// if (rootNoteId) {
|
||||
// $chatAdapter.subscribeToEventAndResponses(rootNoteId);
|
||||
// }
|
||||
}
|
||||
prevChatConfiguration = chatConfiguration;
|
||||
}
|
||||
|
||||
function getEventById(eventId) {
|
||||
let event = events.find((e) => e.id === eventId);
|
||||
event = event || responseEvents.find((e) => e.id === eventId);
|
||||
return event;
|
||||
}
|
||||
|
||||
async function sendMessage() {
|
||||
const input = document.getElementById("message-input");
|
||||
const message = input.value;
|
||||
input.value = "";
|
||||
let extraParams = { tags: [], tagPubKeys: [] };
|
||||
|
||||
// if this is the rootLevel we want to tag the owner of the site's pubkey
|
||||
if (!rootNoteId && websiteOwnerPubkey) {
|
||||
extraParams.tagPubKeys = [websiteOwnerPubkey];
|
||||
}
|
||||
|
||||
// if we are responding to an event, we want to tag the event and the pubkey
|
||||
if ($selectedMessage) {
|
||||
extraParams.tags.push(["e", $selectedMessage, "wss://nos.lol", "root"]);
|
||||
extraParams.tagPubKeys.push(getEventById($selectedMessage).pubkey);
|
||||
}
|
||||
|
||||
// if (rootNoteId) {
|
||||
// // mark it as a response to the most recent event
|
||||
// const mostRecentEvent = events[events.length - 1];
|
||||
// // go through all the tags and add them to the new message
|
||||
// if (mostRecentEvent) {
|
||||
// mostRecentEvent.tags.forEach(tag => {
|
||||
// if (tag[0] === 'e') {
|
||||
// extraParams.tags.push(tag);
|
||||
// }
|
||||
// })
|
||||
// extraParams.tags.push(['e', mostRecentEvent.id]);
|
||||
// extraParams.tags.push(['p', mostRecentEvent.pubkey]);
|
||||
// }
|
||||
// }
|
||||
|
||||
const noteId = await $chatAdapter.send(message, extraParams);
|
||||
|
||||
if (!rootNoteId) {
|
||||
rootNoteId = noteId;
|
||||
localStorage.setItem("rootNoteId", rootNoteId);
|
||||
}
|
||||
}
|
||||
|
||||
async function inputKeyDown(event) {
|
||||
autoExpandTextarea(event);
|
||||
if (event.key === "Enter" && !event.ctrlKey) {
|
||||
sendMessage();
|
||||
event.preventDefault();
|
||||
}
|
||||
if (event.key === "Enter" && event.ctrlKey) {
|
||||
messageInput += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
function messageReceived(message) {
|
||||
const messageLastEventTag = message.tags
|
||||
.filter((tag) => tag[0] === "e")
|
||||
.pop();
|
||||
let isThread;
|
||||
|
||||
if (chatConfiguration.chatType === "GLOBAL") {
|
||||
isThread = message.tags.filter((tag) => tag[0] === "e").length >= 1;
|
||||
} else if (chatConfiguration.chatType === "GROUP") {
|
||||
isThread =
|
||||
message.tags.filter(
|
||||
(tag) => tag[0] === "e" && tag[1] !== chatConfiguration.chatId
|
||||
).length >= 1;
|
||||
} else {
|
||||
const pubkeysTagged = message.tags
|
||||
.filter((tag) => tag[0] === "p")
|
||||
.map((tag) => tag[1]);
|
||||
isThread = new Set(pubkeysTagged).size >= 2;
|
||||
}
|
||||
|
||||
if (!responses[message.id]) {
|
||||
responses[message.id] = [];
|
||||
}
|
||||
|
||||
if (isThread) {
|
||||
// get the last "e" tag, which is tagging the immediate parent
|
||||
const lastETag = message.tags.filter((tag) => tag[0] === "e").pop();
|
||||
if (lastETag && lastETag[1]) {
|
||||
// if there is one, add it to the response
|
||||
if (!responses[lastETag[1]]) {
|
||||
responses[lastETag[1]] = [];
|
||||
}
|
||||
prevChatConfiguration = chatConfiguration;
|
||||
responses[lastETag[1]].push(message);
|
||||
}
|
||||
|
||||
responseEvents.push(message);
|
||||
responseEvents = responseEvents;
|
||||
} else {
|
||||
// insert message so that it's chronologically ordered by created_at
|
||||
// let index = 0;
|
||||
// while (
|
||||
// index < events.length &&
|
||||
// events[index].created_at < message.created_at
|
||||
// ) {
|
||||
// index++;
|
||||
// }
|
||||
let index = events.length;
|
||||
while (index > 0 && events[index - 1].created_at < message.created_at) {
|
||||
index--;
|
||||
}
|
||||
events.splice(index, 0, message);
|
||||
events = events;
|
||||
}
|
||||
|
||||
function getEventById(eventId) {
|
||||
let event = events.find(e => e.id === eventId);
|
||||
event = event || responseEvents.find(e => e.id === eventId);
|
||||
return event;
|
||||
responses = responses;
|
||||
|
||||
//scrollDown();
|
||||
}
|
||||
|
||||
function scrollUp() {
|
||||
animateScroll.scrollToTop({
|
||||
container: mainElement,
|
||||
offset: 0,
|
||||
duration: 50,
|
||||
});
|
||||
}
|
||||
|
||||
function zapReceived(zap) {
|
||||
const event = events.find((event) => event.id === zap.zappedEvent);
|
||||
if (!event) {
|
||||
return;
|
||||
}
|
||||
|
||||
async function sendMessage() {
|
||||
const input = document.getElementById('message-input');
|
||||
const message = input.value;
|
||||
input.value = '';
|
||||
let extraParams = { tags: [], tagPubKeys: [] };
|
||||
if (!$zapsPerMessage[event.id]) $zapsPerMessage[event.id] = [];
|
||||
$zapsPerMessage[event.id].push(zap);
|
||||
}
|
||||
|
||||
// if this is the rootLevel we want to tag the owner of the site's pubkey
|
||||
if (!rootNoteId && websiteOwnerPubkey) { extraParams.tagPubKeys = [websiteOwnerPubkey] }
|
||||
|
||||
// if we are responding to an event, we want to tag the event and the pubkey
|
||||
if ($selectedMessage) {
|
||||
extraParams.tags.push(['e', $selectedMessage, "wss://nos.lol", "root"]);
|
||||
extraParams.tagPubKeys.push(getEventById($selectedMessage).pubkey);
|
||||
}
|
||||
|
||||
// if (rootNoteId) {
|
||||
// // mark it as a response to the most recent event
|
||||
// const mostRecentEvent = events[events.length - 1];
|
||||
// // go through all the tags and add them to the new message
|
||||
// if (mostRecentEvent) {
|
||||
// mostRecentEvent.tags.forEach(tag => {
|
||||
// if (tag[0] === 'e') {
|
||||
// extraParams.tags.push(tag);
|
||||
// }
|
||||
// })
|
||||
// extraParams.tags.push(['e', mostRecentEvent.id]);
|
||||
// extraParams.tags.push(['p', mostRecentEvent.pubkey]);
|
||||
// }
|
||||
// }
|
||||
|
||||
const noteId = await $chatAdapter.send(message, extraParams);
|
||||
|
||||
if (!rootNoteId) {
|
||||
rootNoteId = noteId;
|
||||
localStorage.setItem('rootNoteId', rootNoteId);
|
||||
}
|
||||
function reactionReceived(reaction) {
|
||||
const event = events.find((event) => event.id === reaction.id);
|
||||
if (!event) {
|
||||
return;
|
||||
}
|
||||
|
||||
async function inputKeyDown(event) {
|
||||
if (event.key === 'Enter') {
|
||||
sendMessage();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
event.reactions = event.reactions || [];
|
||||
event.reactions.push(reaction);
|
||||
events = events;
|
||||
}
|
||||
|
||||
function messageReceived(message) {
|
||||
const messageLastEventTag = message.tags.filter(tag => tag[0] === 'e').pop();
|
||||
let isThread;
|
||||
let rootNoteId;
|
||||
let channelMetadata = {};
|
||||
|
||||
if (chatConfiguration.chatType === 'GLOBAL') {
|
||||
isThread = message.tags.filter(tag => tag[0] === 'e').length >= 1;
|
||||
} else if (chatConfiguration.chatType === 'GROUP') {
|
||||
isThread = message.tags.filter(tag => tag[0] === 'e' && tag[1] !== chatConfiguration.chatId).length >= 1;
|
||||
} else {
|
||||
const pubkeysTagged = message.tags.filter(tag => tag[0] === 'p').map(tag => tag[1]);
|
||||
isThread = new Set(pubkeysTagged).size >= 2;
|
||||
}
|
||||
onMount(() => {
|
||||
$chatAdapter.on("message", messageReceived);
|
||||
|
||||
if (!responses[message.id]) { responses[message.id] = [] };
|
||||
|
||||
if (isThread) {
|
||||
// get the last "e" tag, which is tagging the immediate parent
|
||||
const lastETag = message.tags.filter(tag => tag[0] === 'e').pop();
|
||||
if (lastETag && lastETag[1]) {
|
||||
// if there is one, add it to the response
|
||||
if (!responses[lastETag[1]]) {
|
||||
responses[lastETag[1]] = [];
|
||||
}
|
||||
responses[lastETag[1]].push(message);
|
||||
}
|
||||
|
||||
responseEvents.push(message);
|
||||
responseEvents = responseEvents;
|
||||
} else {
|
||||
// insert message so that it's chronologically ordered by created_at
|
||||
let index = 0;
|
||||
while (index < events.length && events[index].created_at < message.created_at) {
|
||||
index++;
|
||||
}
|
||||
events.splice(index, 0, message);
|
||||
events = events;
|
||||
}
|
||||
|
||||
responses = responses;
|
||||
|
||||
scrollDown()
|
||||
}
|
||||
|
||||
function scrollDown() {
|
||||
animateScroll.scrollToBottom({
|
||||
container: document.getElementById('messages-container'),
|
||||
offset: 999999, // hack, oh well, browsers suck
|
||||
duration: 50
|
||||
})
|
||||
}
|
||||
|
||||
function zapReceived(zap) {
|
||||
const event = events.find(event => event.id === zap.zappedEvent);
|
||||
if (!event) { return; }
|
||||
|
||||
if (!$zapsPerMessage[event.id]) $zapsPerMessage[event.id] = [];
|
||||
$zapsPerMessage[event.id].push(zap);
|
||||
}
|
||||
|
||||
function reactionReceived(reaction) {
|
||||
const event = events.find(event => event.id === reaction.id);
|
||||
if (!event) { return; }
|
||||
|
||||
event.reactions = event.reactions || [];
|
||||
event.reactions.push(reaction);
|
||||
events = events;
|
||||
}
|
||||
|
||||
let rootNoteId;
|
||||
let channelMetadata = {};
|
||||
|
||||
onMount(() => {
|
||||
$chatAdapter.on('message', messageReceived);
|
||||
|
||||
$chatAdapter.on('connectivity', (e) => {
|
||||
connectivityStatus = e;
|
||||
})
|
||||
|
||||
$chatAdapter.on('reaction', reactionReceived);
|
||||
$chatAdapter.on('zap', zapReceived);
|
||||
$chatAdapter.on('deleted', (deletedEvents) => {
|
||||
deletedEvents.forEach(deletedEventId => {
|
||||
const index = events.findIndex(event => event.id === deletedEventId);
|
||||
if (index !== -1) {
|
||||
events[index].deleted = true;
|
||||
events = events;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
$chatAdapter.on('profile', ({pubkey, profile}) => {
|
||||
let profiles = $chatData.profiles;
|
||||
profiles[pubkey] = profile;
|
||||
|
||||
chatData.set({ profiles, ...$chatData })
|
||||
})
|
||||
|
||||
$chatAdapter.on('channelMetadata', (event) => {
|
||||
channelMetadata = JSON.parse(event.content);
|
||||
})
|
||||
$chatAdapter.on("connectivity", (e) => {
|
||||
connectivityStatus = e;
|
||||
});
|
||||
|
||||
let connectivityStatus = {};
|
||||
let connectedRelays = 0;
|
||||
let totalRelays = 0;
|
||||
|
||||
$: {
|
||||
connectedRelays = Object.values(connectivityStatus).filter(status => status === 'connected').length;
|
||||
totalRelays = Object.values(connectivityStatus).length;
|
||||
|
||||
if ($chatAdapter?.pubkey && !profiles[$chatAdapter.pubkey]) {
|
||||
$chatAdapter.reqProfile($chatAdapter.pubkey)
|
||||
$chatAdapter.on("reaction", reactionReceived);
|
||||
$chatAdapter.on("zap", zapReceived);
|
||||
$chatAdapter.on("deleted", (deletedEvents) => {
|
||||
deletedEvents.forEach((deletedEventId) => {
|
||||
const index = events.findIndex((event) => event.id === deletedEventId);
|
||||
if (index !== -1) {
|
||||
events[index].deleted = true;
|
||||
events = events;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$chatAdapter.on("profile", ({ pubkey, profile }) => {
|
||||
let profiles = $chatData.profiles;
|
||||
profiles[pubkey] = profile;
|
||||
|
||||
chatData.set({ profiles, ...$chatData });
|
||||
});
|
||||
|
||||
$chatAdapter.on("channelMetadata", (event) => {
|
||||
channelMetadata = JSON.parse(event.content);
|
||||
});
|
||||
|
||||
if (browser)
|
||||
document.addEventListener('keydown', handleGlobalKeydown);
|
||||
if (mainElement)
|
||||
mainElement.addEventListener("scroll", checkVisibility);
|
||||
checkVisibility(); // initial check
|
||||
});
|
||||
|
||||
let connectivityStatus = {};
|
||||
let connectedRelays = 0;
|
||||
let totalRelays = 0;
|
||||
|
||||
$: {
|
||||
connectedRelays = Object.values(connectivityStatus).filter(
|
||||
(status) => status === "connected"
|
||||
).length;
|
||||
totalRelays = Object.values(connectivityStatus).length;
|
||||
|
||||
if ($chatAdapter?.pubkey && !profiles[$chatAdapter.pubkey]) {
|
||||
$chatAdapter.reqProfile($chatAdapter.pubkey);
|
||||
}
|
||||
}
|
||||
|
||||
let connectedChatId;
|
||||
|
||||
$: if (connectedChatId !== $chatAdapter?.chatId) {
|
||||
connectedChatId = $chatAdapter?.chatId;
|
||||
channelMetadata = {};
|
||||
}
|
||||
|
||||
$: profiles = $chatData.profiles;
|
||||
|
||||
function selectParent() {
|
||||
if (chatConfiguration.chatType === "GROUP") {
|
||||
$selectedMessage = null;
|
||||
} else {
|
||||
// get the last tagged event in the tags array of the current $selectedMessage
|
||||
const lastETag = getEventById($selectedMessage)
|
||||
.tags.filter((tag) => tag[0] === "e")
|
||||
.pop();
|
||||
const lastETagId = lastETag && lastETag[1];
|
||||
|
||||
$selectedMessage = lastETagId;
|
||||
}
|
||||
|
||||
let connectedChatId;
|
||||
scrollDown();
|
||||
}
|
||||
|
||||
$: if (connectedChatId !== $chatAdapter?.chatId) {
|
||||
connectedChatId = $chatAdapter?.chatId;
|
||||
channelMetadata = {};
|
||||
let ownName;
|
||||
$: ownName = $chatAdapter?.pubkey ? pubkeyName($chatAdapter.pubkey) : "";
|
||||
|
||||
$: profiles = $chatData.profiles;
|
||||
|
||||
$: profilePicture =
|
||||
(profiles[$chatAdapter.pubkey] && profiles[$chatAdapter.pubkey].picture) ||
|
||||
`https://robohash.org/${$chatAdapter.pubkey.slice(0, 1)}.png?set=set1`;
|
||||
|
||||
function pubkeyName(pubkey) {
|
||||
let name;
|
||||
|
||||
if (profiles[$chatAdapter.pubkey]) {
|
||||
let self = profiles[$chatAdapter.pubkey];
|
||||
|
||||
// https://xkcd.com/927/
|
||||
name = self.display_name || self.displayName || self.name || self.nip05;
|
||||
}
|
||||
|
||||
$: profiles = $chatData.profiles;
|
||||
|
||||
function selectParent() {
|
||||
if (chatConfiguration.chatType === 'GROUP') {
|
||||
$selectedMessage = null;
|
||||
} else {
|
||||
// get the last tagged event in the tags array of the current $selectedMessage
|
||||
const lastETag = getEventById($selectedMessage).tags.filter(tag => tag[0] === 'e').pop();
|
||||
const lastETagId = lastETag && lastETag[1];
|
||||
|
||||
$selectedMessage = lastETagId;
|
||||
}
|
||||
|
||||
scrollDown()
|
||||
if (!name) {
|
||||
name = `Anonymous [${pubkey.slice(0, 6)}]`;
|
||||
}
|
||||
|
||||
let ownName;
|
||||
$: ownName = $chatAdapter?.pubkey ? pubkeyName($chatAdapter.pubkey) : "";
|
||||
return name;
|
||||
}
|
||||
|
||||
function pubkeyName(pubkey) {
|
||||
let name;
|
||||
function autoExpandTextarea(event) {
|
||||
//console.log('autoExpandTextarea');
|
||||
const textarea = event.target;
|
||||
textarea.style.height = 'auto';
|
||||
textarea.style.height = `${textarea.scrollHeight - 16}px`;
|
||||
}
|
||||
|
||||
if (profiles[$chatAdapter.pubkey]) {
|
||||
let self = profiles[$chatAdapter.pubkey];
|
||||
let messageInput = "";
|
||||
let messageElement;
|
||||
|
||||
// https://xkcd.com/927/
|
||||
name = self.display_name ||
|
||||
self.displayName ||
|
||||
self.name ||
|
||||
self.nip05;
|
||||
onDestroy(() => {
|
||||
if (browser)
|
||||
document.removeEventListener('keydown', handleGlobalKeydown);
|
||||
mainElement.removeEventListener("scroll", checkVisibility);
|
||||
});
|
||||
|
||||
}
|
||||
let anchor;
|
||||
|
||||
function isElementInViewport(el) {
|
||||
const rect = el.getBoundingClientRect();
|
||||
return (
|
||||
rect.top >= 0 &&
|
||||
rect.left >= 0 &&
|
||||
rect.bottom <= (mainElement.innerHeight || document.documentElement.clientHeight) &&
|
||||
rect.right <= (mainElement.innerWidth || document.documentElement.clientWidth)
|
||||
);
|
||||
}
|
||||
|
||||
if (!name) { name = `Anonymous [${pubkey.slice(0, 6)}]`; }
|
||||
|
||||
return name;
|
||||
function checkVisibility() {
|
||||
//console.log('check')
|
||||
if (isElementInViewport(messageElement)) {
|
||||
anchor.style.display = 'none';
|
||||
} else {
|
||||
anchor.style.display = 'block';
|
||||
anchor.style.transform = 'translateY(6rem)';
|
||||
}
|
||||
}
|
||||
|
||||
function handleGlobalKeydown(event) {
|
||||
// Check for Ctrl + /
|
||||
if (event.ctrlKey && event.key === ' ') {
|
||||
|
||||
// Focus the input element
|
||||
messageElement.focus();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="
|
||||
bg-purple-700 text-white
|
||||
-mx-4 -mt-5 mb-3
|
||||
px-4 py-3
|
||||
overflow-clip
|
||||
flex flex-row justify-between items-center
|
||||
">
|
||||
<!-- TOP -->
|
||||
{#if $chatAdapter?.pubkey}
|
||||
<section class="profile">
|
||||
<a class="toolbar__avatar" href="nostr:{$chatAdapter.pubkey}">
|
||||
<p class="hide-on-mobile">
|
||||
{ownName}
|
||||
</p>
|
||||
<img src={profilePicture} alt="{ownName}'s avatar" />
|
||||
</a>
|
||||
</section>
|
||||
{/if}
|
||||
{#if events}
|
||||
<section class="stats">
|
||||
<p class="stats__count">
|
||||
{events.length} comments
|
||||
</p>
|
||||
</section>
|
||||
{/if}
|
||||
{#if totalRelays}
|
||||
<section class="relays">
|
||||
<!-- <Relays bind:relays on:update={handleUpdate} /> -->
|
||||
<div class="stats__relays">
|
||||
{connectedRelays}/{totalRelays} relays
|
||||
<div class="relay-dots">
|
||||
{#each Array(totalRelays) as _, i}
|
||||
<span
|
||||
class="relay {connectedRelays > i ? 'relay--active' : ''}"
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<div class="text-lg font-semibold">
|
||||
{#if $chatAdapter?.pubkey}
|
||||
{ownName}
|
||||
{/if}
|
||||
</div>
|
||||
<section class="input">
|
||||
<textarea
|
||||
type="text"
|
||||
id="message-input"
|
||||
class=""
|
||||
placeholder="leave a comment"
|
||||
rows="4"
|
||||
on:keydown={inputKeyDown}
|
||||
bind:value={messageInput}
|
||||
bind:this={messageElement}
|
||||
/>
|
||||
<button type="button" class="btn btn--comment" on:click|preventDefault={sendMessage} disabled={!messageInput}>
|
||||
<svg width="250" height="250" viewBox="0 0 250 250" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path vector-effect="non-scaling-stroke" d="M177.203 67.4466C175.78 68.8692 174.784 71.1454 174.784 72.4258C174.784 73.9907 171.939 75.5556 165.253 77.5473C74.9157 104.293 24.1278 118.946 21.8516 118.946C18.295 118.946 15.023 119.48 15.023 114.251C13.8849 111.264 3.7842 110.837 1.36573 113.824C0.227621 115.105 -0.199168 120.226 0.0853578 127.766L0.512147 139.574C0.512147 139.574 0.938937 141.85 7.6253 142.277C14.3117 142.704 15.4498 140.143 15.4498 137.582C15.4498 134.595 16.3033 134.595 21.5671 134.595C26.8308 134.595 50.3042 140.57 52.0114 142.277C52.4382 142.561 51.8691 145.549 51.0155 148.963C48.8816 156.788 50.1619 163.19 54.8566 167.884C58.2709 171.299 78.6145 178.696 100.523 184.387C111.62 187.232 120.298 182.111 123.712 170.445C124.708 167.173 126.13 164.47 126.984 164.47C127.838 164.47 138.934 167.458 151.595 171.156C169.947 176.42 174.784 178.412 174.784 180.261C174.784 186.378 190.433 186.343 190.433 180.83C190.433 178.341 190.433 175.709 190.433 126.059V69.296C190.433 63.4633 180.048 64.3168 177.203 67.4466ZM90.5647 153.231L116.741 160.913L115.887 165.75C114.856 172.295 110.766 176.136 106.214 176.136C103.084 176.278 73.9199 167.742 64.815 164.185C58.9823 161.909 57.5596 158.21 59.5513 150.67C60.5471 147.256 62.112 144.695 62.9656 144.98C63.8192 145.264 76.3383 148.963 90.5647 153.231Z" fill="black"/>
|
||||
<path vector-effect="non-scaling-stroke" d="M222.869 85.5143C210.35 92.7697 209.212 94.1924 212.2 97.1799C214.191 99.1716 215.756 98.6025 227.422 91.9161C236.527 86.6524 240.225 83.8072 239.941 82.1C239.087 77.9744 233.966 78.9702 222.869 85.5143Z" fill="black"/>
|
||||
<path vector-effect="non-scaling-stroke" d="M214.191 120.795C212.484 125.348 215.329 126.201 232.543 125.775C248.903 125.348 249.472 125.205 249.899 122.076C250.326 118.946 250.184 118.946 232.685 118.946C219.17 118.946 214.76 119.373 214.191 120.795Z" fill="black"/>
|
||||
<path vector-effect="non-scaling-stroke" d="M211.204 148.252C209.923 151.524 210.635 152.235 223.296 159.491C235.104 166.32 239.514 167.031 239.514 162.478C239.514 160.629 235.957 157.641 227.422 152.805C214.334 145.407 212.484 144.838 211.204 148.252Z" fill="black"/>
|
||||
</svg>
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<span class="text-xs flex flex-col items-end mt-2 text-gray-200 gap-1">
|
||||
<div class="flex flex-row gap-1 overflow-clip">
|
||||
{#each Array(totalRelays) as _, i}
|
||||
<span class="
|
||||
inline-block
|
||||
rounded-full
|
||||
{connectedRelays > i ? 'bg-green-500' : 'bg-gray-300'}
|
||||
w-2 h-2
|
||||
"></span>
|
||||
{/each}
|
||||
</div>
|
||||
<button class="btn btn--scroll-to-top" bind:this={anchor} on:click={scrollUp}>leave a comment 👆️</button>
|
||||
|
||||
{connectedRelays}/{totalRelays} relays
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{#if channelMetadata.name}
|
||||
<div class="flex flex-row gap-2 mb-3 bg-zinc-300 text-zinc-800 px-4 py-2 -mx-4 -mt-3">
|
||||
{#if channelMetadata.picture}
|
||||
<img src={channelMetadata.picture} class="w-12 h-12 rounded-full" />
|
||||
{/if}
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div class="font-extrabold text-xl">{channelMetadata.name}</div>
|
||||
{#if channelMetadata.about}
|
||||
<div class="text-sm truncate font-regular">{channelMetadata.about}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $selectedMessage}
|
||||
{#if !getEventById($selectedMessage)}
|
||||
<h1>Couldn't find event with ID {$selectedMessage}</h1>
|
||||
<section id="messages-container" class="comments">
|
||||
<div class="events">
|
||||
{#if $selectedMessage}
|
||||
<NostrNote
|
||||
event={getEventById($selectedMessage)}
|
||||
{responses}
|
||||
{websiteOwnerPubkey}
|
||||
/>
|
||||
{:else}
|
||||
<div class="flex flex-row mb-3">
|
||||
<a href='#' on:click|preventDefault={selectParent}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 12h-15m0 0l6.75 6.75M4.5 12l6.75-6.75" />
|
||||
</svg>
|
||||
{#each events as event}
|
||||
<NostrNote {event} {responses} {websiteOwnerPubkey} />
|
||||
{#if event.deleted}
|
||||
👆 deleted
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="events--empty"><svg width="5411" height="2123" viewBox="0 0 5411 2123" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3380.65 973.032C3418.5 958.188 3455.05 962.571 3463.09 983.057C3471.12 1003.56 3457.08 1044.01 3419.24 1058.85C3381.39 1073.71 3351.98 1063.54 3343.94 1043.05C3335.9 1022.57 3342.8 987.889 3380.65 973.032ZM4573.98 1400.11L4627.9 1410.25C4627.3 1343.87 4635.19 1279.91 4651.56 1218.35C4664.87 1168.39 4622.82 1178.14 4597.83 1199.7C4532.73 1255.89 4482.65 1295.84 4398.44 1323.92C4452.51 1308.23 4494.79 1292.51 4525.34 1276.73C4548.65 1304.36 4564.04 1332.94 4568.68 1362.16C4572.11 1375.77 4573.87 1388.42 4573.98 1400.11ZM4631.05 1474.38L4554.13 1459.41C4540.53 1475.44 4519.95 1487.5 4492.38 1495.55C4545.74 1499.86 4593.27 1504.8 4634.99 1510.38C4633.41 1498.3 4632.1 1486.3 4631.05 1474.38ZM4111.98 1470.93C4130.81 1468.83 4150.26 1465.16 4170.34 1459.96C4246.91 1498.24 4350.64 1523.59 4481.54 1536C4541.88 1545.67 4595.36 1552.07 4642 1555.22C4649.15 1594.76 4659.02 1635.07 4671.63 1676.15C4686.1 1718.95 4725.49 1805.03 4789.82 1934.38C4884.53 1932.78 4948.47 1949.91 4981.63 1985.77C4959.63 1925.21 4904.08 1896.91 4824.5 1898.92C4799.19 1870.42 4767.43 1797.59 4729.22 1680.4C4716.52 1642.51 4707.18 1601.54 4701.15 1557.51C4743.56 1557.7 4778.42 1554.33 4805.75 1547.35C4779.24 1537.11 4742.95 1527.96 4696.89 1519.85C4695.91 1508.99 4695.11 1497.95 4694.5 1486.73L4916.99 1530.06L4693.42 1459.91C4693.26 1454.1 4693.15 1448.24 4693.08 1442.35L4828.01 1447.91L4693.07 1422.52C4693.53 1363.01 4698.74 1299.16 4708.68 1230.93C4713.38 1198.78 4711.9 1146.9 4691.37 1120.9C4662.99 1084.95 4612.76 1079.02 4570.51 1082.47C4509.35 1087.45 4449.72 1097.2 4391.64 1111.7C4440.19 1063.71 4483.02 1007.98 4529.75 962.192C4555.13 937.327 4588.51 924.173 4609.88 965.206C4715.84 1168.84 4865.92 1345.13 5029.49 1457.19C5086.57 1410.5 5141.77 1388.92 5195.07 1392.46C5151.96 1368.34 5094.67 1371.64 5034.25 1408.11C4926.14 1352.32 4798.13 1195.33 4639.99 931.808C4613.8 888.183 4558.12 865.244 4509.68 883.117C4460.51 901.275 4315.82 985.788 4192.91 1070.24C4080.21 1023.03 3967.15 987.108 3893.34 967.221C3883.16 1009.89 3865.53 1047.66 3840.47 1080.49C3854.52 1054.98 3866.84 1024.09 3876.66 989.325C3880.67 975.103 3886.44 964.062 3878.72 950.73C3871.68 938.544 3854.39 932.05 3826.88 931.248C3731.85 921.424 3632.36 916.093 3578.95 919.712C3584.86 949.635 3581.66 985.95 3566.18 1011.73C3574.99 980.99 3574.87 955.709 3564.07 934.415C3552.16 910.948 3532.78 907.867 3509.77 901.687C3482.31 894.316 3456.79 889.891 3433.3 888.486C3562.71 629.059 3669.88 377.849 3725.05 159.076C3690.14 281.186 3515.99 712.504 3410.25 888.092C3387.96 888.719 3367.8 892.426 3349.87 899.287C3350.85 897.038 3351.72 894.618 3352.45 892.027C3410.8 512.544 3366.43 215.374 3219.34 0.491378C3369.8 271.908 3394.35 590.242 3327.45 910.229C3299.68 927.057 3278.9 953.526 3265.74 989.962C3255.13 1019.63 3247.47 1046.15 3242.75 1069.55C3219.44 1053.93 3196.62 1045.4 3174.28 1043.96C3135.32 1037.91 3103.4 1039.61 3078.51 1049.06C3040.8 1052.4 3006.51 1049.88 2975.65 1041.5C2937.13 1031.04 2930.67 1066.82 2970.77 1072.25C3011.16 1077.7 3046.4 1074.21 3076.5 1061.8C3105.03 1074.98 3137.01 1073.06 3172.44 1056.05C3188.29 1072.41 3210.55 1083.93 3239.19 1090.63C3237.54 1102.94 3236.86 1114.21 3237.15 1124.45C3231.8 1124.83 3226.59 1125.45 3221.51 1126.32C3193.98 1137.16 3172.7 1148.72 3157.69 1160.96C3134.87 1233.43 3123.91 1281.3 3124.8 1304.58C3164.41 1314.8 3176.55 1274.7 3161.23 1184.28C3170.59 1184.77 3179.31 1187.39 3185.62 1191.12C3193.88 1196.01 3199.57 1195.71 3207.37 1190.21C3219.25 1181.83 3229.39 1168.96 3237.79 1151.65L3241 1152.4C3256.75 1209.57 3319.99 1216.33 3430.76 1172.63C3472.25 1169.49 3511.98 1144.71 3549.97 1098.28C3540.59 1160.87 3555.3 1201.62 3594.12 1220.6C3605.78 1229.74 3617.35 1238.56 3628.83 1247.02C3603.06 1278.47 3584.12 1398.61 3582.72 1438C3574.41 1472.99 3558.7 1507.24 3530.65 1543.62C3491.22 1519.83 3455 1527.14 3426.96 1562.68C3471.97 1550.53 3508.83 1553.88 3537.54 1572.73C3569.07 1553.5 3597.2 1511.15 3621.92 1445.7C3638.27 1353.91 3654.57 1307 3670.82 1304.98C3675.88 1353.33 3698.91 1355.27 3716.6 1342.96C3725.79 1336.58 3732.57 1327.93 3736.73 1318.18C3740.3 1320.24 3743.87 1322.27 3747.42 1324.26C3747.01 1336.26 3751.17 1350.73 3761.93 1366.66C3795.38 1416.15 3832.07 1443.51 3872.95 1442.68C3913.73 1441.84 3930.41 1444.09 3930.49 1497.35C3930.61 1581.12 3942.1 1655.31 3964.92 1719.93C3987.99 1759.24 4020.17 1800.96 4061.44 1845.1C4117.43 1845.98 4155.67 1865.08 4176.15 1902.4C4168.52 1844.84 4134.75 1820.65 4073.79 1822.52C4048.9 1797.11 4019.1 1754.39 3996.26 1707.36C3978.13 1643.55 3971.08 1571.49 3963.24 1478.25C3963.91 1442.74 3958.22 1417.63 3945.11 1398.75C3955 1400.43 3964.79 1401.76 3974.47 1402.71C3981.98 1447.36 4024.15 1480.75 4111.98 1470.93Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2031.03 973.032C1993.18 958.188 1956.63 962.571 1948.59 983.057C1940.55 1003.55 1954.6 1044.01 1992.44 1058.85C2030.29 1073.71 2059.7 1063.54 2067.74 1043.05C2075.78 1022.56 2068.88 987.889 2031.03 973.032ZM837.693 1400.11L783.773 1410.25C784.374 1343.87 776.483 1279.91 760.114 1218.35C746.809 1168.39 788.86 1178.14 813.851 1199.7C878.945 1255.89 929.03 1295.84 1013.23 1323.92C959.172 1308.23 916.888 1292.51 886.337 1276.73C863.026 1304.36 847.633 1332.94 842.999 1362.16C839.563 1375.77 837.803 1388.42 837.693 1400.11ZM780.625 1474.38L857.543 1459.41C871.144 1475.44 891.726 1487.5 919.294 1495.55C865.94 1499.86 818.412 1504.8 776.686 1510.38C778.269 1498.3 779.58 1486.3 780.625 1474.38ZM1299.7 1470.93C1280.87 1468.83 1261.42 1465.16 1241.34 1459.96C1164.77 1498.24 1061.04 1523.59 930.134 1536C869.793 1545.67 816.322 1552.07 769.682 1555.22C762.523 1594.76 752.661 1635.07 740.049 1676.15C725.581 1718.95 686.184 1805.03 621.857 1934.38C527.15 1932.78 463.206 1949.91 430.05 1985.77C452.046 1925.21 507.6 1896.91 587.177 1898.92C612.491 1870.42 644.243 1797.59 682.454 1680.4C695.161 1642.51 704.501 1601.54 710.523 1557.51C668.116 1557.7 633.255 1554.33 605.931 1547.35C632.438 1537.11 668.731 1527.96 714.79 1519.85C715.771 1508.99 716.569 1497.95 717.179 1486.73L494.692 1530.06L718.261 1459.91C718.415 1454.1 718.53 1448.24 718.593 1442.35L583.662 1447.91L718.609 1422.52C718.151 1363.01 712.94 1299.16 702.995 1230.93C698.302 1198.78 699.777 1146.9 720.307 1120.9C748.691 1084.95 798.92 1079.02 841.172 1082.46C902.332 1087.45 961.954 1097.2 1020.03 1111.7C971.492 1063.71 928.661 1007.98 881.925 962.192C856.545 937.327 823.163 924.172 801.802 965.206C695.841 1168.84 545.755 1345.13 382.185 1457.19C325.104 1410.5 269.903 1388.92 216.604 1392.46C259.722 1368.34 317.008 1371.64 377.426 1408.11C485.537 1352.32 613.548 1195.33 771.691 931.808C797.88 888.183 853.56 865.244 901.996 883.117C951.171 901.275 1095.85 985.787 1218.77 1070.24C1331.47 1023.03 1444.53 987.108 1518.34 967.221C1528.52 1009.89 1546.14 1047.66 1571.2 1080.49C1557.16 1054.98 1544.84 1024.09 1535.01 989.325C1531.01 975.103 1525.24 964.062 1532.95 950.73C1540 938.544 1557.28 932.05 1584.8 931.247C1679.83 921.424 1779.32 916.092 1832.73 919.712C1826.82 949.635 1830.02 985.95 1845.5 1011.73C1836.68 980.99 1836.81 955.709 1847.61 934.415C1859.51 910.948 1878.9 907.866 1901.91 901.687C1929.36 894.316 1954.89 889.891 1978.37 888.486C1848.97 629.058 1741.8 377.849 1686.62 159.076C1721.54 281.185 1895.68 712.504 2001.43 888.092C2023.71 888.719 2043.88 892.426 2061.8 899.287C2060.82 897.038 2059.96 894.618 2059.23 892.027C2000.88 512.544 2045.25 215.374 2192.33 0.491134C2041.87 271.908 2017.32 590.242 2084.23 910.229C2111.99 927.057 2132.78 953.525 2145.93 989.962C2156.55 1019.63 2164.21 1046.15 2168.92 1069.55C2192.24 1053.93 2215.06 1045.4 2237.4 1043.96C2276.36 1037.91 2308.28 1039.61 2333.16 1049.06C2370.88 1052.4 2405.17 1049.88 2436.02 1041.5C2474.55 1031.04 2481 1066.82 2440.9 1072.25C2400.52 1077.7 2365.28 1074.21 2335.18 1061.8C2306.64 1074.98 2274.66 1073.06 2239.24 1056.05C2223.38 1072.41 2201.13 1083.93 2172.49 1090.63C2174.14 1102.94 2174.82 1114.21 2174.53 1124.45C2179.87 1124.83 2185.09 1125.45 2190.17 1126.32C2217.7 1137.16 2238.98 1148.72 2253.99 1160.95C2276.81 1233.43 2287.77 1281.3 2286.88 1304.58C2247.27 1314.8 2235.13 1274.7 2250.45 1184.28C2241.09 1184.77 2232.36 1187.39 2226.05 1191.12C2217.79 1196.01 2212.11 1195.71 2204.31 1190.21C2192.43 1181.83 2182.29 1168.96 2173.88 1151.65L2170.68 1152.4C2154.93 1209.57 2091.69 1216.33 1980.91 1172.63C1939.43 1169.49 1899.7 1144.71 1861.71 1098.28C1871.09 1160.87 1856.38 1201.62 1817.55 1220.6C1805.9 1229.74 1794.33 1238.56 1782.85 1247.01C1808.61 1278.47 1827.55 1398.61 1828.96 1438C1837.27 1472.99 1852.97 1507.24 1881.03 1543.62C1920.45 1519.83 1956.67 1527.14 1984.72 1562.68C1939.71 1550.53 1902.85 1553.88 1874.14 1572.73C1842.61 1553.5 1814.47 1511.15 1789.76 1445.7C1773.41 1353.91 1757.11 1307 1740.86 1304.98C1735.8 1353.33 1712.77 1355.27 1695.08 1342.96C1685.88 1336.58 1679.11 1327.93 1674.95 1318.18C1671.37 1320.24 1667.81 1322.27 1664.25 1324.26C1664.66 1336.26 1660.51 1350.73 1649.75 1366.66C1616.29 1416.15 1579.61 1443.51 1538.73 1442.68C1497.95 1441.84 1481.27 1444.09 1481.19 1497.35C1481.06 1581.12 1469.58 1655.31 1446.75 1719.93C1423.69 1759.24 1391.51 1800.96 1350.23 1845.1C1294.25 1845.98 1256.01 1865.08 1235.53 1902.4C1243.16 1844.84 1276.93 1820.65 1337.89 1822.52C1362.78 1797.11 1392.58 1754.39 1415.42 1707.36C1433.55 1643.54 1440.59 1571.49 1448.43 1478.25C1447.77 1442.74 1453.46 1417.63 1466.57 1398.75C1456.68 1400.43 1446.89 1401.76 1437.21 1402.71C1429.7 1447.36 1387.53 1480.75 1299.7 1470.93Z" fill="black"/>
|
||||
</svg></div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if channelMetadata.name}
|
||||
<div class="">
|
||||
{#if channelMetadata.picture}
|
||||
<img src={channelMetadata.picture} class="" alt="{channelMetadata.name} thumbnail" />
|
||||
{/if}
|
||||
|
||||
<div class="">
|
||||
<div class="">{channelMetadata.name}</div>
|
||||
{#if channelMetadata.about}
|
||||
<div class="">{channelMetadata.about}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $selectedMessage}
|
||||
{#if !getEventById($selectedMessage)}
|
||||
<h1>Couldn't find event with ID {$selectedMessage}</h1>
|
||||
{:else}
|
||||
<div class="">
|
||||
<a href="#" on:click|preventDefault={selectParent}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class=""
|
||||
>
|
||||
<path
|
||||
vector-effect="non-scaling-stroke"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M19.5 12h-15m0 0l6.75 6.75M4.5 12l6.75-6.75"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<div class="flex flex-col ml-2">
|
||||
<span class="text-lg text-black overflow-hidden whitespace-nowrap text-ellipsis">
|
||||
{getEventById($selectedMessage).content}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="flex flex-col ml-2">
|
||||
<span class="text-lg text-black overflow-hidden whitespace-nowrap text-ellipsis">
|
||||
{getEventById($selectedMessage).content}
|
||||
</span>
|
||||
</div> -->
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<div id="messages-container" class="overflow-auto -mx-4 px-4" style="height: 50vh; min-height: 300px;">
|
||||
<div id="messages-container-inner" class="flex flex-col gap-4">
|
||||
{#if $selectedMessage}
|
||||
<NostrNote event={getEventById($selectedMessage)} {responses} {websiteOwnerPubkey} />
|
||||
{:else}
|
||||
{#each events as event}
|
||||
<NostrNote {event} {responses} {websiteOwnerPubkey} />
|
||||
{#if event.deleted}
|
||||
👆 deleted
|
||||
{/if}
|
||||
{:else}
|
||||
<p>no comments</p>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex flex-col">
|
||||
<!-- <div class="
|
||||
border-y border-y-slate-200
|
||||
-mx-4 my-2 bg-slate-100 text-black text-sm
|
||||
px-4 py-2
|
||||
">
|
||||
{#if chatConfiguration.chatType === 'DM'}
|
||||
<b>Encrypted chat:</b>
|
||||
only your chat partner can see these messages.
|
||||
{:else if chatConfiguration.chatType === 'GROUP'}
|
||||
<b>Public chat:</b>
|
||||
anyone can see these messages.
|
||||
{:else}
|
||||
<b>Public notes:</b>
|
||||
your followers see your messages on their timeline
|
||||
{/if}
|
||||
</div> -->
|
||||
|
||||
<div class="flex flex-row gap-2 -mx-1">
|
||||
<textarea
|
||||
type="text"
|
||||
id="message-input"
|
||||
class="
|
||||
-mb-2
|
||||
p-2
|
||||
w-full
|
||||
resize-none
|
||||
rounded-xl
|
||||
text-gray-600
|
||||
border
|
||||
" placeholder="leave a comment"
|
||||
rows=1
|
||||
on:keydown={inputKeyDown}
|
||||
></textarea>
|
||||
<button type="button" class="inline-flex items-center rounded-full border border-transparent bg-purple-700 p-3 text-white shadow-sm hover:bg-purple-600 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" on:click|preventDefault={sendMessage}>
|
||||
<!-- Heroicon name: outline/plus -->
|
||||
<svg aria-hidden="true" class="w-6 h-6 rotate-90" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M10.894 2.553a1 1 0 00-1.788 0l-7 14a1 1 0 001.169 1.409l5-1.429A1 1 0 009 15.571V11a1 1 0 112 0v4.571a1 1 0 00.725.962l5 1.428a1 1 0 001.17-1.408l-7-14z"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
</style>
|
||||
|
||||
section.input {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
section.input textarea {
|
||||
height: auto;
|
||||
padding: 1rem;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
section.input button {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.events {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
section.profile {
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
}
|
||||
|
||||
section.relays {
|
||||
position: fixed;
|
||||
bottom: 1rem;
|
||||
right: 2rem;
|
||||
}
|
||||
|
||||
.toolbar__avatar{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 1rem;
|
||||
color: var(--c-bright);
|
||||
}
|
||||
|
||||
.toolbar__avatar img{
|
||||
width: 2rem;
|
||||
border-radius: 2rem;
|
||||
outline: 1px solid var(--c-lines);
|
||||
}
|
||||
|
||||
section.stats {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.stats__count{
|
||||
font-family: 'Barlow Condensed', sans-serif;
|
||||
font-size: 55px;
|
||||
font-weight: 100;
|
||||
line-height: .8;
|
||||
color: var(--c-lines);
|
||||
}
|
||||
|
||||
.relay-dots {
|
||||
display: flex;
|
||||
gap:.5rem;
|
||||
}
|
||||
|
||||
.relay{
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
border-radius: 11px;
|
||||
border: 1px solid var(--c-lines);
|
||||
}
|
||||
|
||||
.relay--active{
|
||||
border-color: var(--c-marker);
|
||||
background-color: var(--c-marker);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
section.input textarea{
|
||||
resize: none;
|
||||
background-color: var(--c-lines);
|
||||
border: none;
|
||||
}
|
||||
|
||||
section.input textarea::placeholder{
|
||||
color: var(--c-bright);
|
||||
}
|
||||
|
||||
section.input textarea:focus{
|
||||
outline: none;
|
||||
background-color: var(--c-bright);
|
||||
}
|
||||
|
||||
section.input textarea:focus + button{
|
||||
border-color: var(--c-bright);
|
||||
}
|
||||
|
||||
section.input textarea:focus + button{
|
||||
--color: var(--c-bright);
|
||||
}
|
||||
|
||||
section.input textarea:focus + button svg path{
|
||||
stroke: var(--c-bright);
|
||||
}
|
||||
|
||||
.btn--comment {
|
||||
--color: var(--c-lines);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.btn--comment:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn--comment:disabled svg path{
|
||||
fill:none;
|
||||
stroke: var(--c-lines);
|
||||
}
|
||||
|
||||
.btn--comment svg{
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
.btn--comment svg path{
|
||||
fill: var(--color);
|
||||
}
|
||||
|
||||
.events--empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-height: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.events--empty svg{
|
||||
max-width: 50%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.events--empty svg path{
|
||||
fill: rgba(0,0,0,.3);
|
||||
}
|
||||
|
||||
.comments {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.events {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
|
||||
.stats__relays{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .5rem;
|
||||
align-items: end;
|
||||
color: var(--c-lines);
|
||||
}
|
||||
|
||||
.btn.btn--scroll-to-top {
|
||||
transition: all .3s;
|
||||
transform: translateY(-3rem);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 5rem;
|
||||
/* z-index: 2; */
|
||||
background: var(--c-bright);
|
||||
padding: 0.25em 0.75em;
|
||||
/* display: inline-block; */
|
||||
align-self: center;
|
||||
border-radius: 2rem;
|
||||
font-size: 1.5rem;
|
||||
color: black;
|
||||
font-weight: 300;
|
||||
text-decoration: none;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,480 +0,0 @@
|
|||
<script>
|
||||
import {
|
||||
chatAdapter,
|
||||
chatData,
|
||||
selectedMessage,
|
||||
zapsPerMessage,
|
||||
} from "./lib/store";
|
||||
import { onMount } from "svelte";
|
||||
import NostrNote from "./NostrNote2.svelte";
|
||||
import * as animateScroll from "svelte-scrollto";
|
||||
|
||||
let events = [];
|
||||
let responseEvents = [];
|
||||
let responses = {};
|
||||
let profiles = {};
|
||||
|
||||
export let websiteOwnerPubkey;
|
||||
export let chatConfiguration;
|
||||
let prevChatConfiguration;
|
||||
|
||||
$: {
|
||||
if (chatConfiguration !== prevChatConfiguration && $chatAdapter) {
|
||||
$chatAdapter.setChatConfiguration(
|
||||
chatConfiguration.chatType,
|
||||
chatConfiguration.chatTags,
|
||||
chatConfiguration.chatReferenceTags,
|
||||
chatConfiguration.chatId
|
||||
);
|
||||
events = [];
|
||||
responses = {};
|
||||
rootNoteId = null;
|
||||
localStorage.removeItem("rootNoteId");
|
||||
|
||||
// rootNoteId = localStorage.getItem('rootNoteId');
|
||||
// if (rootNoteId) {
|
||||
// $chatAdapter.subscribeToEventAndResponses(rootNoteId);
|
||||
// }
|
||||
}
|
||||
prevChatConfiguration = chatConfiguration;
|
||||
}
|
||||
|
||||
function getEventById(eventId) {
|
||||
let event = events.find((e) => e.id === eventId);
|
||||
event = event || responseEvents.find((e) => e.id === eventId);
|
||||
return event;
|
||||
}
|
||||
|
||||
async function sendMessage() {
|
||||
const input = document.getElementById("message-input");
|
||||
const message = input.value;
|
||||
input.value = "";
|
||||
let extraParams = { tags: [], tagPubKeys: [] };
|
||||
|
||||
// if this is the rootLevel we want to tag the owner of the site's pubkey
|
||||
if (!rootNoteId && websiteOwnerPubkey) {
|
||||
extraParams.tagPubKeys = [websiteOwnerPubkey];
|
||||
}
|
||||
|
||||
// if we are responding to an event, we want to tag the event and the pubkey
|
||||
if ($selectedMessage) {
|
||||
extraParams.tags.push(["e", $selectedMessage, "wss://nos.lol", "root"]);
|
||||
extraParams.tagPubKeys.push(getEventById($selectedMessage).pubkey);
|
||||
}
|
||||
|
||||
// if (rootNoteId) {
|
||||
// // mark it as a response to the most recent event
|
||||
// const mostRecentEvent = events[events.length - 1];
|
||||
// // go through all the tags and add them to the new message
|
||||
// if (mostRecentEvent) {
|
||||
// mostRecentEvent.tags.forEach(tag => {
|
||||
// if (tag[0] === 'e') {
|
||||
// extraParams.tags.push(tag);
|
||||
// }
|
||||
// })
|
||||
// extraParams.tags.push(['e', mostRecentEvent.id]);
|
||||
// extraParams.tags.push(['p', mostRecentEvent.pubkey]);
|
||||
// }
|
||||
// }
|
||||
|
||||
const noteId = await $chatAdapter.send(message, extraParams);
|
||||
|
||||
if (!rootNoteId) {
|
||||
rootNoteId = noteId;
|
||||
localStorage.setItem("rootNoteId", rootNoteId);
|
||||
}
|
||||
}
|
||||
|
||||
async function inputKeyDown(event) {
|
||||
if (event.key === "Enter") {
|
||||
sendMessage();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
function messageReceived(message) {
|
||||
const messageLastEventTag = message.tags
|
||||
.filter((tag) => tag[0] === "e")
|
||||
.pop();
|
||||
let isThread;
|
||||
|
||||
if (chatConfiguration.chatType === "GLOBAL") {
|
||||
isThread = message.tags.filter((tag) => tag[0] === "e").length >= 1;
|
||||
} else if (chatConfiguration.chatType === "GROUP") {
|
||||
isThread =
|
||||
message.tags.filter(
|
||||
(tag) => tag[0] === "e" && tag[1] !== chatConfiguration.chatId
|
||||
).length >= 1;
|
||||
} else {
|
||||
const pubkeysTagged = message.tags
|
||||
.filter((tag) => tag[0] === "p")
|
||||
.map((tag) => tag[1]);
|
||||
isThread = new Set(pubkeysTagged).size >= 2;
|
||||
}
|
||||
|
||||
if (!responses[message.id]) {
|
||||
responses[message.id] = [];
|
||||
}
|
||||
|
||||
if (isThread) {
|
||||
// get the last "e" tag, which is tagging the immediate parent
|
||||
const lastETag = message.tags.filter((tag) => tag[0] === "e").pop();
|
||||
if (lastETag && lastETag[1]) {
|
||||
// if there is one, add it to the response
|
||||
if (!responses[lastETag[1]]) {
|
||||
responses[lastETag[1]] = [];
|
||||
}
|
||||
responses[lastETag[1]].push(message);
|
||||
}
|
||||
|
||||
responseEvents.push(message);
|
||||
responseEvents = responseEvents;
|
||||
} else {
|
||||
// insert message so that it's chronologically ordered by created_at
|
||||
let index = 0;
|
||||
while (
|
||||
index < events.length &&
|
||||
events[index].created_at < message.created_at
|
||||
) {
|
||||
index++;
|
||||
}
|
||||
events.splice(index, 0, message);
|
||||
events = events;
|
||||
}
|
||||
|
||||
responses = responses;
|
||||
|
||||
scrollDown();
|
||||
}
|
||||
|
||||
function scrollDown() {
|
||||
animateScroll.scrollToBottom({
|
||||
container: document.getElementById("messages-container"),
|
||||
offset: 999999, // hack, oh well, browsers suck
|
||||
duration: 50,
|
||||
});
|
||||
}
|
||||
|
||||
function zapReceived(zap) {
|
||||
const event = events.find((event) => event.id === zap.zappedEvent);
|
||||
if (!event) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$zapsPerMessage[event.id]) $zapsPerMessage[event.id] = [];
|
||||
$zapsPerMessage[event.id].push(zap);
|
||||
}
|
||||
|
||||
function reactionReceived(reaction) {
|
||||
const event = events.find((event) => event.id === reaction.id);
|
||||
if (!event) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.reactions = event.reactions || [];
|
||||
event.reactions.push(reaction);
|
||||
events = events;
|
||||
}
|
||||
|
||||
let rootNoteId;
|
||||
let channelMetadata = {};
|
||||
|
||||
onMount(() => {
|
||||
$chatAdapter.on("message", messageReceived);
|
||||
|
||||
$chatAdapter.on("connectivity", (e) => {
|
||||
connectivityStatus = e;
|
||||
});
|
||||
|
||||
$chatAdapter.on("reaction", reactionReceived);
|
||||
$chatAdapter.on("zap", zapReceived);
|
||||
$chatAdapter.on("deleted", (deletedEvents) => {
|
||||
deletedEvents.forEach((deletedEventId) => {
|
||||
const index = events.findIndex((event) => event.id === deletedEventId);
|
||||
if (index !== -1) {
|
||||
events[index].deleted = true;
|
||||
events = events;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$chatAdapter.on("profile", ({ pubkey, profile }) => {
|
||||
let profiles = $chatData.profiles;
|
||||
profiles[pubkey] = profile;
|
||||
|
||||
chatData.set({ profiles, ...$chatData });
|
||||
});
|
||||
|
||||
$chatAdapter.on("channelMetadata", (event) => {
|
||||
channelMetadata = JSON.parse(event.content);
|
||||
});
|
||||
});
|
||||
|
||||
let connectivityStatus = {};
|
||||
let connectedRelays = 0;
|
||||
let totalRelays = 0;
|
||||
|
||||
$: {
|
||||
connectedRelays = Object.values(connectivityStatus).filter(
|
||||
(status) => status === "connected"
|
||||
).length;
|
||||
totalRelays = Object.values(connectivityStatus).length;
|
||||
|
||||
if ($chatAdapter?.pubkey && !profiles[$chatAdapter.pubkey]) {
|
||||
$chatAdapter.reqProfile($chatAdapter.pubkey);
|
||||
}
|
||||
}
|
||||
|
||||
let connectedChatId;
|
||||
|
||||
$: if (connectedChatId !== $chatAdapter?.chatId) {
|
||||
connectedChatId = $chatAdapter?.chatId;
|
||||
channelMetadata = {};
|
||||
}
|
||||
|
||||
$: profiles = $chatData.profiles;
|
||||
|
||||
function selectParent() {
|
||||
if (chatConfiguration.chatType === "GROUP") {
|
||||
$selectedMessage = null;
|
||||
} else {
|
||||
// get the last tagged event in the tags array of the current $selectedMessage
|
||||
const lastETag = getEventById($selectedMessage)
|
||||
.tags.filter((tag) => tag[0] === "e")
|
||||
.pop();
|
||||
const lastETagId = lastETag && lastETag[1];
|
||||
|
||||
$selectedMessage = lastETagId;
|
||||
}
|
||||
|
||||
scrollDown();
|
||||
}
|
||||
|
||||
let ownName;
|
||||
$: ownName = $chatAdapter?.pubkey ? pubkeyName($chatAdapter.pubkey) : "";
|
||||
|
||||
$: profiles = $chatData.profiles;
|
||||
|
||||
$: profilePicture =
|
||||
(profiles[$chatAdapter.pubkey] && profiles[$chatAdapter.pubkey].picture) ||
|
||||
`https://robohash.org/${$chatAdapter.pubkey.slice(0, 1)}.png?set=set1`;
|
||||
|
||||
function pubkeyName(pubkey) {
|
||||
let name;
|
||||
|
||||
if (profiles[$chatAdapter.pubkey]) {
|
||||
let self = profiles[$chatAdapter.pubkey];
|
||||
|
||||
// https://xkcd.com/927/
|
||||
name = self.display_name || self.displayName || self.name || self.nip05;
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
name = `Anonymous [${pubkey.slice(0, 6)}]`;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- TOP -->
|
||||
<div class="toolbar">
|
||||
{#if $chatAdapter?.pubkey}
|
||||
<a class="toolbar__avatar" href="https://iris.to/{$chatAdapter.pubkey}">
|
||||
<p class="">
|
||||
{ownName}
|
||||
</p>
|
||||
<img src={profilePicture} alt="{ownName}'s avatar" />
|
||||
</a>
|
||||
{/if}
|
||||
<div class="toolbar__stats">
|
||||
{#if events}
|
||||
<p class="stats__count">
|
||||
{events.length}
|
||||
</p>
|
||||
{/if}
|
||||
<!-- <Relays bind:relays on:update={handleUpdate} /> -->
|
||||
{#if totalRelays}
|
||||
<div class="stats__relays">
|
||||
{connectedRelays}/{totalRelays} relays
|
||||
<div class="relay-dots">
|
||||
{#each Array(totalRelays) as _, i}
|
||||
<span
|
||||
class="relay {connectedRelays > i ? 'relay--active' : ''}"
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BOTTOM -->
|
||||
<div id="messages-container" class="content content--scrolling">
|
||||
<div class="events">
|
||||
{#if $selectedMessage}
|
||||
<NostrNote
|
||||
event={getEventById($selectedMessage)}
|
||||
{responses}
|
||||
{websiteOwnerPubkey}
|
||||
/>
|
||||
{:else}
|
||||
{#each events as event}
|
||||
<NostrNote {event} {responses} {websiteOwnerPubkey} />
|
||||
{#if event.deleted}
|
||||
👆 deleted
|
||||
{/if}
|
||||
{:else}
|
||||
<p>no comments</p>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if channelMetadata.name}
|
||||
<div class="">
|
||||
{#if channelMetadata.picture}
|
||||
<img src={channelMetadata.picture} class="" />
|
||||
{/if}
|
||||
|
||||
<div class="">
|
||||
<div class="">{channelMetadata.name}</div>
|
||||
{#if channelMetadata.about}
|
||||
<div class="">{channelMetadata.about}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $selectedMessage}
|
||||
{#if !getEventById($selectedMessage)}
|
||||
<h1>Couldn't find event with ID {$selectedMessage}</h1>
|
||||
{:else}
|
||||
<div class="">
|
||||
<a href="#" on:click|preventDefault={selectParent}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class=""
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M19.5 12h-15m0 0l6.75 6.75M4.5 12l6.75-6.75"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<!-- <div class="flex flex-col ml-2">
|
||||
<span class="text-lg text-black overflow-hidden whitespace-nowrap text-ellipsis">
|
||||
{getEventById($selectedMessage).content}
|
||||
</span>
|
||||
</div> -->
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- MESSAGE INPUT -->
|
||||
|
||||
<div class="message-input">
|
||||
<textarea
|
||||
type="text"
|
||||
id="message-input"
|
||||
class=""
|
||||
placeholder="leave a comment"
|
||||
rows="1"
|
||||
on:keydown={inputKeyDown}
|
||||
/>
|
||||
<button type="button" class="" on:click|preventDefault={sendMessage}>
|
||||
<!-- Heroicon name: outline/plus -->
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="w-6 h-6 rotate-90"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><path
|
||||
d="M10.894 2.553a1 1 0 00-1.788 0l-7 14a1 1 0 001.169 1.409l5-1.429A1 1 0 009 15.571V11a1 1 0 112 0v4.571a1 1 0 00.725.962l5 1.428a1 1 0 001.17-1.408l-7-14z"
|
||||
/></svg
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
.content {
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.message-input {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.message-input textarea {
|
||||
height: auto;
|
||||
padding: 1rem;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.message-input button {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.events {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.toolbar__avatar{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 1rem;
|
||||
color: var(--c-bright);
|
||||
}
|
||||
|
||||
.toolbar__avatar img{
|
||||
width: 2rem;
|
||||
border-radius: 2rem;
|
||||
outline: 1px solid var(--c-lines);
|
||||
}
|
||||
|
||||
.toolbar__stats {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-grow: 1;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.stats__count{
|
||||
font-family: 'Barlow Condensed', sans-serif;
|
||||
font-size: 55px;
|
||||
font-weight: 100;
|
||||
line-height: .8;
|
||||
}
|
||||
|
||||
.relay-dots {
|
||||
display: flex;
|
||||
gap:.5rem;
|
||||
}
|
||||
|
||||
.relay{
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border-radius: 13px;
|
||||
border: 1px solid var(--c-lines);
|
||||
}
|
||||
|
||||
.relay--active{
|
||||
border-color: var(--c-bright);
|
||||
background-color: var(--c-marker);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -107,9 +107,13 @@
|
|||
</script>
|
||||
|
||||
<h1 class="">
|
||||
How would you like to connect?
|
||||
Welcome to DiSseNT
|
||||
</h1>
|
||||
|
||||
<h2 class="">
|
||||
How would you like to connect?
|
||||
</h2>
|
||||
|
||||
{#if publicKey}
|
||||
<p class="">
|
||||
Nostr Connect is a WIP, not fully implemented yet!
|
||||
|
|
@ -135,21 +139,21 @@
|
|||
Cancel
|
||||
</button>
|
||||
{:else if !publicKey}
|
||||
<div class="">
|
||||
<div class="btn-list">
|
||||
{#if hasNostrNip07}
|
||||
<button class="" on:click|preventDefault={useNip07}>
|
||||
Browser Extension (NIP-07)
|
||||
<button class="btn" on:click|preventDefault={useNip07}>
|
||||
Browser Extension <span class="btn__subheading">(NIP-07)</span>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
|
||||
<button class="" on:click|preventDefault={useNip46}>
|
||||
Nostr Connect (NIP-46)
|
||||
<button class="btn" on:click|preventDefault={useNip46}>
|
||||
Nostr Connect<span class="btn__subheading">(NIP-46)</span>
|
||||
</button>
|
||||
|
||||
<button class="" on:click|preventDefault={useDiscardableKeys}>
|
||||
<button class="btn" on:click|preventDefault={useDiscardableKeys}>
|
||||
Anonymous
|
||||
<span class="">
|
||||
<span class="btn__subheading">
|
||||
(Ephemeral Keys)
|
||||
</span>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<script>
|
||||
// Imports: Library and Svelte Store
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let url;
|
||||
|
||||
// Local Variables
|
||||
let title = null;
|
||||
let description = null;
|
||||
let thumbnail = null;
|
||||
|
|
@ -10,29 +10,36 @@
|
|||
let isLoading = true;
|
||||
let isError = false;
|
||||
let urlAbbr;
|
||||
|
||||
let imageLoaded = false;
|
||||
let imageError = false;
|
||||
let showContent = true;
|
||||
|
||||
const threshold = 100;
|
||||
const buffer = 0; // Adjust this value as needed
|
||||
|
||||
export let scrollPosition;
|
||||
export let url;
|
||||
|
||||
function onImageLoad() {
|
||||
imageLoaded = true;
|
||||
// Reactive Statements
|
||||
$: {
|
||||
if (scrollPosition <= threshold - buffer) {
|
||||
showContent = true;
|
||||
} else if (scrollPosition >= threshold + buffer) {
|
||||
showContent = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onImageError() {
|
||||
imageError = true;
|
||||
}
|
||||
|
||||
$: {
|
||||
const cleanedUrl = typeof url === 'string' ? url.replace(/^https?:\/\//, '') : '';
|
||||
urlAbbr = cleanedUrl.length > 20 ? cleanedUrl.substring(0, 20) + '...' : cleanedUrl;
|
||||
}
|
||||
|
||||
$: fetchMetaData(url);
|
||||
|
||||
// Lifecycle Methods
|
||||
onMount(async () => {
|
||||
fetchMetaData(url);
|
||||
});
|
||||
|
||||
// Helper Functions
|
||||
async function fetchMetaData(url){
|
||||
isError = false;
|
||||
try {
|
||||
|
|
@ -43,13 +50,13 @@
|
|||
const text = await res.text();
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(text, 'text/html');
|
||||
|
||||
|
||||
// Check multiple possible meta tags for each property
|
||||
const titleTags = ['title', 'og:title', 'twitter:title'];
|
||||
const descriptionTags = ['description', 'og:description', 'twitter:description'];
|
||||
const thumbnailTags = ['image', 'og:image', 'twitter:image'];
|
||||
const publishDateTags = ['article:published_time', 'published_time', 'date'];
|
||||
|
||||
|
||||
title = getMetaContent(doc, titleTags);
|
||||
description = getMetaContent(doc, descriptionTags);
|
||||
thumbnail = getMetaContent(doc, thumbnailTags);
|
||||
|
|
@ -61,7 +68,6 @@
|
|||
isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function getMetaContent(doc, tagNames) {
|
||||
for (const tagName of tagNames) {
|
||||
const element = doc.querySelector(`meta[name="${tagName}"], meta[property="${tagName}"]`);
|
||||
|
|
@ -71,41 +77,52 @@
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Event Handlers and UI Logic
|
||||
function onImageLoad() {
|
||||
imageLoaded = true;
|
||||
}
|
||||
function onImageError() {
|
||||
imageError = true;
|
||||
}
|
||||
|
||||
</script>
|
||||
{#if isLoading}
|
||||
<div class="metadata__text">
|
||||
<h1>{urlAbbr}</h1>
|
||||
<h1 class="{showContent ? '' : 'content--shown'}">{urlAbbr}</h1>
|
||||
<div class="metadata__content{showContent ? '' : ' content--hidden'}">
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
{:else if isError}
|
||||
<div class="metadata__text">
|
||||
<h1>{urlAbbr}</h1>
|
||||
<h1 class="{showContent ? '' : 'content--shown'}">{urlAbbr}</h1>
|
||||
<div class="metadata__content{showContent ? '' : ' content--hidden'}">
|
||||
<p>There was a problem displaying info from this website.</p>
|
||||
</div>
|
||||
{:else if !title && !description && !thumbnail && !publishDate}
|
||||
<div class="metadata__text">
|
||||
<h1>{urlAbbr}</h1>
|
||||
<h1 class="{showContent ? '' : 'content--shown'}">{urlAbbr}</h1>
|
||||
<div class="metadata__content{showContent ? '' : ' content--hidden'}">
|
||||
<p>No data found.</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="metadata__text">
|
||||
{#if title}
|
||||
<h1>{title}</h1>
|
||||
{/if}
|
||||
<a href="{url}" target="_blank" rel="noreferrer">{urlAbbr}</a>
|
||||
{#if description}
|
||||
<p>{description}</p>
|
||||
{/if}
|
||||
{#if publishDate}
|
||||
<p>Published on: {new Date(publishDate).toLocaleDateString()}</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="metadata__thumbnail">
|
||||
{#if thumbnail}
|
||||
<img src="{thumbnail}" alt="Thumbnail for {title}" on:load={onImageLoad} on:error={onImageError} />
|
||||
{imageLoaded}
|
||||
{imageError}
|
||||
{/if}
|
||||
{#if title}
|
||||
<h1 class="{showContent ? '' : 'content--shown'}">{title}</h1>
|
||||
{/if}
|
||||
<div class="metadata__content{showContent ? '' : ' content--hidden'}">
|
||||
<div class="metadata__text">
|
||||
<a href="{url}" target="_blank" rel="noreferrer">{urlAbbr}</a>
|
||||
{#if description}
|
||||
<p>{description}</p>
|
||||
{/if}
|
||||
{#if publishDate}
|
||||
<p>Published on: {new Date(publishDate).toLocaleDateString()}</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="metadata__thumbnail">
|
||||
{#if thumbnail}
|
||||
<img src="{thumbnail}" alt="Thumbnail for {title}" on:load={onImageLoad} on:error={onImageError} />
|
||||
<!-- {imageLoaded}
|
||||
{imageError} -->
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
|
@ -118,7 +135,14 @@ h1 {
|
|||
font-size: 1.5rem;
|
||||
color: var(--c-3);
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
margin: -1rem -1rem 0 -1rem;
|
||||
padding: 1rem 1rem 1px 1rem;
|
||||
background: linear-gradient(to bottom, #381100 0%, #3f1300 100%);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
h1.content--shown{
|
||||
box-shadow: #3f1300 0 0 10px 10px;
|
||||
}
|
||||
|
||||
a{
|
||||
|
|
@ -140,6 +164,21 @@ p {
|
|||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.metadata__content {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
transition: all .2s ease-in-out;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.metadata__content.content--hidden {
|
||||
overflow:hidden;
|
||||
opacity: 0;
|
||||
transform: translateY(-300px);
|
||||
}
|
||||
|
||||
.metadata__text {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
|
@ -153,7 +192,7 @@ p {
|
|||
}
|
||||
|
||||
img{
|
||||
max-height: 100%;
|
||||
max-height: 6rem;
|
||||
outline: 1px solid rgba(255, 255, 255, .5);
|
||||
border-radius: .5rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,207 +1,331 @@
|
|||
<script>
|
||||
import { afterUpdate, onMount } from 'svelte';
|
||||
import { selectedMessage, zappingMessage, zapsPerMessage } from './lib/store';
|
||||
import { chatData, chatAdapter } from './lib/store';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
import ZapAmountButton from './ZapAmountButton.svelte';
|
||||
// import { prettifyContent } from '$lib/utils';
|
||||
export let event;
|
||||
export let responses;
|
||||
export let websiteOwnerPubkey;
|
||||
import { afterUpdate, onMount } from "svelte";
|
||||
import { selectedMessage, zappingMessage, zapsPerMessage } from "./lib/store";
|
||||
import { chatData, chatAdapter } from "./lib/store";
|
||||
import { nip19 } from "nostr-tools";
|
||||
import ZapAmountButton from "./ZapAmountButton.svelte";
|
||||
// import { prettifyContent } from '$lib/utils';
|
||||
|
||||
let profiles = {};
|
||||
let profilePicture;
|
||||
let npub;
|
||||
let zappingIt;
|
||||
let hovering;
|
||||
let mobilePR;
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
|
||||
let zappedAmount = 0;
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
function selectMessage() {
|
||||
if ($selectedMessage === event.id) {
|
||||
$selectedMessage = null;
|
||||
} else {
|
||||
$selectedMessage = event.id;
|
||||
}
|
||||
export let event;
|
||||
export let responses;
|
||||
export let websiteOwnerPubkey;
|
||||
|
||||
let profiles = {};
|
||||
let profilePicture;
|
||||
let npub;
|
||||
let zappingIt;
|
||||
let hovering;
|
||||
let mobilePR;
|
||||
|
||||
let relativeTimeFromNow;
|
||||
|
||||
let zappedAmount = 0;
|
||||
|
||||
function selectMessage() {
|
||||
if ($selectedMessage === event.id) {
|
||||
$selectedMessage = null;
|
||||
} else {
|
||||
$selectedMessage = event.id;
|
||||
}
|
||||
}
|
||||
|
||||
// delay-fetch responses
|
||||
onMount(() => {
|
||||
$chatAdapter.delayedSubscribe(
|
||||
{kinds: [1, 42, 9735], '#e': [event.id]}
|
||||
, 'responses', 500)
|
||||
})
|
||||
// delay-fetch responses
|
||||
onMount(() => {
|
||||
$chatAdapter.delayedSubscribe(
|
||||
{ kinds: [1, 42, 9735], "#e": [event.id] },
|
||||
"responses",
|
||||
500
|
||||
);
|
||||
});
|
||||
|
||||
const byWebsiteOwner = !!websiteOwnerPubkey === event.pubkey;
|
||||
const byWebsiteOwner = !!websiteOwnerPubkey === event.pubkey;
|
||||
|
||||
$: profiles = $chatData.profiles;
|
||||
$: displayName = profiles[event.pubkey] && profiles[event.pubkey].display_name || `[${event.pubkey.slice(0, 6)}]`;
|
||||
// $: nip05 = profiles[event.pubkey] && profiles[event.pubkey].nip05;
|
||||
$: zappingIt = $zappingMessage === event.id;
|
||||
$: {
|
||||
try {
|
||||
npub = nip19.npubEncode(event.pubkey);
|
||||
} catch (e) {
|
||||
npub = event.pubkey;
|
||||
}
|
||||
$: profiles = $chatData.profiles;
|
||||
$: displayName =
|
||||
(profiles[event.pubkey] && profiles[event.pubkey].display_name) ||
|
||||
`[${event.pubkey.slice(0, 6)}]`;
|
||||
// $: nip05 = profiles[event.pubkey] && profiles[event.pubkey].nip05;
|
||||
$: zappingIt = $zappingMessage === event.id;
|
||||
$: {
|
||||
try {
|
||||
npub = nip19.npubEncode(event.pubkey);
|
||||
} catch (e) {
|
||||
npub = event.pubkey;
|
||||
}
|
||||
}
|
||||
|
||||
$chatAdapter.on('zap', () => {
|
||||
zappedAmount = $zapsPerMessage[event.id]?.reduce((acc, zap) => acc + zap.amount, 0) || 0;
|
||||
});
|
||||
$chatAdapter.on("zap", () => {
|
||||
zappedAmount =
|
||||
$zapsPerMessage[event.id]?.reduce((acc, zap) => acc + zap.amount, 0) || 0;
|
||||
});
|
||||
|
||||
$: {
|
||||
zappedAmount = $zapsPerMessage[event.id]?.reduce((acc, zap) => acc + zap.amount, 0) || 0;
|
||||
$: {
|
||||
zappedAmount =
|
||||
$zapsPerMessage[event.id]?.reduce((acc, zap) => acc + zap.amount, 0) || 0;
|
||||
}
|
||||
|
||||
afterUpdate(() => {
|
||||
zappedAmount =
|
||||
$zapsPerMessage[event.id]?.reduce((acc, zap) => acc + zap.amount, 0) || 0;
|
||||
});
|
||||
|
||||
$: profilePicture =
|
||||
(profiles[event.pubkey] && profiles[event.pubkey].picture) ||
|
||||
`https://robohash.org/${event.pubkey.slice(0, 1)}.png?set=set1`;
|
||||
|
||||
// const repliedIds = event.tags.filter(e => e[0] === 'e').map(e => e[1]);
|
||||
|
||||
let timestamp = new Date(event.created_at * 1000);
|
||||
|
||||
$: {
|
||||
const now = dayjs();
|
||||
const then = dayjs(timestamp);
|
||||
const diffInSeconds = now.diff(then, 'second');
|
||||
const diffInMinutes = now.diff(then, 'minute');
|
||||
const diffInHours = now.diff(then, 'hour');
|
||||
const diffInDays = now.diff(then, 'day');
|
||||
const diffInMonths = now.diff(then, 'month');
|
||||
const diffInYears = now.diff(then, 'year');
|
||||
|
||||
if (diffInSeconds < 10) {
|
||||
relativeTimeFromNow = "Now";
|
||||
} else if (diffInSeconds < 60) {
|
||||
relativeTimeFromNow = `${diffInSeconds}s`;
|
||||
} else if (diffInMinutes < 60) {
|
||||
relativeTimeFromNow = `${diffInMinutes}m`;
|
||||
} else if (diffInHours < 24) {
|
||||
relativeTimeFromNow = `${diffInHours}h`;
|
||||
} else if (diffInDays < 365) {
|
||||
relativeTimeFromNow = then.format('MMM D');
|
||||
} else {
|
||||
if (diffInYears >= 1 && diffInMonths >= 6) {
|
||||
relativeTimeFromNow = then.format('MMM D, YYYY');
|
||||
} else {
|
||||
relativeTimeFromNow = then.format('MMM D');
|
||||
}
|
||||
}
|
||||
|
||||
afterUpdate(() => {
|
||||
zappedAmount = $zapsPerMessage[event.id]?.reduce((acc, zap) => acc + zap.amount, 0) || 0;
|
||||
});
|
||||
|
||||
$: profilePicture = profiles[event.pubkey] && profiles[event.pubkey].picture || `https://robohash.org/${event.pubkey.slice(0, 1)}.png?set=set1`;
|
||||
|
||||
// const repliedIds = event.tags.filter(e => e[0] === 'e').map(e => e[1]);
|
||||
|
||||
let timestamp = new Date(event.created_at * 1000);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="
|
||||
flex flex-col gap-4
|
||||
p-2-lg mb-3
|
||||
text-wrap
|
||||
relative
|
||||
"
|
||||
on:mouseenter={() => (hovering = true)}
|
||||
on:mouseleave={() => (hovering = false)}
|
||||
>
|
||||
<div class="flex flex-row gap-3">
|
||||
<div class="min-w-fit flex flex-col gap-2">
|
||||
<a href={`nostr:${npub}`}>
|
||||
<img src="{profilePicture}" class="
|
||||
block w-8 h-8 rounded-full
|
||||
{byWebsiteOwner ? 'ring-purple-700 ring-4' : ''}
|
||||
" alt="" />
|
||||
</a>
|
||||
|
||||
<button
|
||||
class="
|
||||
rounded-full
|
||||
{zappedAmount > 0 ? 'opacity-100 text-base' : 'bg-orange-500 opacity-10 text-xl'}
|
||||
w-8 h-8
|
||||
flex items-center
|
||||
justify-center
|
||||
hover:opacity-100
|
||||
"
|
||||
on:click|preventDefault={() => $zappingMessage = $zappingMessage === event.id ? null : event.id}
|
||||
>
|
||||
{#if zappedAmount > 0}
|
||||
<p class="flex flex-col items-center my-4">
|
||||
⚡️
|
||||
<span class="text-orange-500 font-semibold">
|
||||
{zappedAmount/1000}
|
||||
</span>
|
||||
</p>
|
||||
{:else}
|
||||
⚡️
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<div class="
|
||||
{zappingIt ?
|
||||
'w-full rounded-full bg-white drop-shadow-xl justify-between border-2 border-gray-200' :
|
||||
' rounded-full w-8 h-8 justify-center'
|
||||
}
|
||||
flex items-center ml-5 mt-10 z-10">
|
||||
{#if zappingIt}
|
||||
{#if mobilePR}
|
||||
<div class="flex flex-col gap-3 w-full">
|
||||
<a href={`lightning:${mobilePR}`} class="text-center w-full p-3 bg-black text-white rounded-t-xl">Open in wallet</a>
|
||||
<button class="bg-white rounder-b-xl p-3" on:click={() => { $zappingMessage = null; }}>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-row items-stretch justify-between w-full">
|
||||
<div class="flex flex-col hover:bg-orange-500 text-white rounded-full w-12 h-12 items-center justify-center cursor-pointer">
|
||||
<ZapAmountButton icon="👍" amount={500} {event} bind:mobilePR={mobilePR} />
|
||||
</div>
|
||||
<div class="flex flex-col hover:bg-orange-500 text-white rounded-full w-12 h-12 items-center justify-center cursor-pointer">
|
||||
<ZapAmountButton icon="🤙" amount={2500} amountDisplay={'2.5k'} {event} bind:mobilePR={mobilePR} />
|
||||
</div>
|
||||
<div class="flex flex-col hover:bg-orange-500 text-white rounded-full w-12 h-12 items-center justify-center cursor-pointer">
|
||||
<ZapAmountButton icon="🙌" amount={5000} amountDisplay={'5k'} {event} bind:mobilePR={mobilePR} />
|
||||
</div>
|
||||
<div class="flex flex-col hover:bg-orange-500 text-white rounded-full w-12 h-12 items-center justify-center cursor-pointer">
|
||||
<ZapAmountButton icon="🧡" amount={10000} amountDisplay={'10k'} {event} bind:mobilePR={mobilePR} />
|
||||
</div>
|
||||
<div class="flex flex-col hover:bg-orange-500 text-white rounded-full w-12 h-12 items-center justify-center cursor-pointer">
|
||||
<ZapAmountButton icon="🤯" amount={100000} amountDisplay={'100k'} {event} bind:mobilePR={mobilePR} />
|
||||
</div>
|
||||
<div class="flex flex-col hover:bg-orange-500 text-white rounded-full w-12 h-12 items-center justify-center cursor-pointer">
|
||||
<ZapAmountButton icon="😎" amount={1000000} amountDisplay={'1M'} {event} bind:mobilePR={mobilePR} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
<article class="event">
|
||||
<div class="event__content">
|
||||
<!-- AVATAR-->
|
||||
<div class="event__avatar">
|
||||
<a href={`nostr:${npub}`}
|
||||
><img
|
||||
src={profilePicture}
|
||||
alt="{displayName}'s avatar"
|
||||
/></a
|
||||
>
|
||||
<!-- <button
|
||||
class="zap-btn {zappedAmount > 0
|
||||
? 'zap-btn--zapped'
|
||||
: ''}"
|
||||
on:click|preventDefault={() =>
|
||||
($zappingMessage = $zappingMessage === event.id ? null : event.id)}
|
||||
>
|
||||
{#if zappedAmount > 0}
|
||||
<p>
|
||||
⚡️
|
||||
<span>
|
||||
{zappedAmount / 1000}
|
||||
</span>
|
||||
</p>
|
||||
{:else}
|
||||
⚡️
|
||||
{/if}
|
||||
</button> -->
|
||||
<!-- <div
|
||||
class="
|
||||
{zappingIt
|
||||
? 'w-full rounded-full bg-white drop-shadow-xl justify-between border-2 border-white/50'
|
||||
: ' rounded-full w-8 h-8 justify-center'}
|
||||
flex items-center ml-5 mt-10 z-10"
|
||||
>
|
||||
{#if zappingIt}
|
||||
{#if mobilePR}
|
||||
<div class="">
|
||||
<a
|
||||
href={`lightning:${mobilePR}`}
|
||||
class=""
|
||||
>Open in wallet</a
|
||||
>
|
||||
<button
|
||||
class=""
|
||||
on:click={() => {
|
||||
$zappingMessage = null;
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- <span class="text-base font-semibold text-clip">{displayName}</span>
|
||||
{#if nip05}
|
||||
<span class="text-sm text-gray-400">{nip05}</span>
|
||||
{/if} -->
|
||||
</div>
|
||||
|
||||
<div class="w-full overflow-hidden">
|
||||
<div class="flex flex-row justify-between text-center overflow-clip text-clip w-full">
|
||||
{:else}
|
||||
<div>
|
||||
<div>
|
||||
<ZapAmountButton icon="👍" amount={500} {event} bind:mobilePR />
|
||||
</div>
|
||||
<div>
|
||||
<ZapAmountButton
|
||||
icon="🤙"
|
||||
amount={2500}
|
||||
amountDisplay={"2.5k"}
|
||||
{event}
|
||||
bind:mobilePR
|
||||
/>
|
||||
</div>
|
||||
<div >
|
||||
<ZapAmountButton
|
||||
icon="🙌"
|
||||
amount={5000}
|
||||
amountDisplay={"5k"}
|
||||
{event}
|
||||
bind:mobilePR
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<ZapAmountButton
|
||||
icon="🧡"
|
||||
amount={10000}
|
||||
amountDisplay={"10k"}
|
||||
{event}
|
||||
bind:mobilePR
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<ZapAmountButton
|
||||
icon="🤯"
|
||||
amount={100000}
|
||||
amountDisplay={"100k"}
|
||||
{event}
|
||||
bind:mobilePR
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<ZapAmountButton
|
||||
icon="😎"
|
||||
amount={1000000}
|
||||
amountDisplay={"1M"}
|
||||
{event}
|
||||
bind:mobilePR
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="
|
||||
max-h-64 text-base
|
||||
cursor-pointer
|
||||
border border-slate-200
|
||||
{$selectedMessage === event.id ? 'bg-purple-700 text-white' : 'bg-white text-gray-900 hover:bg-slate-100'}
|
||||
p-4 py-2 overflow-auto rounded-2xl
|
||||
shadow-sm
|
||||
" on:click|preventDefault={()=>{selectMessage(event.id)}}
|
||||
on:keydown|preventDefault={()=>{selectMessage(event.id)}}
|
||||
on:keyup|preventDefault={()=>{selectMessage(event.id)}}
|
||||
>
|
||||
{event.content}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row-reverse justify-between mt-1 overflow-clip items-center relative">
|
||||
<div class="text-xs text-gray-400 text-ellipsis overflow-clip whitespace-nowrap">
|
||||
<span class="py-2">
|
||||
{timestamp.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{#if byWebsiteOwner}
|
||||
<div class="text-purple-500 text-xs">
|
||||
Website owner
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-xs text-gray-400">
|
||||
{displayName}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if responses[event.id].length > 0}
|
||||
<div class="pl-5 border-l border-l-gray-400 flex flex-col gap-4">
|
||||
{#each responses[event.id] as response}
|
||||
<svelte:self {websiteOwnerPubkey} event={response} {responses} />
|
||||
{/each}
|
||||
|
||||
<!-- TEXT-->
|
||||
<div class="event__text">
|
||||
<header>
|
||||
<h1>
|
||||
{displayName}
|
||||
</h1>
|
||||
<span title="{timestamp.toLocaleString()}">
|
||||
{relativeTimeFromNow}
|
||||
</span>
|
||||
</header>
|
||||
<p class="event__message">
|
||||
{event.content}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if responses[event.id].length > 0}
|
||||
<div class="event__responses">
|
||||
{#each responses[event.id] as response}
|
||||
<svelte:self {websiteOwnerPubkey} event={response} {responses} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</article>
|
||||
|
||||
<style>
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.event{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem 1rem 1rem 1rem;
|
||||
border-bottom: 1px solid var(--c-lines);
|
||||
color: var(--c-3);
|
||||
margin: 0 -1rem;
|
||||
}
|
||||
|
||||
.event:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.event__content{
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.event__avatar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.event__avatar img {
|
||||
aspect-ratio: 1;
|
||||
width: 40px;
|
||||
border-radius: 40px;
|
||||
outline: 1px solid var(--c-lines);
|
||||
}
|
||||
|
||||
.event__text {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .5rem;
|
||||
}
|
||||
|
||||
header{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
header h1 {
|
||||
font-weight: 900;
|
||||
font-size: 1rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p{
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
header span{
|
||||
margin-right: .5rem;
|
||||
color: var(--c-bright);
|
||||
}
|
||||
|
||||
.zap-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50px;
|
||||
background-color: orange;
|
||||
color: white;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.event__responses{
|
||||
border-left: 1px solid var(--c-lines);
|
||||
margin-left: calc((40px - 1rem) / 2);
|
||||
}
|
||||
|
||||
.event__message {
|
||||
overflow-wrap: anywhere;
|
||||
line-height: 1.4em;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,325 +0,0 @@
|
|||
<script>
|
||||
import { afterUpdate, onMount } from "svelte";
|
||||
import { selectedMessage, zappingMessage, zapsPerMessage } from "./lib/store";
|
||||
import { chatData, chatAdapter } from "./lib/store";
|
||||
import { nip19 } from "nostr-tools";
|
||||
import ZapAmountButton from "./ZapAmountButton.svelte";
|
||||
// import { prettifyContent } from '$lib/utils';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
export let event;
|
||||
export let responses;
|
||||
export let websiteOwnerPubkey;
|
||||
|
||||
let profiles = {};
|
||||
let profilePicture;
|
||||
let npub;
|
||||
let zappingIt;
|
||||
let hovering;
|
||||
let mobilePR;
|
||||
|
||||
let relativeTimeFromNow;
|
||||
|
||||
let zappedAmount = 0;
|
||||
|
||||
function selectMessage() {
|
||||
if ($selectedMessage === event.id) {
|
||||
$selectedMessage = null;
|
||||
} else {
|
||||
$selectedMessage = event.id;
|
||||
}
|
||||
}
|
||||
|
||||
// delay-fetch responses
|
||||
onMount(() => {
|
||||
$chatAdapter.delayedSubscribe(
|
||||
{ kinds: [1, 42, 9735], "#e": [event.id] },
|
||||
"responses",
|
||||
500
|
||||
);
|
||||
});
|
||||
|
||||
const byWebsiteOwner = !!websiteOwnerPubkey === event.pubkey;
|
||||
|
||||
$: profiles = $chatData.profiles;
|
||||
$: displayName =
|
||||
(profiles[event.pubkey] && profiles[event.pubkey].display_name) ||
|
||||
`[${event.pubkey.slice(0, 6)}]`;
|
||||
// $: nip05 = profiles[event.pubkey] && profiles[event.pubkey].nip05;
|
||||
$: zappingIt = $zappingMessage === event.id;
|
||||
$: {
|
||||
try {
|
||||
npub = nip19.npubEncode(event.pubkey);
|
||||
} catch (e) {
|
||||
npub = event.pubkey;
|
||||
}
|
||||
}
|
||||
|
||||
$chatAdapter.on("zap", () => {
|
||||
zappedAmount =
|
||||
$zapsPerMessage[event.id]?.reduce((acc, zap) => acc + zap.amount, 0) || 0;
|
||||
});
|
||||
|
||||
$: {
|
||||
zappedAmount =
|
||||
$zapsPerMessage[event.id]?.reduce((acc, zap) => acc + zap.amount, 0) || 0;
|
||||
}
|
||||
|
||||
afterUpdate(() => {
|
||||
zappedAmount =
|
||||
$zapsPerMessage[event.id]?.reduce((acc, zap) => acc + zap.amount, 0) || 0;
|
||||
});
|
||||
|
||||
$: profilePicture =
|
||||
(profiles[event.pubkey] && profiles[event.pubkey].picture) ||
|
||||
`https://robohash.org/${event.pubkey.slice(0, 1)}.png?set=set1`;
|
||||
|
||||
// const repliedIds = event.tags.filter(e => e[0] === 'e').map(e => e[1]);
|
||||
|
||||
let timestamp = new Date(event.created_at * 1000);
|
||||
|
||||
$: {
|
||||
const now = dayjs();
|
||||
const then = dayjs(timestamp);
|
||||
const diffInSeconds = now.diff(then, 'second');
|
||||
const diffInMinutes = now.diff(then, 'minute');
|
||||
const diffInHours = now.diff(then, 'hour');
|
||||
const diffInDays = now.diff(then, 'day');
|
||||
const diffInMonths = now.diff(then, 'month');
|
||||
const diffInYears = now.diff(then, 'year');
|
||||
|
||||
if (diffInSeconds < 10) {
|
||||
relativeTimeFromNow = "Now";
|
||||
} else if (diffInSeconds < 60) {
|
||||
relativeTimeFromNow = `${diffInSeconds}s`;
|
||||
} else if (diffInMinutes < 60) {
|
||||
relativeTimeFromNow = `${diffInMinutes}m`;
|
||||
} else if (diffInHours < 24) {
|
||||
relativeTimeFromNow = `${diffInHours}h`;
|
||||
} else if (diffInDays < 365) {
|
||||
relativeTimeFromNow = then.format('MMM D');
|
||||
} else {
|
||||
if (diffInYears >= 1 && diffInMonths >= 6) {
|
||||
relativeTimeFromNow = then.format('MMM D, YYYY');
|
||||
} else {
|
||||
relativeTimeFromNow = then.format('MMM D');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<article class="event">
|
||||
<div class="event__content">
|
||||
<!-- AVATAR-->
|
||||
<div class="event__avatar">
|
||||
<a href={`nostr:${npub}`}
|
||||
><img
|
||||
src={profilePicture}
|
||||
alt="{displayName}'s avatar"
|
||||
/></a
|
||||
>
|
||||
<button
|
||||
class="zap-btn {zappedAmount > 0
|
||||
? 'zap-btn--zapped'
|
||||
: ''}"
|
||||
on:click|preventDefault={() =>
|
||||
($zappingMessage = $zappingMessage === event.id ? null : event.id)}
|
||||
>
|
||||
{#if zappedAmount > 0}
|
||||
<p>
|
||||
⚡️
|
||||
<span>
|
||||
{zappedAmount / 1000}
|
||||
</span>
|
||||
</p>
|
||||
{:else}
|
||||
⚡️
|
||||
{/if}
|
||||
</button>
|
||||
<!-- <div
|
||||
class="
|
||||
{zappingIt
|
||||
? 'w-full rounded-full bg-white drop-shadow-xl justify-between border-2 border-white/50'
|
||||
: ' rounded-full w-8 h-8 justify-center'}
|
||||
flex items-center ml-5 mt-10 z-10"
|
||||
>
|
||||
{#if zappingIt}
|
||||
{#if mobilePR}
|
||||
<div class="">
|
||||
<a
|
||||
href={`lightning:${mobilePR}`}
|
||||
class=""
|
||||
>Open in wallet</a
|
||||
>
|
||||
<button
|
||||
class=""
|
||||
on:click={() => {
|
||||
$zappingMessage = null;
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div>
|
||||
<div>
|
||||
<ZapAmountButton icon="👍" amount={500} {event} bind:mobilePR />
|
||||
</div>
|
||||
<div>
|
||||
<ZapAmountButton
|
||||
icon="🤙"
|
||||
amount={2500}
|
||||
amountDisplay={"2.5k"}
|
||||
{event}
|
||||
bind:mobilePR
|
||||
/>
|
||||
</div>
|
||||
<div >
|
||||
<ZapAmountButton
|
||||
icon="🙌"
|
||||
amount={5000}
|
||||
amountDisplay={"5k"}
|
||||
{event}
|
||||
bind:mobilePR
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<ZapAmountButton
|
||||
icon="🧡"
|
||||
amount={10000}
|
||||
amountDisplay={"10k"}
|
||||
{event}
|
||||
bind:mobilePR
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<ZapAmountButton
|
||||
icon="🤯"
|
||||
amount={100000}
|
||||
amountDisplay={"100k"}
|
||||
{event}
|
||||
bind:mobilePR
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<ZapAmountButton
|
||||
icon="😎"
|
||||
amount={1000000}
|
||||
amountDisplay={"1M"}
|
||||
{event}
|
||||
bind:mobilePR
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<!-- TEXT-->
|
||||
<div class="event__text">
|
||||
<header>
|
||||
<h1>
|
||||
{displayName}
|
||||
</h1>
|
||||
<span title="{timestamp.toLocaleString()}">
|
||||
{relativeTimeFromNow}
|
||||
</span>
|
||||
</header>
|
||||
<p class="event__message">
|
||||
{event.content}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if responses[event.id].length > 0}
|
||||
<div class="event__responses">
|
||||
{#each responses[event.id] as response}
|
||||
<svelte:self {websiteOwnerPubkey} event={response} {responses} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</article>
|
||||
|
||||
<style>
|
||||
|
||||
.event{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid var(--c-lines);
|
||||
color: var(--c-3);
|
||||
}
|
||||
|
||||
.event__content{
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.event__avatar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.event__avatar img {
|
||||
aspect-ratio: 1;
|
||||
width: 40px;
|
||||
border-radius: 40px;
|
||||
outline: 1px solid var(--c-lines);
|
||||
}
|
||||
|
||||
.event__text {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .5rem;
|
||||
}
|
||||
|
||||
header{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
header h1 {
|
||||
font-weight: 900;
|
||||
font-size: 1rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p{
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
header span{
|
||||
margin-right: .5rem;
|
||||
color: var(--c-bright);
|
||||
}
|
||||
|
||||
.zap-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50px;
|
||||
background-color: orange;
|
||||
color: white;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.event__responses{
|
||||
border-left: 1px solid var(--c-lines);
|
||||
margin-left: calc((40px - 1rem) / 2);
|
||||
}
|
||||
|
||||
.event__message {
|
||||
overflow-wrap: anywhere;
|
||||
line-height: 1.4em;
|
||||
}
|
||||
</style>
|
||||
187
src/app.css
187
src/app.css
|
|
@ -12,6 +12,16 @@
|
|||
|
||||
body {
|
||||
margin: 0;
|
||||
background: linear-gradient(to bottom right, #000, #0e1217);
|
||||
padding: 0 1rem 0 1rem;
|
||||
}
|
||||
|
||||
body > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -22,25 +32,46 @@ div {
|
|||
}
|
||||
*/
|
||||
|
||||
|
||||
/* main > section {
|
||||
outline: 1px dotted orange;
|
||||
} */
|
||||
|
||||
.page-outer {
|
||||
border-radius: 1rem 1rem 0 0;
|
||||
border-top: 1px solid var(--c-lines);
|
||||
border-left: 1px solid var(--c-lines);
|
||||
border-right: 1px solid var(--c-lines);
|
||||
/* padding: 0 1rem 0 1rem; */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
#381100 0%,
|
||||
#a13000 99.98%,
|
||||
#b93700 99.99%,
|
||||
#ff4c00 100%
|
||||
),
|
||||
linear-gradient(to left, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0.3) 100%);
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
main {
|
||||
font-family: Work Sans, sans-serif;
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
#381100 0%,
|
||||
#a13000 99.98%,
|
||||
#b93700 99.99%,
|
||||
#ff4c00 100%
|
||||
),
|
||||
linear-gradient(to left, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0.3) 100%);
|
||||
}
|
||||
|
||||
.panel {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
width: 566px;
|
||||
overflow-y: auto;
|
||||
background-color: rgb(56, 17, 0, 0.2);
|
||||
padding: 1rem;
|
||||
gap: 1rem;
|
||||
border-left: 1px solid var(--c-lines);
|
||||
border-right: 1px solid var(--c-lines);
|
||||
}
|
||||
|
||||
.panel .toolbar {
|
||||
|
|
@ -71,7 +102,7 @@ main {
|
|||
}
|
||||
|
||||
button {
|
||||
outline: 1px solid var(--c-lines);
|
||||
border: 1px solid var(--c-lines);
|
||||
color: var(--c-lines);
|
||||
padding: 1em 2rem;
|
||||
}
|
||||
|
|
@ -101,7 +132,7 @@ button {
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.content--scrolling::before {
|
||||
/* .content--scrolling::before {
|
||||
content: "";
|
||||
position: sticky;
|
||||
top: 0;
|
||||
|
|
@ -110,15 +141,135 @@ button {
|
|||
height: 100%;
|
||||
background-color: var(--c-lines);
|
||||
z-index: 1;
|
||||
}
|
||||
} */
|
||||
|
||||
button {
|
||||
padding: 0.5em 1em;
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--c-lines);
|
||||
cursor: pointer;
|
||||
font-family: 'Work Sans', sans-serif;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
border: 1px solid var(--c-bright);
|
||||
color: var(--c-bright)
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--c-bright);
|
||||
font-weight: 500;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: var(--c-bright);
|
||||
font-weight: 500;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 598px) {
|
||||
.hide-on-mobile {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1140px) {
|
||||
section.metadata h1{
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
||||
.event .event {
|
||||
padding-right: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.event .event .event__responces {
|
||||
margin-left: calc((40px) / 2) !important;
|
||||
}
|
||||
|
||||
.btn__subheading {
|
||||
display: block;
|
||||
font-size: .8rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.btn-list{
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.btn-list .btn {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.sheet p {
|
||||
color: var(--c-bright)
|
||||
}
|
||||
|
||||
.sheet a{
|
||||
color: var(--c-marker)
|
||||
}
|
||||
|
||||
.sheet li {
|
||||
color: var(--c-bright)
|
||||
}
|
||||
|
||||
.sheet blockquote {
|
||||
font-size: 1.1rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
section.nav ul{
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
code{
|
||||
color: #bbb;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
section.nav a{
|
||||
color: var(--c-bright);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
section.nav a:hover{
|
||||
color: var(--c-bright);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.sheet .btn{
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.btn.btn--lightning {
|
||||
color: var(--c-bright);
|
||||
background-color: var(--c-bright);
|
||||
color: black;
|
||||
padding: .25em .75em;
|
||||
border-radius: 2em;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
font-weight: 400;
|
||||
gap: .5em
|
||||
}
|
||||
|
||||
.sheet pre code {
|
||||
line-break: anywhere;
|
||||
color: var(--c-bright);
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.sheet .support-links {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
80
src/lib/README.md
Normal file
80
src/lib/README.md
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
# What is DiSseNT?
|
||||
[DiSseNT](https://dsnt.chat) is my attempt at ressurecting a valuable [project](https://github.com/gab-ai-inc/gab-dissenter-extension/issues/117) to enable truely trustless and free commentary on the web. DiSseNT is a web app built on [NOSTR](https://github.com/nostr-protocol/nostr), and based on [Nostri.chat](https://github.com/pablof7z/nostr-chat-widget), that [associates](#how-does-it-work) a set of NOSTR messages with a given URL.
|
||||
|
||||
This standalone web app is a first step - browser plugins and native apps will be the next (and perhaps more important) steps.
|
||||
|
||||
# But why?
|
||||
It's valuable to have public debate on important issues. In the web-centric world that we live in, most important issues are represented by urls (a news article on CNN, a post on X.com, a video on YouTube, etc) and consequently, much of that debate is going to happen online and refer directly to those urls.
|
||||
|
||||
**Also** in the world we live in, most of that debate is tightly controlled by third parties. Most public commentary is hosted on servers owned by the likes of X.com, Facebook, YouTube and Reddit. Commentary on press articles and blogs is frequently stored on Disqus servers or Medium servers. Even alternative media outlets like Substack control their own commentary. With this control, necessarily (and perhaps understandably) comes censorship. With this censorship, the effectiveness of the public forum to suss out truth is diminished.
|
||||
|
||||
The goal of DiSseNT is to be a universal comment section for the web. The commentary will be controlled by no one, stored everywhere and nowhere, and tied [*](#archival)forever to the source material.
|
||||
|
||||
|
||||
# How do I use it?
|
||||
1. In the top bar, type the url of a real webpage and hit enter, or paste a url.
|
||||
- If the url represents a real page, the title of the page and other meta data should appear.
|
||||
- If there are comments [associated](#how-does-it-work) with this page on NOSTR, they should appear.
|
||||
1. Type a comment and then submit it by clicking send, or hitting enter.
|
||||
- Hold control while hitting enter to get a new line.
|
||||
|
||||
<h1 id="how-does-it-work">How does it work?</h1>
|
||||
|
||||
At the moment, DiSsenT is fundamentally a fork of [Nostri.chat](https://github.com/pablof7z/nostr-chat-widget), an in-page support chat widget designed by Pablof7z, with trivial cosmetic changes, and one important functional change:
|
||||
|
||||
> All chats are created with "GROUP" as the `chatType` and a hex version of the provided url as the `chatId`.
|
||||
|
||||
# Roadmap
|
||||
- [x] basic posting, associated with a url
|
||||
- [x] display meta info
|
||||
- [ ] log out *(sept 2023)*
|
||||
- [ ] reply to comments *(sept 2023)*
|
||||
- [ ] zap comments *(sept 2023)*
|
||||
- [ ] search comments *(fall 2023)*
|
||||
- [ ] filter comments *(fall 2023)*
|
||||
- [ ] sort comments
|
||||
- [ ] cache comments
|
||||
- [ ] relay picker
|
||||
- [ ] **chromium extension**
|
||||
- [ ] **PWA**
|
||||
- [ ] view all dsnt'd urls
|
||||
- [ ] search dsnt'd urls
|
||||
- [ ] markdown comments
|
||||
- [ ] automatic PDF storage of referenced webpages
|
||||
- [ ] dark mode / light mode
|
||||
|
||||
# Supporting the Roadmap
|
||||
|
||||
<div class="support-links">
|
||||
<a class="btn" href='https://ko-fi.com/O4O1OZX1V' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi2.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
|
||||
<a class="btn" href="https://liberapay.com/spencer.flagg/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a>
|
||||
<a class="btn btn--lightning" href="lightning:crimsonbird599@getalby.com" title="tip on the lightning network">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_21_2)">
|
||||
<mask id="mask0_21_2" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16">
|
||||
<path d="M16 0H0V16H16V0Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_21_2)">
|
||||
<path d="M7.99902 16.0002C12.4173 16.0002 15.999 12.4185 15.999 8.00024C15.999 3.58197 12.4173 0.000244141 7.99902 0.000244141C3.58075 0.000244141 -0.000976562 3.58197 -0.000976562 8.00024C-0.000976562 12.4185 3.58075 16.0002 7.99902 16.0002Z" fill="#7B1AF7"/>
|
||||
<path d="M4.52538 8.17306L9.85872 3.5773C10.0911 3.42847 10.3126 3.5773 10.1708 3.83261L8.46865 7.18015H11.5041C11.5041 7.18015 11.9864 7.18015 11.5041 7.57732L6.25588 12.2014C5.88708 12.5135 5.63177 12.3433 5.88708 11.861L7.53247 8.59859H4.52538C4.52538 8.59859 4.04311 8.59859 4.52538 8.17306Z" fill="white"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_21_2">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
Tip Lightning
|
||||
</a>
|
||||
</div>
|
||||
|
||||
**Tip Monero**
|
||||
```
|
||||
84ENScA84suRz2pF1eptxS5FRfyYJcMX9VWrpfUQsUfiY8RVDFkfZCJEKHUQLNu5GJUiuwVtjJGSSiPnNX4PVz2dHPQe44T
|
||||
```
|
||||
|
||||
|
||||
<h1 id="archival">Archival</h1>
|
||||
|
||||
Ultimately I'd love to incorporate some automatic PDF storage of referenced webpages. Perhaps on IPFS?
|
||||
16
src/lightning.svg
Normal file
16
src/lightning.svg
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_21_2)">
|
||||
<mask id="mask0_21_2" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16">
|
||||
<path d="M16 0H0V16H16V0Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_21_2)">
|
||||
<path d="M7.99902 16.0002C12.4173 16.0002 15.999 12.4185 15.999 8.00024C15.999 3.58197 12.4173 0.000244141 7.99902 0.000244141C3.58075 0.000244141 -0.000976562 3.58197 -0.000976562 8.00024C-0.000976562 12.4185 3.58075 16.0002 7.99902 16.0002Z" fill="#7B1AF7"/>
|
||||
<path d="M4.52538 8.17306L9.85872 3.5773C10.0911 3.42847 10.3126 3.5773 10.1708 3.83261L8.46865 7.18015H11.5041C11.5041 7.18015 11.9864 7.18015 11.5041 7.57732L6.25588 12.2014C5.88708 12.5135 5.63177 12.3433 5.88708 11.861L7.53247 8.59859H4.52538C4.52538 8.59859 4.04311 8.59859 4.52538 8.17306Z" fill="white"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_21_2">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1,007 B |
|
|
@ -1,430 +1,414 @@
|
|||
<script>
|
||||
import 'websocket-polyfill';
|
||||
import Container from '../Container.svelte';
|
||||
import Widget from '../Widget.svelte';
|
||||
import { chatAdapter } from '../lib/store';
|
||||
// Imports: Library and Svelte Store
|
||||
import { onMount, onDestroy, afterUpdate } from "svelte";
|
||||
import { fade } from 'svelte/transition';
|
||||
import { browser } from '$app/environment';
|
||||
import { chatAdapter, url, relays } from '../lib/store';
|
||||
|
||||
let chatStarted;
|
||||
let chatType = 'GROUP';
|
||||
let websiteOwnerPubkey = 'fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52';
|
||||
let chatTags = [];
|
||||
let chatId = '9cef2eead5d91df42eba09be363f1272107e911685126ea5e261ac2d93299478';
|
||||
let chatReferenceTags = [];
|
||||
const relays = [
|
||||
'wss://relay.f7z.io',
|
||||
'wss://nos.lol',
|
||||
'wss://relay.nostr.band',
|
||||
];
|
||||
// Imports: Polyfills and Components
|
||||
import "websocket-polyfill";
|
||||
import KeyPrompt from "../KeyPrompt.svelte";
|
||||
import ConnectedWidget from "../ConnectedWidget.svelte";
|
||||
import MetaData from "../MetaData.svelte";
|
||||
import Brand from "../Brand.svelte";
|
||||
|
||||
$: currentTopic = [...chatTags, ...chatReferenceTags][0]
|
||||
// Local Variables
|
||||
let chatStarted;
|
||||
let chatType = "GROUP";
|
||||
let websiteOwnerPubkey = "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52";
|
||||
let chatTags = [];
|
||||
let chatReferenceTags = [];
|
||||
let searchElement;
|
||||
let inputUrl = "";
|
||||
let refreshKey = 1;
|
||||
let scrollPosition = 0;
|
||||
let searchIsFocused;
|
||||
let mainElement;
|
||||
|
||||
function currentTopic(topic) {
|
||||
return [...chatTags, ...chatReferenceTags].includes(topic)
|
||||
}
|
||||
// Reactive Statements
|
||||
//$: currentTopic = [...chatTags, ...chatReferenceTags][0];
|
||||
$: chatId = $url && stringToHex($url);
|
||||
$: chatStarted = !!$chatAdapter;
|
||||
$: if (chatStarted) {
|
||||
afterUpdate(() => {
|
||||
//if (searchElement) searchElement.focus();
|
||||
});
|
||||
refreshWidget();
|
||||
}
|
||||
|
||||
// Lifecycle Methods
|
||||
onMount(() => {
|
||||
if (browser) document.addEventListener('keydown', handleGlobalKeydown);
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (browser) document.removeEventListener('keydown', handleGlobalKeydown);
|
||||
});
|
||||
|
||||
// Helper Functions
|
||||
function stringToHex(str) {
|
||||
let result = "";
|
||||
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
// Convert each character to its char code
|
||||
const charCode = str.charCodeAt(i);
|
||||
|
||||
// Convert the char code to its hex representation
|
||||
result += charCode.toString(16).padStart(2, "0");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const isValidUrl = (str) => {
|
||||
const pattern = new RegExp(
|
||||
"^(https?:\\/\\/)?" + // protocol
|
||||
"((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name
|
||||
"((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address
|
||||
"(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path
|
||||
"(\\?[;&a-z\\d%_.~+=-]*)?" + // query string
|
||||
"(\\#[-a-z\\d_]*)?$",
|
||||
"i"
|
||||
); // fragment locator
|
||||
return !!pattern.test(str);
|
||||
};
|
||||
|
||||
const startsWithProtocol = (str) => {
|
||||
return str.startsWith("http://") || str.startsWith("https://");
|
||||
};
|
||||
|
||||
// Event Handlers and UI Logic
|
||||
function handleEnterKey(event) {
|
||||
if (event.keyCode === 13 && isValidUrl(inputUrl)) {
|
||||
showComments(inputUrl);
|
||||
}
|
||||
}
|
||||
|
||||
function handlePaste(event) {
|
||||
let thisEvent = event.clipboardData.getData('Text');
|
||||
showComments(thisEvent);
|
||||
}
|
||||
|
||||
function handleScroll(event) {
|
||||
scrollPosition = event.target.scrollTop;
|
||||
}
|
||||
|
||||
function handleGlobalKeydown(event) {
|
||||
// Check for Ctrl + /
|
||||
if (event.ctrlKey && event.key === '/') {
|
||||
|
||||
searchIsFocused = true;
|
||||
// Focus the input element
|
||||
searchElement.focus();
|
||||
// Clear the input
|
||||
searchElement.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function searchFocused() {
|
||||
searchIsFocused = true;
|
||||
}
|
||||
|
||||
function searchBlurred(e) {
|
||||
//console.log(e);
|
||||
searchIsFocused = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function updateFocusState(event) {
|
||||
setTimeout(() => {
|
||||
searchIsFocused = (event.type === "focus");
|
||||
//console.log("isFocused: ", searchIsFocused);
|
||||
}, 50);
|
||||
}
|
||||
|
||||
function showComments(str) {
|
||||
console.log(str);
|
||||
if (!str.startsWith("http://") && !str.startsWith("https://")) {
|
||||
$url = "https://" + str;
|
||||
} else {
|
||||
$url = str;
|
||||
}
|
||||
inputUrl = "";
|
||||
chatType = "GROUP";
|
||||
chatTags = [];
|
||||
chatReferenceTags = [$url];
|
||||
}
|
||||
|
||||
function refreshWidget() {
|
||||
refreshKey++;
|
||||
}
|
||||
|
||||
// function currentTopic(topic) {
|
||||
// return [...chatTags, ...chatReferenceTags].includes(topic);
|
||||
// }
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Nostri.chat / A NOSTR chat widget you control</title>
|
||||
<meta property="og:url" content="https://nostri.chat/">
|
||||
<meta name="description" content="A chat widget you own, powered by nostr" />
|
||||
<meta property="og:description" content="A chat widget you own, powered by nostr" />
|
||||
<title>DiSseNT - The web's comment section</title>
|
||||
<meta property="og:url" content="https://dsnt.chat/" />
|
||||
<meta name="description" content="The web's comment section." />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="The web's comment section."
|
||||
/>
|
||||
</svelte:head>
|
||||
|
||||
<section class="
|
||||
lg:min-h-screen
|
||||
text-white
|
||||
bg-gradient-to-b from-orange-500 to-orange-800
|
||||
">
|
||||
<div class="lg:min-h-screen mx-auto w-full lg:max-w-7xl py-5 xl:py-10
|
||||
flex flex-col md:flex-row
|
||||
gap-20 items-center px-4 lg:px-0
|
||||
relative
|
||||
">
|
||||
<div class="
|
||||
md:w-7/12 gap-10
|
||||
">
|
||||
<section id="hero" style="min-height: 50vh;">
|
||||
<h1 class="
|
||||
text-6xl
|
||||
font-black
|
||||
my-2
|
||||
">Nostri.chat</h1>
|
||||
<nav class="content">
|
||||
<div class="search-wrapper">
|
||||
<div class="search">
|
||||
<div class="search__bar {searchIsFocused ? 'search__bar--focused' : ''}">
|
||||
{#if !startsWithProtocol(inputUrl)}
|
||||
<span>https://</span>
|
||||
{/if}
|
||||
<input
|
||||
type="search"
|
||||
bind:value={inputUrl}
|
||||
bind:this={searchElement}
|
||||
placeholder="type or paste a URL"
|
||||
on:keyup={handleEnterKey}
|
||||
on:paste={handlePaste}
|
||||
on:focus={updateFocusState}
|
||||
on:blur={updateFocusState}
|
||||
/>
|
||||
</div>
|
||||
{#if isValidUrl(inputUrl)}
|
||||
<button transition:fade={{ delay: 250, duration: 300 }} class="search__btn" on:click={showComments(inputUrl)} title="Show Comments">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-message" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="var(--color)" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M8 9h8" />
|
||||
<path d="M8 13h6" />
|
||||
<path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12z" />
|
||||
</svg>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<h2 class="
|
||||
text-2xl lg:text-4xl
|
||||
text-bold
|
||||
">A chat widget for your site, powered by nostr</h2>
|
||||
<div class="page-outer">
|
||||
<main class="content--scrolling" on:scroll={handleScroll} bind:this={mainElement}>
|
||||
|
||||
<section class="nav">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href='/about'>About</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</section>
|
||||
|
||||
<p class="
|
||||
max-w-prose
|
||||
text-2xl
|
||||
text-gray-200
|
||||
tracking-wide
|
||||
leading-9
|
||||
my-5
|
||||
">
|
||||
Simple, interoperable
|
||||
communication with your visitors, in a way
|
||||
that gives you and them complete ownership
|
||||
over the data.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
{#if !chatStarted}
|
||||
<section class="brand">
|
||||
<Brand />
|
||||
</section>
|
||||
<section class="key-prompt">
|
||||
<KeyPrompt
|
||||
{websiteOwnerPubkey}
|
||||
chatConfiguration={{
|
||||
chatType,
|
||||
chatId,
|
||||
chatTags,
|
||||
chatReferenceTags,
|
||||
}}
|
||||
relays={$relays}
|
||||
/>
|
||||
</section>
|
||||
{/if}
|
||||
{#if chatStarted && $url}
|
||||
<section class="brand hide-on-mobile">
|
||||
<Brand />
|
||||
</section>
|
||||
|
||||
<div class="
|
||||
flex-row items-center justify-center
|
||||
min-h-screen
|
||||
hidden md:flex
|
||||
md:w-5/12
|
||||
">
|
||||
<div class="
|
||||
shadow-2xl
|
||||
bg-gray-100/90 backdrop-blur-md mb-5 w-96 max-w-screen-sm text-black rounded-3xl px-4 py-5 overflow-auto
|
||||
flex flex-col justify-end
|
||||
fixed
|
||||
" style="{chatStarted ? 'max-height: 80vh;' : 'padding: 4rem 2rem !important;'}">
|
||||
<Container chatConfiguration={{
|
||||
chatType,
|
||||
chatId,
|
||||
chatTags,
|
||||
chatReferenceTags,
|
||||
}} {relays} bind:chatStarted={chatStarted} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="shortcuts hide-on-mobile">
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Ctrl + /</strong> to change the URL
|
||||
</li>
|
||||
<li>
|
||||
<strong>Ctrl + space</strong> to type a comment
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="
|
||||
min-h-screen
|
||||
py-5
|
||||
lg:py-16
|
||||
" style="min-height: 50vh;">
|
||||
<div class="mx-auto w-full lg:max-w-7xl py-5 xl:py-10
|
||||
flex flex-col lg:flex-row
|
||||
gap-20 px-4 lg:px-0
|
||||
" style="min-height: 50vh;">
|
||||
<div class="md:w-7/12 flex flex-col gap-8">
|
||||
<div>
|
||||
<h1 class="text-6xl lg:text-7xl font-black">
|
||||
Innovative modes
|
||||
</h1>
|
||||
<section class="metadata">
|
||||
{#if $url}
|
||||
<MetaData url={$url} scrollPosition={scrollPosition}/>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<p class="
|
||||
text-2xl font-extralight
|
||||
">
|
||||
Because we use Nostr for communicating,
|
||||
<b>Nostri.chat</b>
|
||||
can use some new, creative approaches to using chat widget,
|
||||
depending on what you want to achieve.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
<h2 class="text-3xl text-orange-600 font-black">
|
||||
Classic mode
|
||||
<span class="text-2xl text-slate-500 font-extralight block">encrypted 1-on-1 chats</span>
|
||||
</h2>
|
||||
|
||||
<p class="
|
||||
text-xl text-gray-500 text-justify
|
||||
font-light
|
||||
leading-8
|
||||
">
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Sapiente quae eveniet placeat, obcaecati nesciunt nam iure. Culpa omnis hic eaque illum alias iure autem atque? Distinctio facilis recusandae omnis expedita.
|
||||
</p>
|
||||
|
||||
{#if $chatAdapter}
|
||||
{#if chatType === 'DM'}
|
||||
<button class="px-4 rounded border-2 border-orange-700 py-2 text-orange-700 text-lg w-full font-semibold">
|
||||
Active
|
||||
</button>
|
||||
{:else}
|
||||
<button class="px-4 rounded bg-orange-700 py-2 text-white text-lg w-full font-semibold" on:click={()=>{ chatType='DM'; chatTags=[]; chatReferenceTags=[] }}>
|
||||
Try it
|
||||
</button>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
<h2 class="text-3xl text-orange-600 font-black">
|
||||
<div class="flex flex-row gap-2">
|
||||
<span>🫂</span>
|
||||
<span class="flex flex-col">
|
||||
<span>Public chat groups</span>
|
||||
<span class="text-2xl text-slate-500 font-extralight block">public groups</span>
|
||||
</span>
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
<p class="
|
||||
text-xl text-gray-500 text-justify
|
||||
font-light
|
||||
leading-8
|
||||
">
|
||||
Embed NIP-28 public chat groups on your site.
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col lg:flex-row justify-between mt-10 gap-10 mb-6">
|
||||
<div class="flex flex-col lg:w-1/2 items-center gap-4 border p-4 shadow-md rounded-lg w-fit">
|
||||
<h3 class="
|
||||
text-black
|
||||
text-lg
|
||||
font-semibold
|
||||
">Group chat</h3>
|
||||
<span class="inline-flex rounded-md">
|
||||
<button type="button" class="
|
||||
inline-flex items-center rounded-l-md border px-4 py-2 text-md font-medium
|
||||
{chatType === 'GROUP' && chatId === '9cef2eead5d91df42eba09be363f1272107e911685126ea5e261ac2d93299478' ?
|
||||
'text-white bg-orange-700 border-orange-900'
|
||||
:
|
||||
'border-gray-300 bg-white text-gray-700'}
|
||||
:ring-indigo-500"
|
||||
on:click={()=>{ chatType='GROUP'; chatTags=[]; chatId='9cef2eead5d91df42eba09be363f1272107e911685126ea5e261ac2d93299478' }}
|
||||
>
|
||||
#Test
|
||||
</button>
|
||||
|
||||
<button type="button" class="
|
||||
inline-flex items-center rounded-r-md border px-4 py-2 text-md font-medium
|
||||
{chatType === 'GROUP' && chatId === 'a6f436a59fdb5e23c757b1e30478742996c54413df777843e0a731af56a96eea' ?
|
||||
'text-white bg-orange-700 border-orange-900'
|
||||
:
|
||||
'border-gray-300 bg-white text-gray-700'}
|
||||
:ring-indigo-500"
|
||||
on:click={()=>{ chatType='GROUP'; chatTags=[]; chatId='a6f436a59fdb5e23c757b1e30478742996c54413df777843e0a731af56a96eea' }}
|
||||
>
|
||||
#NDK
|
||||
</button>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="text-3xl text-orange-600 font-black">
|
||||
<div class="flex flex-row gap-2">
|
||||
<span>🔖</span>
|
||||
<span class="flex flex-col">
|
||||
<span>Tagged Global Chat</span>
|
||||
<span class="text-2xl text-slate-500 font-extralight block">public discussion/support</span>
|
||||
</span>
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
<p class="
|
||||
text-xl text-gray-500 text-justify
|
||||
font-light
|
||||
leading-8
|
||||
">
|
||||
Imagine having a global chat on your website about a certain topic.
|
||||
Anyone can participate, from your website or from any Nostr client.
|
||||
</p>
|
||||
|
||||
|
||||
<div class="flex flex-col lg:flex-row justify-between mt-10 gap-10">
|
||||
<div class="flex flex-col items-center gap-4 border p-4 shadow-md rounded-lg w-fit lg:w-full">
|
||||
<h3 class="
|
||||
text-black
|
||||
text-lg
|
||||
font-semibold
|
||||
">🔖 Topic-based chats</h3>
|
||||
|
||||
<span class="inline-flex rounded-md">
|
||||
<button type="button" class="
|
||||
inline-flex items-center rounded-l-md border px-4 py-2 text-md font-medium
|
||||
{currentTopic === 'nostrica' ?
|
||||
'text-white bg-orange-700 border-orange-900'
|
||||
:
|
||||
'border-gray-300 bg-white text-gray-700'}
|
||||
" on:click={()=>{ chatType='GLOBAL'; chatTags=['nostrica']; chatReferenceTags=[] }}>
|
||||
#nostrica
|
||||
</button>
|
||||
|
||||
<button type="button" class="
|
||||
inline-flex items-center rounded-r-md border px-4 py-2 text-md font-medium
|
||||
{currentTopic === 'bitcoin' ?
|
||||
'text-white bg-orange-700 border-orange-900'
|
||||
:
|
||||
'border-gray-300 bg-white text-gray-700'}
|
||||
" on:click={()=>{ chatType='GLOBAL'; chatTags=['bitcoin']; chatReferenceTags=[] }}>
|
||||
#bitcoin
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center gap-4 border p-4 shadow-md rounded-lg w-fit lg:w-full">
|
||||
<h3 class="
|
||||
text-black
|
||||
text-lg
|
||||
font-semibold
|
||||
">🌎 Website-based chats</h3>
|
||||
<span class="inline-flex rounded-md">
|
||||
<button type="button" class="
|
||||
inline-flex items-center rounded-l-md border px-4 py-2 text-md font-medium
|
||||
{currentTopic === 'https://nostri.chat' ?
|
||||
'text-white bg-orange-700 border-orange-900'
|
||||
:
|
||||
'border-gray-300 bg-white text-gray-700'}
|
||||
:ring-indigo-500"
|
||||
on:click={()=>{ chatType='GLOBAL'; chatTags=[]; chatReferenceTags=['https://nostri.chat'] }}
|
||||
>
|
||||
<span class="opacity-50 font-normal">https://</span>nostri.chat
|
||||
</button>
|
||||
<button type="button" class="
|
||||
inline-flex items-center rounded-r-md border px-4 py-2 text-md font-medium
|
||||
{currentTopic === 'https://psbt.io' ?
|
||||
'text-white bg-orange-700 border-orange-900'
|
||||
:
|
||||
'border-gray-300 bg-white text-gray-700'}
|
||||
:ring-indigo-500"
|
||||
on:click={()=>{ chatType='GLOBAL'; chatTags=[]; chatReferenceTags=['https://psbt.io'] }}
|
||||
>
|
||||
<span class="opacity-50 font-normal">https://</span>psbt.io
|
||||
</button>
|
||||
<button type="button" class="
|
||||
inline-flex items-center rounded-r-md border px-4 py-2 text-md font-medium
|
||||
{currentTopic === 'https://a.com' ?
|
||||
'text-white bg-orange-700 border-orange-900'
|
||||
:
|
||||
'border-gray-300 bg-white text-gray-700'}
|
||||
:ring-indigo-500"
|
||||
on:click={()=>{ chatType='GLOBAL'; chatTags=[]; chatReferenceTags=['https://a.com'] }}
|
||||
>
|
||||
<span class="opacity-50 font-normal">a 🌎️</span>
|
||||
</button>
|
||||
<button type="button" class="
|
||||
inline-flex items-center rounded-r-md border px-4 py-2 text-md font-medium
|
||||
{currentTopic === 'https://a.com' ?
|
||||
'text-white bg-orange-700 border-orange-900'
|
||||
:
|
||||
'border-gray-300 bg-white text-gray-700'}
|
||||
:ring-indigo-500"
|
||||
on:click={()=>{ chatType='GROUP'; chatTags=[]; chatReferenceTags=['https://a.com'] }}
|
||||
>
|
||||
<span class="opacity-50 font-normal">a 👥</span>
|
||||
</button>
|
||||
<button type="button" class="
|
||||
inline-flex items-center rounded-r-md border px-4 py-2 text-md font-medium
|
||||
{currentTopic === 'https://b.com' ?
|
||||
'text-white bg-orange-700 border-orange-900'
|
||||
:
|
||||
'border-gray-300 bg-white text-gray-700'}
|
||||
:ring-indigo-500"
|
||||
on:click={()=>{ chatType='GLOBAL'; chatTags=[]; chatReferenceTags=['https://b.com'] }}
|
||||
>
|
||||
<span class="opacity-50 font-normal">b 🌎️</span>
|
||||
</button>
|
||||
<button type="button" class="
|
||||
inline-flex items-center rounded-r-md border px-4 py-2 text-md font-medium
|
||||
{currentTopic === 'https://b.com' ?
|
||||
'text-white bg-orange-700 border-orange-900'
|
||||
:
|
||||
'border-gray-300 bg-white text-gray-700'}
|
||||
:ring-indigo-500"
|
||||
on:click={()=>{ chatType='GROUP'; chatTags=[]; chatReferenceTags=['https://b.com'] }}
|
||||
>
|
||||
<span class="opacity-50 font-normal">b 👥</span>
|
||||
</button>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="
|
||||
min-h-screen
|
||||
py-5
|
||||
lg:py-16
|
||||
bg-slate-100
|
||||
" style="min-height: 50vh;">
|
||||
<div class="mx-auto w-full lg:max-w-7xl py-5 xl:py-10
|
||||
flex flex-col lg:flex-row
|
||||
gap-20 items-center px-4 lg:px-0
|
||||
" style="min-height: 50vh;">
|
||||
<div class="md:w-4/5 lg:w-3/5 grid grid-cols-1 gap-8">
|
||||
|
||||
<div>
|
||||
<h1 class="text-7xl font-black">
|
||||
Easy-peasy setup
|
||||
</h1>
|
||||
|
||||
<p class="
|
||||
text-2xl font-extralight
|
||||
">
|
||||
Just drop this snippet on your website and you're good to go.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="text-xl font-semibold">Public group chat (GROUP)</div>
|
||||
<pre class ="
|
||||
p-4
|
||||
bg-white
|
||||
overflow-auto
|
||||
">
|
||||
<script
|
||||
src="https://nostri.chat/public/bundle.js"
|
||||
<b>data-chat-type</b>="<span class="text-orange-500">GROUP</span>"
|
||||
<b>data-chat-id</b>="<span class="text-orange-500"><GROUP_ID_IN_HEX_FORMAT></span>"
|
||||
<b>data-relays</b>="<span class="text-orange-500">wss://relay.f7z.io,wss://nos.lol,wss://relay.nostr.band</span>"
|
||||
></script>
|
||||
<link rel="stylesheet" href="https://nostri.chat/public/bundle.css"></pre>
|
||||
|
||||
<div class="text-xl font-semibold">Public global notes (kind-1 short notes)</div>
|
||||
<pre class ="
|
||||
p-4
|
||||
bg-white
|
||||
overflow-auto
|
||||
">
|
||||
<script
|
||||
src="https://nostri.chat/public/bundle.js"
|
||||
<b>data-chat-type</b>="<span class="text-orange-500">GLOBAL</span>"
|
||||
<b>data-chat-tags</b>="<span class="text-orange-500">bitcoin</span>"
|
||||
<b>data-relays</b>="<span class="text-orange-500">wss://relay.f7z.io,wss://nos.lol,wss://relay.nostr.band</span>"
|
||||
></script>
|
||||
<link rel="stylesheet" href="https://nostri.chat/public/bundle.css"></pre>
|
||||
|
||||
<div class="text-xl font-semibold">Encrypted DMs</div>
|
||||
<pre class ="
|
||||
p-4
|
||||
bg-white
|
||||
overflow-auto
|
||||
">
|
||||
<script
|
||||
src="https://nostri.chat/public/bundle.js"
|
||||
<b>data-chat-type</b>="<span class="text-orange-500">DM</span>"
|
||||
<b>data-website-owner-pubkey</b>="<span class="text-orange-500">YOUR_PUBKEY_IN_HEX_FORMAT</span>"
|
||||
<b>data-relays</b>="<span class="text-orange-500">wss://relay.f7z.io,wss://nos.lol,wss://relay.nostr.band</span>"
|
||||
></script>
|
||||
<link rel="stylesheet" href="https://nostri.chat/public/bundle.css"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="md:hidden">
|
||||
<Widget chatConfiguration={{
|
||||
chatTags,
|
||||
chatReferenceTags,
|
||||
}} {websiteOwnerPubkey} {chatType} {chatId} {relays} bind:chatStarted={chatStarted} />
|
||||
{#each [refreshKey] as key (key)}
|
||||
<ConnectedWidget
|
||||
{websiteOwnerPubkey}
|
||||
chatConfiguration={{
|
||||
chatType,
|
||||
chatId,
|
||||
chatTags,
|
||||
chatReferenceTags,
|
||||
}}
|
||||
relays={$relays}
|
||||
mainElement={mainElement}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer class="py-6 bg-orange-900 font-mono text-white text-center mt-12 px-10">
|
||||
<div class="flex justify-center flex-row">
|
||||
<div class="text-sm">
|
||||
NOSTRI.CHAT
|
||||
by
|
||||
<a class="text-purple-50 hover:text-orange-400" href="https://pablof7z.com">
|
||||
@pablof7z
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
/* div { border: solid red 1px; } */
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
</style>
|
||||
|
||||
|
||||
@media only screen and (max-width: 598px) {
|
||||
main {
|
||||
width: fit-content;
|
||||
border: none;
|
||||
}
|
||||
nav {
|
||||
width: 100% !important;
|
||||
}
|
||||
section {
|
||||
position: relative !important;
|
||||
top: auto !important;
|
||||
left: auto !important;
|
||||
right: auto !important;
|
||||
}
|
||||
.search__bar{
|
||||
font-size: 5vmin !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1140px) {
|
||||
section {
|
||||
position: relative !important;
|
||||
top: auto !important;
|
||||
left: auto !important;
|
||||
right: auto !important;
|
||||
}
|
||||
|
||||
section.brand {
|
||||
order: -1;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
section.shortcuts {
|
||||
order: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
nav{
|
||||
width: 566px;
|
||||
align-self: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
|
||||
}
|
||||
|
||||
section.brand {
|
||||
position: fixed;
|
||||
bottom: 1rem;
|
||||
left: 2rem;
|
||||
}
|
||||
|
||||
section.metadata {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
position: sticky;
|
||||
top:0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
section.nav{
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
left: 1rem;
|
||||
}
|
||||
|
||||
section.shortcuts {
|
||||
position: fixed;
|
||||
top:6rem;
|
||||
left: 2rem;
|
||||
}
|
||||
|
||||
section.shortcuts ul {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
section.shortcuts li {
|
||||
list-style-type: none;
|
||||
color: var(--c-lines);
|
||||
}
|
||||
|
||||
.search-wrapper {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.search {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.search button {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
text-transform: uppercase;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.search__btn svg {
|
||||
width: 2rem;
|
||||
--color: var(--c-bright);
|
||||
}
|
||||
|
||||
.search__btn {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.search__bar {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--c-lines);
|
||||
font-size: 2rem;
|
||||
align-items: center;
|
||||
padding: .5rem;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.search__bar span{
|
||||
color: rgba(255, 255, 255, 0.25);
|
||||
font-family: Work Sans, sans-serif;
|
||||
}
|
||||
|
||||
.search__bar input {
|
||||
color: rgba(255, 255, 255);
|
||||
transition: all .3s ease;
|
||||
background-color: transparent;
|
||||
font-size: inherit;
|
||||
border: none;
|
||||
font-weight: inherit;
|
||||
font-family: Work Sans, sans-serif;
|
||||
width: calc(100% - 10rem);
|
||||
}
|
||||
|
||||
.search__bar input::placeholder {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.search__bar input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.search__bar.search__bar--focused {
|
||||
border-bottom: 1px solid var(--c-bright);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
|||
60
src/routes/about/+page.svelte
Normal file
60
src/routes/about/+page.svelte
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<script>
|
||||
import Readme from '$lib/README.md';
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>About DiSseNT</title>
|
||||
<meta property="og:url" content="https://dsnt.chat/about" />
|
||||
<meta name="description" content="The web's comment section." />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="The web's comment section."
|
||||
/>
|
||||
</svelte:head>
|
||||
|
||||
<section class="nav">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href='/'>👈️ back</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</section>
|
||||
|
||||
<div class="page">
|
||||
<div class="sheet content--scrolling">
|
||||
<Readme />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
section.nav{
|
||||
font-family: Work Sans, sans-serif;
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
left: 1rem;
|
||||
}
|
||||
|
||||
.page {
|
||||
font-family: Work Sans, sans-serif;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.sheet{
|
||||
font-family: Work Sans, sans-serif;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
width: 566px;
|
||||
overflow-y: auto;
|
||||
background-color: rgb(56, 17, 0, 0.2);
|
||||
padding: 1rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<title>Demo page</title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<link rel="stylesheet" href="public/bundle.css">
|
||||
<body>
|
||||
|
||||
<h1>Svelte embedding demo</h1>
|
||||
|
||||
<p>Below,we have inserted a <code>script</code> tag that should
|
||||
renter a Svelte component upon loading this page.</p>
|
||||
|
||||
<script
|
||||
src="/public/bundle.js"
|
||||
data-website-owner-pubkey="fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52"
|
||||
data-chat-type="GROUP"
|
||||
data-chat-id="a6f436a59fdb5e23c757b1e30478742996c54413df777843e0a731af56a96eea"
|
||||
data-relays="wss://relay.f7z.io,wss://nos.lol,wss://relay.nostr.info,wss://nostr-pub.wellorder.net,wss://relay.current.fyi,wss://relay.nostr.band"
|
||||
></script>
|
||||
|
||||
<p>This text will come after the embedded content.</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,254 +0,0 @@
|
|||
<script>
|
||||
|
||||
import { afterUpdate } from 'svelte';
|
||||
|
||||
import 'websocket-polyfill';
|
||||
import Container from '../../Container.svelte';
|
||||
import KeyPrompt from '../../KeyPrompt.svelte';
|
||||
import ConnectedWidget from '../../ConnectedWidget.svelte';
|
||||
import Widget from '../../Widget.svelte';
|
||||
import { chatAdapter } from '../../lib/store';
|
||||
import Relays from '../../Relays.svelte';
|
||||
|
||||
let chatStarted;
|
||||
let chatType = 'GROUP';
|
||||
let websiteOwnerPubkey = 'fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52';
|
||||
let chatTags = [];
|
||||
//let chatId = '9cef2eead5d91df42eba09be363f1272107e911685126ea5e261ac2d93299478';
|
||||
let chatReferenceTags = [];
|
||||
let relays = [
|
||||
'wss://relay.f7z.io',
|
||||
// 'wss://nos.lol',
|
||||
// 'wss://relay.nostr.band',
|
||||
// 'wss://nostr-pub.wellorder.net',
|
||||
// 'wss://relay.damus.io',
|
||||
// 'wss://relay.f7z.io'
|
||||
];
|
||||
|
||||
$: currentTopic = [...chatTags, ...chatReferenceTags][0]
|
||||
|
||||
$: chatId = url && stringToHex(url)
|
||||
|
||||
function handleUpdate(event) {
|
||||
relays = event.detail;
|
||||
refreshWidget();
|
||||
}
|
||||
|
||||
function stringToHex(str) {
|
||||
let result = '';
|
||||
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
// Convert each character to its char code
|
||||
const charCode = str.charCodeAt(i);
|
||||
|
||||
// Convert the char code to its hex representation
|
||||
result += charCode.toString(16).padStart(2, '0');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
$: chatStarted = !!$chatAdapter
|
||||
|
||||
function currentTopic(topic) {
|
||||
return [...chatTags, ...chatReferenceTags].includes(topic)
|
||||
}
|
||||
|
||||
$: if (chatStarted) {
|
||||
afterUpdate(() => {
|
||||
if (searchElement) searchElement.focus();
|
||||
});
|
||||
}
|
||||
|
||||
let searchElement;
|
||||
|
||||
let inputUrl = "";
|
||||
let url = null;
|
||||
|
||||
const isValidUrl = (str) => {
|
||||
const pattern = new RegExp(
|
||||
"^(https?:\\/\\/)?" + // protocol
|
||||
"((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name
|
||||
"((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address
|
||||
"(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path
|
||||
"(\\?[;&a-z\\d%_.~+=-]*)?" + // query string
|
||||
"(\\#[-a-z\\d_]*)?$",
|
||||
"i"
|
||||
); // fragment locator
|
||||
return !!pattern.test(str);
|
||||
};
|
||||
|
||||
const startsWithProtocol = (str) => {
|
||||
return (str.startsWith("http://") || str.startsWith("https://"));
|
||||
};
|
||||
|
||||
function handleEnterKey(event) {
|
||||
if (event.keyCode === 13 && isValidUrl(inputUrl)) {
|
||||
showComments(inputUrl);
|
||||
}
|
||||
}
|
||||
|
||||
function showComments(str) {
|
||||
if (!str.startsWith("http://") && !str.startsWith("https://")) {
|
||||
url = "https://" + str;
|
||||
} else {
|
||||
url = str;
|
||||
}
|
||||
inputUrl = "";
|
||||
chatType='GROUP';
|
||||
chatTags=[];
|
||||
chatReferenceTags=[url];
|
||||
}
|
||||
|
||||
function handlePaste(event) {
|
||||
showComments(event);
|
||||
inputUrl = "";
|
||||
}
|
||||
|
||||
let refreshKey = 1;
|
||||
|
||||
function refreshWidget() {
|
||||
refreshKey++;
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Nostri.chat / A NOSTR chat widget you control</title>
|
||||
<meta property="og:url" content="https://nostri.chat/">
|
||||
<meta name="description" content="A chat widget you own, powered by nostr" />
|
||||
<meta property="og:description" content="A chat widget you own, powered by nostr" />
|
||||
</svelte:head>
|
||||
|
||||
<main>
|
||||
|
||||
<section id="hero">
|
||||
<h1 class="
|
||||
text-6xl
|
||||
font-black
|
||||
my-2
|
||||
">Dissent</h1>
|
||||
|
||||
<h2 class="
|
||||
text-2xl lg:text-4xl
|
||||
text-bold
|
||||
">The comments section of the internet.</h2>
|
||||
|
||||
<pre>
|
||||
{chatType}
|
||||
{chatId}
|
||||
{chatTags}
|
||||
{chatReferenceTags}
|
||||
</pre>
|
||||
</section>
|
||||
|
||||
{#if !chatStarted}
|
||||
<section>
|
||||
<KeyPrompt {websiteOwnerPubkey} chatConfiguration={{
|
||||
chatType,
|
||||
chatId,
|
||||
chatTags,
|
||||
chatReferenceTags,
|
||||
}} {relays} />
|
||||
</section>
|
||||
{:else}
|
||||
|
||||
<section>
|
||||
<div class="max-w-prose text-2xl text-gray-200 tracking-wide leading-9">
|
||||
<div class="search-container p-2 w-full resize-none rounded-xl text-gray-600 border back bg-white">
|
||||
{#if !startsWithProtocol(inputUrl)}
|
||||
<span>https://</span>
|
||||
{/if}
|
||||
<input
|
||||
type="text"
|
||||
bind:value={inputUrl}
|
||||
bind:this="{searchElement}"
|
||||
placeholder="paste or enter a URL"
|
||||
on:keyup={handleEnterKey}
|
||||
on:paste={handlePaste(inputUrl)}
|
||||
/>
|
||||
</div>
|
||||
{#if isValidUrl(inputUrl)}
|
||||
<div class="p-2">
|
||||
<button class="bg-purple-900 hover:bg-purple-700 w-full p-4 rounded-xl text-center font-regular text-gray-200" on:click={showComments(inputUrl)}>Show Comments</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="justify-center">
|
||||
{#if url}
|
||||
<h3 class="text-2xl lg:text-3xl text-bold url">{url}</h3>
|
||||
<div class="shadow-2xl bg-gray-100/90 backdrop-blur-md mb-5 w-96 max-w-screen-sm text-black rounded-3xl px-4 py-5 overflow-auto flex flex-col justify-end ">
|
||||
{#each [refreshKey] as key (key)}
|
||||
<ConnectedWidget {websiteOwnerPubkey} chatConfiguration={{
|
||||
chatType,
|
||||
chatId,
|
||||
chatTags,
|
||||
chatReferenceTags,
|
||||
}} {relays} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<Relays bind:relays on:update={handleUpdate} />
|
||||
|
||||
<div
|
||||
class="flex flex-col justify-start items-start gap-[25px] px-[23px] py-[17px] bg-gradient-to-br from-[#f10e0e] to-[#0c84e0]"
|
||||
>
|
||||
<div class="flex flex-col justify-start items-start flex-grow-0 flex-shrink-0 relative">
|
||||
<p class="flex-grow-0 flex-shrink-0 text-4xl font-bold text-left text-[#eee]">Heading</p>
|
||||
<p class="flex-grow-0 flex-shrink-0 text-2xl text-left text-[#eee]">sub heading</p>
|
||||
</div>
|
||||
<div class="flex flex-col justify-start items-start flex-grow-0 flex-shrink-0 relative gap-2">
|
||||
<p class="flex-grow-0 flex-shrink-0 text-xs text-left text-white">Label</p>
|
||||
<div class="flex justify-start items-center flex-grow-0 flex-shrink-0 gap-4">
|
||||
<div
|
||||
class="flex justify-start items-center flex-grow-0 flex-shrink-0 w-[203px] relative overflow-hidden gap-2.5 p-2.5 rounded-[20px] bg-white"
|
||||
>
|
||||
<p class="flex-grow-0 flex-shrink-0 text-xs text-left text-black">search by typing</p>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col justify-center items-center flex-grow-0 flex-shrink-0 w-[42px] h-[42px] overflow-hidden gap-2.5 px-px py-[3px] rounded-[30px] bg-[#03d403]"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
/* div { border: solid red 1px; } */
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
main{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
section{
|
||||
width: 90vw;
|
||||
max-width: 600px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.search-container input {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
h3.url{
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,235 +0,0 @@
|
|||
<script>
|
||||
import { onMount, afterUpdate } from "svelte";
|
||||
|
||||
import "websocket-polyfill";
|
||||
import KeyPrompt from "../../KeyPrompt.svelte";
|
||||
import ConnectedWidget from "../../ConnectedWidget2.svelte";
|
||||
//import Relays from "../../Relays.svelte";
|
||||
import MetaData from "../../MetaData.svelte";
|
||||
import Brand from "../../Brand.svelte";
|
||||
|
||||
import { chatAdapter, url, relays } from '../../lib/store';
|
||||
|
||||
let chatStarted;
|
||||
let chatType = "GROUP";
|
||||
let websiteOwnerPubkey =
|
||||
"fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52";
|
||||
let chatTags = [];
|
||||
//let chatId = '9cef2eead5d91df42eba09be363f1272107e911685126ea5e261ac2d93299478';
|
||||
let chatReferenceTags = [];
|
||||
|
||||
$: currentTopic = [...chatTags, ...chatReferenceTags][0];
|
||||
|
||||
$: chatId = $url && stringToHex($url);
|
||||
|
||||
function handleUpdate(event) {
|
||||
relays = event.detail;
|
||||
refreshWidget();
|
||||
}
|
||||
|
||||
function stringToHex(str) {
|
||||
let result = "";
|
||||
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
// Convert each character to its char code
|
||||
const charCode = str.charCodeAt(i);
|
||||
|
||||
// Convert the char code to its hex representation
|
||||
result += charCode.toString(16).padStart(2, "0");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
$: chatStarted = !!$chatAdapter;
|
||||
|
||||
function currentTopic(topic) {
|
||||
return [...chatTags, ...chatReferenceTags].includes(topic);
|
||||
}
|
||||
|
||||
$: if (chatStarted) {
|
||||
afterUpdate(() => {
|
||||
if (searchElement) searchElement.focus();
|
||||
});
|
||||
refreshWidget();
|
||||
}
|
||||
|
||||
let searchElement;
|
||||
|
||||
let inputUrl = "";
|
||||
|
||||
const isValidUrl = (str) => {
|
||||
const pattern = new RegExp(
|
||||
"^(https?:\\/\\/)?" + // protocol
|
||||
"((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name
|
||||
"((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address
|
||||
"(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path
|
||||
"(\\?[;&a-z\\d%_.~+=-]*)?" + // query string
|
||||
"(\\#[-a-z\\d_]*)?$",
|
||||
"i"
|
||||
); // fragment locator
|
||||
return !!pattern.test(str);
|
||||
};
|
||||
|
||||
const startsWithProtocol = (str) => {
|
||||
return str.startsWith("http://") || str.startsWith("https://");
|
||||
};
|
||||
|
||||
function handleEnterKey(event) {
|
||||
if (event.keyCode === 13 && isValidUrl(inputUrl)) {
|
||||
showComments(inputUrl);
|
||||
}
|
||||
}
|
||||
|
||||
function showComments(str) {
|
||||
if (!str.startsWith("http://") && !str.startsWith("https://")) {
|
||||
$url = "https://" + str;
|
||||
} else {
|
||||
$url = str;
|
||||
}
|
||||
inputUrl = "";
|
||||
chatType = "GROUP";
|
||||
chatTags = [];
|
||||
chatReferenceTags = [$url];
|
||||
}
|
||||
|
||||
function handlePaste(event) {
|
||||
showComments(event);
|
||||
inputUrl = "";
|
||||
}
|
||||
|
||||
let refreshKey = 1;
|
||||
|
||||
function refreshWidget() {
|
||||
refreshKey++;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>DiSseNT - The web's comment section</title>
|
||||
<meta property="og:url" content="https://dsnt.chat/" />
|
||||
<meta name="description" content="The web's comment section." />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="The web's comment section."
|
||||
/>
|
||||
</svelte:head>
|
||||
|
||||
<main>
|
||||
<div class="panel">
|
||||
<section class="toolbar">
|
||||
{#if $url}
|
||||
<MetaData url={$url} />
|
||||
{/if}
|
||||
</section>
|
||||
<section class="content">
|
||||
<div class="search-wrapper">
|
||||
<div class="search">
|
||||
<div class="search__bar">
|
||||
{#if !startsWithProtocol(inputUrl)}
|
||||
<span>https://</span>
|
||||
{/if}
|
||||
<input
|
||||
type="text"
|
||||
bind:value={inputUrl}
|
||||
bind:this={searchElement}
|
||||
placeholder="paste or enter a URL"
|
||||
on:keyup={handleEnterKey}
|
||||
on:paste={handlePaste(inputUrl)}
|
||||
/>
|
||||
</div>
|
||||
{#if isValidUrl(inputUrl)}
|
||||
<button class="search__btn" on:click={showComments(inputUrl)}>show comments</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="brand">
|
||||
<Brand />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT -->
|
||||
<div class="sidebar">
|
||||
{#if !chatStarted}
|
||||
<section>
|
||||
<KeyPrompt
|
||||
{websiteOwnerPubkey}
|
||||
chatConfiguration={{
|
||||
chatType,
|
||||
chatId,
|
||||
chatTags,
|
||||
chatReferenceTags,
|
||||
}}
|
||||
relays={$relays}
|
||||
/>
|
||||
</section>
|
||||
{/if}
|
||||
{#if chatStarted && $url}
|
||||
{#each [refreshKey] as key (key)}
|
||||
<ConnectedWidget
|
||||
{websiteOwnerPubkey}
|
||||
chatConfiguration={{
|
||||
chatType,
|
||||
chatId,
|
||||
chatTags,
|
||||
chatReferenceTags,
|
||||
}}
|
||||
relays={$relays}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.brand {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.search-wrapper {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.search__bar {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--c-lines);
|
||||
font-size: 2rem;
|
||||
align-items: center;
|
||||
padding: .5rem;
|
||||
}
|
||||
|
||||
.search__bar span{
|
||||
color: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
.search__bar input {
|
||||
color: rgba(255, 255, 255);
|
||||
transition: all .3s ease;
|
||||
background-color: transparent;
|
||||
flex-grow: 1;
|
||||
font-size: inherit;
|
||||
border: none;
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
.search__bar input::placeholder {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.search__bar input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
<div
|
||||
class="w-[1440px] h-[1024px] relative overflow-hidden"
|
||||
style="background: linear-gradient(to bottom, #381100 0%, #a13000 99.98%, #b93700 99.99%, #ff4c00 100%);"
|
||||
>
|
||||
<div class="w-[566px] h-[1024px] absolute left-[874px] top-0 overflow-hidden">
|
||||
<div
|
||||
class="flex flex-col justify-start items-start w-[566px] h-[822px] absolute left-0 top-[202px] overflow-hidden bg-[#381100]/20 border-t-0 border-r-0 border-b-0 border-l border-white/50"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col justify-start items-start self-stretch flex-grow-0 flex-shrink-0 overflow-hidden gap-2.5 p-[30px] border-t-0 border-r-0 border-b border-l-0 border-white/50"
|
||||
>
|
||||
<div
|
||||
class="flex justify-start items-start self-stretch flex-grow-0 flex-shrink-0 gap-[26px]"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col justify-start items-center flex-grow-0 flex-shrink-0 relative gap-[9px]"
|
||||
>
|
||||
<img
|
||||
class="flex-grow-0 flex-shrink-0 w-[70px] h-[70px] rounded-[118.52px]"
|
||||
src=""
|
||||
/><svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="flex-grow-0 flex-shrink-0"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
>
|
||||
<circle cx="20" cy="20" r="19.5" stroke="#FF8A00"></circle>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex flex-col justify-start items-start flex-grow relative gap-2.5">
|
||||
<div
|
||||
class="flex justify-between items-start self-stretch flex-grow-0 flex-shrink-0 relative pr-[15px]"
|
||||
>
|
||||
<p class="flex-grow-0 flex-shrink-0 text-[15px] font-bold text-left text-white">
|
||||
Name
|
||||
</p>
|
||||
<p class="flex-grow-0 flex-shrink-0 text-[15px] font-bold text-left text-white">
|
||||
17h
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
class="self-stretch flex-grow-0 flex-shrink-0 w-[410px] text-[15px] text-left text-white"
|
||||
>
|
||||
Hahaha. There are so many people on the “system” and do side work for cash and do very
|
||||
very well. Tons of people selling their food cards for cash. Single moms with multiple
|
||||
baby daddies and a cash side business while also on the system. And so much more. It’s
|
||||
so wide spread.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="w-[566px] h-[202px] absolute left-0 top-0 overflow-hidden bg-[#381100]/20 border-t-0 border-r-0 border-b border-l-0 border-white/50"
|
||||
>
|
||||
<p class="absolute left-3.5 top-[122px] text-[55px] font-thin text-left text-white">35</p>
|
||||
<div class="flex justify-start items-start absolute left-[474px] top-[164px] gap-[7px]">
|
||||
<div class="flex-grow-0 flex-shrink-0 w-[13px] h-[13px] rounded-[13px] bg-[#d9d9d9]"></div>
|
||||
<div class="flex-grow-0 flex-shrink-0 w-[13px] h-[13px] rounded-[13px] bg-[#d9d9d9]"></div>
|
||||
<div class="flex-grow-0 flex-shrink-0 w-[13px] h-[13px] rounded-[13px] bg-[#d9d9d9]"></div>
|
||||
<div class="flex-grow-0 flex-shrink-0 w-[13px] h-[13px] rounded-[13px] bg-[#d9d9d9]"></div>
|
||||
</div>
|
||||
<div class="flex justify-end items-center w-52 absolute left-[339px] top-[17px] gap-2.5">
|
||||
<p class="flex-grow-0 flex-shrink-0 text-[15px] font-bold text-left text-white">Name</p>
|
||||
<img
|
||||
class="flex-grow-0 flex-shrink-0 w-[30px] h-[30px] rounded-[118.52px]"
|
||||
src=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col justify-start items-start w-[874px] h-[1024px] absolute left-0 top-0 overflow-hidden border-t-0 border-r-0 border-b-0 border-l border-white/50"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col justify-start items-start flex-grow-0 flex-shrink-0 h-[202px] w-[874px] relative overflow-hidden gap-2.5 p-4 bg-[#381100]/20 border-t-0 border-r-0 border-b border-l-0 border-white/50"
|
||||
>
|
||||
<p class="flex-grow-0 flex-shrink-0 text-3xl text-left text-white">
|
||||
FileGator - Simple Self-Hosted File Server
|
||||
</p>
|
||||
<p class="flex-grow-0 flex-shrink-0 text-[15px] text-left text-white/50">
|
||||
https://noted.lol/filegator/
|
||||
</p>
|
||||
<p class="flex-grow-0 flex-shrink-0 w-[510px] text-[15px] text-left text-white">
|
||||
FileGator is your personal file butler, serving up a self-hosted buffet of file management
|
||||
goodness where you're the chef, and your files are the main course – all with the added
|
||||
bonus of keeping your data more locked down than a treasure chest in a dragon's den.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex-grow-0 flex-shrink-0 w-[874px] h-[822px] relative">
|
||||
<p class="absolute left-9 top-[657px] text-[81.52728271484375px] text-left">
|
||||
<span class="text-[81.52728271484375px] text-left text-white">D</span
|
||||
><span class="text-[81.52728271484375px] text-left text-white/50">i</span
|
||||
><span class="text-[81.52728271484375px] text-left text-white">s</span
|
||||
><span class="text-[81.52728271484375px] text-left text-white/50">se</span
|
||||
><span class="text-[81.52728271484375px] text-left text-white">nt</span>
|
||||
</p>
|
||||
<p class="absolute left-9 top-[766px] text-[18.14524269104004px] text-left text-white">
|
||||
The web’s comment section.
|
||||
</p>
|
||||
<div
|
||||
class="flex justify-start items-center w-[659px] h-[86px] absolute left-[55px] top-[211px] gap-2.5 px-5 py-[21px] border-t-0 border-r-0 border-b border-l-0 border-white/50"
|
||||
>
|
||||
<p class="flex-grow-0 flex-shrink-0 text-4xl font-extralight text-left text-white/50">
|
||||
type or paste a web address
|
||||
</p>
|
||||
</div>
|
||||
<div class="w-[86px] h-[86px] absolute left-[713px] top-[210px] bg-black/20"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* div { border: solid skyblue 1px; } */
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
</style>
|
||||
|
|
@ -6,7 +6,8 @@ const config = {
|
|||
kit: {
|
||||
adapter: adapter({ out: 'build' }),
|
||||
},
|
||||
preprocess: vitePreprocess()
|
||||
preprocess: vitePreprocess(),
|
||||
extensions: [".svelte", ".md"],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,19 @@
|
|||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
// import { sveltekit } from '@sveltejs/kit/vite';
|
||||
|
||||
const config = {
|
||||
plugins: [sveltekit()]
|
||||
};
|
||||
// const config = {
|
||||
// plugins: [sveltekit()]
|
||||
// };
|
||||
|
||||
export default config;
|
||||
// export default config;
|
||||
|
||||
// vite.config.js
|
||||
import { defineConfig } from "vite";
|
||||
import { sveltekit } from "@sveltejs/kit/vite"
|
||||
import svelteMd from "vite-plugin-svelte-md";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
svelteMd(), // <--
|
||||
sveltekit(),
|
||||
],
|
||||
});
|
||||
Loading…
Reference in a new issue