From f6d2f98eae1c9a6fd466f6bad8a5ac4aaa4f73e1 Mon Sep 17 00:00:00 2001 From: Gigi Date: Wed, 8 Oct 2025 07:06:42 +0100 Subject: [PATCH] refactor: make zap split sliders independent using weights - Changed from percentage-based to weight-based zap splits - All three sliders (highlighter, author, Boris) are now independent - Weights are normalized to calculate actual percentages - UI shows both weight value and calculated percentage - Added migration logic for users with old percentage-based settings - Each slider can be adjusted without affecting the others - Prevents interdependent slider behavior that was confusing Breaking change: Settings now use zapSplitHighlighterWeight, zapSplitAuthorWeight, and zapSplitBorisWeight instead of zapSplitPercentage and borisSupportPercentage --- src/components/Settings.tsx | 30 ++++++++-- src/components/Settings/ZapSettings.tsx | 58 ++++++++++++++------ src/services/highlightCreationService.ts | 70 +++++++++++++++--------- src/services/settingsService.ts | 8 +-- 4 files changed, 113 insertions(+), 53 deletions(-) diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index d031df7c..7f4741f8 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -24,8 +24,9 @@ const DEFAULT_SETTINGS: UserSettings = { defaultHighlightVisibilityNostrverse: true, defaultHighlightVisibilityFriends: true, defaultHighlightVisibilityMine: true, - zapSplitPercentage: 50, - borisSupportPercentage: 2.1, + zapSplitHighlighterWeight: 50, + zapSplitBorisWeight: 2.1, + zapSplitAuthorWeight: 50, } interface SettingsProps { @@ -35,11 +36,32 @@ interface SettingsProps { } const Settings: React.FC = ({ settings, onSave, onClose }) => { - const [localSettings, setLocalSettings] = useState(settings) + const [localSettings, setLocalSettings] = useState(() => { + // Migrate old settings format to new weight-based format + const migrated = { ...settings } + const anySettings = migrated as Record + if ('zapSplitPercentage' in anySettings && !('zapSplitHighlighterWeight' in migrated)) { + migrated.zapSplitHighlighterWeight = (anySettings.zapSplitPercentage as number) ?? 50 + migrated.zapSplitAuthorWeight = 100 - ((anySettings.zapSplitPercentage as number) ?? 50) + } + if ('borisSupportPercentage' in anySettings && !('zapSplitBorisWeight' in migrated)) { + migrated.zapSplitBorisWeight = (anySettings.borisSupportPercentage as number) ?? 2.1 + } + return migrated + }) const isInitialMount = useRef(true) useEffect(() => { - setLocalSettings(settings) + const migrated = { ...settings } + const anySettings = migrated as Record + if ('zapSplitPercentage' in anySettings && !('zapSplitHighlighterWeight' in migrated)) { + migrated.zapSplitHighlighterWeight = (anySettings.zapSplitPercentage as number) ?? 50 + migrated.zapSplitAuthorWeight = 100 - ((anySettings.zapSplitPercentage as number) ?? 50) + } + if ('borisSupportPercentage' in anySettings && !('zapSplitBorisWeight' in migrated)) { + migrated.zapSplitBorisWeight = (anySettings.borisSupportPercentage as number) ?? 2.1 + } + setLocalSettings(migrated) }, [settings]) useEffect(() => { diff --git a/src/components/Settings/ZapSettings.tsx b/src/components/Settings/ZapSettings.tsx index c5d3c294..4f6da63d 100644 --- a/src/components/Settings/ZapSettings.tsx +++ b/src/components/Settings/ZapSettings.tsx @@ -7,34 +7,53 @@ interface ZapSettingsProps { } const ZapSettings: React.FC = ({ settings, onUpdate }) => { - const highlighterPercentage = settings.zapSplitPercentage ?? 50 - const borisPercentage = settings.borisSupportPercentage ?? 2.1 - const authorPercentage = Math.max(0, 100 - highlighterPercentage - borisPercentage) + const highlighterWeight = settings.zapSplitHighlighterWeight ?? 50 + const borisWeight = settings.zapSplitBorisWeight ?? 2.1 + const authorWeight = settings.zapSplitAuthorWeight ?? 50 + + // Calculate actual percentages from weights + const totalWeight = highlighterWeight + borisWeight + authorWeight + const highlighterPercentage = totalWeight > 0 ? (highlighterWeight / totalWeight) * 100 : 0 + const borisPercentage = totalWeight > 0 ? (borisWeight / totalWeight) * 100 : 0 + const authorPercentage = totalWeight > 0 ? (authorWeight / totalWeight) * 100 : 0 return (

Zap Splits

- +
- You: {highlighterPercentage}% - Author(s): {authorPercentage.toFixed(1)}% - Boris: {borisPercentage}% + Weight: {highlighterWeight} + ({highlighterPercentage.toFixed(1)}%)
onUpdate({ zapSplitPercentage: parseInt(e.target.value) })} + value={highlighterWeight} + onChange={(e) => onUpdate({ zapSplitHighlighterWeight: parseInt(e.target.value) })} className="zap-split-slider" /> -
- When you highlight nostr-native content, zaps will be split between you (curator) and the author(s). - If the content has multiple authors, their share is divided proportionally. +
+
+ +
+ +
+
+ Weight: {authorWeight} + ({authorPercentage.toFixed(1)}%)
+ onUpdate({ zapSplitAuthorWeight: parseInt(e.target.value) })} + className="zap-split-slider" + />
@@ -42,22 +61,25 @@ const ZapSettings: React.FC = ({ settings, onUpdate }) => {
- {borisPercentage.toFixed(1)}% + Weight: {borisWeight.toFixed(1)} + ({borisPercentage.toFixed(1)}%)
onUpdate({ borisSupportPercentage: parseFloat(e.target.value) })} + value={borisWeight} + onChange={(e) => onUpdate({ zapSplitBorisWeight: parseFloat(e.target.value) })} className="zap-split-slider" /> -
- Optional: Include a small percentage for Boris development and maintenance. -
+ +
+ Weights determine zap splits when highlighting nostr-native content. + If the content has multiple authors, their share is divided proportionally. +
) } diff --git a/src/services/highlightCreationService.ts b/src/services/highlightCreationService.ts index 5985bbb8..31394d56 100644 --- a/src/services/highlightCreationService.ts +++ b/src/services/highlightCreationService.ts @@ -79,9 +79,26 @@ export async function createHighlight( // Add zap tags for nostr-native content (NIP-57 Appendix G) if (typeof source === 'object' && 'kind' in source) { - const zapSplitPercentage = settings?.zapSplitPercentage ?? 50 - const borisSupportPercentage = settings?.borisSupportPercentage ?? 2.1 - addZapTags(highlightEvent, account.pubkey, source, zapSplitPercentage, borisSupportPercentage) + // Migrate old settings format to new weight-based format if needed + let highlighterWeight = settings?.zapSplitHighlighterWeight + let borisWeight = settings?.zapSplitBorisWeight + let authorWeight = settings?.zapSplitAuthorWeight + + const anySettings = settings as Record | undefined + if (!highlighterWeight && anySettings && 'zapSplitPercentage' in anySettings) { + highlighterWeight = anySettings.zapSplitPercentage as number + authorWeight = 100 - (anySettings.zapSplitPercentage as number) + } + if (!borisWeight && anySettings && 'borisSupportPercentage' in anySettings) { + borisWeight = anySettings.borisSupportPercentage as number + } + + // Use defaults if still undefined + highlighterWeight = highlighterWeight ?? 50 + borisWeight = borisWeight ?? 2.1 + authorWeight = authorWeight ?? 50 + + addZapTags(highlightEvent, account.pubkey, source, highlighterWeight, borisWeight, authorWeight) } // Sign the event @@ -194,37 +211,38 @@ function extractContext(selectedText: string, articleContent: string): string | * @param event The highlight event to add zap tags to * @param highlighterPubkey The pubkey of the user creating the highlight * @param sourceEvent The source event (may contain existing zap tags) - * @param highlighterPercentage Percentage (0-100) to give to the highlighter (default 50) - * @param borisPercentage Percentage (0-100) to give to Boris (default 2.1) + * @param highlighterWeight Weight to give to the highlighter (default 50) + * @param borisWeight Weight to give to Boris (default 2.1) + * @param authorWeight Weight to give to author(s) (default 50) */ function addZapTags( event: NostrEvent, highlighterPubkey: string, sourceEvent: NostrEvent, - highlighterPercentage: number = 50, - borisPercentage: number = 2.1 + highlighterWeight: number = 50, + borisWeight: number = 2.1, + authorWeight: number = 50 ): void { // Use a reliable relay for zap metadata lookup (first non-local relay) const zapRelay = RELAYS.find(r => !r.includes('localhost')) || RELAYS[0] - // Calculate author group percentage (what remains after highlighter and Boris) - const authorGroupPercentage = Math.max(0, 100 - highlighterPercentage - borisPercentage) - // Extract existing zap tags from source event (the "author group") const existingZapTags = sourceEvent.tags.filter(tag => tag[0] === 'zap') // Add zap tag for the highlighter - event.tags.push(['zap', highlighterPubkey, zapRelay, highlighterPercentage.toString()]) - - // Add zap tag for Boris (if percentage > 0 and Boris is not the highlighter) - if (borisPercentage > 0 && BORIS_PUBKEY !== highlighterPubkey) { - event.tags.push(['zap', BORIS_PUBKEY, zapRelay, borisPercentage.toFixed(1)]) + if (highlighterWeight > 0) { + event.tags.push(['zap', highlighterPubkey, zapRelay, highlighterWeight.toString()]) } - if (existingZapTags.length > 0) { + // Add zap tag for Boris (if weight > 0 and Boris is not the highlighter) + if (borisWeight > 0 && BORIS_PUBKEY !== highlighterPubkey) { + event.tags.push(['zap', BORIS_PUBKEY, zapRelay, borisWeight.toFixed(1)]) + } + + if (existingZapTags.length > 0 && authorWeight > 0) { // Calculate total weight from existing zap tags const totalExistingWeight = existingZapTags.reduce((sum, tag) => { - const weight = parseInt(tag[3] || '1', 10) + const weight = parseFloat(tag[3] || '1') return sum + weight }, 0) @@ -236,25 +254,23 @@ function addZapTags( // Skip if this is the highlighter or Boris (they already have their shares) if (authorPubkey === highlighterPubkey || authorPubkey === BORIS_PUBKEY) continue - const originalWeight = parseInt(zapTag[3] || '1', 10) + const originalWeight = parseFloat(zapTag[3] || '1') const originalRelay = zapTag[2] || zapRelay - // Calculate proportional weight: (original weight / total weight) * author group percentage - const adjustedWeight = (originalWeight / totalExistingWeight) * authorGroupPercentage + // Calculate proportional weight: (original weight / total weight) * author group weight + const adjustedWeight = (originalWeight / totalExistingWeight) * authorWeight // Only add if weight is greater than 0 if (adjustedWeight > 0) { event.tags.push(['zap', authorPubkey, originalRelay, adjustedWeight.toFixed(1)]) } } - } else { - // No existing zap tags, use simple split between highlighter, Boris, and source author + } else if (authorWeight > 0) { + // No existing zap tags, give full author weight to source author - // Add zap tag for the original author (only if different from highlighter and Boris, and if weight > 0) - if (sourceEvent.pubkey !== highlighterPubkey && - sourceEvent.pubkey !== BORIS_PUBKEY && - authorGroupPercentage > 0) { - event.tags.push(['zap', sourceEvent.pubkey, zapRelay, authorGroupPercentage.toFixed(1)]) + // Add zap tag for the original author (only if different from highlighter and Boris) + if (sourceEvent.pubkey !== highlighterPubkey && sourceEvent.pubkey !== BORIS_PUBKEY) { + event.tags.push(['zap', sourceEvent.pubkey, zapRelay, authorWeight.toFixed(1)]) } } } diff --git a/src/services/settingsService.ts b/src/services/settingsService.ts index 0b626092..8194bf62 100644 --- a/src/services/settingsService.ts +++ b/src/services/settingsService.ts @@ -35,10 +35,10 @@ export interface UserSettings { defaultHighlightVisibilityNostrverse?: boolean defaultHighlightVisibilityFriends?: boolean defaultHighlightVisibilityMine?: boolean - // Zap split percentage for highlights (0-100, default 50) - zapSplitPercentage?: number - // Boris support percentage (0-100, default 2.1) - borisSupportPercentage?: number + // Zap split weights (treated as relative weights, not strict percentages) + zapSplitHighlighterWeight?: number // default 50 + zapSplitBorisWeight?: number // default 2.1 + zapSplitAuthorWeight?: number // default 50 } export async function loadSettings(