diff --git a/package-lock.json b/package-lock.json index bd7313c5..81a8a206 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/package.json b/package.json index b73adf46..42ccf199 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "dev": "vite", "build": "vite build", "preview": "vite preview", - "lint": "eslint src/*/** --quiet" + "lint": "eslint src/*/** --quiet", + "check": "svelte-check --tsconfig ./tsconfig.json --threshold error" }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^1.1.0", @@ -23,6 +24,7 @@ "@bugsnag/js": "^7.18.0", "@fortawesome/fontawesome-free": "^6.2.1", "@noble/secp256k1": "^1.7.0", + "@tsconfig/svelte": "^3.0.0", "classnames": "^2.3.2", "compressorjs": "^1.1.1", "dexie": "^3.2.2", @@ -31,8 +33,10 @@ "hurdak": "github:ConsignCloud/hurdak", "nostr-tools": "^1.1.1", "ramda": "^0.28.0", + "svelte-check": "^3.0.3", "svelte-link-preview": "^0.3.3", "svelte-loading-spinners": "^0.3.4", + "svelte-preprocess": "^5.0.1", "svelte-routing": "^1.6.0", "svelte-switch": "^0.0.5", "throttle-debounce": "^5.0.0", diff --git a/src/App.svelte b/src/App.svelte index 80bb3bca..e0355f01 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -130,7 +130,7 @@ document.body.style.position = `fixed` } } else { - document.body.style = '' + document.body.setAttribute('style', '') window.scrollTo(0, scrollY) } }) @@ -151,18 +151,18 @@ {#key params.npub} - + {/key} {#key params.entity} - + {/key} {#key params.entity} - + {/key} @@ -173,7 +173,7 @@ {#key params.entity} - + {/key} @@ -259,10 +259,10 @@ border-b border-medium z-10" >
- +
- + Coracle Logo

Coracle

