mirror of
https://github.com/dergigi/boris.git
synced 2026-02-16 12:34:41 +01:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1609416e1e | ||
|
|
00d9fbdbab | ||
|
|
239ab5763d |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "boris",
|
||||
"version": "0.2.5",
|
||||
"version": "0.2.6",
|
||||
"description": "A minimal nostr client for bookmark management",
|
||||
"homepage": "https://xn--bris-v0b.com/",
|
||||
"type": "module",
|
||||
|
||||
@@ -109,7 +109,8 @@ const Bookmarks: React.FC<BookmarksProps> = ({ relayPool, onLogout }) => {
|
||||
currentArticle,
|
||||
selectedUrl,
|
||||
readerContent,
|
||||
onHighlightCreated: (highlight) => setHighlights(prev => [highlight, ...prev])
|
||||
onHighlightCreated: (highlight) => setHighlights(prev => [highlight, ...prev]),
|
||||
settings
|
||||
})
|
||||
|
||||
// Load nostr-native article if naddr is in URL
|
||||
|
||||
@@ -23,6 +23,7 @@ const DEFAULT_SETTINGS: UserSettings = {
|
||||
defaultHighlightVisibilityNostrverse: true,
|
||||
defaultHighlightVisibilityFriends: true,
|
||||
defaultHighlightVisibilityMine: true,
|
||||
zapSplitPercentage: 50,
|
||||
}
|
||||
|
||||
interface SettingsProps {
|
||||
|
||||
@@ -107,6 +107,27 @@ const ReadingDisplaySettings: React.FC<ReadingDisplaySettingsProps> = ({ setting
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="setting-group">
|
||||
<label className="setting-label">Zap Split for Highlights</label>
|
||||
<div className="zap-split-container">
|
||||
<div className="zap-split-labels">
|
||||
<span className="zap-split-label">You: {settings.zapSplitPercentage ?? 50}%</span>
|
||||
<span className="zap-split-label">Author: {100 - (settings.zapSplitPercentage ?? 50)}%</span>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="100"
|
||||
value={settings.zapSplitPercentage ?? 50}
|
||||
onChange={(e) => onUpdate({ zapSplitPercentage: parseInt(e.target.value) })}
|
||||
className="zap-split-slider"
|
||||
/>
|
||||
<div className="zap-split-description">
|
||||
When highlighting nostr-native content, zaps will be split between you and the author.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="setting-preview">
|
||||
<div className="preview-label">Preview</div>
|
||||
<div
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faChevronRight, faRightFromBracket, faRightToBracket, faUserCircle, faGear, faRotate } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faChevronRight, faRightFromBracket, faRightToBracket, faUserCircle, faGear, faRotate, faHome } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Hooks } from 'applesauce-react'
|
||||
import { useEventModel } from 'applesauce-react/hooks'
|
||||
import { Models } from 'applesauce-core'
|
||||
@@ -17,6 +18,7 @@ interface SidebarHeaderProps {
|
||||
|
||||
const SidebarHeader: React.FC<SidebarHeaderProps> = ({ onToggleCollapse, onLogout, onOpenSettings, onRefresh, isRefreshing }) => {
|
||||
const [isConnecting, setIsConnecting] = useState(false)
|
||||
const navigate = useNavigate()
|
||||
const activeAccount = Hooks.useActiveAccount()
|
||||
const accountManager = Hooks.useAccountManager()
|
||||
const profile = useEventModel(Models.ProfileModel, activeAccount ? [activeAccount.pubkey] : null)
|
||||
@@ -61,6 +63,13 @@ const SidebarHeader: React.FC<SidebarHeaderProps> = ({ onToggleCollapse, onLogou
|
||||
<FontAwesomeIcon icon={faChevronRight} />
|
||||
</button>
|
||||
<div className="sidebar-header-right">
|
||||
<IconButton
|
||||
icon={faHome}
|
||||
onClick={() => navigate('/')}
|
||||
title="Home"
|
||||
ariaLabel="Home"
|
||||
variant="ghost"
|
||||
/>
|
||||
{onRefresh && (
|
||||
<IconButton
|
||||
icon={faRotate}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Highlight } from '../types/highlights'
|
||||
import { ReadableContent } from '../services/readerService'
|
||||
import { createHighlight, eventToHighlight } from '../services/highlightCreationService'
|
||||
import { HighlightButtonRef } from '../components/HighlightButton'
|
||||
import { UserSettings } from '../services/settingsService'
|
||||
|
||||
interface UseHighlightCreationParams {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -14,6 +15,7 @@ interface UseHighlightCreationParams {
|
||||
selectedUrl: string | undefined
|
||||
readerContent: ReadableContent | undefined
|
||||
onHighlightCreated: (highlight: Highlight) => void
|
||||
settings?: UserSettings
|
||||
}
|
||||
|
||||
export const useHighlightCreation = ({
|
||||
@@ -22,7 +24,8 @@ export const useHighlightCreation = ({
|
||||
currentArticle,
|
||||
selectedUrl,
|
||||
readerContent,
|
||||
onHighlightCreated
|
||||
onHighlightCreated,
|
||||
settings
|
||||
}: UseHighlightCreationParams) => {
|
||||
const highlightButtonRef = useRef<HighlightButtonRef>(null)
|
||||
|
||||
@@ -56,7 +59,9 @@ export const useHighlightCreation = ({
|
||||
source,
|
||||
activeAccount,
|
||||
relayPool,
|
||||
contentForContext
|
||||
contentForContext,
|
||||
undefined,
|
||||
settings
|
||||
)
|
||||
|
||||
console.log('✅ Highlight created successfully!')
|
||||
@@ -67,7 +72,7 @@ export const useHighlightCreation = ({
|
||||
} catch (error) {
|
||||
console.error('Failed to create highlight:', error)
|
||||
}
|
||||
}, [activeAccount, relayPool, currentArticle, selectedUrl, readerContent, onHighlightCreated])
|
||||
}, [activeAccount, relayPool, currentArticle, selectedUrl, readerContent, onHighlightCreated, settings])
|
||||
|
||||
return {
|
||||
highlightButtonRef,
|
||||
|
||||
@@ -2020,6 +2020,74 @@ body {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.zap-split-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.zap-split-labels {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.zap-split-label {
|
||||
font-size: 0.9rem;
|
||||
color: #ccc;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.zap-split-slider {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: linear-gradient(to right, #646cff 0%, #646cff 50%, #f97316 50%, #f97316 100%);
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.zap-split-slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: white;
|
||||
border: 2px solid #646cff;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.zap-split-slider::-moz-range-thumb {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: white;
|
||||
border: 2px solid #646cff;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.zap-split-slider::-webkit-slider-thumb:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 0 8px rgba(100, 108, 255, 0.5);
|
||||
}
|
||||
|
||||
.zap-split-slider::-moz-range-thumb:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 0 8px rgba(100, 108, 255, 0.5);
|
||||
}
|
||||
|
||||
.zap-split-description {
|
||||
font-size: 0.8rem;
|
||||
color: #999;
|
||||
line-height: 1.4;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.settings-footer {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { NostrEvent } from 'nostr-tools'
|
||||
import { Helpers } from 'applesauce-core'
|
||||
import { RELAYS } from '../config/relays'
|
||||
import { Highlight } from '../types/highlights'
|
||||
import { UserSettings } from './settingsService'
|
||||
|
||||
const {
|
||||
getHighlightText,
|
||||
@@ -30,7 +31,8 @@ export async function createHighlight(
|
||||
account: IAccount,
|
||||
relayPool: RelayPool,
|
||||
contentForContext?: string,
|
||||
comment?: string
|
||||
comment?: string,
|
||||
settings?: UserSettings
|
||||
): Promise<NostrEvent> {
|
||||
if (!selectedText || !source) {
|
||||
throw new Error('Missing required data to create highlight')
|
||||
@@ -72,6 +74,12 @@ export async function createHighlight(
|
||||
highlightEvent.tags.push(['alt', 'Highlight created by Boris. readwithboris.com'])
|
||||
}
|
||||
|
||||
// Add zap tags for nostr-native content (NIP-57 Appendix G)
|
||||
if (typeof source === 'object' && 'kind' in source) {
|
||||
const zapSplitPercentage = settings?.zapSplitPercentage ?? 50
|
||||
addZapTags(highlightEvent, account.pubkey, source.pubkey, zapSplitPercentage)
|
||||
}
|
||||
|
||||
// Sign the event
|
||||
const signedEvent = await factory.sign(highlightEvent)
|
||||
|
||||
@@ -176,6 +184,36 @@ function extractContext(selectedText: string, articleContent: string): string |
|
||||
return contextParts.length > 1 ? contextParts.join(' ') : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds zap tags to a highlight event for split payments (NIP-57 Appendix G)
|
||||
* @param event The highlight event to add zap tags to
|
||||
* @param highlighterPubkey The pubkey of the user creating the highlight
|
||||
* @param authorPubkey The pubkey of the original article author
|
||||
* @param highlighterPercentage Percentage (0-100) to give to the highlighter (default 50)
|
||||
*/
|
||||
function addZapTags(
|
||||
event: NostrEvent,
|
||||
highlighterPubkey: string,
|
||||
authorPubkey: string,
|
||||
highlighterPercentage: number = 50
|
||||
): void {
|
||||
// Calculate weights based on percentage
|
||||
// Using simple integer weights where highlighterPercentage:authorPercentage ratio is maintained
|
||||
const highlighterWeight = Math.round(highlighterPercentage)
|
||||
const authorWeight = Math.round(100 - highlighterPercentage)
|
||||
|
||||
// Use a reliable relay for zap metadata lookup (first non-local relay)
|
||||
const zapRelay = RELAYS.find(r => !r.includes('localhost')) || RELAYS[0]
|
||||
|
||||
// Add zap tag for the highlighter
|
||||
event.tags.push(['zap', highlighterPubkey, zapRelay, highlighterWeight.toString()])
|
||||
|
||||
// Add zap tag for the original author (only if different from highlighter)
|
||||
if (authorPubkey !== highlighterPubkey) {
|
||||
event.tags.push(['zap', authorPubkey, zapRelay, authorWeight.toString()])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a NostrEvent to a Highlight object for immediate UI display
|
||||
*/
|
||||
|
||||
@@ -35,6 +35,8 @@ export interface UserSettings {
|
||||
defaultHighlightVisibilityNostrverse?: boolean
|
||||
defaultHighlightVisibilityFriends?: boolean
|
||||
defaultHighlightVisibilityMine?: boolean
|
||||
// Zap split percentage for highlights (0-100, default 50)
|
||||
zapSplitPercentage?: number
|
||||
}
|
||||
|
||||
export async function loadSettings(
|
||||
|
||||
Reference in New Issue
Block a user