forked from coracle/flotilla
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cee6c3c164 | |||
| 06d0ae2798 | |||
| b129ef4242 | |||
| 48a45f3a3a | |||
| ce1fb396e3 | |||
| e95c57bcb7 | |||
| 414f5a5ace | |||
| a331d24bb1 | |||
| fb53e53411 |
@@ -1,5 +1,14 @@
|
||||
# Changelog
|
||||
|
||||
# 0.2.6
|
||||
|
||||
* Add reply to long-press menu
|
||||
* Fix @-mentions
|
||||
* Replace nsec.app signup with njump.me
|
||||
* Add new messages button in rooms
|
||||
* Add media server settings
|
||||
* Add build hash to about page
|
||||
|
||||
# 0.2.5
|
||||
|
||||
* Improve room and data loading
|
||||
|
||||
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "social.flotilla"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 5
|
||||
versionName "0.2.5"
|
||||
versionCode 6
|
||||
versionName "0.2.6"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -14,6 +14,10 @@ fi
|
||||
# https://stackoverflow.com/a/69127685/1467342
|
||||
eval "$temp_env"
|
||||
|
||||
if [[ -z $VITE_BUILD_HASH ]]; then
|
||||
export VITE_BUILD_HASH=$(git rev-parse --short HEAD)
|
||||
fi
|
||||
|
||||
if [[ $VITE_PLATFORM_LOGO =~ ^https://* ]]; then
|
||||
curl $VITE_PLATFORM_LOGO > static/logo.png
|
||||
export VITE_PLATFORM_LOGO=static/logo.png
|
||||
|
||||
Generated
+13
-13
@@ -21,9 +21,9 @@
|
||||
"@vite-pwa/assets-generator": "^0.2.6",
|
||||
"@vite-pwa/sveltekit": "^0.6.6",
|
||||
"@welshman/app": "~0.0.41",
|
||||
"@welshman/content": "~0.0.15",
|
||||
"@welshman/content": "~0.0.16",
|
||||
"@welshman/dvm": "~0.0.14",
|
||||
"@welshman/editor": "~0.0.8",
|
||||
"@welshman/editor": "~0.0.10",
|
||||
"@welshman/feeds": "~0.0.30",
|
||||
"@welshman/lib": "~0.0.38",
|
||||
"@welshman/net": "~0.0.46",
|
||||
@@ -4508,13 +4508,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@welshman/content": {
|
||||
"version": "0.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@welshman/content/-/content-0.0.15.tgz",
|
||||
"integrity": "sha512-y0f0iLIaHUqEJJ0ziRWbGw13mg0tOLTKpHQNgIXJ03PD3xGHBaQ5xPWiOI8XeUt35KgrayvQZHsaqfAsOWkwag==",
|
||||
"version": "0.0.16",
|
||||
"resolved": "https://registry.npmjs.org/@welshman/content/-/content-0.0.16.tgz",
|
||||
"integrity": "sha512-042LG1rgyzDvwPzEISliY7wG0Hb8s+efhRR15DBtFBnbzZy5xRLihOiw/N6S2bMN+BoY6iBnilantwr0yfV5Cg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^7.0.2",
|
||||
"@welshman/lib": "~0.0.34",
|
||||
"@welshman/lib": "~0.0.37",
|
||||
"nostr-tools": "^2.7.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -4535,9 +4535,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@welshman/editor": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@welshman/editor/-/editor-0.0.8.tgz",
|
||||
"integrity": "sha512-9zQy1MjbGhTARTPH3QEgyhEGNKy239VSD5Jo4llsiUUhHQVNQj1KpdSVQnTLOwUBbfSLeYPIfMRUm2Gq5Cn/Ew==",
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@welshman/editor/-/editor-0.0.10.tgz",
|
||||
"integrity": "sha512-685+KUYrGHzSgz2V6hRRltyp3LMhdHKvYBqkc9DyDNZoz3qqn4pUsOF+SMGSBnisiXBEZrKj2d52Jv46FT81iQ==",
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.9.1",
|
||||
"@tiptap/extension-code": "^2.9.1",
|
||||
@@ -4554,7 +4554,7 @@
|
||||
"@tiptap/suggestion": "^2.9.1",
|
||||
"@welshman/lib": "~0.0.36",
|
||||
"@welshman/util": "~0.0.53",
|
||||
"nostr-editor": "^0.0.4-pre.6",
|
||||
"nostr-editor": "^0.0.4-pre.7",
|
||||
"nostr-tools": "^2.8.1",
|
||||
"svelte": "^4.0.0",
|
||||
"svelte-tiptap": "^1.0.0"
|
||||
@@ -9880,9 +9880,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nostr-editor": {
|
||||
"version": "0.0.4-pre.6",
|
||||
"resolved": "https://registry.npmjs.org/nostr-editor/-/nostr-editor-0.0.4-pre.6.tgz",
|
||||
"integrity": "sha512-njOWThC8tfvyw59rcCeJjwTycZ0QdwBYURpMJAzPVoWFjxZmzmTeBNNznf00g3U7jwXde0VuI9OElzAaTdkz8w==",
|
||||
"version": "0.0.4-pre.7",
|
||||
"resolved": "https://registry.npmjs.org/nostr-editor/-/nostr-editor-0.0.4-pre.7.tgz",
|
||||
"integrity": "sha512-Xg+562ywJLLUa2nZ+XoJDarqRjx7fdc2/oKNuuxoAitp95LFTXGyupDBigJs8Q9o9GVpGLsfCbXTeIGT/b9G8g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "flotilla",
|
||||
"version": "0.2.5",
|
||||
"version": "0.2.6",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
@@ -53,9 +53,9 @@
|
||||
"@vite-pwa/assets-generator": "^0.2.6",
|
||||
"@vite-pwa/sveltekit": "^0.6.6",
|
||||
"@welshman/app": "~0.0.41",
|
||||
"@welshman/content": "~0.0.15",
|
||||
"@welshman/content": "~0.0.16",
|
||||
"@welshman/dvm": "~0.0.14",
|
||||
"@welshman/editor": "~0.0.8",
|
||||
"@welshman/editor": "~0.0.10",
|
||||
"@welshman/feeds": "~0.0.30",
|
||||
"@welshman/lib": "~0.0.38",
|
||||
"@welshman/net": "~0.0.46",
|
||||
|
||||
+34
-42
@@ -185,45 +185,53 @@
|
||||
@apply -m-1 min-h-12 p-1;
|
||||
}
|
||||
|
||||
.tiptap[contenteditable="true"] {
|
||||
.tiptap {
|
||||
--tiptap-object-bg: var(--base-100);
|
||||
--tiptap-object-fg: var(--base-content);
|
||||
--tiptap-active-bg: var(--primary);
|
||||
--tiptap-active-fg: var(--primary-content);
|
||||
}
|
||||
|
||||
.tiptap-suggestions {
|
||||
--tiptap-object-bg: var(--base-100);
|
||||
--tiptap-object-fg: var(--base-content);
|
||||
--tiptap-active-bg: var(--base-300);
|
||||
--tiptap-active-fg: var(--base-content);
|
||||
}
|
||||
|
||||
.tiptap {
|
||||
@apply max-h-[350px] overflow-y-auto p-2 px-4;
|
||||
}
|
||||
|
||||
.chat-editor .tiptap[contenteditable="true"] {
|
||||
@apply rounded-box bg-base-300;
|
||||
.tiptap p.is-editor-empty:first-child::before {
|
||||
opacity: 40%;
|
||||
}
|
||||
|
||||
.input-editor .tiptap[contenteditable="true"] {
|
||||
@apply input input-bordered h-auto p-[.65rem];
|
||||
.chat-editor .tiptap {
|
||||
@apply rounded-box bg-base-300 pr-12;
|
||||
}
|
||||
|
||||
.note-editor .tiptap[contenteditable="true"] {
|
||||
.note-editor .tiptap {
|
||||
--tiptap-object-bg: var(--base-200);
|
||||
@apply input input-bordered h-auto min-h-32 rounded-box p-[.65rem] pb-6;
|
||||
}
|
||||
|
||||
.tiptap pre code {
|
||||
@apply link-content block w-full;
|
||||
.input-editor .tiptap {
|
||||
--tiptap-object-bg: var(--base-200);
|
||||
@apply input input-bordered h-auto p-[.65rem];
|
||||
}
|
||||
|
||||
.tiptap p code {
|
||||
@apply link-content;
|
||||
}
|
||||
/* link-content, based on tiptap */
|
||||
|
||||
.link-content,
|
||||
.tiptap [tag] {
|
||||
@apply max-w-full overflow-hidden text-ellipsis whitespace-nowrap rounded bg-neutral px-1 text-neutral-content no-underline;
|
||||
}
|
||||
|
||||
.link-content.link-content-selected {
|
||||
@apply bg-primary text-primary-content;
|
||||
}
|
||||
|
||||
.tiptap p.is-editor-empty:first-child::before {
|
||||
content: attr(data-placeholder);
|
||||
float: left;
|
||||
height: 0;
|
||||
pointer-events: none;
|
||||
opacity: 50%;
|
||||
.link-content {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
border-radius: 3px;
|
||||
padding: 0 0.25rem;
|
||||
background-color: var(--base-100);
|
||||
color: var(--base-content);
|
||||
}
|
||||
|
||||
/* date input */
|
||||
@@ -248,19 +256,3 @@ emoji-picker {
|
||||
--input-font-color: var(--base-content);
|
||||
--outline-color: var(--base-100);
|
||||
}
|
||||
|
||||
/* tiptap */
|
||||
|
||||
.tiptap {
|
||||
--tiptap-object-bg: var(--base-100);
|
||||
--tiptap-object-fg: var(--base-content);
|
||||
--tiptap-active-bg: var(--primary);
|
||||
--tiptap-active-fg: var(--primary-content);
|
||||
}
|
||||
|
||||
.tiptap-suggestions {
|
||||
--tiptap-object-bg: var(--base-100);
|
||||
--tiptap-object-fg: var(--base-content);
|
||||
--tiptap-active-bg: var(--base-300);
|
||||
--tiptap-active-fg: var(--base-content);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
transition:slide>
|
||||
<p class="text-primary">Replying to @{displayProfileByPubkey(event.pubkey)}</p>
|
||||
{#key event.id}
|
||||
<Content {event} minLength={100} maxLength={300} expandMode="disabled" />
|
||||
<Content {event} hideMedia minLength={100} maxLength={300} expandMode="disabled" />
|
||||
{/key}
|
||||
<Button class="absolute right-2 top-2 cursor-pointer" on:click={clear}>
|
||||
<Icon icon="close-circle" />
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
const reply = () => replyTo(event)
|
||||
|
||||
const onLongPress = () => pushModal(ChannelMessageMenuMobile, {url, event})
|
||||
const onLongPress = () => pushModal(ChannelMessageMenuMobile, {url, event, reply})
|
||||
|
||||
const openProfile = () => pushModal(ProfileDetail, {pubkey: event.pubkey})
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
export let url
|
||||
export let event
|
||||
export let reply
|
||||
|
||||
const onEmoji = (emoji: NativeEmoji) => {
|
||||
history.back()
|
||||
@@ -19,6 +20,11 @@
|
||||
|
||||
const showEmojiPicker = () => pushModal(EmojiPicker, {onClick: onEmoji}, {replaceState: true})
|
||||
|
||||
const sendReply = () => {
|
||||
history.back()
|
||||
reply()
|
||||
}
|
||||
|
||||
const showInfo = () => pushModal(EventInfo, {event}, {replaceState: true})
|
||||
|
||||
const showDelete = () => pushModal(ConfirmDelete, {url, event})
|
||||
@@ -29,6 +35,10 @@
|
||||
<Icon size={4} icon="smile-circle" />
|
||||
Send Reaction
|
||||
</Button>
|
||||
<Button class="btn btn-neutral w-full" on:click={sendReply}>
|
||||
<Icon size={4} icon="reply" />
|
||||
Send Reply
|
||||
</Button>
|
||||
<Button class="btn btn-neutral" on:click={showInfo}>
|
||||
<Icon size={4} icon="code-2" />
|
||||
Message Details
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
</video>
|
||||
{:else if url.match(/\.(jpe?g|png|gif|webp)$/)}
|
||||
<button type="button" on:click|stopPropagation|preventDefault={expand}>
|
||||
<img alt="Link preview" src={imgproxy(url)} class="m-auto max-h-96" />
|
||||
<img alt="Link preview" src={imgproxy(url)} class="m-auto max-h-96 rounded-box" />
|
||||
</button>
|
||||
{:else}
|
||||
{#await loadPreview()}
|
||||
|
||||
@@ -1,107 +1,50 @@
|
||||
<script lang="ts">
|
||||
import {postJson, assoc} from "@welshman/lib"
|
||||
import {makeSecret, Nip46Broker} from "@welshman/signer"
|
||||
import {pubkey, loadHandle, updateSession} from "@welshman/app"
|
||||
import {postJson} from "@welshman/lib"
|
||||
import {isMobile} from "@lib/html"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Field from "@lib/components/Field.svelte"
|
||||
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Divider from "@lib/components/Divider.svelte"
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
import LogIn from "@app/components/LogIn.svelte"
|
||||
import InfoNostr from "@app/components/InfoNostr.svelte"
|
||||
import SignUpSuccess from "@app/components/SignUpSuccess.svelte"
|
||||
import {pushModal, clearModals} from "@app/modal"
|
||||
import {setChecked} from "@app/notifications"
|
||||
import {BURROW_URL, PLATFORM_NAME, NIP46_PERMS} from "@app/state"
|
||||
import {pushModal} from "@app/modal"
|
||||
import {BURROW_URL, PLATFORM_NAME} from "@app/state"
|
||||
import {pushToast} from "@app/toast"
|
||||
import {loginWithNip46} from "@app/commands"
|
||||
|
||||
const relays = ["wss://relay.nsec.app"]
|
||||
const ac = window.location.origin
|
||||
|
||||
const signerDomain = "nsec.app"
|
||||
const at = isMobile ? "android" : "web"
|
||||
|
||||
const signerPubkey = "e24a86943d37a91ab485d6f9a7c66097c25ddd67e8bd1b75ed252a3c266cf9bb"
|
||||
const nstart = `https://start.njump.me/?an=Flotilla&at=${at}&ac=${ac}`
|
||||
|
||||
const login = () => pushModal(LogIn)
|
||||
|
||||
const withLoading =
|
||||
(cb: (...args: any[]) => any) =>
|
||||
async (...args: any[]) => {
|
||||
loading = true
|
||||
const signupPassword = async () => {
|
||||
loading = true
|
||||
|
||||
try {
|
||||
await cb(...args)
|
||||
} finally {
|
||||
loading = false
|
||||
try {
|
||||
const res = await postJson(BURROW_URL + "/user", {email, password})
|
||||
|
||||
if (res.error) {
|
||||
pushToast({message: res.error, theme: "error"})
|
||||
} else {
|
||||
pushModal(SignUpSuccess, {email}, {replaceState: true})
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
|
||||
const signupPassword = withLoading(async () => {
|
||||
const res = await postJson(BURROW_URL + "/user", {email, password})
|
||||
|
||||
if (res.error) {
|
||||
return pushToast({message: res.error, theme: "error"})
|
||||
}
|
||||
|
||||
pushModal(SignUpSuccess, {email}, {replaceState: true})
|
||||
})
|
||||
|
||||
const signupNsecApp = withLoading(async () => {
|
||||
const handle = await loadHandle(`${username}@${signerDomain}`)
|
||||
|
||||
if (handle?.pubkey) {
|
||||
return pushToast({
|
||||
theme: "error",
|
||||
message: "Sorry, it looks like that account already exists. Try logging in instead.",
|
||||
})
|
||||
}
|
||||
|
||||
const clientSecret = makeSecret()
|
||||
const broker = Nip46Broker.get({
|
||||
relays,
|
||||
clientSecret,
|
||||
signerPubkey,
|
||||
algorithm: "nip04",
|
||||
})
|
||||
|
||||
const userPubkey = await broker.createAccount(username, signerDomain, NIP46_PERMS)
|
||||
|
||||
if (!userPubkey) {
|
||||
return pushToast({
|
||||
theme: "error",
|
||||
message: "Sorry, it looks like something went wrong. Please try again.",
|
||||
})
|
||||
}
|
||||
|
||||
// Now we can log in. Use the user's pubkey for the handler (legacy stuff)
|
||||
const success = await loginWithNip46({relays, signerPubkey: userPubkey, clientSecret})
|
||||
|
||||
if (!success) {
|
||||
return pushToast({
|
||||
theme: "error",
|
||||
message: "Sorry, it looks like something went wrong. Please try again.",
|
||||
})
|
||||
}
|
||||
|
||||
updateSession($pubkey!, assoc("email", email))
|
||||
pushToast({message: "Successfully logged in!"})
|
||||
setChecked("*")
|
||||
clearModals()
|
||||
})
|
||||
}
|
||||
|
||||
const signup = () => {
|
||||
if (BURROW_URL) {
|
||||
signupPassword()
|
||||
} else {
|
||||
signupNsecApp()
|
||||
}
|
||||
}
|
||||
|
||||
let email = ""
|
||||
let password = ""
|
||||
let username = ""
|
||||
let loading = false
|
||||
</script>
|
||||
|
||||
@@ -136,29 +79,12 @@
|
||||
on other nostr applications, you can create a nostr key yourself, or export your key from {PLATFORM_NAME}
|
||||
later.
|
||||
</p>
|
||||
{:else}
|
||||
<Field>
|
||||
<div class="flex items-center gap-2" slot="input">
|
||||
<label class="input input-bordered flex w-full items-center gap-2">
|
||||
<Icon icon="user-rounded" />
|
||||
<input bind:value={username} class="grow" type="text" placeholder="username" />
|
||||
</label>
|
||||
@{signerDomain}
|
||||
</div>
|
||||
</Field>
|
||||
<Button type="submit" class="btn btn-primary" disabled={loading || !username}>
|
||||
<Spinner {loading}>Sign Up</Spinner>
|
||||
<Icon icon="alt-arrow-right" />
|
||||
</Button>
|
||||
<Divider>Or</Divider>
|
||||
{/if}
|
||||
<Divider>Or</Divider>
|
||||
<Link
|
||||
external
|
||||
href="https://nosta.me"
|
||||
class="btn {username || email || password ? 'btn-neutral' : 'btn-primary'}">
|
||||
<a href={nstart} class="btn {email || password ? 'btn-neutral' : 'btn-primary'}">
|
||||
<Icon icon="square-share-line" />
|
||||
Get started on Nosta.me
|
||||
</Link>
|
||||
Get started on njump
|
||||
</a>
|
||||
<div class="text-sm">
|
||||
Already have an account?
|
||||
<Button class="link" on:click={login}>Log in instead</Button>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Tippy from "@lib/components/Tippy.svelte"
|
||||
|
||||
export let value: string
|
||||
export let options: string[]
|
||||
export let options: string[] = []
|
||||
export let allowCreate = false
|
||||
|
||||
let input: Element
|
||||
@@ -20,7 +20,7 @@
|
||||
createSearch(options, {
|
||||
getValue: identity,
|
||||
fuseOptions: {keys: [""]},
|
||||
}),
|
||||
}).searchValues,
|
||||
)
|
||||
|
||||
const select = (newValue: string) => {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import {hexToBytes, bytesToHex} from "@noble/hashes/utils"
|
||||
import * as nip19 from "nostr-tools/nip19"
|
||||
|
||||
export const displayList = <T>(xs: T[], conj = "and", n = 6, locale = "en-US") => {
|
||||
const stringItems = xs.map(String)
|
||||
|
||||
@@ -11,3 +14,13 @@ export const displayList = <T>(xs: T[], conj = "and", n = 6, locale = "en-US") =
|
||||
|
||||
return new Intl.ListFormat(locale, {style: "long", type: "conjunction"}).format(stringItems)
|
||||
}
|
||||
|
||||
export const nsecEncode = (secret: string) => nip19.nsecEncode(hexToBytes(secret))
|
||||
|
||||
export const nsecDecode = (nsec: string) => {
|
||||
const {type, data} = nip19.decode(nsec)
|
||||
|
||||
if (type !== "nsec") throw new Error(`Invalid nsec: ${nsec}`)
|
||||
|
||||
return bytesToHex(data)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script lang="ts">
|
||||
import "@src/app.css"
|
||||
import {onMount} from "svelte"
|
||||
import {nip19} from "nostr-tools"
|
||||
import * as nip19 from "nostr-tools/nip19"
|
||||
import {get, derived} from "svelte/store"
|
||||
import {App} from "@capacitor/app"
|
||||
import {dev} from "$app/environment"
|
||||
import {goto} from "$app/navigation"
|
||||
import {bytesToHex, hexToBytes} from "@noble/hashes/utils"
|
||||
import {identity, sleep, take, sortBy, ago, now, HOUR, WEEK, MONTH, Worker} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
@@ -21,6 +22,7 @@
|
||||
getPubkeyTagValues,
|
||||
getListTags,
|
||||
} from "@welshman/util"
|
||||
import {Nip46Broker, getPubkey, makeSecret} from "@welshman/signer"
|
||||
import {
|
||||
relays,
|
||||
handles,
|
||||
@@ -39,6 +41,7 @@
|
||||
getRelayUrls,
|
||||
subscribe,
|
||||
userInboxRelaySelections,
|
||||
addSession,
|
||||
} from "@welshman/app"
|
||||
import * as lib from "@welshman/lib"
|
||||
import * as util from "@welshman/util"
|
||||
@@ -49,9 +52,10 @@
|
||||
import ModalContainer from "@app/components/ModalContainer.svelte"
|
||||
import {setupTracking} from "@app/tracking"
|
||||
import {setupAnalytics} from "@app/analytics"
|
||||
import {nsecDecode} from "@lib/util"
|
||||
import {theme} from "@app/theme"
|
||||
import {INDEXER_RELAYS, userMembership, ensureUnwrapped, canDecrypt} from "@app/state"
|
||||
import {loadUserData} from "@app/commands"
|
||||
import {loadUserData, loginWithNip46} from "@app/commands"
|
||||
import {listenForNotifications} from "@app/requests"
|
||||
import * as commands from "@app/commands"
|
||||
import * as requests from "@app/requests"
|
||||
@@ -82,6 +86,34 @@
|
||||
...notifications,
|
||||
})
|
||||
|
||||
// Nstart login
|
||||
if (window.location.hash?.startsWith("#nostr-login")) {
|
||||
const params = new URLSearchParams(window.location.hash.slice(1))
|
||||
const login = params.get("nostr-login")
|
||||
|
||||
let success = false
|
||||
|
||||
try {
|
||||
if (login?.startsWith("bunker://")) {
|
||||
success = await loginWithNip46({
|
||||
clientSecret: makeSecret(),
|
||||
...Nip46Broker.parseBunkerUrl(login),
|
||||
})
|
||||
} else if (login) {
|
||||
const secret = nsecDecode(login)
|
||||
|
||||
addSession({method: "nip01", secret, pubkey: getPubkey(secret)})
|
||||
success = true
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
if (success) {
|
||||
goto("/home")
|
||||
}
|
||||
}
|
||||
|
||||
if (!db) {
|
||||
setupTracking()
|
||||
setupAnalytics()
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import {pubkey, signer, userMutes, tagPubkey, publishThunk} from "@welshman/app"
|
||||
import Field from "@lib/components/Field.svelte"
|
||||
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import ProfileMultiSelect from "@app/components/ProfileMultiSelect.svelte"
|
||||
import {pushToast} from "@app/toast"
|
||||
@@ -79,6 +80,24 @@
|
||||
{settings.send_delay === 1000 ? "second" : "seconds"}.
|
||||
</p>
|
||||
</FieldInline>
|
||||
<Field>
|
||||
<p slot="label">Media Server</p>
|
||||
<div slot="input" class="flex gap-2">
|
||||
<select bind:value={settings.upload_type} class="select select-bordered">
|
||||
<option value="nip96">NIP 96 (default)</option>
|
||||
<option value="blossom">Blossom</option>
|
||||
</select>
|
||||
<label class="input input-bordered flex flex-grow items-center gap-2">
|
||||
<Icon icon="link-round" />
|
||||
{#if settings.upload_type === "nip96"}
|
||||
<input class="grow" bind:value={settings.nip96_urls[0]} />
|
||||
{:else}
|
||||
<input class="grow" bind:value={settings.blossom_urls[0]} />
|
||||
{/if}
|
||||
</label>
|
||||
</div>
|
||||
<p slot="info">Choose a media server type and url for files you upload to flotilla.</p>
|
||||
</Field>
|
||||
<div class="mt-4 flex flex-row items-center justify-between gap-4">
|
||||
<Button class="btn btn-neutral" on:click={reset}>Discard Changes</Button>
|
||||
<Button type="submit" class="btn btn-primary">Save Changes</Button>
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
import {PLATFORM_NAME} from "@app/state"
|
||||
import {pushModal} from "@app/modal"
|
||||
|
||||
const hash = import.meta.env.VITE_BUILD_HASH
|
||||
|
||||
const pubkey = "97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322"
|
||||
|
||||
const openProfile = () => pushModal(ProfileDetail, {pubkey})
|
||||
@@ -48,6 +50,9 @@
|
||||
class="link"
|
||||
href="https://www.figma.com/community/file/1166831539721848736">480 Design</Link>
|
||||
</p>
|
||||
{#if hash}
|
||||
<p class="text-xs">Running build {hash.slice(0, 8)}</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex justify-center gap-4">
|
||||
<div class="tooltip" data-tip="Source Code">
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
import {now} from "@welshman/lib"
|
||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||
import {createEvent, MESSAGE, DELETE, REACTION} from "@welshman/util"
|
||||
import {formatTimestampAsDate, publishThunk, deriveRelay, repository} from "@welshman/app"
|
||||
import {slide, fade} from "@lib/transition"
|
||||
import {formatTimestampAsDate, pubkey, publishThunk, deriveRelay, repository} from "@welshman/app"
|
||||
import {slide, fade, fly} from "@lib/transition"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
@@ -26,7 +26,7 @@
|
||||
displayChannel,
|
||||
getEventsForUrl,
|
||||
} from "@app/state"
|
||||
import {setChecked} from "@app/notifications"
|
||||
import {setChecked, checked} from "@app/notifications"
|
||||
import {
|
||||
nip29,
|
||||
addRoomMembership,
|
||||
@@ -40,6 +40,7 @@
|
||||
import {pushToast} from "@app/toast"
|
||||
|
||||
const {room = GENERAL} = $page.params
|
||||
const lastChecked = $checked[$page.url.pathname]
|
||||
const content = popKey<string>("content") || ""
|
||||
const url = decodeRelay($page.params.relay)
|
||||
const filter = {kinds: [MESSAGE], "#h": [room]}
|
||||
@@ -89,11 +90,33 @@
|
||||
clearParent()
|
||||
}
|
||||
|
||||
const onScroll = () => {
|
||||
showScrollButton = Math.abs(element?.scrollTop || 0) > 1500
|
||||
|
||||
if (!newMessages || newMessagesSeen) {
|
||||
showFixedNewMessages = false
|
||||
} else {
|
||||
const {y} = newMessages.getBoundingClientRect()
|
||||
|
||||
if (y > 300) {
|
||||
newMessagesSeen = true
|
||||
} else {
|
||||
showFixedNewMessages = y < 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const scrollToNewMessages = () =>
|
||||
newMessages.scrollIntoView({behavior: "smooth", block: "center"})
|
||||
|
||||
const scrollToBottom = () => element.scrollTo({top: 0, behavior: "smooth"})
|
||||
|
||||
let parent: TrustedEvent | undefined
|
||||
let loading = true
|
||||
let element: HTMLElement
|
||||
let newMessages: HTMLElement
|
||||
let newMessagesSeen = false
|
||||
let showFixedNewMessages = false
|
||||
let showScrollButton = false
|
||||
let cleanup: () => void
|
||||
let events: Readable<TrustedEvent[]>
|
||||
@@ -107,6 +130,7 @@
|
||||
|
||||
let previousDate
|
||||
let previousPubkey
|
||||
let newMessagesSeen = false
|
||||
|
||||
if (events) {
|
||||
for (const event of $events.toReversed()) {
|
||||
@@ -118,6 +142,16 @@
|
||||
|
||||
const date = formatTimestampAsDate(created_at)
|
||||
|
||||
if (
|
||||
!newMessagesSeen &&
|
||||
event.pubkey !== $pubkey &&
|
||||
lastChecked &&
|
||||
created_at > lastChecked
|
||||
) {
|
||||
elements.push({type: "new-messages", id: "new-messages"})
|
||||
newMessagesSeen = true
|
||||
}
|
||||
|
||||
if (date !== previousDate) {
|
||||
elements.push({type: "date", value: date, id: date, showPubkey: false})
|
||||
}
|
||||
@@ -136,13 +170,12 @@
|
||||
}
|
||||
|
||||
elements.reverse()
|
||||
|
||||
setTimeout(onScroll, 100)
|
||||
}
|
||||
|
||||
$: {
|
||||
if (element) {
|
||||
element.addEventListener("scroll", () => {
|
||||
showScrollButton = Math.abs(element.scrollTop) > 1500
|
||||
})
|
||||
;({events, cleanup} = makeFeed({
|
||||
element,
|
||||
relays: [url],
|
||||
@@ -191,9 +224,19 @@
|
||||
</PageBar>
|
||||
<div
|
||||
class="scroll-container -mt-2 flex flex-grow flex-col-reverse overflow-y-auto overflow-x-hidden py-2"
|
||||
on:scroll={onScroll}
|
||||
bind:this={element}>
|
||||
{#each elements as { type, id, value, showPubkey } (id)}
|
||||
{#if type === "date"}
|
||||
{#if type === "new-messages"}
|
||||
<div
|
||||
bind:this={newMessages}
|
||||
class="flex items-center py-2 text-xs transition-colors"
|
||||
class:opacity-0={showFixedNewMessages}>
|
||||
<div class="h-px flex-grow bg-primary" />
|
||||
<p class="rounded-full bg-primary px-2 py-1 text-primary-content">New Messages</p>
|
||||
<div class="h-px flex-grow bg-primary" />
|
||||
</div>
|
||||
{:else if type === "date"}
|
||||
<Divider>{value}</Divider>
|
||||
{:else}
|
||||
<div in:slide class:-mt-1={!showPubkey}>
|
||||
@@ -209,6 +252,15 @@
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
{#if showFixedNewMessages}
|
||||
<div class="relative z-feature flex justify-center">
|
||||
<div transition:fly={{duration: 200}} class="fixed top-12">
|
||||
<Button class="btn btn-primary btn-xs rounded-full" on:click={scrollToNewMessages}>
|
||||
New Messages
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div>
|
||||
{#if parent}
|
||||
<ChannelComposeParent event={parent} clear={clearParent} />
|
||||
|
||||
Reference in New Issue
Block a user