10 Commits

Author SHA1 Message Date
Jon Staab
c7eec167cf Fix scroll down z index 2025-12-08 09:27:38 -08:00
Jon Staab
7bae956ffa Release 1.6.2 2025-12-08 09:22:49 -08:00
Jon Staab
a2f59a5b1b Fix some modal bugs 2025-12-08 09:19:41 -08:00
Jon Staab
df56af9b0e Bump version 2025-12-05 09:51:15 -08:00
Jon Staab
83f7f9584f Fix duplicate rooms 2025-12-04 17:06:50 -08:00
Jon Staab
a2d440e54f Fix dialog z index 2025-12-04 16:01:39 -08:00
Jon Staab
4132e8449b Fix recent missing events in feeds 2025-12-04 15:56:05 -08:00
Jon Staab
ee444416e4 Fall back to file name as hash for images 2025-12-04 14:37:59 -08:00
Jon Staab
10c12c3c48 Improve time based chat partitioning 2025-12-04 14:29:12 -08:00
Jon Staab
db3775ae99 Fix timezone parsing in AlertAdd 2025-12-04 11:20:54 -08:00
15 changed files with 114 additions and 72 deletions

View File

@@ -1,9 +1,22 @@
# Changelog
# Current
# 1.6.3
* Fix scroll down button z index
# 1.6.2
* Fix modal scrolling and style
# 1.6.1
* Fix skinny profile images
* Custom handler for relay urls
* Improve time based chat partitioning
* Improve authenticated image access interop
* Fix image detail dialog
* Fix zapper loading
* Fix recent events missing in feeds
# 1.6.0

View File

