Browse Source

handled weird highlights from shakespeare

added highlight quote bar and removed quotation marks

fixed highlight preview

added "create highlight" menu item
imwald
Silberengel 4 months ago
parent
commit
7d11d43cc8
  1. 89
      src/components/Note/Highlight/index.tsx
  2. 25
      src/components/NoteOptions/index.tsx
  3. 92
      src/components/NoteOptions/useMenuActions.tsx
  4. 2
      src/components/PostEditor/HighlightEditor.tsx
  5. 31
      src/components/PostEditor/PostContent.tsx
  6. 86
      src/components/PostEditor/PostTextarea/Preview.tsx
  7. 12
      src/components/PostEditor/PostTextarea/index.tsx
  8. 14
      src/components/PostEditor/index.tsx
  9. 4
      src/components/UniversalContent/HighlightSourcePreview.tsx
  10. 6
      src/components/ui/command.tsx

89
src/components/Note/Highlight/index.tsx

@ -4,6 +4,42 @@ import { nip19 } from 'nostr-tools' @@ -4,6 +4,42 @@ import { nip19 } from 'nostr-tools'
import logger from '@/lib/logger'
import HighlightSourcePreview from '@/components/UniversalContent/HighlightSourcePreview'
/**
* Check if a string is a URL or Nostr address
*/
function isUrlOrNostrAddress(value: string | undefined): boolean {
if (!value || typeof value !== 'string') {
return false
}
// Check if it's a URL (http://, https://, or starts with common URL patterns)
try {
if (value.startsWith('http://') || value.startsWith('https://') || value.startsWith('ws://') || value.startsWith('wss://')) {
new URL(value) // Validate it's a proper URL
return true
}
} catch {
// Not a valid URL
}
// Check if it's a Nostr address (nostr: prefix or bech32 encoded)
if (value.startsWith('nostr:')) {
return true
}
// Check if it's a bech32 encoded Nostr address
try {
const decoded = nip19.decode(value)
if (['npub', 'nprofile', 'nevent', 'naddr', 'note', 'nrelay'].includes(decoded.type)) {
return true
}
} catch {
// Not a valid Nostr address
}
return false
}
export default function Highlight({
event,
className
@ -15,6 +51,7 @@ export default function Highlight({ @@ -15,6 +51,7 @@ export default function Highlight({
// Extract the source (e-tag, a-tag, or r-tag) with improved priority handling
let source = null
let quoteSource: string | null = null // For plain text r-tags that aren't URLs/Nostr addresses
let sourceTag: string[] | undefined
// Check for 'source' marker first (highest priority)
@ -50,13 +87,13 @@ export default function Highlight({ @@ -50,13 +87,13 @@ export default function Highlight({
// Process the selected source tag
if (sourceTag) {
if (sourceTag[0] === 'e') {
if (sourceTag[0] === 'e' && sourceTag[1]) {
source = {
type: 'event' as const,
value: sourceTag[1],
bech32: nip19.noteEncode(sourceTag[1])
}
} else if (sourceTag[0] === 'a') {
} else if (sourceTag[0] === 'a' && sourceTag[1]) {
const [kind, pubkey, identifier] = sourceTag[1].split(':')
const relay = sourceTag[2]
source = {
@ -70,11 +107,17 @@ export default function Highlight({ @@ -70,11 +107,17 @@ export default function Highlight({
})
}
} else if (sourceTag[0] === 'r') {
// Check if the r-tag value is a URL or Nostr address
if (sourceTag[1] && isUrlOrNostrAddress(sourceTag[1])) {
source = {
type: 'url' as const,
value: sourceTag[1],
bech32: sourceTag[1]
}
} else if (sourceTag[1]) {
// It's plain text, store it as a quote source
quoteSource = sourceTag[1]
}
}
}
@ -90,30 +133,56 @@ export default function Highlight({ @@ -90,30 +133,56 @@ export default function Highlight({
<div className="flex-1 min-w-0">
{/* Full quoted text with highlighted portion */}
{context && (
<div className="text-base font-normal mb-3 whitespace-pre-wrap break-words">
<div className="text-base font-normal mb-3 whitespace-pre-wrap break-words border-l-4 border-green-500 pl-4">
{contextTag && highlightedText ? (
// If we have both context and highlighted text, show the highlight within the context
<div>
{context.split(highlightedText).map((part, index) => (
{(() => {
// Strip outer quotation marks if present
let cleanContext = context.trim()
if (cleanContext.startsWith('"') && cleanContext.endsWith('"')) {
cleanContext = cleanContext.slice(1, -1).trim()
}
// Strip outer quotation marks from highlighted text if present
let cleanHighlightedText = highlightedText.trim()
if (cleanHighlightedText.startsWith('"') && cleanHighlightedText.endsWith('"')) {
cleanHighlightedText = cleanHighlightedText.slice(1, -1).trim()
}
return cleanContext.split(cleanHighlightedText).map((part, index) => (
<span key={index}>
{part}
{index < context.split(highlightedText).length - 1 && (
{index < cleanContext.split(cleanHighlightedText).length - 1 && (
<mark className="bg-green-200 dark:bg-green-800 px-1 rounded">
{highlightedText}
{cleanHighlightedText}
</mark>
)}
</span>
))}
))
})()}
</div>
) : (
// If no context tag, just show the content as a regular quote
<blockquote className="italic">
"{context}"
</blockquote>
<div>
{(() => {
// Strip outer quotation marks if present
let cleanContext = context.trim()
if (cleanContext.startsWith('"') && cleanContext.endsWith('"')) {
cleanContext = cleanContext.slice(1, -1).trim()
}
return cleanContext
})()}
</div>
)}
</div>
)}
{/* Quote source (plain text r-tag) */}
{quoteSource && (
<div className="mt-3 text-sm text-gray-600 dark:text-gray-400 italic">
{quoteSource.trimStart().startsWith('—') ? quoteSource : `${quoteSource}`}
</div>
)}
{/* Source preview card */}
{source && (
<div className="mt-3">

25
src/components/NoteOptions/index.tsx

@ -7,6 +7,8 @@ import { MobileMenu } from './MobileMenu' @@ -7,6 +7,8 @@ import { MobileMenu } from './MobileMenu'
import RawEventDialog from './RawEventDialog'
import ReportDialog from './ReportDialog'
import { SubMenuAction, useMenuActions } from './useMenuActions'
import PostEditor from '../PostEditor'
import { HighlightData } from '../PostEditor/HighlightEditor'
export default function NoteOptions({ event, className }: { event: Event; className?: string }) {
const { isSmallScreen } = useScreenSize()
@ -16,6 +18,9 @@ export default function NoteOptions({ event, className }: { event: Event; classN @@ -16,6 +18,9 @@ export default function NoteOptions({ event, className }: { event: Event; classN
const [showSubMenu, setShowSubMenu] = useState(false)
const [activeSubMenu, setActiveSubMenu] = useState<SubMenuAction[]>([])
const [subMenuTitle, setSubMenuTitle] = useState('')
const [isPostEditorOpen, setIsPostEditorOpen] = useState(false)
const [initialHighlightData, setInitialHighlightData] = useState<HighlightData | undefined>(undefined)
const [highlightDefaultContent, setHighlightDefaultContent] = useState<string>('')
const closeDrawer = () => {
setIsDrawerOpen(false)
@ -38,7 +43,13 @@ export default function NoteOptions({ event, className }: { event: Event; classN @@ -38,7 +43,13 @@ export default function NoteOptions({ event, className }: { event: Event; classN
showSubMenuActions,
setIsRawEventDialogOpen,
setIsReportDialogOpen,
isSmallScreen
isSmallScreen,
openHighlightEditor: (highlightData: HighlightData, eventContent?: string) => {
setInitialHighlightData(highlightData)
setHighlightDefaultContent(eventContent || '')
setIsPostEditorOpen(true)
closeDrawer()
}
})
const trigger = useMemo(
@ -81,6 +92,18 @@ export default function NoteOptions({ event, className }: { event: Event; classN @@ -81,6 +92,18 @@ export default function NoteOptions({ event, className }: { event: Event; classN
isOpen={isReportDialogOpen}
closeDialog={() => setIsReportDialogOpen(false)}
/>
<PostEditor
open={isPostEditorOpen}
setOpen={(open) => {
setIsPostEditorOpen(open)
if (!open) {
setInitialHighlightData(undefined)
setHighlightDefaultContent('')
}
}}
defaultContent={highlightDefaultContent}
initialHighlightData={initialHighlightData}
/>
</div>
)
}

92
src/components/NoteOptions/useMenuActions.tsx

@ -11,7 +11,7 @@ import { useMuteList } from '@/providers/MuteListProvider' @@ -11,7 +11,7 @@ import { useMuteList } from '@/providers/MuteListProvider'
import { useNostr } from '@/providers/NostrProvider'
import { BIG_RELAY_URLS, FAST_READ_RELAY_URLS, FAST_WRITE_RELAY_URLS } from '@/constants'
import client from '@/services/client.service'
import { Bell, BellOff, Code, Copy, Link, SatelliteDish, Trash2, TriangleAlert, Pin, FileDown, Globe, BookOpen } from 'lucide-react'
import { Bell, BellOff, Code, Copy, Link, SatelliteDish, Trash2, TriangleAlert, Pin, FileDown, Globe, BookOpen, Highlighter } from 'lucide-react'
import { Event, kinds } from 'nostr-tools'
import { nip19 } from 'nostr-tools'
import { useMemo, useState, useEffect } from 'react'
@ -42,6 +42,7 @@ interface UseMenuActionsProps { @@ -42,6 +42,7 @@ interface UseMenuActionsProps {
setIsRawEventDialogOpen: (open: boolean) => void
setIsReportDialogOpen: (open: boolean) => void
isSmallScreen: boolean
openHighlightEditor?: (highlightData: import('../PostEditor/HighlightEditor').HighlightData, eventContent?: string) => void
}
export function useMenuActions({
@ -50,7 +51,8 @@ export function useMenuActions({ @@ -50,7 +51,8 @@ export function useMenuActions({
showSubMenuActions,
setIsRawEventDialogOpen,
setIsReportDialogOpen,
isSmallScreen
isSmallScreen,
openHighlightEditor
}: UseMenuActionsProps) {
const { t } = useTranslation()
const { pubkey, attemptDelete, publish } = useNostr()
@ -347,6 +349,21 @@ export function useMenuActions({ @@ -347,6 +349,21 @@ export function useMenuActions({
}
}, [isArticleType, event, dTag])
// Check if this is an OP event that can be highlighted
const isOPEvent = useMemo(() => {
return (
event.kind === kinds.ShortTextNote || // 1
event.kind === kinds.LongFormArticle || // 30023
event.kind === ExtendedKind.WIKI_ARTICLE || // 30818
event.kind === ExtendedKind.WIKI_ARTICLE_MARKDOWN || // 30817
event.kind === ExtendedKind.PUBLICATION || // 30040
event.kind === ExtendedKind.PUBLICATION_CONTENT || // 30041
event.kind === ExtendedKind.DISCUSSION || // 11
event.kind === ExtendedKind.COMMENT || // 1111
(event.kind === kinds.Zap && (event.tags.some(tag => tag[0] === 'e') || event.tags.some(tag => tag[0] === 'a'))) // Zap receipt
)
}, [event.kind, event.tags])
const menuActions: MenuAction[] = useMemo(() => {
// Export functions for articles
const exportAsMarkdown = () => {
@ -443,8 +460,73 @@ export function useMenuActions({ @@ -443,8 +460,73 @@ export function useMenuActions({
navigator.clipboard.writeText(toNjump(getNoteBech32Id(event)))
closeDrawer()
}
}
]
// Add "Create Highlight" action for OP events
if (isOPEvent && openHighlightEditor) {
actions.push({
icon: Highlighter,
label: t('Create Highlight'),
onClick: () => {
try {
// For addressable events (publications, long-form articles with d-tag), use naddr
// For regular events, use nevent
let sourceValue: string
let sourceHexId: string | undefined
if (kinds.isAddressableKind(event.kind) || kinds.isReplaceableKind(event.kind)) {
// Generate naddr for addressable/replaceable events
const dTag = event.tags.find(tag => tag[0] === 'd')?.[1] || ''
if (dTag) {
const relays = event.tags
.filter(tag => tag[0] === 'relay')
.map(tag => tag[1])
.filter(Boolean)
try {
sourceValue = nip19.naddrEncode({
kind: event.kind,
pubkey: event.pubkey,
identifier: dTag,
relays: relays.length > 0 ? relays : undefined
})
sourceHexId = undefined // naddr doesn't have a single hex ID
} catch (error) {
logger.error('Error generating naddr for highlight', { error })
// Fallback to nevent
sourceValue = getNoteBech32Id(event)
sourceHexId = event.id
}
} else {
// No d-tag, use nevent
sourceValue = getNoteBech32Id(event)
sourceHexId = event.id
}
} else {
// Regular event, use nevent
sourceValue = getNoteBech32Id(event)
sourceHexId = event.id
}
const highlightData: import('../PostEditor/HighlightEditor').HighlightData = {
sourceType: 'nostr',
sourceValue,
sourceHexId
// context field is left empty - user can add it later if needed
}
// Pass the event content as defaultContent for the main editor field
openHighlightEditor(highlightData, event.content)
} catch (error) {
logger.error('Error creating highlight from event', { error, eventId: event.id })
toast.error(t('Failed to create highlight'))
}
},
{
separator: true
})
}
actions.push({
icon: Code,
label: t('View raw event'),
onClick: () => {
@ -452,8 +534,7 @@ export function useMenuActions({ @@ -452,8 +534,7 @@ export function useMenuActions({
setIsRawEventDialogOpen(true)
},
separator: true
}
]
})
// Add export options for article-type events
if (isArticleType) {
@ -621,6 +702,7 @@ export function useMenuActions({ @@ -621,6 +702,7 @@ export function useMenuActions({
pubkey,
isMuted,
isSmallScreen,
openHighlightEditor,
broadcastSubMenu,
closeDrawer,
showSubMenuActions,

2
src/components/PostEditor/HighlightEditor.tsx

@ -149,7 +149,7 @@ export default function HighlightEditor({ @@ -149,7 +149,7 @@ export default function HighlightEditor({
value={context}
onChange={(e) => setContext(e.target.value)}
placeholder={t('Paste the entire original passage that contains your highlight')}
rows={3}
rows={12}
/>
<p className="text-xs text-muted-foreground">
{t('The main editor above should contain only the text you want to highlight. This field should contain the full quote or paragraph for context.')}

31
src/components/PostEditor/PostContent.tsx

@ -38,12 +38,14 @@ export default function PostContent({ @@ -38,12 +38,14 @@ export default function PostContent({
defaultContent = '',
parentEvent,
close,
openFrom
openFrom,
initialHighlightData
}: {
defaultContent?: string
parentEvent?: Event
close: () => void
openFrom?: string[]
initialHighlightData?: HighlightData
}) {
const { t } = useTranslation()
const { pubkey, publish, checkLogin } = useNostr()
@ -64,11 +66,13 @@ export default function PostContent({ @@ -64,11 +66,13 @@ export default function PostContent({
const [extractedMentions, setExtractedMentions] = useState<string[]>([])
const [isProtectedEvent, setIsProtectedEvent] = useState(false)
const [additionalRelayUrls, setAdditionalRelayUrls] = useState<string[]>([])
const [isHighlight, setIsHighlight] = useState(false)
const [highlightData, setHighlightData] = useState<HighlightData>({
const [isHighlight, setIsHighlight] = useState(!!initialHighlightData)
const [highlightData, setHighlightData] = useState<HighlightData>(
initialHighlightData || {
sourceType: 'nostr',
sourceValue: ''
})
}
)
const [pollCreateData, setPollCreateData] = useState<TPollCreateData>({
isMultipleChoice: false,
options: ['', ''],
@ -106,6 +110,22 @@ export default function PostContent({ @@ -106,6 +110,22 @@ export default function PostContent({
highlightData
])
// Clear highlight data when initialHighlightData changes or is removed
useEffect(() => {
if (initialHighlightData) {
// Set highlight mode and data when provided
setIsHighlight(true)
setHighlightData(initialHighlightData)
} else {
// Clear highlight mode and data when not provided
setIsHighlight(false)
setHighlightData({
sourceType: 'nostr',
sourceValue: ''
})
}
}, [initialHighlightData])
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false
@ -469,7 +489,8 @@ export default function PostContent({ @@ -469,7 +489,8 @@ export default function PostContent({
onUploadStart={handleUploadStart}
onUploadProgress={handleUploadProgress}
onUploadEnd={handleUploadEnd}
kind={isPublicMessage ? ExtendedKind.PUBLIC_MESSAGE : isPoll ? ExtendedKind.POLL : kinds.ShortTextNote}
kind={isHighlight ? kinds.Highlights : isPublicMessage ? ExtendedKind.PUBLIC_MESSAGE : isPoll ? ExtendedKind.POLL : kinds.ShortTextNote}
highlightData={isHighlight ? highlightData : undefined}
/>
{isPoll && (
<PollEditor

86
src/components/PostEditor/PostTextarea/Preview.tsx

@ -3,19 +3,24 @@ import { transformCustomEmojisInContent } from '@/lib/draft-event' @@ -3,19 +3,24 @@ import { transformCustomEmojisInContent } from '@/lib/draft-event'
import { createFakeEvent } from '@/lib/event'
import { cleanUrl } from '@/lib/url'
import { cn } from '@/lib/utils'
import { kinds, nip19 } from 'nostr-tools'
import { useMemo } from 'react'
import Content from '../../Content'
import Highlight from '../../Note/Highlight'
import { HighlightData } from '../HighlightEditor'
export default function Preview({
content,
className,
kind = 1
kind = 1,
highlightData
}: {
content: string
className?: string
kind?: number
highlightData?: HighlightData
}) {
const { content: processedContent, emojiTags } = useMemo(
const { content: processedContent, emojiTags, highlightTags } = useMemo(
() => {
// Clean tracking parameters from URLs in the preview
const cleanedContent = content.replace(
@ -28,18 +33,81 @@ export default function Preview({ @@ -28,18 +33,81 @@ export default function Preview({
}
}
)
return transformCustomEmojisInContent(cleanedContent)
const { content: processed, emojiTags: tags } = transformCustomEmojisInContent(cleanedContent)
// Build highlight tags if this is a highlight
let highlightTags: string[][] = []
if (kind === kinds.Highlights && highlightData) {
// Add source tag
if (highlightData.sourceType === 'url') {
highlightTags.push(['r', highlightData.sourceValue, 'source'])
} else if (highlightData.sourceType === 'nostr') {
// For preview, we'll use a simple e-tag with the source value
// The actual tag building happens in createHighlightDraftEvent
if (highlightData.sourceHexId) {
highlightTags.push(['e', highlightData.sourceHexId])
} else if (highlightData.sourceValue) {
// Try to extract hex ID from bech32 if possible
try {
const decoded = nip19.decode(highlightData.sourceValue)
if (decoded.type === 'note' || decoded.type === 'nevent') {
const hexId = decoded.type === 'note' ? decoded.data : decoded.data.id
highlightTags.push(['e', hexId])
} else if (decoded.type === 'naddr') {
const { kind, pubkey, identifier } = decoded.data
highlightTags.push(['a', `${kind}:${pubkey}:${identifier}`])
}
} catch {
// If decoding fails, just use the source value as-is for preview
highlightTags.push(['r', highlightData.sourceValue])
}
}
}
// Add context tag if provided
if (highlightData.context) {
highlightTags.push(['context', highlightData.context])
}
}
return {
content: processed,
emojiTags: tags,
highlightTags
}
},
[content]
[content, kind, highlightData]
)
// Combine emoji tags and highlight tags
const allTags = useMemo(() => {
return [...emojiTags, ...highlightTags]
}, [emojiTags, highlightTags])
const fakeEvent = useMemo(() => {
return createFakeEvent({
content: processedContent,
tags: allTags,
kind
})
}, [processedContent, allTags, kind])
// For highlights, use the Highlight component for proper formatting
if (kind === kinds.Highlights) {
return (
<Card className={cn('p-3', className)}>
<Highlight
event={fakeEvent}
className="pointer-events-none"
/>
</Card>
)
}
return (
<Card className={cn('p-3', className)}>
<Content
event={createFakeEvent({
content: processedContent,
tags: emojiTags,
kind
})}
event={fakeEvent}
className="pointer-events-none h-full"
mustLoadMedia
/>

12
src/components/PostEditor/PostTextarea/index.tsx

@ -13,7 +13,7 @@ import Text from '@tiptap/extension-text' @@ -13,7 +13,7 @@ import Text from '@tiptap/extension-text'
import { TextSelection } from '@tiptap/pm/state'
import { EditorContent, useEditor } from '@tiptap/react'
import { Event } from 'nostr-tools'
import { Dispatch, forwardRef, SetStateAction, useImperativeHandle } from 'react'
import { Dispatch, forwardRef, SetStateAction, useImperativeHandle, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ClipboardAndDropHandler } from './ClipboardAndDropHandler'
import Emoji from './Emoji'
@ -21,6 +21,7 @@ import emojiSuggestion from './Emoji/suggestion' @@ -21,6 +21,7 @@ import emojiSuggestion from './Emoji/suggestion'
import Mention from './Mention'
import mentionSuggestion from './Mention/suggestion'
import Preview from './Preview'
import { HighlightData } from '../HighlightEditor'
export type TPostTextareaHandle = {
appendText: (text: string, addNewline?: boolean) => void
@ -41,6 +42,7 @@ const PostTextarea = forwardRef< @@ -41,6 +42,7 @@ const PostTextarea = forwardRef<
onUploadProgress?: (file: File, progress: number) => void
onUploadEnd?: (file: File) => void
kind?: number
highlightData?: HighlightData
}
>(
(
@ -54,11 +56,13 @@ const PostTextarea = forwardRef< @@ -54,11 +56,13 @@ const PostTextarea = forwardRef<
onUploadStart,
onUploadProgress,
onUploadEnd,
kind = 1
kind = 1,
highlightData
},
ref
) => {
const { t } = useTranslation()
const [activeTab, setActiveTab] = useState('edit')
const editor = useEditor({
extensions: [
Document,
@ -160,7 +164,7 @@ const PostTextarea = forwardRef< @@ -160,7 +164,7 @@ const PostTextarea = forwardRef<
}
return (
<Tabs defaultValue="edit" className="space-y-2">
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-2">
<TabsList>
<TabsTrigger value="edit">{t('Edit')}</TabsTrigger>
<TabsTrigger value="preview">{t('Preview')}</TabsTrigger>
@ -169,7 +173,7 @@ const PostTextarea = forwardRef< @@ -169,7 +173,7 @@ const PostTextarea = forwardRef<
<EditorContent className="tiptap" editor={editor} />
</TabsContent>
<TabsContent value="preview">
<Preview content={text} className={className} kind={kind} />
<Preview content={text} className={className} kind={kind} highlightData={highlightData} />
</TabsContent>
</Tabs>
)

14
src/components/PostEditor/index.tsx

@ -24,26 +24,34 @@ export default function PostEditor({ @@ -24,26 +24,34 @@ export default function PostEditor({
parentEvent,
open,
setOpen,
openFrom
openFrom,
initialHighlightData
}: {
defaultContent?: string
parentEvent?: Event
open: boolean
setOpen: Dispatch<boolean>
openFrom?: string[]
initialHighlightData?: import('./HighlightEditor').HighlightData
}) {
const { isSmallScreen } = useScreenSize()
// If initialHighlightData is provided and we're creating a highlight from an event,
// we need to pass the event content as defaultContent for the main editor
// Note: This is handled separately - we'll pass the event content when opening from menu
const effectiveDefaultContent = defaultContent
const content = useMemo(() => {
return (
<PostContent
defaultContent={defaultContent}
defaultContent={effectiveDefaultContent}
parentEvent={parentEvent}
close={() => setOpen(false)}
openFrom={openFrom}
initialHighlightData={initialHighlightData}
/>
)
}, [defaultContent, parentEvent, openFrom, setOpen])
}, [effectiveDefaultContent, parentEvent, openFrom, setOpen, initialHighlightData])
if (isSmallScreen) {
return (

4
src/components/UniversalContent/HighlightSourcePreview.tsx

@ -36,7 +36,9 @@ export default function HighlightSourcePreview({ source, className }: HighlightS @@ -36,7 +36,9 @@ export default function HighlightSourcePreview({ source, className }: HighlightS
const decoded = nip19.decode(source.bech32)
if (decoded.type === 'nevent' || decoded.type === 'note') {
content = (
<div className="max-h-[300px] overflow-hidden border-b border-gray-200 dark:border-gray-700">
<EmbeddedNote noteId={source.value} className="w-full" />
</div>
)
}
} catch (error) {
@ -67,7 +69,9 @@ export default function HighlightSourcePreview({ source, className }: HighlightS @@ -67,7 +69,9 @@ export default function HighlightSourcePreview({ source, className }: HighlightS
const decoded = nip19.decode(source.bech32)
if (decoded.type === 'naddr') {
content = (
<div className="max-h-[300px] overflow-hidden border-b border-gray-200 dark:border-gray-700">
<EmbeddedNote noteId={source.bech32} className="w-full" />
</div>
)
}
} catch (error) {

6
src/components/ui/command.tsx

@ -35,9 +35,9 @@ const CommandDialog = ({ @@ -35,9 +35,9 @@ const CommandDialog = ({
}: DialogProps & { classNames?: { content?: string } }) => {
return (
<Dialog {...props}>
<DialogHeader className="hidden">
<DialogTitle />
<DialogDescription />
<DialogHeader className="sr-only">
<DialogTitle>Command Menu</DialogTitle>
<DialogDescription>Search and select a command</DialogDescription>
</DialogHeader>
<DialogContent
className={cn(

Loading…
Cancel
Save