{#if $mostRecentAlert > $lastCheckedAlerts || hasNewMessages} @@ -272,12 +272,12 @@ {#if keys.canSign()} {/if} diff --git a/src/agent/data.js b/src/agent/data.js index 49a71109..917b8a42 100644 --- a/src/agent/data.js +++ b/src/agent/data.js @@ -31,7 +31,7 @@ export const ready = writable(false) export const people = writable([]) // Bootstrap our people observable -db.people.toArray().then($p => { +db.table('people').toArray().then($p => { people.set(createMap('pubkey', $p)) ready.set(true) }) @@ -51,7 +51,7 @@ export const updatePeople = async updates => { people.update($people => ({...$people, ...updates})) // Sync to our database - await db.people.bulkPut(Object.values(updates)) + await db.table('people').bulkPut(Object.values(updates)) } // Hooks @@ -126,7 +126,7 @@ const processProfileEvents = async events => { } }, default: () => { - console.log(`Received unsupported event type ${event.kind}`) + console.log(`Received unsupported event type ${e.kind}`) }, }), updated_at: now(), @@ -155,7 +155,7 @@ const processRoomEvents = async events => { continue } - const room = await db.rooms.get(roomId) + const room = await db.table('rooms').get(roomId) // Merge edits but don't let old ones override new ones if (room?.edited_at > e.created_at) { @@ -178,7 +178,7 @@ const processRoomEvents = async events => { } } - await db.rooms.bulkPut(Object.values(updates)) + await db.table('rooms').bulkPut(Object.values(updates)) } const processMessages = async events => { @@ -187,7 +187,7 @@ const processMessages = async events => { .map(e => ({...e, recipient: Tags.from(e).type("p").values().first()})) if (messages.length > 0) { - await db.messages.bulkPut(messages) + await db.table('messages').bulkPut(messages) } } diff --git a/src/agent/index.js b/src/agent/index.ts similarity index 93% rename from src/agent/index.js rename to src/agent/index.ts index de8c74b0..a641d7b8 100644 --- a/src/agent/index.js +++ b/src/agent/index.ts @@ -37,7 +37,7 @@ export const getFollows = pubkey => { return Tags.wrap(person?.petnames || defaults.petnames).values().all() } -export const getRelays = pubkey => { +export const getRelays = (pubkey?: string) => { let relays = getPerson(pubkey)?.relays if (!relays?.length) { @@ -71,7 +71,7 @@ export const publish = async (relays, event) => { return signedEvent } -export const load = async (relays, filter, opts) => { +export const load = async (relays, filter, opts?) => { const events = await pool.request(relays, filter, opts) await processEvents(events) @@ -79,7 +79,7 @@ export const load = async (relays, filter, opts) => { return events } -export const listen = async (relays, filter, onEvent, {shouldProcess = true} = {}) => { +export const listen = async (relays, filter, onEvent, {shouldProcess = true}: any = {}) => { const sub = await pool.subscribe(relays, filter) sub.onEvent(e => { diff --git a/src/agent/keys.js b/src/agent/keys.ts similarity index 75% rename from src/agent/keys.js rename to src/agent/keys.ts index 9bd9285c..0b6f5ed5 100644 --- a/src/agent/keys.js +++ b/src/agent/keys.ts @@ -7,8 +7,8 @@ let signingFunction const pubkey = synced('agent/user/pubkey') const privkey = synced('agent/user/privkey') -const hasExtension = () => Boolean(window.nostr) -const canSign = () => Boolean(hasExtension() || get(privkey)) +const getExtension = () => (window as {nostr?: any}).nostr +const canSign = () => Boolean(getExtension() || get(privkey)) const setPrivateKey = _privkey => { privkey.set(_privkey) @@ -16,9 +16,11 @@ const setPrivateKey = _privkey => { } const setPublicKey = _pubkey => { - if (window.nostr) { + const nostr = getExtension() + + if (nostr) { signingFunction = async event => { - const {sig} = await window.nostr.signEvent(event) + const {sig} = await nostr.signEvent(event) return sig } @@ -44,8 +46,9 @@ const sign = async event => { const getCrypt = () => { const $privkey = get(privkey) + const nostr = getExtension() - if (!$privkey && !hasExtension()) { + if (!$privkey && !nostr) { throw new Error('No encryption method available.') } @@ -53,13 +56,13 @@ const getCrypt = () => { encrypt: (pubkey, message) => { return $privkey ? nip04.encrypt($privkey, pubkey, message) - : window.nostr.nip04.encrypt(pubkey, message) + : nostr.nip04.encrypt(pubkey, message) }, decrypt: async (pubkey, message) => { try { return $privkey ? nip04.decrypt($privkey, pubkey, message) - : await window.nostr.nip04.decrypt(pubkey, message) + : await nostr.nip04.decrypt(pubkey, message) } catch (e) { console.error(e) return `` @@ -72,6 +75,6 @@ const getCrypt = () => { setPublicKey(get(pubkey)) export default { - pubkey, privkey, hasExtension, canSign, setPrivateKey, setPublicKey, clear, + pubkey, privkey, canSign, setPrivateKey, setPublicKey, clear, sign, getCrypt, } diff --git a/src/agent/pool.js b/src/agent/pool.js index 45e480e8..a01b4f71 100644 --- a/src/agent/pool.js +++ b/src/agent/pool.js @@ -71,7 +71,7 @@ const findConnection = url => find(whereEq({url}), connections) const connect = async url => { const conn = findConnection(url) || new Connection(url) - await db.relays.put({url}) + await db.table('relays').put({url}) await Promise.race([conn.connect(), sleep(5000)]) if (conn.status === 'ready') { diff --git a/src/app/alerts.js b/src/app/alerts.js index 305f9ba7..fe98c6fd 100644 --- a/src/app/alerts.js +++ b/src/app/alerts.js @@ -17,7 +17,7 @@ const onChunk = async (relays, pubkey, events) => { const context = await loaders.loadContext(relays, events, {threshold: 2}) const notes = threadify(events, context, {muffle: getMuffle()}) - await db.alerts.bulkPut(notes) + await db.table('alerts').bulkPut(notes) mostRecentAlert.update($t => events.reduce((t, e) => Math.max(t, e.created_at), $t)) } diff --git a/src/app/index.js b/src/app/index.ts similarity index 96% rename from src/app/index.js rename to src/app/index.ts index 3a76e49d..b07798de 100644 --- a/src/app/index.js +++ b/src/app/index.ts @@ -14,7 +14,7 @@ import loaders from 'src/app/loaders' export {toast, modal, settings, alerts, messages, logUsage} -export const login = async ({privkey, pubkey}, usingExtension = false) => { +export const login = async ({privkey, pubkey}: {privkey?: string, pubkey?: string}, usingExtension = false) => { if (privkey) { keys.setPrivateKey(privkey) } else { @@ -79,7 +79,7 @@ export const setRelayWriteCondition = async (url, write) => { } } -export const render = (note, {showEntire = false}) => { +export const renderNote = (note, {showEntire = false}) => { const shouldEllipsize = note.content.length > 500 && !showEntire const $people = get(people) const peopleByPubkey = createMap( diff --git a/src/app/loaders.js b/src/app/loaders.ts similarity index 97% rename from src/app/loaders.js rename to src/app/loaders.ts index 6681ceca..8dc28081 100644 --- a/src/app/loaders.js +++ b/src/app/loaders.ts @@ -44,7 +44,7 @@ const loadNetwork = async (relays, pubkey) => { await loadPeople(tags.relays(), tags.values().all()) } -const loadContext = async (relays, notes, {loadParents = false, depth = 0, ...opts} = {}) => { +const loadContext = async (relays, notes, {loadParents = false, depth = 0, ...opts}: any = {}) => { notes = ensurePlural(notes) if (notes.length === 0) { @@ -58,7 +58,7 @@ const loadContext = async (relays, notes, {loadParents = false, depth = 0, ...op const parentTags = uniq(chunk.map(findReply).filter(identity)) const parentIds = Tags.wrap(parentTags).values().all() const combinedRelays = uniq(relays.concat(Tags.wrap(parentTags).relays())) - const filter = [{kinds: [1, 7], '#e': chunkIds}] + const filter = [{kinds: [1, 7], '#e': chunkIds} as {}] if (authors.length > 0) { filter.push({kinds: personKinds, authors}) diff --git a/src/app/messages.js b/src/app/messages.js index fc2547ad..e850fec1 100644 --- a/src/app/messages.js +++ b/src/app/messages.js @@ -25,7 +25,7 @@ const listen = async (relays, pubkey) => { // Reload annotated messages, don't alert about messages to self const messages = reject( e => e.pubkey === e.recipient, - await db.messages.toArray() + await db.table('messages').toArray() ) if (messages.length > 0) { diff --git a/src/app/ui.js b/src/app/ui.ts similarity index 90% rename from src/app/ui.js rename to src/app/ui.ts index b34ebbe7..ad740834 100644 --- a/src/app/ui.js +++ b/src/app/ui.ts @@ -1,6 +1,7 @@ import Bugsnag from "@bugsnag/js" import {prop} from "ramda" import {uuid} from "hurdak/lib/hurdak" +import type {Writable} from 'svelte/store' import {navigate} from "svelte-routing" import {nip19} from 'nostr-tools' import {writable, get} from "svelte/store" @@ -15,7 +16,11 @@ export const routes = { // Toast -export const toast = writable(null) +export interface Toast extends Writable { + show(type: string, message: string, timeout?: number): void +} + +export const toast = writable(null) as Toast toast.show = (type, message, timeout = 5) => { const id = uuid() @@ -29,6 +34,7 @@ toast.show = (type, message, timeout = 5) => { }, timeout * 1000) } + // Modals export const modal = { diff --git a/src/main.js b/src/main.js index fbc6d284..d60b0d51 100644 --- a/src/main.js +++ b/src/main.js @@ -9,9 +9,6 @@ Bugsnag.start({ import App from 'src/App.svelte' -// Annoying global always fails silently. TODO: figure out an eslint rule instead -window.find = null - const app = new App({ target: document.getElementById('app') }) diff --git a/src/partials/Card.svelte b/src/partials/Card.svelte index fac3e63a..042a87a0 100644 --- a/src/partials/Card.svelte +++ b/src/partials/Card.svelte @@ -6,7 +6,7 @@ export let invertColors = false -
  • -
  • + diff --git a/src/partials/Channel.svelte b/src/partials/Channel.svelte index 5cfb41e7..b8fddc2d 100644 --- a/src/partials/Channel.svelte +++ b/src/partials/Channel.svelte @@ -9,10 +9,10 @@ import Anchor from 'src/partials/Anchor.svelte' import Spinner from 'src/partials/Spinner.svelte' import {user, getPerson} from 'src/agent' - import {render} from 'src/app' + import {renderNote} from 'src/app' export let name - export let link + export let link = null export let about export let picture export let loadMessages @@ -42,7 +42,7 @@ } // flex-col means the first is the last - const getLastListItem = () => document.querySelector('ul[name=messages] li') + const getLastListItem = () => document.querySelector('ul[class=channel-messages] li') const stickToBottom = async (behavior, cb) => { const shouldStick = window.scrollY + window.innerHeight > document.body.scrollHeight - 200 @@ -123,7 +123,7 @@
    -
      +
        {#each annotatedMessages as m (m.id)}
      • {#if type === 'chat' && m.showPerson} @@ -142,7 +142,7 @@ 'bg-light text-black rounded-br-none': type === 'dm' && m.person.pubkey === $user.pubkey, 'bg-dark rounded-bl-none': type === 'dm' && m.person.pubkey !== $user.pubkey, })}> - {@html render(m, {showEntire: true})} + {@html renderNote(m, {showEntire: true})}
    @@ -157,7 +157,9 @@
    - navigate("/chat")} /> + {/if}
    @@ -199,12 +201,12 @@ on:keypress={onKeyPress} class="w-full p-2 text-white bg-medium placeholder:text-light outline-0 resize-none" /> -
    -
    +
    {#if showNewMessages} diff --git a/src/partials/Compose.svelte b/src/partials/Compose.svelte index 525fe1a7..3c0a28be 100644 --- a/src/partials/Compose.svelte +++ b/src/partials/Compose.svelte @@ -180,13 +180,13 @@ {#if suggestions.length > 0}
    {#each suggestions as person, i (person.pubkey)} -
    pickSuggestion(person)}> -
    + {/each}
    {/if} diff --git a/src/partials/Input.svelte b/src/partials/Input.svelte index e5550ce6..b09fb179 100644 --- a/src/partials/Input.svelte +++ b/src/partials/Input.svelte @@ -2,7 +2,7 @@ import cx from "classnames" export let wrapperClass = "" - export let value + export let value = "" const className = cx( $$props.class, diff --git a/src/partials/Like.svelte b/src/partials/Like.svelte index 0ad25bb4..7b65b374 100644 --- a/src/partials/Like.svelte +++ b/src/partials/Like.svelte @@ -24,29 +24,29 @@ } -
    modal.set({type: 'note/detail', note})}>
    - + {#if isOpen} -
    -
    +
    + {/if}

    {formatTimestamp(note.created_at)}

    {ellipsize(note.content, 120)}
    -
    + diff --git a/src/partials/Modal.svelte b/src/partials/Modal.svelte index 2cea8b81..f84e6ecb 100644 --- a/src/partials/Modal.svelte +++ b/src/partials/Modal.svelte @@ -12,7 +12,7 @@ }} />
    -
    diff --git a/src/partials/Note.svelte b/src/partials/Note.svelte index eb9bad17..aad26c87 100644 --- a/src/partials/Note.svelte +++ b/src/partials/Note.svelte @@ -1,4 +1,4 @@ - { - if (!e.target.closest('.fa-reply') && !e.target.closest('.note-reply')) { - resetReply() - } - }} + on:click={onBodyClick} on:keydown={e => { if (e.key === 'Escape') { resetReply() @@ -150,30 +156,28 @@

    {:else}
    -

    {@html render(note, {showEntire})}

    +

    {@html renderNote(note, {showEntire})}

    {#each links.slice(-2) as link}
    -
    e.stopPropagation()}> +
    +
    {/each}
    - +
    - like ? deleteReaction(like) : react("+")} /> + on:click|stopPropagation={() => like ? deleteReaction(like) : react("+")} /> {likes.length}
    - react("-")} /> +
    @@ -185,13 +189,13 @@
    -
    -
    +
    {#if replyMentions.length > 0} @@ -201,7 +205,7 @@
    {#each replyMentions as p}
    - removeMention(p)} /> +
    {/each} @@ -214,10 +218,10 @@ {#if depth > 0}
    {#if !showEntire && note.replies.length > 3} -
    +
    + {/if} {#each note.replies.slice(showEntire ? 0 : -3) as r (r.id)} diff --git a/src/partials/Notes.svelte b/src/partials/Notes.svelte index b69e2779..33340fba 100644 --- a/src/partials/Notes.svelte +++ b/src/partials/Notes.svelte @@ -48,12 +48,12 @@ {#if newNotes.length > 0} -
    Load {quantify(newNotes.length, 'new note')} -
    + {/if}
    diff --git a/src/partials/Preview.svelte b/src/partials/Preview.svelte index d971a873..3e8dfcb1 100644 --- a/src/partials/Preview.svelte +++ b/src/partials/Preview.svelte @@ -36,7 +36,7 @@ href={url} class="rounded border border-solid border-medium flex flex-col bg-white overflow-hidden"> {#if preview.image} - + Link preview
    {/if} {#if preview.title} diff --git a/src/partials/Room.svelte b/src/partials/Room.svelte index 3a873a69..0655bb65 100644 --- a/src/partials/Room.svelte +++ b/src/partials/Room.svelte @@ -33,7 +33,7 @@ }) -
  • setRoom(room)} in:fly={{y: 20}}> @@ -81,4 +81,4 @@

    {/if}
  • - + diff --git a/src/partials/RoomBadge.svelte b/src/partials/RoomBadge.svelte index 05da05ab..052ee526 100644 --- a/src/partials/RoomBadge.svelte +++ b/src/partials/RoomBadge.svelte @@ -3,7 +3,7 @@ export let setRoom -
    setRoom(room.id)}>
    {/if}
    -
    + diff --git a/src/partials/SelectButton.svelte b/src/partials/SelectButton.svelte index d2258972..681ef27f 100644 --- a/src/partials/SelectButton.svelte +++ b/src/partials/SelectButton.svelte @@ -9,14 +9,14 @@
    {#each options as option, i} - 0, "bg-accent": value === option, })} on:click={() => { value = option }}> {option} - + {/each}
    diff --git a/src/partials/Tabs.svelte b/src/partials/Tabs.svelte index 1edd022f..b6006e28 100644 --- a/src/partials/Tabs.svelte +++ b/src/partials/Tabs.svelte @@ -7,13 +7,13 @@ export let setActiveTab -
      +
      {#each tabs as tab} -
    • setActiveTab(tab)}> {toTitle(tab)} -
    • + {/each} -
    +
    diff --git a/src/routes/Alerts.svelte b/src/routes/Alerts.svelte index 8a296f4b..bc0b6fe4 100644 --- a/src/routes/Alerts.svelte +++ b/src/routes/Alerts.svelte @@ -18,7 +18,7 @@ return createScroller(async () => { limit += 10 - const events = await db.alerts.toArray() + const events = await db.table('alerts').toArray() const notes = events.filter(e => e.kind === 1) const likes = events.filter(e => e.kind === 7) diff --git a/src/routes/Bech32Entity.svelte b/src/routes/Bech32Entity.svelte index 8e8b1387..93e39aa9 100644 --- a/src/routes/Bech32Entity.svelte +++ b/src/routes/Bech32Entity.svelte @@ -1,4 +1,4 @@ - diff --git a/src/routes/Chat.svelte b/src/routes/Chat.svelte index b52a1289..12b566c5 100644 --- a/src/routes/Chat.svelte +++ b/src/routes/Chat.svelte @@ -19,8 +19,8 @@ const {mostRecentByPubkey} = messages const rooms = lq(async () => { - const rooms = await db.rooms.where('joined').equals(1).toArray() - const messages = await db.messages.toArray() + const rooms = await db.table('rooms').where('joined').equals(1).toArray() + const messages = await db.table('messages').toArray() const pubkeys = without([$user.pubkey], uniq(messages.flatMap(m => [m.pubkey, m.recipient]))) await loaders.loadPeople(getRelays(), pubkeys) @@ -31,7 +31,7 @@ }) const search = lq(async () => { - const rooms = await db.rooms.where('joined').equals(0).toArray() + const rooms = await db.table('rooms').where('joined').equals(0).toArray() roomsCount = rooms.length @@ -49,11 +49,11 @@ } const joinRoom = id => { - db.rooms.where('id').equals(id).modify({joined: 1}) + db.table('rooms').where('id').equals(id).modify({joined: 1}) } const leaveRoom = id => { - db.rooms.where('id').equals(id).modify({joined: 0}) + db.table('rooms').where('id').equals(id).modify({joined: 0}) } onMount(() => { diff --git a/src/routes/ChatRoom.svelte b/src/routes/ChatRoom.svelte index 598c85b1..8744ea77 100644 --- a/src/routes/ChatRoom.svelte +++ b/src/routes/ChatRoom.svelte @@ -1,4 +1,4 @@ - diff --git a/src/routes/Messages.svelte b/src/routes/Messages.svelte index ab47f500..0e28acc5 100644 --- a/src/routes/Messages.svelte +++ b/src/routes/Messages.svelte @@ -1,4 +1,4 @@ - diff --git a/src/views/SignUp.svelte b/src/views/SignUp.svelte index 340fd574..b3ac82a5 100644 --- a/src/views/SignUp.svelte +++ b/src/views/SignUp.svelte @@ -1,4 +1,4 @@ -