Files
boris/src/services/settingsService.ts
Gigi 17b5ffd96e feat: implement three-level highlight system
- Add three highlight levels: nostrverse (all), friends (followed), and mine (user's own)
- Create contactService to fetch user's follow list from kind 3 events
- Add three configurable colors in settings (purple, orange, yellow defaults)
- Replace mode switcher with independent toggle buttons for each level
- Update highlight rendering to apply level-specific colors using CSS custom properties
- Add CSS styles for three-level highlights in both marker and underline modes
- Classify highlights dynamically based on user's context and follow list
- All three levels can be shown/hidden independently via toggle buttons
2025-10-05 20:11:10 +01:00

112 lines
3.1 KiB
TypeScript

import { IEventStore, mapEventsToStore } from 'applesauce-core'
import { APP_DATA_KIND, getAppDataContent } from 'applesauce-core/helpers/app-data'
import { AppDataBlueprint } from 'applesauce-factory/blueprints'
import { EventFactory } from 'applesauce-factory'
import { RelayPool, onlyEvents } from 'applesauce-relay'
import { NostrEvent } from 'nostr-tools'
import { firstValueFrom } from 'rxjs'
const SETTINGS_IDENTIFIER = 'com.dergigi.boris.user-settings'
export interface UserSettings {
collapseOnArticleOpen?: boolean
defaultViewMode?: 'compact' | 'cards' | 'large'
showUnderlines?: boolean
sidebarCollapsed?: boolean
highlightsCollapsed?: boolean
readingFont?: string
fontSize?: number
highlightStyle?: 'marker' | 'underline'
highlightColor?: string
// Three-level highlight colors
highlightColorNostrverse?: string
highlightColorFriends?: string
highlightColorMine?: string
}
export async function loadSettings(
relayPool: RelayPool,
eventStore: IEventStore,
pubkey: string,
relays: string[]
): Promise<UserSettings | null> {
return new Promise((resolve) => {
let hasResolved = false
const timeout = setTimeout(() => {
if (!hasResolved) {
hasResolved = true
resolve(null)
}
}, 5000)
const sub = relayPool
.subscription(relays, {
kinds: [APP_DATA_KIND],
authors: [pubkey],
'#d': [SETTINGS_IDENTIFIER]
})
.pipe(onlyEvents(), mapEventsToStore(eventStore))
.subscribe({
complete: async () => {
clearTimeout(timeout)
if (!hasResolved) {
hasResolved = true
try {
const event = await firstValueFrom(
eventStore.replaceable(APP_DATA_KIND, pubkey, SETTINGS_IDENTIFIER)
)
if (event) {
const content = getAppDataContent<UserSettings>(event)
resolve(content || null)
} else {
resolve(null)
}
} catch {
resolve(null)
}
}
},
error: () => {
clearTimeout(timeout)
if (!hasResolved) {
hasResolved = true
resolve(null)
}
}
})
setTimeout(() => {
sub.unsubscribe()
}, 5000)
})
}
export async function saveSettings(
relayPool: RelayPool,
eventStore: IEventStore,
factory: EventFactory,
settings: UserSettings,
relays: string[]
): Promise<void> {
const draft = await factory.create(AppDataBlueprint, SETTINGS_IDENTIFIER, settings, false)
const signed = await factory.sign(draft)
eventStore.add(signed)
await relayPool.publish(relays, signed)
}
export function watchSettings(
eventStore: IEventStore,
pubkey: string,
callback: (settings: UserSettings | null) => void
) {
return eventStore.replaceable(APP_DATA_KIND, pubkey, SETTINGS_IDENTIFIER).subscribe((event: NostrEvent | undefined) => {
if (event) {
const content = getAppDataContent<UserSettings>(event)
callback(content || null)
} else {
callback(null)
}
})
}