Browse Source

more bug-fixes

imwald
Silberengel 4 months ago
parent
commit
e3ce5f7b8d
  1. 137
      src/components/CacheRelaysSetting/index.tsx
  2. 30
      src/components/PostEditor/PollEditor.tsx
  3. 1
      src/components/PostEditor/PostContent.tsx
  4. 79
      src/pages/secondary/PostSettingsPage/CacheRelayOnlySetting.tsx

137
src/components/CacheRelaysSetting/index.tsx

@ -57,11 +57,11 @@ export default function CacheRelaysSetting() { @@ -57,11 +57,11 @@ export default function CacheRelaysSetting() {
const [loadingItems, setLoadingItems] = useState(false)
const [wordWrapEnabled, setWordWrapEnabled] = useState(true)
const [searchQuery, setSearchQuery] = useState('')
const [consoleLogs, setConsoleLogs] = useState<Array<{ type: string; message: string; timestamp: number }>>([])
const [consoleLogs, setConsoleLogs] = useState<Array<{ type: string; message: string; formattedParts?: Array<{ text: string; style?: string }>; timestamp: number }>>([])
const [showConsoleLogs, setShowConsoleLogs] = useState(false)
const [consoleLogSearch, setConsoleLogSearch] = useState('')
const [consoleLogLevel, setConsoleLogLevel] = useState<'error' | 'warn' | 'info' | 'log' | 'all'>('error')
const consoleLogRef = useRef<Array<{ type: string; message: string; timestamp: number }>>([])
const consoleLogRef = useRef<Array<{ type: string; message: string; formattedParts?: Array<{ text: string; style?: string }>; timestamp: number }>>([])
const sensors = useSensors(
useSensor(PointerSensor, {
@ -401,7 +401,9 @@ export default function CacheRelaysSetting() { @@ -401,7 +401,9 @@ export default function CacheRelaysSetting() {
}
}
// Capture console logs - start capturing immediately when component mounts
// Capture console logs and logger output - start capturing immediately when component mounts
// Note: The logger uses console.log/error/warn/info internally, so intercepting console methods
// will automatically capture all logger output (debug, info, warn, error, perf, component, etc.)
useEffect(() => {
const originalLog = console.log
const originalError = console.error
@ -409,20 +411,64 @@ export default function CacheRelaysSetting() { @@ -409,20 +411,64 @@ export default function CacheRelaysSetting() {
const originalInfo = console.info
const captureLog = (type: string, ...args: any[]) => {
const message = args.map(arg => {
if (typeof arg === 'object') {
try {
return JSON.stringify(arg, null, 2)
} catch {
// Handle console formatting with %c placeholders for CSS styling
// Console.log supports %c for CSS styling: console.log('%cText', 'color: red')
let message = ''
let formattedParts: Array<{ text: string; style?: string }> = []
if (args.length > 0 && typeof args[0] === 'string' && args[0].includes('%c')) {
// Handle %c formatting
const formatString = args[0]
const parts = formatString.split(/%c/g)
formattedParts = []
for (let i = 0; i < parts.length; i++) {
const text = parts[i]
const style = i < args.length - 1 && typeof args[i + 1] === 'string' ? args[i + 1] : undefined
formattedParts.push({ text, style })
}
// Also include remaining args
const remainingArgs = args.slice(parts.length)
if (remainingArgs.length > 0) {
const remainingText = remainingArgs.map(arg => {
if (typeof arg === 'object') {
try {
return JSON.stringify(arg, null, 2)
} catch {
return String(arg)
}
}
return String(arg)
}).join(' ')
if (formattedParts.length > 0) {
formattedParts[formattedParts.length - 1].text += ' ' + remainingText
} else {
formattedParts.push({ text: remainingText })
}
}
return String(arg)
}).join(' ')
// Create a plain text version for search/filtering
message = formattedParts.map(p => p.text).join('')
} else {
// Normal formatting - convert all args to strings
message = args.map(arg => {
if (typeof arg === 'object') {
try {
return JSON.stringify(arg, null, 2)
} catch {
return String(arg)
}
}
return String(arg)
}).join(' ')
formattedParts = [{ text: message }]
}
const logEntry = {
type,
message,
formattedParts,
timestamp: Date.now()
}
@ -438,6 +484,7 @@ export default function CacheRelaysSetting() { @@ -438,6 +484,7 @@ export default function CacheRelaysSetting() {
}
}
// Intercept console methods - this will capture all logger output since logger uses console internally
console.log = (...args: any[]) => {
captureLog('log', ...args)
originalLog.apply(console, args)
@ -836,16 +883,6 @@ export default function CacheRelaysSetting() { @@ -836,16 +883,6 @@ export default function CacheRelaysSetting() {
{t('View Console Logs')} ({consoleLogRef.current.length})
</Button>
</div>
{Object.keys(cacheInfo).length > 0 && (
<div className="text-xs text-muted-foreground space-y-1 mt-2">
<div className="font-semibold">{t('Cache Statistics:')}</div>
{Object.entries(cacheInfo).map(([storeName, count]) => (
<div key={storeName}>
{storeName}: {count} {t('items')}
</div>
))}
</div>
)}
</div>
{isSmallScreen ? (
@ -1290,7 +1327,35 @@ export default function CacheRelaysSetting() { @@ -1290,7 +1327,35 @@ export default function CacheRelaysSetting() {
[{log.type}]
</span>
<pre className="flex-1 overflow-x-auto whitespace-pre-wrap break-words">
{log.message}
{log.formattedParts ? (
log.formattedParts.map((part, i) => {
if (part.style) {
// Parse CSS string like "color:#f1b912" or "color: #f1b912; font-weight: bold"
const styleObj: Record<string, string> = {}
part.style.split(';').forEach(rule => {
const trimmed = rule.trim()
if (trimmed) {
const colonIndex = trimmed.indexOf(':')
if (colonIndex > 0) {
const key = trimmed.substring(0, colonIndex).trim()
const value = trimmed.substring(colonIndex + 1).trim()
// Convert kebab-case to camelCase
const camelKey = key.replace(/-([a-z])/g, (_, c) => c.toUpperCase())
styleObj[camelKey] = value
}
}
})
return (
<span key={i} style={styleObj}>
{part.text}
</span>
)
}
return <span key={i}>{part.text}</span>
})
) : (
log.message
)}
</pre>
</div>
</div>
@ -1379,7 +1444,35 @@ export default function CacheRelaysSetting() { @@ -1379,7 +1444,35 @@ export default function CacheRelaysSetting() {
[{log.type}]
</span>
<pre className="flex-1 overflow-x-auto whitespace-pre-wrap break-words">
{log.message}
{log.formattedParts ? (
log.formattedParts.map((part, i) => {
if (part.style) {
// Parse CSS string like "color:#f1b912" or "color: #f1b912; font-weight: bold"
const styleObj: Record<string, string> = {}
part.style.split(';').forEach(rule => {
const trimmed = rule.trim()
if (trimmed) {
const colonIndex = trimmed.indexOf(':')
if (colonIndex > 0) {
const key = trimmed.substring(0, colonIndex).trim()
const value = trimmed.substring(colonIndex + 1).trim()
// Convert kebab-case to camelCase
const camelKey = key.replace(/-([a-z])/g, (_, c) => c.toUpperCase())
styleObj[camelKey] = value
}
}
})
return (
<span key={i} style={styleObj}>
{part.text}
</span>
)
}
return <span key={i}>{part.text}</span>
})
) : (
log.message
)}
</pre>
</div>
</div>

30
src/components/PostEditor/PollEditor.tsx

@ -2,21 +2,23 @@ import { Button } from '@/components/ui/button' @@ -2,21 +2,23 @@ import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Switch } from '@/components/ui/switch'
import { normalizeUrl } from '@/lib/url'
import { TPollCreateData } from '@/types'
import dayjs from 'dayjs'
import { Eraser, X } from 'lucide-react'
import { Dispatch, SetStateAction, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import PostRelaySelector from './PostRelaySelector'
export default function PollEditor({
pollCreateData,
setPollCreateData,
setIsPoll: _setIsPoll
setIsPoll: _setIsPoll,
content = ''
}: {
pollCreateData: TPollCreateData
setPollCreateData: Dispatch<SetStateAction<TPollCreateData>>
setIsPoll: Dispatch<SetStateAction<boolean>>
content?: string
}) {
const { t } = useTranslation()
const [isMultipleChoice, setIsMultipleChoice] = useState(pollCreateData.isMultipleChoice)
@ -24,21 +26,17 @@ export default function PollEditor({ @@ -24,21 +26,17 @@ export default function PollEditor({
const [endsAt, setEndsAt] = useState(
pollCreateData.endsAt ? dayjs(pollCreateData.endsAt * 1000).format('YYYY-MM-DDTHH:mm') : ''
)
const [relayUrls, setRelayUrls] = useState(pollCreateData.relays.join(', '))
const [additionalRelayUrls, setAdditionalRelayUrls] = useState<string[]>(pollCreateData.relays)
const [_isProtectedEvent, setIsProtectedEvent] = useState(false)
useEffect(() => {
setPollCreateData({
isMultipleChoice,
options,
endsAt: endsAt ? dayjs(endsAt).startOf('minute').unix() : undefined,
relays: relayUrls
? relayUrls
.split(',')
.map((url) => normalizeUrl(url.trim()))
.filter(Boolean)
: []
relays: additionalRelayUrls
})
}, [isMultipleChoice, options, endsAt, relayUrls])
}, [isMultipleChoice, options, endsAt, additionalRelayUrls, setPollCreateData])
const handleAddOption = () => {
setOptions([...options, ''])
@ -113,13 +111,11 @@ export default function PollEditor({ @@ -113,13 +111,11 @@ export default function PollEditor({
</div>
</div>
<div className="grid gap-2">
<Label htmlFor="relay-urls">{t('Relay URLs (optional, comma-separated)')}</Label>
<Input
id="relay-urls"
value={relayUrls}
onChange={(e) => setRelayUrls(e.target.value)}
placeholder="wss://relay1.com, wss://relay2.com"
<div className="space-y-2">
<PostRelaySelector
setAdditionalRelayUrls={setAdditionalRelayUrls}
setIsProtectedEvent={setIsProtectedEvent}
content={content}
/>
</div>
</div>

1
src/components/PostEditor/PostContent.tsx

@ -1617,6 +1617,7 @@ export default function PostContent({ @@ -1617,6 +1617,7 @@ export default function PostContent({
pollCreateData={pollCreateData}
setPollCreateData={setPollCreateData}
setIsPoll={setIsPoll}
content={text}
/>
)}
{isHighlight && (

79
src/pages/secondary/PostSettingsPage/CacheRelayOnlySetting.tsx

@ -1,46 +1,65 @@ @@ -1,46 +1,65 @@
import { Label } from '@/components/ui/label'
import { Switch } from '@/components/ui/switch'
import { StorageKey } from '@/constants'
import { hasCacheRelays } from '@/lib/private-relays'
import { StorageKey, ExtendedKind } from '@/constants'
import { useNostr } from '@/providers/NostrProvider'
import indexedDb from '@/services/indexed-db.service'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
export default function CacheRelayOnlySetting() {
const { t } = useTranslation()
const { pubkey } = useNostr()
const [enabled, setEnabled] = useState(true) // Default ON when cache exists
const { cacheRelayListEvent, pubkey } = useNostr()
const [hasCacheRelaysAvailable, setHasCacheRelaysAvailable] = useState(false)
const [enabled, setEnabled] = useState(false) // Start as OFF, will be updated based on cache availability
// Check if user has cache relays - check both provider state and IndexedDB as fallback
// Note: Cache relay events use 'r' tags, not 'relay' tags
useEffect(() => {
// Check if user has cache relays first
if (pubkey) {
hasCacheRelays(pubkey)
.then((hasCache) => {
setHasCacheRelaysAvailable(hasCache)
if (hasCache) {
// If cache exists, load from localStorage or default to true (ON)
const stored = window.localStorage.getItem(StorageKey.USE_CACHE_ONLY_FOR_PRIVATE_NOTES)
setEnabled(stored === null ? true : stored === 'true')
} else {
// If no cache, set to false (OFF) and save it
setEnabled(false)
window.localStorage.setItem(StorageKey.USE_CACHE_ONLY_FOR_PRIVATE_NOTES, 'false')
const checkCacheRelays = async () => {
let hasRelays = false
// First check provider state
if (cacheRelayListEvent) {
hasRelays = cacheRelayListEvent.tags.some(tag => tag[0] === 'r' && tag[1])
} else if (pubkey) {
// Fallback: check IndexedDB directly if provider state isn't loaded yet
try {
const storedEvent = await indexedDb.getReplaceableEvent(pubkey, ExtendedKind.CACHE_RELAYS)
if (storedEvent) {
hasRelays = storedEvent.tags.some(tag => tag[0] === 'r' && tag[1])
}
})
.catch(() => {
setHasCacheRelaysAvailable(false)
// If check fails, assume no cache and set to false
} catch (error) {
// Ignore errors
}
}
setHasCacheRelaysAvailable(hasRelays)
// Set enabled state based on cache availability
if (hasRelays) {
// If cache exists, default to true (ON)
// Only respect localStorage if it's explicitly set to 'false' by the user
const stored = window.localStorage.getItem(StorageKey.USE_CACHE_ONLY_FOR_PRIVATE_NOTES)
// Default to ON when cache exists - only set to OFF if user explicitly set it to 'false'
if (stored === 'false') {
setEnabled(false)
window.localStorage.setItem(StorageKey.USE_CACHE_ONLY_FOR_PRIVATE_NOTES, 'false')
})
} else {
setHasCacheRelaysAvailable(false)
setEnabled(false)
window.localStorage.setItem(StorageKey.USE_CACHE_ONLY_FOR_PRIVATE_NOTES, 'false')
} else {
// Default to ON (either null or 'true')
setEnabled(true)
// Save the default ON state if not already set
if (stored === null) {
window.localStorage.setItem(StorageKey.USE_CACHE_ONLY_FOR_PRIVATE_NOTES, 'true')
}
}
} else {
// If no cache, set to false (OFF) and save it
setEnabled(false)
window.localStorage.setItem(StorageKey.USE_CACHE_ONLY_FOR_PRIVATE_NOTES, 'false')
}
}
}, [pubkey])
checkCacheRelays()
}, [cacheRelayListEvent, pubkey])
const handleEnabledChange = (checked: boolean) => {
setEnabled(checked)

Loading…
Cancel
Save