@@ -7,8 +7,8 @@ android {
applicationId "social.flotilla"
minSdk rootProject.ext.minSdkVersion
targetSdk rootProject.ext.targetSdkVersion
versionCode 36
versionName "1.6.0"
versionCode 38
versionName "1.6.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.

View File

@@ -358,14 +358,14 @@
CODE_SIGN_ENTITLEMENTS = "Flotilla Chat.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 26;
CURRENT_PROJECT_VERSION = 28;
DEVELOPMENT_TEAM = S26U9DYW3A;
INFOPLIST_FILE = App/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 1.6.0;
MARKETING_VERSION = 1.6.2;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -384,14 +384,14 @@
CODE_SIGN_ENTITLEMENTS = "Flotilla Chat.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 26;
CURRENT_PROJECT_VERSION = 28;
DEVELOPMENT_TEAM = S26U9DYW3A;
INFOPLIST_FILE = App/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 1.6.0;
MARKETING_VERSION = 1.6.2;
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";

View File

@@ -1,6 +1,6 @@
{
"name": "flotilla",
"version": "1.6.0",
"version": "1.6.2",
"private": true,
"scripts": {
"dev": "vite dev",
@@ -10,7 +10,7 @@
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check src && eslint src",
"format": "git diff head --name-only --diff-filter d | grep -E '(js|ts|svelte)$' | xargs -r prettier --write",
"format": "git diff head --name-only --diff-filter d | grep -E '(js|ts|svelte|css)$' | xargs -r prettier --write",
"format:all": "prettier --write src",
"prepare": "husky"
},
@@ -61,16 +61,16 @@
"@types/throttle-debounce": "^5.0.2",
"@vite-pwa/assets-generator": "^0.2.6",
"@vite-pwa/sveltekit": "^0.6.8",
"@welshman/app": "^0.7.0",
"@welshman/content": "^0.7.0",
"@welshman/editor": "^0.7.0",
"@welshman/feeds": "^0.7.0",
"@welshman/lib": "^0.7.0",
"@welshman/net": "^0.7.0",
"@welshman/router": "^0.7.0",
"@welshman/signer": "^0.7.0",
"@welshman/store": "^0.7.0",
"@welshman/util": "^0.7.0",
"@welshman/app": "^0.7.1",
"@welshman/content": "^0.7.1",
"@welshman/editor": "^0.7.1",
"@welshman/feeds": "^0.7.1",
"@welshman/lib": "^0.7.1",
"@welshman/net": "^0.7.1",
"@welshman/router": "^0.7.1",
"@welshman/signer": "^0.7.1",
"@welshman/store": "^0.7.1",
"@welshman/util": "^0.7.1",
"compressorjs": "^1.2.1",
"daisyui": "^4.12.24",
"date-picker-svelte": "^2.16.0",

BIN
pnpm-lock.yaml generated

Binary file not shown.

View File

@@ -409,5 +409,5 @@ body.keyboard-open .hide-on-keyboard {
}
.chat__scroll-down {
@apply fixed bottom-28 right-4 md:bottom-16;
@apply fixed bottom-28 right-4 z-feature md:bottom-16;
}

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import {onMount} from "svelte"
import {preventDefault} from "@lib/html"
import {randomInt, map, displayList, TIMEZONE, identity} from "@welshman/lib"
import {randomInt, map, displayList, identity, TIMEZONE} from "@welshman/lib"
import {displayRelayUrl, getTagValue, THREAD, MESSAGE, EVENT_TIME, COMMENT} from "@welshman/util"
import type {Filter} from "@welshman/util"
import {makeIntersectionFeed, makeRelayFeed, feedFromFilters} from "@welshman/feeds"
@@ -37,7 +37,7 @@
hideSpaceField = false,
}: Props = $props()
const timezoneOffset = parseInt(TIMEZONE.slice(3)) / 100
const timezoneOffset = parseInt(TIMEZONE.split(":")?.[0] || "00")
const minute = randomInt(0, 59)
const hour = (17 - timezoneOffset) % 24
const WEEKLY = `0 ${minute} ${hour} * * 1`

View File

@@ -46,20 +46,20 @@
</script>
<div class="col-2">
<Button class="btn btn-primary w-full" onclick={showEmojiPicker}>
<Icon size={4} icon={SmileCircle} />
Send Reaction
</Button>
<Button class="btn btn-neutral w-full" onclick={sendReply}>
<Icon size={4} icon={Reply} />
Send Reply
<Button class="btn btn-neutral" onclick={showInfo}>
<Icon size={4} icon={Code2} />
Message Info
</Button>
<Button class="btn btn-neutral w-full" onclick={copyText}>
<Icon size={4} icon={Copy} />
Copy Text
</Button>
<Button class="btn btn-neutral" onclick={showInfo}>
<Icon size={4} icon={Code2} />
Message Details
<Button class="btn btn-neutral w-full" onclick={sendReply}>
<Icon size={4} icon={Reply} />
Send Reply
</Button>
<Button class="btn btn-primary w-full" onclick={showEmojiPicker}>
<Icon size={4} icon={SmileCircle} />
Send Reaction
</Button>
</div>

View File

@@ -21,7 +21,8 @@
.map(tagsFromIMeta)
.find(meta => getTagValue("url", meta) === url) || event.tags
const hash = getTagValue("x", meta)
// Fallback to filename if hash was omitted from the message for interoperability
const hash = getTagValue("x", meta) || url.split(/[\/\.]/).slice(-2)[0]
const key = getTagValue("decryption-key", meta)
const nonce = getTagValue("decryption-nonce", meta)
const algorithm = getTagValue("encryption-algorithm", meta)

View File

@@ -58,12 +58,12 @@
{#if event.pubkey === $pubkey}
<Button class="btn btn-neutral text-error" onclick={showDelete}>
<Icon size={4} icon={TrashBin2} />
Delete
Delete Message
</Button>
{/if}
<Button class="btn btn-neutral" onclick={showInfo}>
<Icon size={4} icon={Code2} />
Show JSON
Message Info
</Button>
{#if path}
<Link class="btn btn-neutral" href={path}>
@@ -71,18 +71,18 @@
View Details
</Link>
{/if}
<Button class="btn btn-neutral w-full" onclick={sendReply}>
<Icon size={4} icon={Reply} />
Reply
</Button>
<Button class="btn btn-neutral w-full" onclick={showEmojiPicker}>
<Icon size={4} icon={SmileCircle} />
React
</Button>
{#if ENABLE_ZAPS}
<ZapButton replaceState {url} {event} class="btn btn-neutral w-full">
<Icon size={4} icon={Bolt} />
Zap
Send Zap
</ZapButton>
{/if}
<Button class="btn btn-neutral w-full" onclick={sendReply}>
<Icon size={4} icon={Reply} />
Send Reply
</Button>
<Button class="btn btn-neutral w-full" onclick={showEmojiPicker}>
<Icon size={4} icon={SmileCircle} />
Send Reaction
</Button>
</div>

View File

@@ -4,7 +4,6 @@ import {
int,
YEAR,
DAY,
assoc,
insertAt,
sortBy,
now,
@@ -33,7 +32,7 @@ import {load, request} from "@welshman/net"
import {repository, makeFeedController, loadRelay, tracker} from "@welshman/app"
import {createScroller} from "@lib/html"
import {daysBetween} from "@lib/util"
import {NOTIFIER_RELAY} from "@app/core/state"
import {NOTIFIER_RELAY, getEventsForUrl} from "@app/core/state"
// Utils
@@ -48,12 +47,9 @@ export const makeFeed = ({
element: HTMLElement
onExhausted?: () => void
}) => {
const initialIds = Array.from(tracker.getIds(url))
const initialFilters = filters.map(assoc("ids", initialIds))
const initialEvents = repository.query(initialFilters)
const seen = new Set(initialEvents.map(e => e.id))
const seen = new Set<string>()
const controller = new AbortController()
const buffer = writable(initialEvents)
const buffer = writable<TrustedEvent[]>([])
const events = writable<TrustedEvent[]>([])
const insertEvent = (event: TrustedEvent) => {
@@ -124,6 +120,10 @@ export const makeFeed = ({
},
})
for (const event of getEventsForUrl(url, filters)) {
insertEvent(event)
}
return {
events,
cleanup: () => {
@@ -147,9 +147,6 @@ export const makeCalendarFeed = ({
}) => {
const interval = int(5, DAY)
const controller = new AbortController()
const initialIds = Array.from(tracker.getIds(url))
const initialFilters = filters.map(assoc("ids", initialIds))
const initialEvents = repository.query(initialFilters)
let exhaustedScrollers = 0
let backwardWindow = [now() - interval, now()]
@@ -159,7 +156,7 @@ export const makeCalendarFeed = ({
const getEnd = (event: TrustedEvent) => parseInt(getTagValue("end", event.tags) || "")
const events = writable(sortBy(getStart, initialEvents))
const events = writable(sortBy(getStart, getEventsForUrl(url, filters)))
const insertEvent = (event: TrustedEvent) => {
const start = getStart(event)

View File

@@ -17,7 +17,6 @@ import {
uniq,
indexBy,
partition,
pushToMapKey,
shuffle,
parseJson,
memoize,
@@ -48,6 +47,7 @@ import {
deriveItemsByKey,
deriveEventsByIdByUrl,
deriveEventsByIdForUrl,
getEventsByIdForUrl,
} from "@welshman/store"
import {isKindFeed, findFeed} from "@welshman/feeds"
import {
@@ -216,6 +216,9 @@ export const deriveEvent = makeDeriveEvent({
onDerive: (filters: Filter[], relays: string[]) => load({filters, relays}),
})
export const getEventsForUrl = (url: string, filters: Filter[]) =>
getEventsByIdForUrl({url, tracker, repository, filters}).values()
export const deriveEventsForUrl = (url: string, filters: Filter[]) =>
deriveArray(deriveEventsByIdForUrl({url, tracker, repository, filters}))
@@ -282,7 +285,7 @@ export const defaultSettings = {
report_usage: true,
report_errors: true,
send_delay: 0,
font_size: 1,
font_size: 1.1,
play_notification_sound: true,
show_notifications_badge: true,
}
@@ -488,7 +491,7 @@ export const roomMetaEventsByIdByUrl = deriveEventsByIdByUrl({
})
export const roomsByUrl = derived(roomMetaEventsByIdByUrl, roomMetaEventsByIdByUrl => {
const result = new Map<string, Room[]>()
const metaByIdByUrl = new Map<string, Map<string, Room>>()
for (const [url, events] of roomMetaEventsByIdByUrl.entries()) {
const [metaEvents, deleteEvents] = partition(spec({kind: ROOM_META}), events.values())
@@ -507,10 +510,24 @@ export const roomsByUrl = derived(roomMetaEventsByIdByUrl, roomMetaEventsByIdByU
continue
}
pushToMapKey(result, url, {...meta, url, id: makeRoomId(url, meta.h)})
let metaById = metaByIdByUrl.get(url)
if (!metaById) {
metaById = new Map()
metaByIdByUrl.set(url, metaById)
}
const id = makeRoomId(url, meta.h)
metaById.set(id, {...meta, url, id})
}
}
const result = new Map<string, Room[]>()
for (const [url, metaById] of metaByIdByUrl.entries()) {
result.set(url, Array.from(metaById.values()))
}
return result
})

View File

@@ -11,13 +11,21 @@
const {onClose = noop, fullscreen = false, children}: Props = $props()
const extraClass = $derived(
!fullscreen &&
cx(
"bg-alt text-base-content overflow-auto text-base-content shadow-md",
"px-4 py-6 bottom-0 left-0 right-0 top-20 rounded-t-box absolute",
"sm:p-6 sm:max-h-[90vh] sm:w-[520px] sm:rounded-box sm:relative sm:top-0 sm:relative",
),
const wrapperClass = $derived(
cx("absolute inset-0 flex sm:relative pointer-events-none", {
"items-center justify-center": fullscreen,
"items-end sm:w-[520px] sm:items-center": !fullscreen,
}),
)
const innerClass = $derived(
cx(
"relative text-base-content text-base-content flex-grow pointer-events-auto",
"px-4 py-6 rounded-t-box sm:p-6 sm:rounded-box sm:mt-0",
{
"bg-alt shadow-m max-h-[90vh] scroll-container overflow-auto": !fullscreen,
},
),
)
</script>
@@ -28,7 +36,9 @@
transition:fade={{duration: 300}}
onclick={onClose}>
</button>
<div class="scroll-container {extraClass}" transition:fly={{duration: 300}}>
{@render children?.()}
<div class={wrapperClass}>
<div class={innerClass} transition:fly={{duration: 300}}>
{@render children?.()}
</div>
</div>
</div>

View File

@@ -5,7 +5,7 @@
import {page} from "$app/stores"
import type {Readable} from "svelte/store"
import type {MakeNonOptional} from "@welshman/lib"
import {now, formatTimestampAsDate, ago, MINUTE} from "@welshman/lib"
import {now, int, formatTimestampAsDate, ago, MINUTE} from "@welshman/lib"
import type {TrustedEvent, EventContent} from "@welshman/util"
import {
makeEvent,
@@ -213,6 +213,7 @@
let previousDate
let previousKind
let previousPubkey
let previousCreatedAt = 0
let newMessagesSeen = false
if (events) {
@@ -249,14 +250,15 @@
type: "note",
value: event,
showPubkey:
date !== previousDate ||
previousPubkey !== event.pubkey ||
event.created_at - previousCreatedAt > int(3, MINUTE) ||
[ROOM_ADD_MEMBER, ROOM_REMOVE_MEMBER].includes(previousKind!),
})
previousDate = date
previousKind = event.kind
previousPubkey = event.pubkey
previousCreatedAt = event.created_at
seen.add(event.id)
}
}

View File

@@ -3,7 +3,7 @@
import {page} from "$app/stores"
import type {Readable} from "svelte/store"
import {readable} from "svelte/store"
import {now, formatTimestampAsDate, MINUTE, ago} from "@welshman/lib"
import {now, int, formatTimestampAsDate, MINUTE, ago} from "@welshman/lib"
import type {TrustedEvent, EventContent} from "@welshman/util"
import {makeEvent, MESSAGE, RELAY_ADD_MEMBER, RELAY_REMOVE_MEMBER} from "@welshman/util"
import {pubkey, publishThunk} from "@welshman/app"
@@ -138,6 +138,7 @@
let previousDate
let previousKind
let previousPubkey
let previousCreatedAt = 0
let newMessagesSeen = false
if (events) {
@@ -174,14 +175,15 @@
type: "note",
value: event,
showPubkey:
date !== previousDate ||
previousPubkey !== event.pubkey ||
event.created_at - previousCreatedAt > int(3, MINUTE) ||
[RELAY_ADD_MEMBER, RELAY_REMOVE_MEMBER].includes(previousKind!),
})
previousDate = date
previousKind = event.kind
previousPubkey = event.pubkey
previousCreatedAt = event.created_at
seen.add(event.id)
}
}