Require braces on conditionals
This commit is contained in:
parent
ef6069e7a4
commit
da6a63870a
88 changed files with 717 additions and 240 deletions
|
|
@ -86,7 +86,9 @@ export const boot = async (
|
|||
await shell.waitFor({state: "attached", timeout: ms(15)})
|
||||
break
|
||||
} catch (e) {
|
||||
if (attempt === 3) throw e
|
||||
if (attempt === 3) {
|
||||
throw e
|
||||
}
|
||||
|
||||
await page.reload()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,9 @@ export const watchFaults = (): FaultWatch => {
|
|||
const record = (line: string, fault: boolean) => {
|
||||
log.push(line)
|
||||
|
||||
if (fault) found.push(line)
|
||||
if (fault) {
|
||||
found.push(line)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -217,7 +217,9 @@ export const test = base.extend<HarnessFixtures, HarnessWorkerFixtures>({
|
|||
let scenario: Maybe<Scenario>
|
||||
|
||||
const requireScenario = () => {
|
||||
if (scenario) return scenario
|
||||
if (scenario) {
|
||||
return scenario
|
||||
}
|
||||
|
||||
throw new Error("Seed a scenario before opening a page for a user")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@ export const makeContextStore = <T>(installer: string) => {
|
|||
get: (context: BrowserContext) => {
|
||||
const value = byContext.get(context)
|
||||
|
||||
if (value) return value
|
||||
if (value) {
|
||||
return value
|
||||
}
|
||||
|
||||
throw new Error(`${installer} was never called for this browser context`)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -56,7 +56,9 @@ export const makePublisher = ({name, enqueue, startedAt, sign}: PublisherOptions
|
|||
})
|
||||
|
||||
return () => {
|
||||
if (value) return value
|
||||
if (value) {
|
||||
return value
|
||||
}
|
||||
|
||||
throw new Error(`An event seeded into "${name}" was read before seed() published it`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,7 +115,9 @@ export const seed = async (
|
|||
const getSpace = (name: SpaceName) => {
|
||||
const space = spaces.get(name)
|
||||
|
||||
if (space) return space
|
||||
if (space) {
|
||||
return space
|
||||
}
|
||||
|
||||
throw new Error(`No space named "${name}" was seeded`)
|
||||
}
|
||||
|
|
@ -123,7 +125,9 @@ export const seed = async (
|
|||
const getOpenRelay = (name: OpenRelayName) => {
|
||||
const relay = opened.find(candidate => candidate.name === name)
|
||||
|
||||
if (relay) return relay
|
||||
if (relay) {
|
||||
return relay
|
||||
}
|
||||
|
||||
throw new Error(`No open relay named "${name}" was seeded`)
|
||||
}
|
||||
|
|
@ -133,8 +137,12 @@ export const seed = async (
|
|||
const roomList = roomLists.get(user.pubkey)
|
||||
const relayList = relayLists.get(user.pubkey)
|
||||
|
||||
if (roomList) events.push(roomList)
|
||||
if (relayList) events.push(relayList)
|
||||
if (roomList) {
|
||||
events.push(roomList)
|
||||
}
|
||||
if (relayList) {
|
||||
events.push(relayList)
|
||||
}
|
||||
|
||||
return events
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,7 +91,9 @@ export const seedSpace = ({zooid, enqueue, startedAt, name}: SeedSpaceOptions):
|
|||
let testRelay: Maybe<TestRelay>
|
||||
|
||||
const relay = () => {
|
||||
if (testRelay) return testRelay
|
||||
if (testRelay) {
|
||||
return testRelay
|
||||
}
|
||||
|
||||
throw new Error(`Space "${name}" has not been seeded yet, await seed() first`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,7 +139,9 @@ const describeClockDrift = (drift: number) => {
|
|||
const getTestUser = (pubkey: string) => {
|
||||
const user = testUsersByPubkey.get(pubkey)
|
||||
|
||||
if (user) return user
|
||||
if (user) {
|
||||
return user
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Cannot publish as ${pubkey}: zooid authenticates every write, so seeded events must be ` +
|
||||
|
|
@ -190,7 +192,9 @@ export class Zooid {
|
|||
// Verifies docker rather than bringing the container up, which `reset` does. Repeat calls are
|
||||
// free, so the fixture can call this per test.
|
||||
start = async () => {
|
||||
if (this.started) return
|
||||
if (this.started) {
|
||||
return
|
||||
}
|
||||
|
||||
const problem = await describeDockerProblem()
|
||||
|
||||
|
|
@ -381,7 +385,9 @@ export class Zooid {
|
|||
const key = `${host} ${user.pubkey}`
|
||||
const session = this.sessions.get(key)
|
||||
|
||||
if (session) return session
|
||||
if (session) {
|
||||
return session
|
||||
}
|
||||
|
||||
const connection = connectToZooid(host)
|
||||
const [, challenge] = await connection.wait(isRelayAuth)
|
||||
|
|
|
|||
|
|
@ -46,11 +46,21 @@ export const makeTestRelay = ({name, url, publish}: TestRelayOptions): TestRelay
|
|||
|
||||
const tags = [["h", h]]
|
||||
|
||||
if (meta.name) tags.push(["name", meta.name])
|
||||
if (meta.about) tags.push(["about", meta.about])
|
||||
if (meta.picture) tags.push(["picture", meta.picture])
|
||||
if (meta.closed) tags.push(["closed"])
|
||||
if (meta.private) tags.push(["private"])
|
||||
if (meta.name) {
|
||||
tags.push(["name", meta.name])
|
||||
}
|
||||
if (meta.about) {
|
||||
tags.push(["about", meta.about])
|
||||
}
|
||||
if (meta.picture) {
|
||||
tags.push(["picture", meta.picture])
|
||||
}
|
||||
if (meta.closed) {
|
||||
tags.push(["closed"])
|
||||
}
|
||||
if (meta.private) {
|
||||
tags.push(["private"])
|
||||
}
|
||||
|
||||
await event(users.admin, makeEvent(ROOM_EDIT_META, {tags, created_at: createdAt}))
|
||||
},
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ export default [
|
|||
"no-useless-escape": "off",
|
||||
"no-extra-semi": "off",
|
||||
"no-async-promise-executor": "off",
|
||||
curly: ["error", "all"],
|
||||
"prefer-const": ["error", {destructuring: "all"}],
|
||||
"no-eq-null": "error",
|
||||
"no-restricted-syntax": [
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ export const argon2id = ({
|
|||
hashLength,
|
||||
outputType,
|
||||
}) => {
|
||||
if (outputType !== "binary") throw new Error("Expected Pomade's binary Argon2id output")
|
||||
if (outputType !== "binary") {
|
||||
throw new Error("Expected Pomade's binary Argon2id output")
|
||||
}
|
||||
return argon2idAsync(password, salt, {
|
||||
t: iterations,
|
||||
m: memorySize,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import {verifyEvent} from "nostr-tools/pure"
|
|||
// Welshman's optional accelerator expects verification to throw on invalid events.
|
||||
export const initNostrWasm = async () => ({
|
||||
verifyEvent(event) {
|
||||
if (!verifyEvent(event)) throw new Error("Invalid Nostr event")
|
||||
if (!verifyEvent(event)) {
|
||||
throw new Error("Invalid Nostr event")
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -15,8 +15,11 @@ const run = (command, args, options = {}) =>
|
|||
const child = spawn(command, args, {cwd: root, env, stdio: "inherit", ...options})
|
||||
child.on("error", reject)
|
||||
child.on("exit", (code, signal) => {
|
||||
if (code === 0) resolve()
|
||||
else reject(new Error(`${command} failed (${signal || code})`))
|
||||
if (code === 0) {
|
||||
resolve()
|
||||
} else {
|
||||
reject(new Error(`${command} failed (${signal || code})`))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -235,7 +235,9 @@ export const attemptRelayAccess = async (url: string, claim = "") => {
|
|||
const thunk = await publishJoinRequest(url, claim)
|
||||
const error = await thunk.waitForError()
|
||||
|
||||
if (shouldIgnoreError(error)) return
|
||||
if (shouldIgnoreError(error)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (error.includes("invite code")) {
|
||||
return "join request rejected"
|
||||
|
|
@ -282,11 +284,21 @@ export class Access {
|
|||
this.claimFailed,
|
||||
],
|
||||
([$loading, $isGeneric, $isExplicit, $roomInviteError, $roomCode, $claimFailed]) => {
|
||||
if ($loading) return "loading" as const
|
||||
if ($isGeneric) return "network" as const
|
||||
if ($isExplicit || $roomInviteError) return "auth" as const
|
||||
if (requireCode && !$roomCode) return "failed" as const
|
||||
if ($claimFailed) return "noclaim" as const
|
||||
if ($loading) {
|
||||
return "loading" as const
|
||||
}
|
||||
if ($isGeneric) {
|
||||
return "network" as const
|
||||
}
|
||||
if ($isExplicit || $roomInviteError) {
|
||||
return "auth" as const
|
||||
}
|
||||
if (requireCode && !$roomCode) {
|
||||
return "failed" as const
|
||||
}
|
||||
if ($claimFailed) {
|
||||
return "noclaim" as const
|
||||
}
|
||||
|
||||
return "ready" as const
|
||||
},
|
||||
|
|
|
|||
|
|
@ -81,7 +81,9 @@ const compareScores = (a: LayoutScore, b: LayoutScore): number => {
|
|||
"aspectDeviation",
|
||||
]
|
||||
for (const field of fields) {
|
||||
if (a[field] !== b[field]) return a[field] - b[field]
|
||||
if (a[field] !== b[field]) {
|
||||
return a[field] - b[field]
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
|
@ -121,7 +123,9 @@ const buildCandidate = (
|
|||
|
||||
const availHeight = (containerHeight - (totalRowCount - 1) * gap) / totalRowCount
|
||||
const availWidth = (containerWidth - (columnCount - 1) * gap) / columnCount
|
||||
if (availWidth <= 0 || availHeight <= 0) return undefined
|
||||
if (availWidth <= 0 || availHeight <= 0) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const {tileWidth, tileHeight, aspectRatio} = fitTile(availWidth, availHeight, minAspect)
|
||||
|
||||
|
|
@ -171,7 +175,9 @@ export const computeAdaptiveGrid = (
|
|||
containerWidth: number,
|
||||
containerHeight: number,
|
||||
): AdaptiveTileGrid | undefined => {
|
||||
if (tileCount <= 0 || containerWidth <= 0 || containerHeight <= 0) return undefined
|
||||
if (tileCount <= 0 || containerWidth <= 0 || containerHeight <= 0) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const minAspect = containerWidth / containerHeight >= 4 / 3 ? 1 : TILE_ASPECT_PORTRAIT
|
||||
|
||||
|
|
@ -186,7 +192,9 @@ export const computeAdaptiveGrid = (
|
|||
containerHeight,
|
||||
minAspect,
|
||||
)
|
||||
if (!candidate) continue
|
||||
if (!candidate) {
|
||||
continue
|
||||
}
|
||||
|
||||
const {tileWidth, tileHeight, aspectRatio} = candidate.rows[0]
|
||||
const verticalOverflow = Math.max(0, candidate.totalHeight - containerHeight)
|
||||
|
|
|
|||
|
|
@ -71,14 +71,18 @@ export const joinVoiceRoom = async (
|
|||
whenTimeout(15_000, {message: "Leaving previous call timed out.", signal: settle.signal}),
|
||||
whenAborted(signal),
|
||||
]).catch(e => {
|
||||
if (e instanceof AbortError) throw e
|
||||
if (e instanceof AbortError) {
|
||||
throw e
|
||||
}
|
||||
})
|
||||
|
||||
// leaveVoiceRoom flips callState to Disconnected; re-assert Joining.
|
||||
callState.set(CallState.Joining)
|
||||
}
|
||||
|
||||
if (signal.aborted) throw new AbortError()
|
||||
if (signal.aborted) {
|
||||
throw new AbortError()
|
||||
}
|
||||
|
||||
const {server_url, participant_token} = await Promise.race([
|
||||
fetchLivekitToken(url, h, signal),
|
||||
|
|
@ -89,7 +93,9 @@ export const joinVoiceRoom = async (
|
|||
whenAborted(signal),
|
||||
])
|
||||
|
||||
if (signal.aborted) throw new AbortError()
|
||||
if (signal.aborted) {
|
||||
throw new AbortError()
|
||||
}
|
||||
|
||||
const liveKitRoom = new LiveKitRoom({adaptiveStream: true, dynacast: true})
|
||||
activeRoom = liveKitRoom
|
||||
|
|
@ -157,7 +163,9 @@ export const joinVoiceRoom = async (
|
|||
clearReconnectSchedule()
|
||||
playJoinSound()
|
||||
} catch (e) {
|
||||
if (isActive()) callState.set(CallState.Disconnected)
|
||||
if (isActive()) {
|
||||
callState.set(CallState.Disconnected)
|
||||
}
|
||||
if (e instanceof AbortError) {
|
||||
clearReconnectSchedule()
|
||||
return
|
||||
|
|
@ -165,14 +173,18 @@ export const joinVoiceRoom = async (
|
|||
throw e
|
||||
} finally {
|
||||
settle.abort()
|
||||
if (isActive()) joinAbortController = undefined
|
||||
if (isActive()) {
|
||||
joinAbortController = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const leaveVoiceRoom = async () => {
|
||||
clearReconnectSchedule()
|
||||
const session = get(currentCallSession)
|
||||
if (!session) return
|
||||
if (!session) {
|
||||
return
|
||||
}
|
||||
|
||||
const audio = new Audio("/leave-voice-room.mp3")
|
||||
audio.play().catch(() => {})
|
||||
|
|
@ -220,7 +232,9 @@ export const cancelJoinVoiceRoom = () => {
|
|||
|
||||
export const toggleMute = async () => {
|
||||
const session = get(currentCallSession)
|
||||
if (!session) return
|
||||
if (!session) {
|
||||
return
|
||||
}
|
||||
|
||||
callMicMuted.update(not)
|
||||
|
||||
|
|
@ -238,7 +252,9 @@ export const toggleMute = async () => {
|
|||
|
||||
export const toggleCamera = async () => {
|
||||
const session = get(currentCallSession)
|
||||
if (!session) return
|
||||
if (!session) {
|
||||
return
|
||||
}
|
||||
|
||||
const cameraOn = !session.cameraOn
|
||||
try {
|
||||
|
|
@ -254,7 +270,9 @@ export const toggleCamera = async () => {
|
|||
|
||||
export const toggleScreenShare = async () => {
|
||||
const session = get(currentCallSession)
|
||||
if (!session) return
|
||||
if (!session) {
|
||||
return
|
||||
}
|
||||
|
||||
const screenShareOn = !session.screenShareOn
|
||||
try {
|
||||
|
|
@ -273,7 +291,9 @@ export const switchCallActiveDevice = async (
|
|||
targetDeviceId: string,
|
||||
): Promise<void> => {
|
||||
const session = get(currentCallSession)
|
||||
if (!session) return
|
||||
if (!session) {
|
||||
return
|
||||
}
|
||||
const id = targetDeviceId === "" ? LIVEKIT_DEFAULT_DEVICE_ID : targetDeviceId
|
||||
try {
|
||||
await session.livekit.switchActiveDevice(kind, id)
|
||||
|
|
@ -321,11 +341,15 @@ currentCallSession.subscribe(session => {
|
|||
if (session) {
|
||||
hadCallSession = true
|
||||
audioResumeListener ??= App.addListener("appStateChange", ({isActive}) => {
|
||||
if (isActive) void reacquireMicrophoneIfNeeded()
|
||||
if (isActive) {
|
||||
void reacquireMicrophoneIfNeeded()
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!hadCallSession) return
|
||||
if (!hadCallSession) {
|
||||
return
|
||||
}
|
||||
hadCallSession = false
|
||||
void audioResumeListener?.then(l => l.remove())
|
||||
audioResumeListener = undefined
|
||||
|
|
@ -334,7 +358,9 @@ currentCallSession.subscribe(session => {
|
|||
})
|
||||
|
||||
const teardownRoom = (livekit: LiveKitRoom) => {
|
||||
if (activeRoom === livekit) activeRoom = undefined
|
||||
if (activeRoom === livekit) {
|
||||
activeRoom = undefined
|
||||
}
|
||||
|
||||
// Dropping the listeners keeps TrackUnsubscribed from removing the hidden
|
||||
// audio elements onTrackSubscribed appended, so detach them here instead.
|
||||
|
|
@ -361,7 +387,9 @@ const syncParticipantMedia = (participant: Participant) => {
|
|||
const state = participantMediaFrom(participant)
|
||||
participantMediaState.update(m => {
|
||||
const prev = m.get(participant.identity)
|
||||
if (prev?.muted === state.muted && prev?.cameraOn === state.cameraOn) return m
|
||||
if (prev?.muted === state.muted && prev?.cameraOn === state.cameraOn) {
|
||||
return m
|
||||
}
|
||||
const next = new Map(m)
|
||||
next.set(participant.identity, state)
|
||||
return next
|
||||
|
|
@ -370,7 +398,9 @@ const syncParticipantMedia = (participant: Participant) => {
|
|||
|
||||
// LiveKit does not emit ParticipantConnected/Disconnected during reconnect.
|
||||
const resyncAfterReconnect = (livekit: LiveKitRoom) => {
|
||||
if (livekit !== activeRoom) return
|
||||
if (livekit !== activeRoom) {
|
||||
return
|
||||
}
|
||||
|
||||
const next = new Map<string, ParticipantMediaState>()
|
||||
for (const p of [livekit.localParticipant, ...livekit.remoteParticipants.values()]) {
|
||||
|
|
@ -379,7 +409,9 @@ const resyncAfterReconnect = (livekit: LiveKitRoom) => {
|
|||
participantMediaState.set(next)
|
||||
|
||||
const session = get(currentCallSession)
|
||||
if (!session) return
|
||||
if (!session) {
|
||||
return
|
||||
}
|
||||
|
||||
const {localParticipant} = livekit
|
||||
callMicMuted.set(!localParticipant.isMicrophoneEnabled)
|
||||
|
|
@ -398,7 +430,9 @@ const fetchLivekitToken = async (
|
|||
): Promise<{server_url: string; participant_token: string}> => {
|
||||
const endpoint = getLivekitEndpoint(url, roomId)
|
||||
|
||||
if (signal?.aborted) throw new DOMException("Aborted", "AbortError")
|
||||
if (signal?.aborted) {
|
||||
throw new DOMException("Aborted", "AbortError")
|
||||
}
|
||||
|
||||
const template = await makeHttpAuth(endpoint, "GET")
|
||||
const signedEvent = await user.get().signer.sign(template)
|
||||
|
|
@ -443,7 +477,9 @@ const setUpMicrophone = async (
|
|||
} catch (e) {
|
||||
// Timeout or microphone rejection: join muted, the call is still usable. A
|
||||
// genuine abort is surfaced to the caller so it can tear down the room.
|
||||
if (e instanceof AbortError) throw e
|
||||
if (e instanceof AbortError) {
|
||||
throw e
|
||||
}
|
||||
if (!(e instanceof TimeoutError)) {
|
||||
pushToast({theme: "error", message: "Could not access microphone"})
|
||||
}
|
||||
|
|
@ -453,12 +489,16 @@ const setUpMicrophone = async (
|
|||
|
||||
const reacquireMicrophoneIfNeeded = async () => {
|
||||
const session = get(currentCallSession)
|
||||
if (!session || get(callMicMuted)) return
|
||||
if (!session || get(callMicMuted)) {
|
||||
return
|
||||
}
|
||||
|
||||
const track = session.livekit.localParticipant.getTrackPublication(
|
||||
Track.Source.Microphone,
|
||||
)?.audioTrack
|
||||
if (!track || track.isMuted || track.isUserProvided) return
|
||||
if (!track || track.isMuted || track.isUserProvided) {
|
||||
return
|
||||
}
|
||||
|
||||
// Mirrors LiveKit's own (mobile-only, video-track-only) reacquisition
|
||||
// check: a capture device that died silently still reports readyState
|
||||
|
|
@ -468,7 +508,9 @@ const reacquireMicrophoneIfNeeded = async () => {
|
|||
const {mediaStreamTrack} = track
|
||||
const needsReacquisition =
|
||||
mediaStreamTrack.readyState !== "live" || mediaStreamTrack.muted || !mediaStreamTrack.enabled
|
||||
if (!needsReacquisition) return
|
||||
if (!needsReacquisition) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await track.restartTrack()
|
||||
|
|
@ -488,7 +530,9 @@ const clearReconnectSchedule = () => {
|
|||
|
||||
const attemptReconnect = async () => {
|
||||
const target = get(callTargetRoom)
|
||||
if (!target) return
|
||||
if (!target) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await joinVoiceRoom(target.url, target.h, reconnectMicMuted, reconnectMicDeviceId)
|
||||
|
|
@ -503,8 +547,12 @@ const attemptReconnect = async () => {
|
|||
}
|
||||
|
||||
const scheduleReconnect = () => {
|
||||
if (reconnectTimeout !== undefined) return
|
||||
if (!get(callTargetRoom)) return
|
||||
if (reconnectTimeout !== undefined) {
|
||||
return
|
||||
}
|
||||
if (!get(callTargetRoom)) {
|
||||
return
|
||||
}
|
||||
if (reconnectAttempt >= RECONNECT_DELAYS.length) {
|
||||
pushToast({theme: "error", message: "Voice connection lost."})
|
||||
return
|
||||
|
|
@ -519,13 +567,17 @@ const scheduleReconnect = () => {
|
|||
}
|
||||
|
||||
const makeOnRoomReconnected = (livekit: LiveKitRoom) => () => {
|
||||
if (livekit !== activeRoom) return
|
||||
if (livekit !== activeRoom) {
|
||||
return
|
||||
}
|
||||
resyncAfterReconnect(livekit)
|
||||
}
|
||||
|
||||
const makeOnRoomDisconnected = (livekit: LiveKitRoom) => (reason?: DisconnectReason) => {
|
||||
// Ignore disconnects from rooms that are no longer the active session.
|
||||
if (livekit !== activeRoom) return
|
||||
if (livekit !== activeRoom) {
|
||||
return
|
||||
}
|
||||
|
||||
// Livekit unsubscribes remote tracks before emitting Disconnected, so
|
||||
// onTrackUnsubscribed has already removed their audio elements by now.
|
||||
|
|
@ -599,10 +651,16 @@ const onLocalTrackUnpublished = (
|
|||
publication: LocalTrackPublication,
|
||||
participant: LocalParticipant,
|
||||
) => {
|
||||
if (publication.source !== Track.Source.ScreenShare) return
|
||||
if (publication.source !== Track.Source.ScreenShare) {
|
||||
return
|
||||
}
|
||||
const session = get(currentCallSession)
|
||||
if (!session || participant.identity !== session.livekit.localParticipant.identity) return
|
||||
if (!session.screenShareOn) return
|
||||
if (!session || participant.identity !== session.livekit.localParticipant.identity) {
|
||||
return
|
||||
}
|
||||
if (!session.screenShareOn) {
|
||||
return
|
||||
}
|
||||
currentCallSession.set({...session, screenShareOn: false})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,9 @@ export const matchesTopic = (event: TrustedEvent, topic: string) =>
|
|||
export const matchesQuery = (event: TrustedEvent, query: string) => {
|
||||
const q = query.trim().toLowerCase()
|
||||
|
||||
if (!q) return true
|
||||
if (!q) {
|
||||
return true
|
||||
}
|
||||
|
||||
const classified = reader(Classified)(event)
|
||||
const haystack = [classified.title(), classified.summary(), event.content]
|
||||
|
|
|
|||
|
|
@ -60,7 +60,9 @@
|
|||
// offsetTop is relative to the nearest positioned ancestor, which the scroll container itself
|
||||
// isn't — comparing bounding rects instead keeps this correct regardless of that
|
||||
const recenter = (target: HTMLElement) => {
|
||||
if (!element) return
|
||||
if (!element) {
|
||||
return
|
||||
}
|
||||
|
||||
const elementRect = element.getBoundingClientRect()
|
||||
const targetRect = target.getBoundingClientRect()
|
||||
|
|
@ -70,7 +72,9 @@
|
|||
}
|
||||
|
||||
$effect(() => {
|
||||
if (!element || !contentEl) return
|
||||
if (!element || !contentEl) {
|
||||
return
|
||||
}
|
||||
|
||||
const unpin = () => {
|
||||
pinnedTarget = undefined
|
||||
|
|
@ -82,7 +86,9 @@
|
|||
// every one of those (unlike a MutationObserver, which misses an image's own load-driven
|
||||
// reflow) and keeps the pin centered until the visitor scrolls themselves.
|
||||
const observer = new ResizeObserver(() => {
|
||||
if (pinnedTarget) recenter(pinnedTarget)
|
||||
if (pinnedTarget) {
|
||||
recenter(pinnedTarget)
|
||||
}
|
||||
})
|
||||
|
||||
observer.observe(contentEl)
|
||||
|
|
|
|||
|
|
@ -68,7 +68,9 @@
|
|||
const selectFiles = () => editor.then(ed => ed.chain().selectFiles().run())
|
||||
|
||||
const submit = async () => {
|
||||
if ($uploading || loading) return
|
||||
if ($uploading || loading) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!title) {
|
||||
return pushToast({
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@
|
|||
const {url, event, rsvps, onShowPeople}: Props = $props()
|
||||
|
||||
const setStatus = async (status: RsvpStatus) => {
|
||||
if (loading) return
|
||||
if (loading) {
|
||||
return
|
||||
}
|
||||
|
||||
loading = true
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,9 @@
|
|||
const spaceName = $derived($callTargetRoom ? displayRelayUrl($callTargetRoom.url) : "")
|
||||
|
||||
const goToRoom = () => {
|
||||
if (!$callTargetRoom) return
|
||||
if (!$callTargetRoom) {
|
||||
return
|
||||
}
|
||||
void navigate(makeRoomPath($callTargetRoom.url, $callTargetRoom.h))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -82,13 +82,17 @@
|
|||
}
|
||||
|
||||
const submit = async () => {
|
||||
if ($uploading || disabled) return
|
||||
if ($uploading || disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
const ed = await editor
|
||||
const content = ed.getText({blockSeparator: "\n"}).trim()
|
||||
const tags = ed.storage.nostr.getEditorTags()
|
||||
|
||||
if (!content) return
|
||||
if (!content) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await onSubmit({content, tags})
|
||||
|
|
|
|||
|
|
@ -26,8 +26,12 @@
|
|||
const activeArg = $derived(draft?.args[draft.activeIndex])
|
||||
|
||||
const choices = $derived.by(() => {
|
||||
if (activeArg?.type === "enum") return activeArg.choices
|
||||
if (activeArg?.type === "bool") return ["true", "false"]
|
||||
if (activeArg?.type === "enum") {
|
||||
return activeArg.choices
|
||||
}
|
||||
if (activeArg?.type === "bool") {
|
||||
return ["true", "false"]
|
||||
}
|
||||
|
||||
return []
|
||||
})
|
||||
|
|
|
|||
|
|
@ -38,7 +38,9 @@
|
|||
const selectFiles = () => editor.then(ed => ed.commands.selectFiles())
|
||||
|
||||
const submit = async () => {
|
||||
if ($uploading || loading) return
|
||||
if ($uploading || loading) {
|
||||
return
|
||||
}
|
||||
|
||||
const ed = await editor
|
||||
const content = ed.getText({blockSeparator: "\n"}).trim()
|
||||
|
|
|
|||
|
|
@ -82,7 +82,9 @@
|
|||
const isBlock = (i: number) => {
|
||||
const parsed = fullContent[i]
|
||||
|
||||
if (!parsed) return false
|
||||
if (!parsed) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (isLink(parsed) && $userSettingsValues.show_media && isStartAndEnd(i)) {
|
||||
return true
|
||||
|
|
@ -98,8 +100,12 @@
|
|||
const isBoundary = (i: number) => {
|
||||
const parsed = fullContent[i]
|
||||
|
||||
if (!parsed || isNewline(parsed)) return true
|
||||
if (isText(parsed)) return Boolean(parsed.value.match(/^\s+$/))
|
||||
if (!parsed || isNewline(parsed)) {
|
||||
return true
|
||||
}
|
||||
if (isText(parsed)) {
|
||||
return Boolean(parsed.value.match(/^\s+$/))
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,9 @@
|
|||
const url = value.url.toString()
|
||||
const isRoomOrRelay = isRoomId(url) || isRelayUrl(url)
|
||||
const [href, external] = call(() => {
|
||||
if (url.startsWith(PLATFORM_URL)) return [url.replace(PLATFORM_URL, ""), false]
|
||||
if (url.startsWith(PLATFORM_URL)) {
|
||||
return [url.replace(PLATFORM_URL, ""), false]
|
||||
}
|
||||
|
||||
return [url, true]
|
||||
})
|
||||
|
|
|
|||
|
|
@ -40,9 +40,15 @@
|
|||
})
|
||||
|
||||
const [href, external] = call(() => {
|
||||
if (roomReference) return [makeRoomPath(roomReference.url, roomReference.h), false]
|
||||
if (relayReference) return [makeSpacePath(relayReference), false]
|
||||
if (url.startsWith(PLATFORM_URL)) return [url.replace(PLATFORM_URL, ""), false]
|
||||
if (roomReference) {
|
||||
return [makeRoomPath(roomReference.url, roomReference.h), false]
|
||||
}
|
||||
if (relayReference) {
|
||||
return [makeSpacePath(relayReference), false]
|
||||
}
|
||||
if (url.startsWith(PLATFORM_URL)) {
|
||||
return [url.replace(PLATFORM_URL, ""), false]
|
||||
}
|
||||
|
||||
return [url, true]
|
||||
})
|
||||
|
|
|
|||
|
|
@ -79,8 +79,12 @@
|
|||
const pubkeyFromEntity = (entity: string) => {
|
||||
const {type, data} = nip19.decode(entity)
|
||||
|
||||
if (type === "npub") return data
|
||||
if (type === "nprofile") return data.pubkey
|
||||
if (type === "npub") {
|
||||
return data
|
||||
}
|
||||
if (type === "nprofile") {
|
||||
return data.pubkey
|
||||
}
|
||||
}
|
||||
|
||||
// Everyone the content mentions, so their names are asked for and awaited rather than read
|
||||
|
|
|
|||
|
|
@ -48,8 +48,12 @@
|
|||
const isBoundary = (i: number) => {
|
||||
const parsed = fullContent[i]
|
||||
|
||||
if (!parsed || isNewline(parsed)) return true
|
||||
if (isText(parsed)) return Boolean(parsed.value.match(/^\s+$/))
|
||||
if (!parsed || isNewline(parsed)) {
|
||||
return true
|
||||
}
|
||||
if (isText(parsed)) {
|
||||
return Boolean(parsed.value.match(/^\s+$/))
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,9 @@
|
|||
const selectFiles = () => editor.then(ed => ed.commands.selectFiles())
|
||||
|
||||
const submit = async () => {
|
||||
if ($uploading) return
|
||||
if ($uploading) {
|
||||
return
|
||||
}
|
||||
|
||||
const ed = await editor
|
||||
let content = ed.getText({blockSeparator: "\n"}).trim()
|
||||
|
|
|
|||
|
|
@ -55,7 +55,9 @@
|
|||
const selectFiles = () => editor.then(ed => ed.commands.selectFiles())
|
||||
|
||||
const submit = async () => {
|
||||
if ($uploading || loading) return
|
||||
if ($uploading || loading) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!title) {
|
||||
return pushToast({
|
||||
|
|
|
|||
|
|
@ -55,7 +55,9 @@
|
|||
const {loading, bunker} = controller
|
||||
|
||||
const onSubmit = async () => {
|
||||
if ($loading) return
|
||||
if ($loading) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const {signerPubkey, connectSecret, relays} = Nip46Broker.parseBunkerUrl($bunker)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@
|
|||
const t = track
|
||||
level = 0
|
||||
|
||||
if (!t || error) return
|
||||
if (!t || error) {
|
||||
return
|
||||
}
|
||||
|
||||
let frame: number | undefined
|
||||
const context = new AudioContext()
|
||||
|
|
@ -50,14 +52,18 @@
|
|||
const NOISE_FLOOR = 0.03
|
||||
const gated = rms < NOISE_FLOOR ? 0 : (rms - NOISE_FLOOR) / (1 - NOISE_FLOOR)
|
||||
smoothed += (Math.min(1, gated * 4) - smoothed) * 0.5
|
||||
if (smoothed < 0.005) smoothed = 0
|
||||
if (smoothed < 0.005) {
|
||||
smoothed = 0
|
||||
}
|
||||
level = smoothed
|
||||
frame = requestAnimationFrame(tick)
|
||||
}
|
||||
tick()
|
||||
|
||||
return () => {
|
||||
if (frame !== undefined) cancelAnimationFrame(frame)
|
||||
if (frame !== undefined) {
|
||||
cancelAnimationFrame(frame)
|
||||
}
|
||||
context.close().catch(() => {})
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@
|
|||
}
|
||||
|
||||
onMount(() => {
|
||||
if (Capacitor.isNativePlatform()) return
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
return
|
||||
}
|
||||
|
||||
audioElement.load()
|
||||
|
||||
|
|
|
|||
|
|
@ -103,7 +103,9 @@
|
|||
}
|
||||
|
||||
const submit = async () => {
|
||||
if (loading) return
|
||||
if (loading) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!title.trim()) {
|
||||
return pushToast({theme: "error", message: "Please provide a title for your poll."})
|
||||
|
|
|
|||
|
|
@ -55,7 +55,9 @@
|
|||
const openChat = () => goToChat([target])
|
||||
|
||||
const toggleFollow = async () => {
|
||||
if (isSelf) return
|
||||
if (isSelf) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isFollowing) {
|
||||
await $followLists.unfollow(target).then(publish)
|
||||
|
|
@ -72,7 +74,9 @@
|
|||
|
||||
input.value = ""
|
||||
|
||||
if (!file || !$profile) return
|
||||
if (!file || !$profile) {
|
||||
return
|
||||
}
|
||||
|
||||
bannerLoading = true
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@
|
|||
const copyCode = () => clip(code)
|
||||
|
||||
const shareProfile = async () => {
|
||||
if (!canShare) return
|
||||
if (!canShare) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await Share.share({url: code})
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@
|
|||
let progress = $state(0)
|
||||
|
||||
$effect(() => {
|
||||
if (!element) return
|
||||
if (!element) {
|
||||
return
|
||||
}
|
||||
|
||||
const target = element
|
||||
|
||||
|
|
|
|||
|
|
@ -62,10 +62,18 @@
|
|||
$relaySearch
|
||||
.searchValues(term)
|
||||
.filter((url: string) => {
|
||||
if (matchRelay?.(url) === false) return false
|
||||
if ($relays.includes(url)) return false
|
||||
if (isIPAddress(url)) return false
|
||||
if ($blockedRelayUrls.includes(url)) return false
|
||||
if (matchRelay?.(url) === false) {
|
||||
return false
|
||||
}
|
||||
if ($relays.includes(url)) {
|
||||
return false
|
||||
}
|
||||
if (isIPAddress(url)) {
|
||||
return false
|
||||
}
|
||||
if ($blockedRelayUrls.includes(url)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
|
|
|||
|
|
@ -100,7 +100,9 @@
|
|||
const hidePopover = () => tippy?.hide()
|
||||
|
||||
const submit = async () => {
|
||||
if ($uploading) return
|
||||
if ($uploading) {
|
||||
return
|
||||
}
|
||||
|
||||
const ed = await editor
|
||||
const content = ed.getText({blockSeparator: "\n"}).trim()
|
||||
|
|
|
|||
|
|
@ -36,7 +36,9 @@
|
|||
const copyInvite = () => clip(invite)
|
||||
|
||||
const shareInvite = async () => {
|
||||
if (!canShare) return
|
||||
if (!canShare) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await Share.share({url: invite})
|
||||
|
|
|
|||
|
|
@ -100,7 +100,9 @@
|
|||
const togglePin = async () => {
|
||||
onClick()
|
||||
|
||||
if (!h) return
|
||||
if (!h) {
|
||||
return
|
||||
}
|
||||
|
||||
// The optimistic write flips isPinned while the publish is in flight, so what this did is read
|
||||
// before it goes out rather than after.
|
||||
|
|
|
|||
|
|
@ -103,7 +103,9 @@
|
|||
}
|
||||
|
||||
const togglePin = async () => {
|
||||
if (!h) return
|
||||
if (!h) {
|
||||
return
|
||||
}
|
||||
|
||||
history.back()
|
||||
|
||||
|
|
|
|||
|
|
@ -61,26 +61,34 @@
|
|||
}
|
||||
|
||||
const prev = () => {
|
||||
if (total === 0) return
|
||||
if (total === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
currentIndex = (currentIndex - 1 + total) % total
|
||||
}
|
||||
|
||||
const next = () => {
|
||||
if (total === 0) return
|
||||
if (total === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
currentIndex = (currentIndex + 1) % total
|
||||
}
|
||||
|
||||
const jumpToMessage = () => {
|
||||
if (!currentEvent) return
|
||||
if (!currentEvent) {
|
||||
return
|
||||
}
|
||||
|
||||
collapse()
|
||||
goToEvent(currentEvent)
|
||||
}
|
||||
|
||||
const onDocumentPointerDown = (event: PointerEvent) => {
|
||||
if (!expanded || !root) return
|
||||
if (!expanded || !root) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!root.contains(event.target as Node)) {
|
||||
collapse()
|
||||
|
|
@ -98,7 +106,9 @@
|
|||
})
|
||||
|
||||
$effect(() => {
|
||||
if (!expanded) return
|
||||
if (!expanded) {
|
||||
return
|
||||
}
|
||||
|
||||
document.addEventListener("pointerdown", onDocumentPointerDown)
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,9 @@
|
|||
return
|
||||
}
|
||||
|
||||
if (toastId || timeout) return
|
||||
if (toastId || timeout) {
|
||||
return
|
||||
}
|
||||
|
||||
// deriveSocketStatus is throttled, so a socket that opens immediately still reports
|
||||
// "Connecting" for the length of the throttle. Wait that out instead of flashing.
|
||||
|
|
|
|||
|
|
@ -38,7 +38,9 @@
|
|||
const copyInvite = () => clip(invite)
|
||||
|
||||
const shareInvite = async () => {
|
||||
if (!canShare) return
|
||||
if (!canShare) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await Share.share({url: invite})
|
||||
|
|
|
|||
|
|
@ -49,7 +49,9 @@
|
|||
const selectFiles = () => editor.then(ed => ed.commands.selectFiles())
|
||||
|
||||
const submit = async () => {
|
||||
if ($uploading || loading) return
|
||||
if ($uploading || loading) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!title) {
|
||||
return pushToast({
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@
|
|||
})
|
||||
|
||||
$effect(() => {
|
||||
if (!containerEl) return
|
||||
if (!containerEl) {
|
||||
return
|
||||
}
|
||||
containerEl.addEventListener("touchmove", onTouchMove, {passive: false})
|
||||
return () => containerEl?.removeEventListener("touchmove", onTouchMove)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -127,13 +127,17 @@
|
|||
|
||||
const primaryTile = $derived.by(() => {
|
||||
const k = $videoPrimaryTileKey
|
||||
if (k === undefined) return undefined
|
||||
if (k === undefined) {
|
||||
return undefined
|
||||
}
|
||||
return videoTiles.find(t => tileKey(t) === k)
|
||||
})
|
||||
|
||||
const secondaryTiles = $derived.by(() => {
|
||||
const p = primaryTile
|
||||
if (p === undefined) return videoTiles
|
||||
if (p === undefined) {
|
||||
return videoTiles
|
||||
}
|
||||
const pk = tileKey(p)
|
||||
return videoTiles.filter(t => tileKey(t) !== pk)
|
||||
})
|
||||
|
|
@ -150,7 +154,9 @@
|
|||
|
||||
$effect(() => {
|
||||
const k = $videoPrimaryTileKey
|
||||
if (k === undefined) return
|
||||
if (k === undefined) {
|
||||
return
|
||||
}
|
||||
if (!videoTiles.some(t => tileKey(t) === k)) {
|
||||
videoPrimaryTileKey.set(undefined)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@
|
|||
$effect(() => {
|
||||
const element = videoElement
|
||||
const activeTrack = track
|
||||
if (!element) return
|
||||
if (!element) {
|
||||
return
|
||||
}
|
||||
activeTrack.attach(element)
|
||||
return () => {
|
||||
activeTrack.detach(element)
|
||||
|
|
|
|||
|
|
@ -35,7 +35,9 @@
|
|||
?.mediaStreamTrack
|
||||
|
||||
const loadDevices = async () => {
|
||||
if (!navigator.mediaDevices?.enumerateDevices) return
|
||||
if (!navigator.mediaDevices?.enumerateDevices) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const devices = await navigator.mediaDevices.enumerateDevices()
|
||||
audioInputs = devices.filter(spec({kind: "audioinput"}))
|
||||
|
|
@ -85,7 +87,9 @@
|
|||
// place with a new MediaStreamTrack rather than emitting a publish event,
|
||||
// so the meter needs an explicit re-read here.
|
||||
const session = $currentCallSession
|
||||
if (session) micTrack = readMicTrack(session)
|
||||
if (session) {
|
||||
micTrack = readMicTrack(session)
|
||||
}
|
||||
}
|
||||
|
||||
const onOutputChange = () => {
|
||||
|
|
|
|||
|
|
@ -48,7 +48,9 @@
|
|||
)
|
||||
|
||||
const handleClick = async (e: MouseEvent) => {
|
||||
if (isActive) return
|
||||
if (isActive) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isJoining) {
|
||||
e.preventDefault()
|
||||
|
|
@ -67,7 +69,9 @@
|
|||
|
||||
$effect(() => {
|
||||
for (const p of $participants) {
|
||||
if (p.pubkey) $profiles.load(p.pubkey, [url])
|
||||
if (p.pubkey) {
|
||||
$profiles.load(p.pubkey, [url])
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,9 @@
|
|||
let micError = $state(false)
|
||||
|
||||
const loadDevices = async () => {
|
||||
if (!navigator.mediaDevices?.enumerateDevices) return
|
||||
if (!navigator.mediaDevices?.enumerateDevices) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const devices = await navigator.mediaDevices.enumerateDevices()
|
||||
audioInputs = devices.filter(spec({kind: "audioinput"}))
|
||||
|
|
@ -75,7 +77,9 @@
|
|||
stopMicPreview()
|
||||
micError = false
|
||||
|
||||
if (withoutMic || !navigator.mediaDevices?.getUserMedia) return
|
||||
if (withoutMic || !navigator.mediaDevices?.getUserMedia) {
|
||||
return
|
||||
}
|
||||
|
||||
let cancelled = false
|
||||
const deviceId = selectedDeviceId
|
||||
|
|
@ -92,7 +96,9 @@
|
|||
previewStream = stream
|
||||
previewTrack = stream.getAudioTracks()[0]
|
||||
} catch {
|
||||
if (!cancelled) micError = true
|
||||
if (!cancelled) {
|
||||
micError = true
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
|
|
@ -108,12 +114,16 @@
|
|||
}
|
||||
|
||||
const handleJoinError = (e: unknown) => {
|
||||
if (e instanceof AbortError) return
|
||||
if (e instanceof AbortError) {
|
||||
return
|
||||
}
|
||||
console.error("Failed to join voice room", e)
|
||||
let message = "Failed to join voice room"
|
||||
if (e instanceof TimeoutError)
|
||||
if (e instanceof TimeoutError) {
|
||||
message = "Connection timed out. Please check your network and try again."
|
||||
else if (e instanceof Error) message = e.message
|
||||
} else if (e instanceof Error) {
|
||||
message = e.message
|
||||
}
|
||||
pushToast({theme: "error", message})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,9 @@
|
|||
const updateMethod = () => openSetup(tenant?.nwc_error ? "nwc" : "card")
|
||||
|
||||
const payOpenInvoice = () => {
|
||||
if (openInvoice) openPayInvoice(openInvoice)
|
||||
if (openInvoice) {
|
||||
openPayInvoice(openInvoice)
|
||||
}
|
||||
}
|
||||
|
||||
const dismissAutopay = () => {
|
||||
|
|
@ -60,7 +62,9 @@
|
|||
let handledInvoiceId: string | undefined
|
||||
$effect(() => {
|
||||
const id = $page.url.searchParams.get("invoice") ?? undefined
|
||||
if (!id || id === handledInvoiceId) return
|
||||
if (!id || id === handledInvoiceId) {
|
||||
return
|
||||
}
|
||||
|
||||
handledInvoiceId = id
|
||||
|
||||
|
|
|
|||
|
|
@ -14,9 +14,15 @@
|
|||
|
||||
// The backend models the invoice lifecycle as timestamps, not a status field.
|
||||
const status = $derived.by(() => {
|
||||
if (isDraft) return "draft"
|
||||
if (invoice.paid_at) return "paid"
|
||||
if (invoice.voided_at) return "void"
|
||||
if (isDraft) {
|
||||
return "draft"
|
||||
}
|
||||
if (invoice.paid_at) {
|
||||
return "paid"
|
||||
}
|
||||
if (invoice.voided_at) {
|
||||
return "void"
|
||||
}
|
||||
return "open"
|
||||
})
|
||||
const isOpen = $derived(status === "open")
|
||||
|
|
|
|||
|
|
@ -77,7 +77,9 @@
|
|||
}
|
||||
|
||||
const markPaid = () => {
|
||||
if (paid) return
|
||||
if (paid) {
|
||||
return
|
||||
}
|
||||
|
||||
paid = true
|
||||
stopPolling()
|
||||
|
|
@ -121,10 +123,14 @@
|
|||
|
||||
// Silent poll until paid or the modal closes.
|
||||
const pollOnce = async () => {
|
||||
if (destroyed || paid) return
|
||||
if (destroyed || paid) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if (await reconcile()) return
|
||||
if (await reconcile()) {
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
// keep polling
|
||||
}
|
||||
|
|
@ -135,7 +141,9 @@
|
|||
}
|
||||
|
||||
const checkPayment = async () => {
|
||||
if (checking) return
|
||||
if (checking) {
|
||||
return
|
||||
}
|
||||
|
||||
checking = true
|
||||
|
||||
|
|
@ -161,7 +169,9 @@
|
|||
}
|
||||
|
||||
const payWithCard = async () => {
|
||||
if (redirecting) return
|
||||
if (redirecting) {
|
||||
return
|
||||
}
|
||||
|
||||
redirecting = true
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,9 @@
|
|||
// The tenant's autopay wallet (a backend NWC, write-only), distinct from the
|
||||
// app's own wallet.
|
||||
const persistNwc = async (url?: string) => {
|
||||
if (!url || saving) return
|
||||
if (!url || saving) {
|
||||
return
|
||||
}
|
||||
|
||||
saving = true
|
||||
|
||||
|
|
@ -79,7 +81,9 @@
|
|||
const useSpendingWallet = () => persistNwc(spendingWalletUrl)
|
||||
|
||||
const disconnectNwc = async () => {
|
||||
if (saving) return
|
||||
if (saving) {
|
||||
return
|
||||
}
|
||||
|
||||
saving = true
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,9 @@
|
|||
}
|
||||
|
||||
const save = async () => {
|
||||
if (!canSave) return
|
||||
if (!canSave) {
|
||||
return
|
||||
}
|
||||
|
||||
saving = true
|
||||
|
||||
|
|
|
|||
|
|
@ -100,7 +100,11 @@ export const displayContentCount = (kind: number, count: number) => {
|
|||
export const DM_KINDS = [DIRECT_MESSAGE, DIRECT_MESSAGE_FILE]
|
||||
|
||||
export const displayReaction = (content: string) => {
|
||||
if (!content || content === "+") return "❤️"
|
||||
if (content === "-") return "👎"
|
||||
if (!content || content === "+") {
|
||||
return "❤️"
|
||||
}
|
||||
if (content === "-") {
|
||||
return "👎"
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,8 +41,12 @@ const mergeSorted = <T>(left: T[], right: T[], compare: (a: T, b: T) => number)
|
|||
merged.push(compare(left[i], right[j]) <= 0 ? left[i++] : right[j++])
|
||||
}
|
||||
|
||||
while (i < left.length) merged.push(left[i++])
|
||||
while (j < right.length) merged.push(right[j++])
|
||||
while (i < left.length) {
|
||||
merged.push(left[i++])
|
||||
}
|
||||
while (j < right.length) {
|
||||
merged.push(right[j++])
|
||||
}
|
||||
|
||||
return merged
|
||||
}
|
||||
|
|
@ -119,7 +123,9 @@ export const makeFeedContext = ({
|
|||
if (deletedChecks.size > 0) {
|
||||
if (removed.size > 0) {
|
||||
for (const checks of deletedChecks.values()) {
|
||||
for (const check of checks) check()
|
||||
for (const check of checks) {
|
||||
check()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const event of added) {
|
||||
|
|
@ -403,7 +409,9 @@ const makeFeedLoader = (load: () => Promise<FeedSpan>) => {
|
|||
let running = false
|
||||
|
||||
const run = async () => {
|
||||
if (running || get(state).status === "exhausted") return
|
||||
if (running || get(state).status === "exhausted") {
|
||||
return
|
||||
}
|
||||
|
||||
running = true
|
||||
|
||||
|
|
@ -425,7 +433,9 @@ const makeFeedLoader = (load: () => Promise<FeedSpan>) => {
|
|||
break
|
||||
}
|
||||
|
||||
if (found > 0) break
|
||||
if (found > 0) {
|
||||
break
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
running = false
|
||||
|
|
@ -608,7 +618,9 @@ export const makeFeed = ({
|
|||
// The window only moves once the relays have answered: a request the socket dropped looks
|
||||
// exactly like an empty span, and walking past it would leave a hole nothing goes back for.
|
||||
const loadOlder = async (): Promise<FeedSpan> => {
|
||||
if (oldest < now() - int(2, YEAR)) return {found: 0, complete: true, exhausted: true}
|
||||
if (oldest < now() - int(2, YEAR)) {
|
||||
return {found: 0, complete: true, exhausted: true}
|
||||
}
|
||||
|
||||
const until = oldest
|
||||
const since = until - olderInterval
|
||||
|
|
@ -641,7 +653,9 @@ export const makeFeed = ({
|
|||
// A limit is answered with the newest events matching it, which reaches away from an anchor in
|
||||
// the past rather than toward it, so this direction walks spans as it always has
|
||||
const loadNewer = async (): Promise<FeedSpan> => {
|
||||
if (newest >= now()) return {found: 0, complete: true, exhausted: true}
|
||||
if (newest >= now()) {
|
||||
return {found: 0, complete: true, exhausted: true}
|
||||
}
|
||||
|
||||
const since = newest
|
||||
const until = Math.min(now(), since + newerInterval)
|
||||
|
|
@ -714,7 +728,9 @@ export const makeCalendarFeed = ({
|
|||
const insertEvents = (newEvents: TrustedEvent[]) => {
|
||||
const valid = newEvents.filter(e => !isNaN(getStart(e)) && !isNaN(getEnd(e)) && !seen.has(e.id))
|
||||
|
||||
if (valid.length === 0) return
|
||||
if (valid.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const event of valid) {
|
||||
seen.add(event.id)
|
||||
|
|
@ -771,7 +787,9 @@ export const makeCalendarFeed = ({
|
|||
}
|
||||
|
||||
const loadOlder = async (): Promise<FeedSpan> => {
|
||||
if (oldest < now() - int(2, YEAR)) return {found: 0, complete: true, exhausted: true}
|
||||
if (oldest < now() - int(2, YEAR)) {
|
||||
return {found: 0, complete: true, exhausted: true}
|
||||
}
|
||||
|
||||
const until = oldest
|
||||
const since = until - interval
|
||||
|
|
@ -785,7 +803,9 @@ export const makeCalendarFeed = ({
|
|||
}
|
||||
|
||||
const loadNewer = async (): Promise<FeedSpan> => {
|
||||
if (newest > now() + int(2, YEAR)) return {found: 0, complete: true, exhausted: true}
|
||||
if (newest > now() + int(2, YEAR)) {
|
||||
return {found: 0, complete: true, exhausted: true}
|
||||
}
|
||||
|
||||
const since = newest
|
||||
const until = since + interval
|
||||
|
|
|
|||
|
|
@ -207,7 +207,9 @@ const getAuthHeader = (): Promise<string> => {
|
|||
|
||||
// A declined signature shouldn't be cached, or the user can't retry.
|
||||
header.catch(() => {
|
||||
if (cachedAuth?.header === header) cachedAuth = undefined
|
||||
if (cachedAuth?.header === header) {
|
||||
cachedAuth = undefined
|
||||
}
|
||||
})
|
||||
|
||||
cachedAuth = {pubkey, expiresAt: Date.now() + AUTH_TTL, header}
|
||||
|
|
@ -232,7 +234,9 @@ const hostingRequest = async (method: string, path: string, init: RequestInit =
|
|||
const hostingJson = async <T>(response: Response): Promise<T> => {
|
||||
const payload: Maybe<HostingResponse<T>> = await response.json().catch(() => undefined)
|
||||
|
||||
if (response.ok && payload) return payload.data
|
||||
if (response.ok && payload) {
|
||||
return payload.data
|
||||
}
|
||||
|
||||
throw new HostingError(payload?.error || `Request failed (${response.status})`, response.status)
|
||||
}
|
||||
|
|
@ -299,7 +303,9 @@ export const listRelayActivity = (id: string) =>
|
|||
export const exportRelayData = async (id: string) => {
|
||||
const response = await hostingRequest("GET", `/relays/${id}/export`)
|
||||
|
||||
if (response.ok) return response.text()
|
||||
if (response.ok) {
|
||||
return response.text()
|
||||
}
|
||||
|
||||
// Only the failure path is a json envelope; success is the dump itself.
|
||||
return hostingJson<string>(response)
|
||||
|
|
@ -336,8 +342,12 @@ export const reconcileInvoice = (invoiceId: string) =>
|
|||
// API utils
|
||||
|
||||
export const flagToBool = (value: number | undefined, fallback: boolean): boolean => {
|
||||
if (value === 0) return false
|
||||
if (value === 1) return true
|
||||
if (value === 0) {
|
||||
return false
|
||||
}
|
||||
if (value === 1) {
|
||||
return true
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
|
|
@ -391,7 +401,9 @@ export const ensureSessionTenant = async () => {
|
|||
const promise = createTenant(hostingReturnUrl())
|
||||
|
||||
promise.catch(() => {
|
||||
if (tenantPromise?.promise === promise) tenantPromise = undefined
|
||||
if (tenantPromise?.promise === promise) {
|
||||
tenantPromise = undefined
|
||||
}
|
||||
})
|
||||
|
||||
tenantPromise = {pubkey: $pubkey, promise}
|
||||
|
|
@ -423,7 +435,9 @@ export const deriveRelayActivity = (id: Readable<Maybe<string>>) =>
|
|||
derived<Readable<Maybe<string>>, RelayActivityState>(
|
||||
id,
|
||||
($id, set) => {
|
||||
if (!$id) return set({loading: false, activity: []})
|
||||
if (!$id) {
|
||||
return set({loading: false, activity: []})
|
||||
}
|
||||
|
||||
set({loading: true, activity: []})
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ import {Keyboard} from "@capacitor/keyboard"
|
|||
import {noop} from "@welshman/lib"
|
||||
|
||||
export const syncKeyboard = () => {
|
||||
if (!Capacitor.isPluginAvailable("Keyboard")) return noop
|
||||
if (!Capacitor.isPluginAvailable("Keyboard")) {
|
||||
return noop
|
||||
}
|
||||
|
||||
const showListener = Keyboard.addListener("keyboardWillShow", () => {
|
||||
document.body.classList.add("keyboard-open")
|
||||
|
|
@ -17,7 +19,9 @@ export const syncKeyboard = () => {
|
|||
// On Android, system-dismissing the IME during pause doesn't fire keyboardWillHide,
|
||||
// so on resume we force a hide to re-sync native insets and clear our CSS state.
|
||||
const resumeListener = App.addListener("appStateChange", ({isActive}) => {
|
||||
if (isActive) Keyboard.hide()
|
||||
if (isActive) {
|
||||
Keyboard.hide()
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
|
|
|
|||
|
|
@ -53,10 +53,14 @@ export const payInvoice = async (invoice: string, msats?: number) => {
|
|||
|
||||
if (isNWCWallet($wallet)) {
|
||||
const params: {invoice: string; amount?: number} = {invoice}
|
||||
if (msats) params.amount = msats
|
||||
if (msats) {
|
||||
params.amount = msats
|
||||
}
|
||||
return getNwcClient().payInvoice(params)
|
||||
} else if (isWebLNWallet($wallet)) {
|
||||
if (msats) throw new Error("Unable to pay zero invoices with webln")
|
||||
if (msats) {
|
||||
throw new Error("Unable to pay zero invoices with webln")
|
||||
}
|
||||
return getWebLn()
|
||||
.enable()
|
||||
.then(() => getWebLn().sendPayment(invoice))
|
||||
|
|
|
|||
|
|
@ -8,8 +8,12 @@ import {logger, wraps, writer} from "@app/core"
|
|||
import {PLATFORM_LOGEE} from "@app/env"
|
||||
|
||||
const formatValue = (value: unknown) => {
|
||||
if (value instanceof Error) return value.stack || value.message
|
||||
if (typeof value === "string") return value
|
||||
if (value instanceof Error) {
|
||||
return value.stack || value.message
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return value
|
||||
}
|
||||
|
||||
return tryCatch(() => JSON.stringify(value)) || String(value)
|
||||
}
|
||||
|
|
@ -72,7 +76,9 @@ export const sendLogs = async () => {
|
|||
|
||||
size += line.length
|
||||
|
||||
if (size > 32_000) break
|
||||
if (size > 32_000) {
|
||||
break
|
||||
}
|
||||
|
||||
lines.unshift(line)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,9 @@ export const syncChecked = () => {
|
|||
|
||||
checked.update($checked => {
|
||||
for (const path of paths) {
|
||||
if (defer && path === defer) continue
|
||||
if (defer && path === defer) {
|
||||
continue
|
||||
}
|
||||
$checked[path] = now()
|
||||
}
|
||||
|
||||
|
|
@ -212,7 +214,9 @@ const latestContentActivity = (url: string, events: TrustedEvent[]) => {
|
|||
for (const event of events) {
|
||||
const target = getContentTarget(event)
|
||||
|
||||
if (!target) continue
|
||||
if (!target) {
|
||||
continue
|
||||
}
|
||||
|
||||
const path = makeContentPath(url, target.kind, target.idOrAddress)
|
||||
|
||||
|
|
@ -329,8 +333,12 @@ export const notifications = derived(
|
|||
([$page, $allNotifications, $deferredRoomPath]) =>
|
||||
new Set(
|
||||
[...$allNotifications].filter(p => {
|
||||
if (!$page.url.pathname.startsWith(p)) return true
|
||||
if ($deferredRoomPath && p === $deferredRoomPath) return true
|
||||
if (!$page.url.pathname.startsWith(p)) {
|
||||
return true
|
||||
}
|
||||
if ($deferredRoomPath && p === $deferredRoomPath) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -44,8 +44,12 @@ export const ingestPolicy: AppPolicy = app =>
|
|||
const event = message[2]
|
||||
const trusted = getSetting("trusted_relays").includes(socket.url)
|
||||
|
||||
if (isDVMKind(event.kind) || isEphemeralKind(event.kind)) return
|
||||
if (!trusted && !verifyEvent(event)) return
|
||||
if (isDVMKind(event.kind) || isEphemeralKind(event.kind)) {
|
||||
return
|
||||
}
|
||||
if (!trusted && !verifyEvent(event)) {
|
||||
return
|
||||
}
|
||||
|
||||
app.tracker.track(event.id, socket.url)
|
||||
app.repository.publish(event)
|
||||
|
|
@ -62,13 +66,27 @@ export const ingestPolicy: AppPolicy = app =>
|
|||
export const authPolicy = makeAppPolicyAuth((socket, $app) => {
|
||||
const $pubkey = app.get().user?.pubkey
|
||||
|
||||
if (!$pubkey) return false
|
||||
if ($app.use(BlockedRelayLists).urls($pubkey).get().includes(socket.url)) return false
|
||||
if (getSetting("relay_auth") === RelayAuthMode.Aggressive) return true
|
||||
if ($app.use(RoomLists).urls($pubkey).get().includes(socket.url)) return true
|
||||
if ($app.use(RelayLists).urls($pubkey).get().includes(socket.url)) return true
|
||||
if (get($app.use(Thunks).history).some(t => t.options.relays.includes(socket.url))) return true
|
||||
if ($app.use(MessagingRelayLists).urls($pubkey).get().includes(socket.url)) return true
|
||||
if (!$pubkey) {
|
||||
return false
|
||||
}
|
||||
if ($app.use(BlockedRelayLists).urls($pubkey).get().includes(socket.url)) {
|
||||
return false
|
||||
}
|
||||
if (getSetting("relay_auth") === RelayAuthMode.Aggressive) {
|
||||
return true
|
||||
}
|
||||
if ($app.use(RoomLists).urls($pubkey).get().includes(socket.url)) {
|
||||
return true
|
||||
}
|
||||
if ($app.use(RelayLists).urls($pubkey).get().includes(socket.url)) {
|
||||
return true
|
||||
}
|
||||
if (get($app.use(Thunks).history).some(t => t.options.relays.includes(socket.url))) {
|
||||
return true
|
||||
}
|
||||
if ($app.use(MessagingRelayLists).urls($pubkey).get().includes(socket.url)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
|
@ -79,8 +97,12 @@ const makeBlockPolicy = ($app: IApp) => (socket: Socket) => {
|
|||
socket.open = () => {
|
||||
const $pubkey = $app.user?.pubkey
|
||||
|
||||
if (BLOCKED_RELAYS.includes(socket.url)) return
|
||||
if ($pubkey && $app.use(BlockedRelayLists).urls($pubkey).get().includes(socket.url)) return
|
||||
if (BLOCKED_RELAYS.includes(socket.url)) {
|
||||
return
|
||||
}
|
||||
if ($pubkey && $app.use(BlockedRelayLists).urls($pubkey).get().includes(socket.url)) {
|
||||
return
|
||||
}
|
||||
|
||||
previousOpen()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,9 @@ export const loadPomadeSessions = async () => {
|
|||
const result = await client.listSessions()
|
||||
|
||||
for (const message of result.messages) {
|
||||
if (!message.res?.items) continue
|
||||
if (!message.res?.items) {
|
||||
continue
|
||||
}
|
||||
|
||||
for (const item of message.res.items) {
|
||||
const existing = sessionMap.get(item.client)
|
||||
|
|
|
|||
|
|
@ -244,7 +244,9 @@ export const groupRoomMessages = ({
|
|||
let previousCreatedAt = 0
|
||||
|
||||
for (const event of events) {
|
||||
if (seenIds.has(event.id)) continue
|
||||
if (seenIds.has(event.id)) {
|
||||
continue
|
||||
}
|
||||
|
||||
seenIds.add(event.id)
|
||||
|
||||
|
|
|
|||
|
|
@ -129,8 +129,12 @@ export const deriveIsMuted = (url: string, h: string) =>
|
|||
export const getNotifyPreference = ({alerts}: SettingsValues, url: string, h?: string) => {
|
||||
const pref = alerts.find(spec({url}))
|
||||
|
||||
if (!pref) return true
|
||||
if (!h) return pref.notify
|
||||
if (!pref) {
|
||||
return true
|
||||
}
|
||||
if (!h) {
|
||||
return pref.notify
|
||||
}
|
||||
|
||||
return pref.notify ? !pref.exceptions.includes(h) : pref.exceptions.includes(h)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ export const signerRequests = derived(
|
|||
const requestsById = new Map<string, SignerRequest>()
|
||||
|
||||
for (const message of $messages) {
|
||||
if (message.source !== "signer") continue
|
||||
if (message.source !== "signer") {
|
||||
continue
|
||||
}
|
||||
|
||||
if (message.status === "pending") {
|
||||
requestsById.set(message.id, {
|
||||
|
|
|
|||
|
|
@ -118,19 +118,37 @@ export const isEventMuted = withGetter(
|
|||
: undefined
|
||||
|
||||
return (e: TrustedEvent) => {
|
||||
if (!$muteList) return false
|
||||
if ($user.pubkey === e.pubkey) return false
|
||||
if (mutedPubkeys.has(e.pubkey)) return true
|
||||
if (mutedEvents.has(e.id)) return true
|
||||
if (mutedAddresses.has(getAddress(e))) return true
|
||||
if (getParents(e).some(v => mutedEvents.has(v) || mutedAddresses.has(v))) return true
|
||||
if (tagValues(topicTags("t"), e.tags).some(t => mutedTopics.has(t))) return true
|
||||
if (!$muteList) {
|
||||
return false
|
||||
}
|
||||
if ($user.pubkey === e.pubkey) {
|
||||
return false
|
||||
}
|
||||
if (mutedPubkeys.has(e.pubkey)) {
|
||||
return true
|
||||
}
|
||||
if (mutedEvents.has(e.id)) {
|
||||
return true
|
||||
}
|
||||
if (mutedAddresses.has(getAddress(e))) {
|
||||
return true
|
||||
}
|
||||
if (getParents(e).some(v => mutedEvents.has(v) || mutedAddresses.has(v))) {
|
||||
return true
|
||||
}
|
||||
if (tagValues(topicTags("t"), e.tags).some(t => mutedTopics.has(t))) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (regex) {
|
||||
const profile = profiles.get().get(e.pubkey)
|
||||
|
||||
if (profile?.display().toLowerCase().match(regex)) return true
|
||||
if (profile?.nip05()?.match(regex)) return true
|
||||
if (profile?.display().toLowerCase().match(regex)) {
|
||||
return true
|
||||
}
|
||||
if (profile?.nip05()?.match(regex)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -56,7 +56,9 @@ export const kv = call(() => {
|
|||
|
||||
const get = async <T>(key: string): Promise<T | undefined> => {
|
||||
const result = await Preferences.get({key})
|
||||
if (!result.value) return undefined
|
||||
if (!result.value) {
|
||||
return undefined
|
||||
}
|
||||
try {
|
||||
return JSON.parse(result.value)
|
||||
} catch (e) {
|
||||
|
|
@ -91,7 +93,9 @@ export const ss = call(() => {
|
|||
}
|
||||
}
|
||||
|
||||
if (!value) return undefined
|
||||
if (!value) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
|
|
@ -402,7 +406,9 @@ class Storage {
|
|||
)
|
||||
|
||||
return this.app.use(Relays).onItem((_url, relay) => {
|
||||
if (relay) enqueue(relay)
|
||||
if (relay) {
|
||||
enqueue(relay)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -416,7 +422,9 @@ class Storage {
|
|||
const enqueue = batch(FLUSH_INTERVAL, idleWrite(table.bulkPut))
|
||||
|
||||
return this.app.use(RelayStats).onItem((_url, stats) => {
|
||||
if (stats) enqueue(stats)
|
||||
if (stats) {
|
||||
enqueue(stats)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -430,7 +438,9 @@ class Storage {
|
|||
const enqueue = batch(FLUSH_INTERVAL, idleWrite(table.bulkPut))
|
||||
|
||||
return this.app.use(Handles).onItem((_nip05, handle) => {
|
||||
if (handle) enqueue(handle)
|
||||
if (handle) {
|
||||
enqueue(handle)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -451,7 +461,9 @@ class Storage {
|
|||
)
|
||||
|
||||
return this.app.use(Zappers).onItem((_lnurl, zapper) => {
|
||||
if (zapper) enqueue(zapper)
|
||||
if (zapper) {
|
||||
enqueue(zapper)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -465,7 +477,9 @@ class Storage {
|
|||
const enqueue = batch(FLUSH_INTERVAL, idleWrite(table.bulkPut))
|
||||
|
||||
return this.app.use(Plaintext).onItem((key, value) => {
|
||||
if (value) enqueue({key, value})
|
||||
if (value) {
|
||||
enqueue({key, value})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,9 @@ type SyncOpts = {
|
|||
}
|
||||
|
||||
const pullAndListen = ({url, signal, filters}: SyncOpts) => {
|
||||
if (signal.aborted) return
|
||||
if (signal.aborted) {
|
||||
return
|
||||
}
|
||||
|
||||
app
|
||||
.get()
|
||||
|
|
|
|||
|
|
@ -23,7 +23,9 @@
|
|||
// When no date is provided the library initializes its time input from the
|
||||
// epoch (a locale-dependent afternoon); reset it to something friendlier
|
||||
onMount(() => {
|
||||
if (!startDate) startDateTime = "12:00"
|
||||
if (!startDate) {
|
||||
startDateTime = "12:00"
|
||||
}
|
||||
})
|
||||
|
||||
const toggle = () => {
|
||||
|
|
|
|||
|
|
@ -24,8 +24,12 @@
|
|||
// When no date is provided the library initializes its time inputs from the
|
||||
// epoch (a locale-dependent afternoon); reset them for the create flow
|
||||
onMount(() => {
|
||||
if (!startDate) startDateTime = "12:00"
|
||||
if (!endDate) endDateTime = "13:00"
|
||||
if (!startDate) {
|
||||
startDateTime = "12:00"
|
||||
}
|
||||
if (!endDate) {
|
||||
endDateTime = "13:00"
|
||||
}
|
||||
})
|
||||
|
||||
const toggle = () => {
|
||||
|
|
|
|||
|
|
@ -61,7 +61,9 @@
|
|||
|
||||
if (isWebkit && isRemoteSvg(url)) {
|
||||
fetchSvg(url).then(blobUrl => {
|
||||
if (!blobUrl) return
|
||||
if (!blobUrl) {
|
||||
return
|
||||
}
|
||||
|
||||
if (canceled) {
|
||||
URL.revokeObjectURL(blobUrl)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@
|
|||
const id = randomId()
|
||||
|
||||
const getImageUrl = (item: string | File): string => {
|
||||
if (typeof item === "string") return item
|
||||
if (typeof item === "string") {
|
||||
return item
|
||||
}
|
||||
return URL.createObjectURL(item)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ export {preventDefault, stopPropagation} from "svelte/legacy"
|
|||
export const documentActive = readable(
|
||||
typeof document === "undefined" ? true : !document.hidden && document.hasFocus(),
|
||||
set => {
|
||||
if (typeof document === "undefined") return
|
||||
if (typeof document === "undefined") {
|
||||
return
|
||||
}
|
||||
|
||||
const update = () => set(!document.hidden && document.hasFocus())
|
||||
|
||||
|
|
@ -36,7 +38,9 @@ export const anchorDatepicker = (wrapper: HTMLElement) => {
|
|||
const anchor = wrapper.querySelector("label")
|
||||
const container = wrapper.querySelector(".calendars-container") as HTMLElement | null
|
||||
|
||||
if (!anchor || !container) return
|
||||
if (!anchor || !container) {
|
||||
return
|
||||
}
|
||||
|
||||
const margin = 8
|
||||
const rect = anchor.getBoundingClientRect()
|
||||
|
|
|
|||
|
|
@ -86,7 +86,9 @@ export class IDB {
|
|||
getAll = async <T>(table: string): Promise<T[]> => {
|
||||
const connection = await this.live()
|
||||
|
||||
if (!connection) return []
|
||||
if (!connection) {
|
||||
return []
|
||||
}
|
||||
|
||||
const tx = connection.transaction(table, "readonly")
|
||||
const store = tx.objectStore(table)
|
||||
|
|
@ -100,7 +102,9 @@ export class IDB {
|
|||
bulkPut = async <T>(table: string, data: Iterable<T>) => {
|
||||
const connection = await this.live()
|
||||
|
||||
if (!connection) return
|
||||
if (!connection) {
|
||||
return
|
||||
}
|
||||
|
||||
const tx = connection.transaction(table, "readwrite")
|
||||
const store = tx.objectStore(table)
|
||||
|
|
@ -121,7 +125,9 @@ export class IDB {
|
|||
bulkDelete = async (table: string, ids: Iterable<string>) => {
|
||||
const connection = await this.live()
|
||||
|
||||
if (!connection) return
|
||||
if (!connection) {
|
||||
return
|
||||
}
|
||||
|
||||
const tx = connection.transaction(table, "readwrite")
|
||||
const store = tx.objectStore(table)
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@ 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}`)
|
||||
if (type !== "nsec") {
|
||||
throw new Error(`Invalid nsec: ${nsec}`)
|
||||
}
|
||||
|
||||
return bytesToHex(data)
|
||||
}
|
||||
|
|
@ -58,12 +60,17 @@ export class TimeoutError extends Error {
|
|||
|
||||
/** Returns a promise that rejects with AbortError when signal aborts. Use with Promise.race. */
|
||||
export const whenAborted = (signal?: AbortSignal) => {
|
||||
if (!signal) return new Promise<never>(() => {})
|
||||
if (!signal) {
|
||||
return new Promise<never>(() => {})
|
||||
}
|
||||
|
||||
return new Promise<never>((_, reject) => {
|
||||
const onAborted = () => reject(new AbortError())
|
||||
if (signal.aborted) onAborted()
|
||||
else signal.addEventListener("abort", onAborted, {once: true})
|
||||
if (signal.aborted) {
|
||||
onAborted()
|
||||
} else {
|
||||
signal.addEventListener("abort", onAborted, {once: true})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -228,7 +228,9 @@
|
|||
// Listen for signer errors, report to user via toast
|
||||
unsubscribers.push(
|
||||
throttled(10_000, signerRequests).subscribe($requests => {
|
||||
if ($toast) return
|
||||
if ($toast) {
|
||||
return
|
||||
}
|
||||
|
||||
const longCutoff = Date.now() - 30_000
|
||||
const shortCutoff = Date.now() - 10_000
|
||||
|
|
|
|||
|
|
@ -66,9 +66,15 @@
|
|||
})
|
||||
|
||||
const status = $derived.by(() => {
|
||||
if (!tenant) return "inactive"
|
||||
if (tenant.churned_at) return "delinquent"
|
||||
if (hasPaidSubscription || openInvoice || autopayConfigured(tenant)) return "active"
|
||||
if (!tenant) {
|
||||
return "inactive"
|
||||
}
|
||||
if (tenant.churned_at) {
|
||||
return "delinquent"
|
||||
}
|
||||
if (hasPaidSubscription || openInvoice || autopayConfigured(tenant)) {
|
||||
return "active"
|
||||
}
|
||||
return "inactive"
|
||||
})
|
||||
|
||||
|
|
@ -77,15 +83,23 @@
|
|||
)
|
||||
|
||||
const nwc = $derived.by<PaymentMethodState>(() => {
|
||||
if (!tenant?.nwc_is_set) return {kind: "not_set_up"}
|
||||
if (tenant.nwc_error) return {kind: "error", message: tenant.nwc_error}
|
||||
if (!tenant?.nwc_is_set) {
|
||||
return {kind: "not_set_up"}
|
||||
}
|
||||
if (tenant.nwc_error) {
|
||||
return {kind: "error", message: tenant.nwc_error}
|
||||
}
|
||||
return {kind: "ok"}
|
||||
})
|
||||
|
||||
const card = $derived.by<PaymentMethodState>(() => {
|
||||
if (!tenant?.stripe_payment_method_id) return {kind: "not_set_up"}
|
||||
if (!tenant?.stripe_payment_method_id) {
|
||||
return {kind: "not_set_up"}
|
||||
}
|
||||
// Don't surface Stripe's raw decline text to the tenant.
|
||||
if (tenant.stripe_error) return {kind: "error", message: "Payment failed"}
|
||||
if (tenant.stripe_error) {
|
||||
return {kind: "error", message: "Payment failed"}
|
||||
}
|
||||
return {kind: "ok"}
|
||||
})
|
||||
|
||||
|
|
@ -95,7 +109,9 @@
|
|||
|
||||
const refetch = async () => {
|
||||
const pk = user.get().pubkey
|
||||
if (!pk) return
|
||||
if (!pk) {
|
||||
return
|
||||
}
|
||||
|
||||
const results = await Promise.allSettled([
|
||||
getTenant(pk),
|
||||
|
|
@ -106,10 +122,18 @@
|
|||
|
||||
const [t, inv, rel, draft] = results
|
||||
|
||||
if (t.status === "fulfilled") tenant = t.value
|
||||
if (inv.status === "fulfilled") invoices = sortBy(invoice => -invoice.created_at, inv.value)
|
||||
if (rel.status === "fulfilled") relays = rel.value
|
||||
if (draft.status === "fulfilled") draftInvoice = draft.value
|
||||
if (t.status === "fulfilled") {
|
||||
tenant = t.value
|
||||
}
|
||||
if (inv.status === "fulfilled") {
|
||||
invoices = sortBy(invoice => -invoice.created_at, inv.value)
|
||||
}
|
||||
if (rel.status === "fulfilled") {
|
||||
relays = rel.value
|
||||
}
|
||||
if (draft.status === "fulfilled") {
|
||||
draftInvoice = draft.value
|
||||
}
|
||||
|
||||
if (results.some(spec({status: "rejected"}))) {
|
||||
pushToast({theme: "error", message: "Failed to refresh billing data"})
|
||||
|
|
@ -127,7 +151,9 @@
|
|||
// invoices is left to the backend's dunning poll, which collects all of them.
|
||||
const reconcile = async () => {
|
||||
const pk = user.get().pubkey
|
||||
if (!pk || reconciling) return
|
||||
if (!pk || reconciling) {
|
||||
return
|
||||
}
|
||||
|
||||
reconciling = true
|
||||
error = ""
|
||||
|
|
@ -178,7 +204,9 @@
|
|||
// Re-run on foreground return (native browser round-trip) to pick up a
|
||||
// completed checkout or a portal-added card.
|
||||
const resumeListener = App.addListener("appStateChange", ({isActive}) => {
|
||||
if (isActive) void reconcile()
|
||||
if (isActive) {
|
||||
void reconcile()
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
|
|
|
|||
|
|
@ -57,7 +57,9 @@
|
|||
return createSearch(options, {
|
||||
getValue: (relay: Relay) => relay.url,
|
||||
sortFn: ({score, item}) => {
|
||||
if (score && score > 0.1) return -score!
|
||||
if (score && score > 0.1) {
|
||||
return -score!
|
||||
}
|
||||
|
||||
const wotScore = $spacePubkeysByUrl.get(item.url)?.size || 0
|
||||
|
||||
|
|
|
|||
|
|
@ -77,7 +77,9 @@
|
|||
// Watch for relay errors and notify the user
|
||||
// Direct links skip Discover — prompt to join when relay is not in the user's space list.
|
||||
$effect(() => {
|
||||
if (getModal()) return
|
||||
if (getModal()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (redirectUrl && redirectUrl !== url && !redirectPrompted.has(url)) {
|
||||
redirectPrompted.add(url)
|
||||
|
|
|
|||
|
|
@ -90,7 +90,9 @@
|
|||
}
|
||||
|
||||
const submit = async () => {
|
||||
if ($uploading || uploadingCover || loading) return
|
||||
if ($uploading || uploadingCover || loading) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!title) {
|
||||
return pushToast({
|
||||
|
|
|
|||
|
|
@ -74,9 +74,15 @@
|
|||
return
|
||||
}
|
||||
|
||||
if (event.key === "ArrowLeft") showPrevious()
|
||||
if (event.key === "ArrowRight") showNext()
|
||||
if (event.key === "t") showToday()
|
||||
if (event.key === "ArrowLeft") {
|
||||
showPrevious()
|
||||
}
|
||||
if (event.key === "ArrowRight") {
|
||||
showNext()
|
||||
}
|
||||
if (event.key === "t") {
|
||||
showToday()
|
||||
}
|
||||
}
|
||||
|
||||
const viewClass = (target: CalendarView) =>
|
||||
|
|
|
|||
|
|
@ -92,7 +92,9 @@
|
|||
const items = $derived.by(() => {
|
||||
const matches = scoped.filter(event => tab === "all" || getStatus(event) === tab)
|
||||
|
||||
if (sort === "new") return sortBy(event => -event.created_at, matches)
|
||||
if (sort === "new") {
|
||||
return sortBy(event => -event.created_at, matches)
|
||||
}
|
||||
|
||||
if (sort === "price-asc") {
|
||||
return sortBy(event => reader(Classified)(event).price()?.amount ?? Infinity, matches)
|
||||
|
|
|
|||
|
|
@ -86,7 +86,9 @@
|
|||
const visibleMembers = $derived.by(() => {
|
||||
const t = term.trim().toLowerCase()
|
||||
|
||||
if (!t) return $memberList
|
||||
if (!t) {
|
||||
return $memberList
|
||||
}
|
||||
|
||||
return $memberList.filter(
|
||||
({pubkey, roleList}) =>
|
||||
|
|
|
|||
Loading…
Reference in a new issue