1st commit of dsnt changes
This commit is contained in:
parent
053a1c8ed2
commit
ad30d14a66
21 changed files with 2902 additions and 72 deletions
19
package-lock.json
generated
19
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "nostri.chat",
|
||||
"version": "0.1.5",
|
||||
"name": "dnst",
|
||||
"version": "0.3.14159",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "nostri.chat",
|
||||
"version": "0.1.5",
|
||||
"name": "dsnt",
|
||||
"version": "0.3.14159",
|
||||
"dependencies": {
|
||||
"@nostr-connect/connect": "^0.2.3",
|
||||
"@nostr-dev-kit/ndk": "^0.3.32",
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
"@sveltejs/adapter-node": "^1.1.7",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"dayjs": "^1.11.9",
|
||||
"emoji-regex": "^10.2.1",
|
||||
"eventemitter3": "^5.0.0",
|
||||
"light-bolt11-decoder": "^3.0.0",
|
||||
|
|
@ -2440,6 +2441,11 @@
|
|||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.9",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz",
|
||||
"integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA=="
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
|
|
@ -10273,6 +10279,11 @@
|
|||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
|
||||
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="
|
||||
},
|
||||
"dayjs": {
|
||||
"version": "1.11.9",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz",
|
||||
"integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "nostri.chat",
|
||||
"name": "dsnt",
|
||||
"version": "0.3.14159",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
|
|
@ -34,6 +34,7 @@
|
|||
"@sveltejs/adapter-node": "^1.1.7",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"dayjs": "^1.11.9",
|
||||
"emoji-regex": "^10.2.1",
|
||||
"eventemitter3": "^5.0.0",
|
||||
"light-bolt11-decoder": "^3.0.0",
|
||||
|
|
|
|||
34
src/Brand.svelte
Normal file
34
src/Brand.svelte
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<p class="logo">
|
||||
<span>D</span><span class="letter--dim">i</span><span>s</span><span class="letter--dim">se</span><span>nt</span>
|
||||
</p>
|
||||
<p class="motto">The web’s comment section.</p>
|
||||
|
||||
<style>
|
||||
|
||||
p {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-family: 'Koulen', sans-serif;
|
||||
letter-spacing: -.1em;
|
||||
color: var(--c-bright);
|
||||
font-size: 82px;
|
||||
margin: 0;
|
||||
line-height: .8;
|
||||
}
|
||||
.logo span{
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
.logo span.letter--dim{
|
||||
color: #cc9680;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.motto{
|
||||
color: var(--c-2);
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -239,7 +239,7 @@
|
|||
|
||||
}
|
||||
|
||||
if (!name) { name = `[${pubkey.slice(0, 6)}]`; }
|
||||
if (!name) { name = `Anonymous [${pubkey.slice(0, 6)}]`; }
|
||||
|
||||
return name;
|
||||
}
|
||||
|
|
@ -321,6 +321,8 @@
|
|||
{#if event.deleted}
|
||||
👆 deleted
|
||||
{/if}
|
||||
{:else}
|
||||
<p>no comments</p>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
|
@ -328,7 +330,7 @@
|
|||
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div class="
|
||||
<!-- <div class="
|
||||
border-y border-y-slate-200
|
||||
-mx-4 my-2 bg-slate-100 text-black text-sm
|
||||
px-4 py-2
|
||||
|
|
@ -343,7 +345,7 @@
|
|||
<b>Public notes:</b>
|
||||
your followers see your messages on their timeline
|
||||
{/if}
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="flex flex-row gap-2 -mx-1">
|
||||
<textarea
|
||||
|
|
@ -357,7 +359,7 @@
|
|||
rounded-xl
|
||||
text-gray-600
|
||||
border
|
||||
" placeholder="Say hello!"
|
||||
" placeholder="leave a comment"
|
||||
rows=1
|
||||
on:keydown={inputKeyDown}
|
||||
></textarea>
|
||||
|
|
|
|||
480
src/ConnectedWidget2.svelte
Normal file
480
src/ConnectedWidget2.svelte
Normal file
|
|
@ -0,0 +1,480 @@
|
|||
<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>
|
||||
|
|
@ -106,94 +106,52 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<h1 class="font-bold text-xl mb-3">
|
||||
<h1 class="">
|
||||
How would you like to connect?
|
||||
</h1>
|
||||
|
||||
{#if publicKey}
|
||||
<p class="text-gray-400 mb-3 font-bold">
|
||||
<p class="">
|
||||
Nostr Connect is a WIP, not fully implemented yet!
|
||||
</p>
|
||||
|
||||
<p class="text-gray-400 mb-3">
|
||||
<p class="">
|
||||
You are currently connected with the following public key:
|
||||
<span>{publicKey}</span>
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
{#if nip46URI}
|
||||
<p class="text-gray-600 mb-3">
|
||||
<p class="">
|
||||
Scan this with your Nostr Connect (click to copy to clipboard)
|
||||
</p>
|
||||
|
||||
<div class="bg-white w-full p-3"
|
||||
<div class=""
|
||||
on:click|preventDefault={Nip46Copy}>
|
||||
<!-- <QR text={nip46URI} /> -->
|
||||
</div>
|
||||
|
||||
<button class="
|
||||
bg-purple-900
|
||||
hover:bg-purple-700
|
||||
w-full
|
||||
p-2
|
||||
rounded-xl
|
||||
text-center
|
||||
font-regular
|
||||
text-white
|
||||
" on:click|preventDefault={() => { nip46URI = null; }}>
|
||||
<button class="" on:click|preventDefault={() => { nip46URI = null; }}>
|
||||
Cancel
|
||||
</button>
|
||||
{:else if !publicKey}
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="">
|
||||
{#if hasNostrNip07}
|
||||
<button class="
|
||||
bg-purple-900
|
||||
hover:bg-purple-700
|
||||
w-full
|
||||
p-4
|
||||
rounded-xl
|
||||
text-center
|
||||
font-regular
|
||||
text-gray-200
|
||||
" on:click|preventDefault={useNip07}>
|
||||
<button class="" on:click|preventDefault={useNip07}>
|
||||
Browser Extension (NIP-07)
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
|
||||
<button class="
|
||||
bg-purple-900
|
||||
hover:bg-purple-700
|
||||
w-full
|
||||
p-4
|
||||
rounded-xl
|
||||
text-center
|
||||
font-regular
|
||||
text-gray-200
|
||||
" on:click|preventDefault={useNip46}>
|
||||
<button class="" on:click|preventDefault={useNip46}>
|
||||
Nostr Connect (NIP-46)
|
||||
</button>
|
||||
|
||||
<button class="
|
||||
bg-purple-900
|
||||
hover:bg-purple-700
|
||||
w-full
|
||||
p-4
|
||||
rounded-xl
|
||||
text-center
|
||||
font-regular
|
||||
text-gray-200
|
||||
" on:click|preventDefault={useDiscardableKeys}>
|
||||
<button class="" on:click|preventDefault={useDiscardableKeys}>
|
||||
Anonymous
|
||||
<span class="text-xs text-gray-300">
|
||||
<span class="">
|
||||
(Ephemeral Keys)
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
</style>
|
||||
{/if}
|
||||
160
src/MetaData.svelte
Normal file
160
src/MetaData.svelte
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let url;
|
||||
|
||||
let title = null;
|
||||
let description = null;
|
||||
let thumbnail = null;
|
||||
let publishDate = null;
|
||||
let isLoading = true;
|
||||
let isError = false;
|
||||
let urlAbbr;
|
||||
|
||||
let imageLoaded = false;
|
||||
let imageError = false;
|
||||
|
||||
function onImageLoad() {
|
||||
imageLoaded = true;
|
||||
}
|
||||
|
||||
function onImageError() {
|
||||
imageError = true;
|
||||
}
|
||||
|
||||
$: {
|
||||
const cleanedUrl = typeof url === 'string' ? url.replace(/^https?:\/\//, '') : '';
|
||||
urlAbbr = cleanedUrl.length > 20 ? cleanedUrl.substring(0, 20) + '...' : cleanedUrl;
|
||||
}
|
||||
|
||||
$: fetchMetaData(url);
|
||||
|
||||
onMount(async () => {
|
||||
fetchMetaData(url);
|
||||
});
|
||||
|
||||
async function fetchMetaData(url){
|
||||
isError = false;
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
throw new Error("Couldn't fetch URL");
|
||||
}
|
||||
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);
|
||||
publishDate = getMetaContent(doc, publishDateTags);
|
||||
} catch (error) {
|
||||
isError = true;
|
||||
console.log('Error fetching URL:', error);
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function getMetaContent(doc, tagNames) {
|
||||
for (const tagName of tagNames) {
|
||||
const element = doc.querySelector(`meta[name="${tagName}"], meta[property="${tagName}"]`);
|
||||
if (element && element.content) {
|
||||
return element.content;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
</script>
|
||||
{#if isLoading}
|
||||
<div class="metadata__text">
|
||||
<h1>{urlAbbr}</h1>
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
{:else if isError}
|
||||
<div class="metadata__text">
|
||||
<h1>{urlAbbr}</h1>
|
||||
<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>
|
||||
<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}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
<style>
|
||||
|
||||
h1 {
|
||||
/* font-size: 2.5rem; */
|
||||
color: var(--c-3);
|
||||
font-size: 1.5rem;
|
||||
color: var(--c-3);
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a{
|
||||
color: var(--c-3);
|
||||
}
|
||||
|
||||
a:visited{
|
||||
color: var(--c-3);
|
||||
}
|
||||
|
||||
a:hover{
|
||||
color: var(--c-bright);
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--c-3);
|
||||
max-width: 60ch;
|
||||
overflow: hidden;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.metadata__text {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.metadata__thumbnail {
|
||||
flex-grow:0;
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
img{
|
||||
max-height: 100%;
|
||||
outline: 1px solid rgba(255, 255, 255, .5);
|
||||
border-radius: .5rem;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -113,7 +113,7 @@
|
|||
'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 absolute ml-5 mt-10 z-10">
|
||||
flex items-center ml-5 mt-10 z-10">
|
||||
{#if zappingIt}
|
||||
{#if mobilePR}
|
||||
<div class="flex flex-col gap-3 w-full">
|
||||
|
|
|
|||
325
src/NostrNote2.svelte
Normal file
325
src/NostrNote2.svelte
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
<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>
|
||||
47
src/Relays.svelte
Normal file
47
src/Relays.svelte
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<script>
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
// Accept relays as a prop
|
||||
export let relays = [];
|
||||
|
||||
// Create a local copy
|
||||
let localRelays = [...relays];
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let showRelays = false;
|
||||
let newRelay = '';
|
||||
|
||||
function toggleRelays() {
|
||||
showRelays = !showRelays;
|
||||
}
|
||||
|
||||
function addRelay() {
|
||||
if (newRelay) {
|
||||
localRelays.push(newRelay);
|
||||
newRelay = '';
|
||||
dispatch('update', localRelays);
|
||||
}
|
||||
}
|
||||
|
||||
function removeRelay(index) {
|
||||
localRelays.splice(index, 1);
|
||||
dispatch('update', localRelays);
|
||||
}
|
||||
</script>
|
||||
|
||||
<button on:click={toggleRelays}>Relays <span>{relays.length}</span></button>
|
||||
|
||||
{#if showRelays}
|
||||
<ul>
|
||||
{#each relays as relay, index}
|
||||
<li>
|
||||
{relay} <button on:click={() => removeRelay(index)}>Remove</button>
|
||||
</li>
|
||||
{/each}
|
||||
<li>
|
||||
<input type="text" bind:value={newRelay} placeholder="Add new relay..." />
|
||||
<button on:click={addRelay}>Add</button>
|
||||
</li>
|
||||
</ul>
|
||||
{/if}
|
||||
124
src/app.css
Normal file
124
src/app.css
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
@import url("https://fonts.googleapis.com/css2?family=Work+Sans:wght@300;400;500;600;700&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@100;300;400;500;600;700&display=swap");
|
||||
@import url('https://fonts.googleapis.com/css2?family=Koulen&display=swap');
|
||||
|
||||
:root {
|
||||
--c-lines: rgba(255, 255, 255, 0.3);
|
||||
--c-2: #cc9680;
|
||||
--c-3: rgba(255, 255, 255, 0.7);
|
||||
--c-bright: white;
|
||||
--c-marker: orange;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
main,
|
||||
section,
|
||||
div {
|
||||
border: dotted orange .5px;
|
||||
}
|
||||
*/
|
||||
|
||||
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;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.panel .toolbar {
|
||||
background-color: rgb(56, 17, 0, 0.2);
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
flex-shrink: 0;
|
||||
width: 566px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: rgb(56, 17, 0, 0.2);
|
||||
border-left: 1px solid var(--c-lines);
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
gap: 0.5rem;
|
||||
padding: 1rem;
|
||||
height: 10rem;
|
||||
border-bottom: 1px solid var(--c-lines);
|
||||
color: var(--c-bright);
|
||||
}
|
||||
|
||||
.sidebar .toolbar {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
button {
|
||||
outline: 1px solid var(--c-lines);
|
||||
color: var(--c-lines);
|
||||
padding: 1em 2rem;
|
||||
}
|
||||
|
||||
/* Webkit browsers like Chrome, Safari */
|
||||
.content--scrolling::-webkit-scrollbar {
|
||||
width: 1rem; /* Set the width */
|
||||
}
|
||||
|
||||
.content--scrolling::-webkit-scrollbar-track {
|
||||
background: transparent; /* Make the track transparent */
|
||||
}
|
||||
|
||||
.content--scrolling::-webkit-scrollbar-thumb {
|
||||
background: var(--c-lines); /* Set the thumb color */
|
||||
}
|
||||
|
||||
.content--scrolling::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--c-bright); /* Change the thumb color when hovered */
|
||||
}
|
||||
|
||||
/* Internet Explorer */
|
||||
.content--scrolling {
|
||||
position: relative;
|
||||
scrollbar-face-color: var(--c-lines);
|
||||
scrollbar-track-color: transparent;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.content--scrolling::before {
|
||||
content: "";
|
||||
position: sticky;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 1px;
|
||||
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;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--c-bright);
|
||||
}
|
||||
|
|
@ -6,10 +6,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<!-- bg-white dark:bg-black -->
|
||||
<body class="
|
||||
|
||||
">
|
||||
<body>
|
||||
<div>%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,49 @@
|
|||
import { writable } from 'svelte/store';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
// Your other stores
|
||||
export const chatAdapter = writable(null);
|
||||
export const chatData = writable({ events: [], profiles: {}});
|
||||
export const selectedMessage = writable(null);
|
||||
export const zappingMessage = writable(null);
|
||||
export const zapsPerMessage = writable({});
|
||||
export const zapsPerMessage = writable({});
|
||||
|
||||
// Default values
|
||||
const defaultRelays = [
|
||||
"wss://relay.f7z.io",
|
||||
"wss://nos.lol",
|
||||
"wss://relay.nostr.band",
|
||||
"wss://nostr-pub.wellorder.net",
|
||||
"wss://relay.damus.io",
|
||||
"wss://relay.f7z.io",
|
||||
"wss://eden.nostr.land",
|
||||
"wss://offchain.pub",
|
||||
"wss://soloco.nl"
|
||||
];
|
||||
|
||||
// Read initial state from sessionStorage if available, otherwise use default values
|
||||
const initialUrl = browser && sessionStorage.getItem('url') !== null
|
||||
? sessionStorage.getItem('url')
|
||||
: null;
|
||||
|
||||
const initialRelays = browser && sessionStorage.getItem('relays') !== null
|
||||
? JSON.parse(sessionStorage.getItem('relays'))
|
||||
: defaultRelays;
|
||||
|
||||
// Create the writable stores
|
||||
export const url = writable(initialUrl);
|
||||
export const relays = writable(initialRelays);
|
||||
|
||||
// Function to synchronize the URL with sessionStorage
|
||||
url.subscribe((currentUrl) => {
|
||||
if (browser) {
|
||||
sessionStorage.setItem("url", currentUrl);
|
||||
}
|
||||
});
|
||||
|
||||
// Function to synchronize the relays with sessionStorage
|
||||
relays.subscribe((currentRelays) => {
|
||||
if (browser) {
|
||||
sessionStorage.setItem("relays", JSON.stringify(currentRelays));
|
||||
}
|
||||
});
|
||||
|
|
|
|||
635
src/milligram.css
Normal file
635
src/milligram.css
Normal file
|
|
@ -0,0 +1,635 @@
|
|||
/*!
|
||||
* Milligram v1.4.1
|
||||
* https://milligram.io
|
||||
*
|
||||
* Copyright (c) 2020 CJ Patoilo
|
||||
* Licensed under the MIT license
|
||||
*/
|
||||
|
||||
*,
|
||||
*:after,
|
||||
*:before {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
font-size: 62.5%;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #606c76;
|
||||
font-family: 'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
|
||||
font-size: 1.6em;
|
||||
font-weight: 300;
|
||||
letter-spacing: .01em;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 0.3rem solid #d1d1d1;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
|
||||
blockquote *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.button,
|
||||
button,
|
||||
input[type='button'],
|
||||
input[type='reset'],
|
||||
input[type='submit'] {
|
||||
background-color: #9b4dca;
|
||||
border: 0.1rem solid #9b4dca;
|
||||
border-radius: .4rem;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
height: 3.8rem;
|
||||
letter-spacing: .1rem;
|
||||
line-height: 3.8rem;
|
||||
padding: 0 3.0rem;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.button:focus, .button:hover,
|
||||
button:focus,
|
||||
button:hover,
|
||||
input[type='button']:focus,
|
||||
input[type='button']:hover,
|
||||
input[type='reset']:focus,
|
||||
input[type='reset']:hover,
|
||||
input[type='submit']:focus,
|
||||
input[type='submit']:hover {
|
||||
background-color: #606c76;
|
||||
border-color: #606c76;
|
||||
color: #fff;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.button[disabled],
|
||||
button[disabled],
|
||||
input[type='button'][disabled],
|
||||
input[type='reset'][disabled],
|
||||
input[type='submit'][disabled] {
|
||||
cursor: default;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.button[disabled]:focus, .button[disabled]:hover,
|
||||
button[disabled]:focus,
|
||||
button[disabled]:hover,
|
||||
input[type='button'][disabled]:focus,
|
||||
input[type='button'][disabled]:hover,
|
||||
input[type='reset'][disabled]:focus,
|
||||
input[type='reset'][disabled]:hover,
|
||||
input[type='submit'][disabled]:focus,
|
||||
input[type='submit'][disabled]:hover {
|
||||
background-color: #9b4dca;
|
||||
border-color: #9b4dca;
|
||||
}
|
||||
|
||||
.button.button-outline,
|
||||
button.button-outline,
|
||||
input[type='button'].button-outline,
|
||||
input[type='reset'].button-outline,
|
||||
input[type='submit'].button-outline {
|
||||
background-color: transparent;
|
||||
color: #9b4dca;
|
||||
}
|
||||
|
||||
.button.button-outline:focus, .button.button-outline:hover,
|
||||
button.button-outline:focus,
|
||||
button.button-outline:hover,
|
||||
input[type='button'].button-outline:focus,
|
||||
input[type='button'].button-outline:hover,
|
||||
input[type='reset'].button-outline:focus,
|
||||
input[type='reset'].button-outline:hover,
|
||||
input[type='submit'].button-outline:focus,
|
||||
input[type='submit'].button-outline:hover {
|
||||
background-color: transparent;
|
||||
border-color: #606c76;
|
||||
color: #606c76;
|
||||
}
|
||||
|
||||
.button.button-outline[disabled]:focus, .button.button-outline[disabled]:hover,
|
||||
button.button-outline[disabled]:focus,
|
||||
button.button-outline[disabled]:hover,
|
||||
input[type='button'].button-outline[disabled]:focus,
|
||||
input[type='button'].button-outline[disabled]:hover,
|
||||
input[type='reset'].button-outline[disabled]:focus,
|
||||
input[type='reset'].button-outline[disabled]:hover,
|
||||
input[type='submit'].button-outline[disabled]:focus,
|
||||
input[type='submit'].button-outline[disabled]:hover {
|
||||
border-color: inherit;
|
||||
color: #9b4dca;
|
||||
}
|
||||
|
||||
.button.button-clear,
|
||||
button.button-clear,
|
||||
input[type='button'].button-clear,
|
||||
input[type='reset'].button-clear,
|
||||
input[type='submit'].button-clear {
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
color: #9b4dca;
|
||||
}
|
||||
|
||||
.button.button-clear:focus, .button.button-clear:hover,
|
||||
button.button-clear:focus,
|
||||
button.button-clear:hover,
|
||||
input[type='button'].button-clear:focus,
|
||||
input[type='button'].button-clear:hover,
|
||||
input[type='reset'].button-clear:focus,
|
||||
input[type='reset'].button-clear:hover,
|
||||
input[type='submit'].button-clear:focus,
|
||||
input[type='submit'].button-clear:hover {
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
color: #606c76;
|
||||
}
|
||||
|
||||
.button.button-clear[disabled]:focus, .button.button-clear[disabled]:hover,
|
||||
button.button-clear[disabled]:focus,
|
||||
button.button-clear[disabled]:hover,
|
||||
input[type='button'].button-clear[disabled]:focus,
|
||||
input[type='button'].button-clear[disabled]:hover,
|
||||
input[type='reset'].button-clear[disabled]:focus,
|
||||
input[type='reset'].button-clear[disabled]:hover,
|
||||
input[type='submit'].button-clear[disabled]:focus,
|
||||
input[type='submit'].button-clear[disabled]:hover {
|
||||
color: #9b4dca;
|
||||
}
|
||||
|
||||
code {
|
||||
background: #f4f5f6;
|
||||
border-radius: .4rem;
|
||||
font-size: 86%;
|
||||
margin: 0 .2rem;
|
||||
padding: .2rem .5rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #f4f5f6;
|
||||
border-left: 0.3rem solid #9b4dca;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
pre > code {
|
||||
border-radius: 0;
|
||||
display: block;
|
||||
padding: 1rem 1.5rem;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 0;
|
||||
border-top: 0.1rem solid #f4f5f6;
|
||||
margin: 3.0rem 0;
|
||||
}
|
||||
|
||||
input[type='color'],
|
||||
input[type='date'],
|
||||
input[type='datetime'],
|
||||
input[type='datetime-local'],
|
||||
input[type='email'],
|
||||
input[type='month'],
|
||||
input[type='number'],
|
||||
input[type='password'],
|
||||
input[type='search'],
|
||||
input[type='tel'],
|
||||
input[type='text'],
|
||||
input[type='url'],
|
||||
input[type='week'],
|
||||
input:not([type]),
|
||||
textarea,
|
||||
select {
|
||||
-webkit-appearance: none;
|
||||
background-color: transparent;
|
||||
border: 0.1rem solid #d1d1d1;
|
||||
border-radius: .4rem;
|
||||
box-shadow: none;
|
||||
box-sizing: inherit;
|
||||
height: 3.8rem;
|
||||
padding: .6rem 1.0rem .7rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input[type='color']:focus,
|
||||
input[type='date']:focus,
|
||||
input[type='datetime']:focus,
|
||||
input[type='datetime-local']:focus,
|
||||
input[type='email']:focus,
|
||||
input[type='month']:focus,
|
||||
input[type='number']:focus,
|
||||
input[type='password']:focus,
|
||||
input[type='search']:focus,
|
||||
input[type='tel']:focus,
|
||||
input[type='text']:focus,
|
||||
input[type='url']:focus,
|
||||
input[type='week']:focus,
|
||||
input:not([type]):focus,
|
||||
textarea:focus,
|
||||
select:focus {
|
||||
border-color: #9b4dca;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
select {
|
||||
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 8" width="30"><path fill="%23d1d1d1" d="M0,0l6,8l6-8"/></svg>') center right no-repeat;
|
||||
padding-right: 3.0rem;
|
||||
}
|
||||
|
||||
select:focus {
|
||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 8" width="30"><path fill="%239b4dca" d="M0,0l6,8l6-8"/></svg>');
|
||||
}
|
||||
|
||||
select[multiple] {
|
||||
background: none;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 6.5rem;
|
||||
}
|
||||
|
||||
label,
|
||||
legend {
|
||||
display: block;
|
||||
font-size: 1.6rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border-width: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input[type='checkbox'],
|
||||
input[type='radio'] {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.label-inline {
|
||||
display: inline-block;
|
||||
font-weight: normal;
|
||||
margin-left: .5rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0 auto;
|
||||
max-width: 112.0rem;
|
||||
padding: 0 2.0rem;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.row.row-no-padding {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.row.row-no-padding > .column {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.row.row-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.row.row-top {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.row.row-bottom {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.row.row-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.row.row-stretch {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.row.row-baseline {
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.row .column {
|
||||
display: block;
|
||||
flex: 1 1 auto;
|
||||
margin-left: 0;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.row .column.column-offset-10 {
|
||||
margin-left: 10%;
|
||||
}
|
||||
|
||||
.row .column.column-offset-20 {
|
||||
margin-left: 20%;
|
||||
}
|
||||
|
||||
.row .column.column-offset-25 {
|
||||
margin-left: 25%;
|
||||
}
|
||||
|
||||
.row .column.column-offset-33, .row .column.column-offset-34 {
|
||||
margin-left: 33.3333%;
|
||||
}
|
||||
|
||||
.row .column.column-offset-40 {
|
||||
margin-left: 40%;
|
||||
}
|
||||
|
||||
.row .column.column-offset-50 {
|
||||
margin-left: 50%;
|
||||
}
|
||||
|
||||
.row .column.column-offset-60 {
|
||||
margin-left: 60%;
|
||||
}
|
||||
|
||||
.row .column.column-offset-66, .row .column.column-offset-67 {
|
||||
margin-left: 66.6666%;
|
||||
}
|
||||
|
||||
.row .column.column-offset-75 {
|
||||
margin-left: 75%;
|
||||
}
|
||||
|
||||
.row .column.column-offset-80 {
|
||||
margin-left: 80%;
|
||||
}
|
||||
|
||||
.row .column.column-offset-90 {
|
||||
margin-left: 90%;
|
||||
}
|
||||
|
||||
.row .column.column-10 {
|
||||
flex: 0 0 10%;
|
||||
max-width: 10%;
|
||||
}
|
||||
|
||||
.row .column.column-20 {
|
||||
flex: 0 0 20%;
|
||||
max-width: 20%;
|
||||
}
|
||||
|
||||
.row .column.column-25 {
|
||||
flex: 0 0 25%;
|
||||
max-width: 25%;
|
||||
}
|
||||
|
||||
.row .column.column-33, .row .column.column-34 {
|
||||
flex: 0 0 33.3333%;
|
||||
max-width: 33.3333%;
|
||||
}
|
||||
|
||||
.row .column.column-40 {
|
||||
flex: 0 0 40%;
|
||||
max-width: 40%;
|
||||
}
|
||||
|
||||
.row .column.column-50 {
|
||||
flex: 0 0 50%;
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.row .column.column-60 {
|
||||
flex: 0 0 60%;
|
||||
max-width: 60%;
|
||||
}
|
||||
|
||||
.row .column.column-66, .row .column.column-67 {
|
||||
flex: 0 0 66.6666%;
|
||||
max-width: 66.6666%;
|
||||
}
|
||||
|
||||
.row .column.column-75 {
|
||||
flex: 0 0 75%;
|
||||
max-width: 75%;
|
||||
}
|
||||
|
||||
.row .column.column-80 {
|
||||
flex: 0 0 80%;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.row .column.column-90 {
|
||||
flex: 0 0 90%;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.row .column .column-top {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.row .column .column-bottom {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.row .column .column-center {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
@media (min-width: 40rem) {
|
||||
.row {
|
||||
flex-direction: row;
|
||||
margin-left: -1.0rem;
|
||||
width: calc(100% + 2.0rem);
|
||||
}
|
||||
.row .column {
|
||||
margin-bottom: inherit;
|
||||
padding: 0 1.0rem;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: #9b4dca;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:focus, a:hover {
|
||||
color: #606c76;
|
||||
}
|
||||
|
||||
dl,
|
||||
ol,
|
||||
ul {
|
||||
list-style: none;
|
||||
margin-top: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
dl dl,
|
||||
dl ol,
|
||||
dl ul,
|
||||
ol dl,
|
||||
ol ol,
|
||||
ol ul,
|
||||
ul dl,
|
||||
ul ol,
|
||||
ul ul {
|
||||
font-size: 90%;
|
||||
margin: 1.5rem 0 1.5rem 3.0rem;
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: decimal inside;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: circle inside;
|
||||
}
|
||||
|
||||
.button,
|
||||
button,
|
||||
dd,
|
||||
dt,
|
||||
li {
|
||||
margin-bottom: 1.0rem;
|
||||
}
|
||||
|
||||
fieldset,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
blockquote,
|
||||
dl,
|
||||
figure,
|
||||
form,
|
||||
ol,
|
||||
p,
|
||||
pre,
|
||||
table,
|
||||
ul {
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
table {
|
||||
border-spacing: 0;
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
border-bottom: 0.1rem solid #e1e1e1;
|
||||
padding: 1.2rem 1.5rem;
|
||||
}
|
||||
|
||||
td:first-child,
|
||||
th:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
td:last-child,
|
||||
th:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 40rem) {
|
||||
table {
|
||||
display: table;
|
||||
overflow-x: initial;
|
||||
}
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-weight: 300;
|
||||
letter-spacing: -.1rem;
|
||||
margin-bottom: 2.0rem;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 4.6rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 3.6rem;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 2.8rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 2.2rem;
|
||||
letter-spacing: -.08rem;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.8rem;
|
||||
letter-spacing: -.05rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1.6rem;
|
||||
letter-spacing: 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.clearfix:after {
|
||||
clear: both;
|
||||
content: ' ';
|
||||
display: table;
|
||||
}
|
||||
|
||||
.float-left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.float-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=milligram.css.map */
|
||||
351
src/normalize.css
vendored
Normal file
351
src/normalize.css
vendored
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
||||
|
||||
/* Document
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Correct the line height in all browsers.
|
||||
* 2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.15; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/* Sections
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the margin in all browsers.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the `main` element consistently in IE.
|
||||
*/
|
||||
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the font size and margin on `h1` elements within `section` and
|
||||
* `article` contexts in Chrome, Firefox, and Safari.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in Firefox.
|
||||
* 2. Show the overflow in Edge and IE.
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box; /* 1 */
|
||||
height: 0; /* 1 */
|
||||
overflow: visible; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background on active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove the bottom border in Chrome 57-
|
||||
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none; /* 1 */
|
||||
text-decoration: underline; /* 2 */
|
||||
text-decoration: underline dotted; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||
* all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the border on images inside links in IE 10.
|
||||
*/
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Change the font styles in all browsers.
|
||||
* 2. Remove the margin in Firefox and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit; /* 1 */
|
||||
font-size: 100%; /* 1 */
|
||||
line-height: 1.15; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the overflow in IE.
|
||||
* 1. Show the overflow in Edge.
|
||||
*/
|
||||
|
||||
button,
|
||||
input { /* 1 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||
* 1. Remove the inheritance of text transform in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select { /* 1 */
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the inability to style clickable types in iOS and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
[type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
appearance: button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner border and padding in Firefox.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the focus styles unset by the previous rule.
|
||||
*/
|
||||
|
||||
button:-moz-focusring,
|
||||
[type="button"]:-moz-focusring,
|
||||
[type="reset"]:-moz-focusring,
|
||||
[type="submit"]:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the padding in Firefox.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the text wrapping in Edge and IE.
|
||||
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||
* 3. Remove the padding so developers are not caught out when they zero out
|
||||
* `fieldset` elements in all browsers.
|
||||
*/
|
||||
|
||||
legend {
|
||||
box-sizing: border-box; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
display: table; /* 1 */
|
||||
max-width: 100%; /* 1 */
|
||||
padding: 0; /* 3 */
|
||||
white-space: normal; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default vertical scrollbar in IE 10+.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in IE 10.
|
||||
* 2. Remove the padding in IE 10.
|
||||
*/
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||
*/
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the odd appearance in Chrome and Safari.
|
||||
* 2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
appearance: textfield;
|
||||
outline-offset: -2px; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||
* 2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/* Interactive
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Add the correct display in Edge, IE 10+, and Firefox.
|
||||
*/
|
||||
|
||||
details {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the correct display in all browsers.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* Misc
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10+.
|
||||
*/
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
7
src/routes/+layout.svelte
Normal file
7
src/routes/+layout.svelte
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<script>
|
||||
//import '../normalize.css'
|
||||
//import '../milligram.css'
|
||||
import '../app.css'
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
|
|
@ -281,6 +281,50 @@
|
|||
>
|
||||
<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>
|
||||
|
|
|
|||
254
src/routes/draft1/+page.svelte
Normal file
254
src/routes/draft1/+page.svelte
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
<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>
|
||||
235
src/routes/draft2/+page.svelte
Normal file
235
src/routes/draft2/+page.svelte
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
<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>
|
||||
123
src/routes/figma/+page.svelte
Normal file
123
src/routes/figma/+page.svelte
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
<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>
|
||||
|
|
@ -10,9 +10,9 @@ let chatReferenceTags = script.getAttribute('data-chat-reference-tags');
|
|||
let relays = script.getAttribute('data-relays');
|
||||
script.parentNode.insertBefore(div, script);
|
||||
|
||||
if (!relays) {
|
||||
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'
|
||||
}
|
||||
// if (!relays) {
|
||||
// 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'
|
||||
// }
|
||||
|
||||
relays = relays.split(',');
|
||||
chatTags = chatTags ? chatTags.split(',') : [];
|
||||
|
|
|
|||
Loading…
Reference in a new issue