Browse Source

bug-fixes

added missing fields to articles
imwald
Silberengel 4 months ago
parent
commit
37ad1b9184
  1. 2
      src/components/Note/Highlight/index.tsx
  2. 948
      src/components/PostEditor/PostContent.tsx
  3. 89
      src/components/PostEditor/PostTextarea/Preview.tsx
  4. 12
      src/components/PostEditor/PostTextarea/index.tsx
  5. 1
      src/constants.ts
  6. 12
      src/index.css
  7. 34
      src/lib/draft-event.ts
  8. 2
      src/lib/kind-description.ts
  9. 92
      src/pages/secondary/PostSettingsPage/CacheRelayOnlySetting.tsx
  10. 2
      src/pages/secondary/PostSettingsPage/index.tsx

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

@ -307,7 +307,7 @@ export default function Highlight({
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
{/* Full quoted text with highlighted portion */} {/* Full quoted text with highlighted portion */}
{context && ( {context && (
<div className="text-base font-normal mb-4 whitespace-pre-wrap break-words border-l-4 border-green-500 pl-5 py-4 leading-relaxed bg-green-50/30 dark:bg-green-950/20 rounded-r-lg"> <div className="note-content text-base font-normal mb-4 whitespace-pre-wrap break-words border-l-4 border-green-500 pl-5 py-4 leading-relaxed bg-green-50/30 dark:bg-green-950/20 rounded-r-lg">
{contextTag && highlightedText ? ( {contextTag && highlightedText ? (
// If we have both context and highlighted text, show the highlight within the context // If we have both context and highlighted text, show the highlight within the context
<div> <div>

948
src/components/PostEditor/PostContent.tsx

File diff suppressed because it is too large Load Diff

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

@ -1,6 +1,7 @@
import { Card } from '@/components/ui/card' import { Card } from '@/components/ui/card'
import { ExtendedKind, POLL_TYPE } from '@/constants' import { ExtendedKind, POLL_TYPE } from '@/constants'
import { transformCustomEmojisInContent } from '@/lib/draft-event' import { transformCustomEmojisInContent } from '@/lib/draft-event'
import { normalizeTopic } from '@/lib/discussion-topics'
import { createFakeEvent } from '@/lib/event' import { createFakeEvent } from '@/lib/event'
import { randomString } from '@/lib/random' import { randomString } from '@/lib/random'
import { cleanUrl } from '@/lib/url' import { cleanUrl } from '@/lib/url'
@ -12,6 +13,7 @@ import ContentPreview from '../../ContentPreview'
import Content from '../../Content' import Content from '../../Content'
import Highlight from '../../Note/Highlight' import Highlight from '../../Note/Highlight'
import MarkdownArticle from '../../Note/MarkdownArticle/MarkdownArticle' import MarkdownArticle from '../../Note/MarkdownArticle/MarkdownArticle'
import AsciidocArticle from '../../Note/AsciidocArticle/AsciidocArticle'
import { HighlightData } from '../HighlightEditor' import { HighlightData } from '../HighlightEditor'
export default function Preview({ export default function Preview({
@ -21,7 +23,8 @@ export default function Preview({
highlightData, highlightData,
pollCreateData, pollCreateData,
mediaImetaTags, mediaImetaTags,
mediaUrl mediaUrl,
articleMetadata
}: { }: {
content: string content: string
className?: string className?: string
@ -30,6 +33,13 @@ export default function Preview({
pollCreateData?: TPollCreateData pollCreateData?: TPollCreateData
mediaImetaTags?: string[][] mediaImetaTags?: string[][]
mediaUrl?: string mediaUrl?: string
articleMetadata?: {
title?: string
summary?: string
image?: string
dTag?: string
topics?: string[]
}
}) { }) {
const { content: processedContent, emojiTags, highlightTags, pollTags } = useMemo( const { content: processedContent, emojiTags, highlightTags, pollTags } = useMemo(
() => { () => {
@ -107,15 +117,36 @@ export default function Preview({
[content, kind, highlightData, pollCreateData] [content, kind, highlightData, pollCreateData]
) )
// Combine emoji tags, highlight tags, poll tags, and media imeta tags // Combine emoji tags, highlight tags, poll tags, media imeta tags, and article metadata tags
const allTags = useMemo(() => { const allTags = useMemo(() => {
const tags = [...emojiTags, ...highlightTags, ...pollTags] const tags = [...emojiTags, ...highlightTags, ...pollTags]
// Add imeta tags for media (voice comments, etc.) // Add imeta tags for media (voice comments, etc.)
if (mediaImetaTags && mediaImetaTags.length > 0) { if (mediaImetaTags && mediaImetaTags.length > 0) {
tags.push(...mediaImetaTags) tags.push(...mediaImetaTags)
} }
// Add article metadata tags for article kinds
if (articleMetadata && (kind === kinds.LongFormArticle || kind === ExtendedKind.WIKI_ARTICLE || kind === ExtendedKind.WIKI_ARTICLE_MARKDOWN || kind === ExtendedKind.PUBLICATION_CONTENT)) {
if (articleMetadata.dTag) {
tags.push(['d', articleMetadata.dTag])
}
if (articleMetadata.title) {
tags.push(['title', articleMetadata.title])
}
if (articleMetadata.summary) {
tags.push(['summary', articleMetadata.summary])
}
if (articleMetadata.image) {
tags.push(['image', articleMetadata.image])
}
if (articleMetadata.topics && articleMetadata.topics.length > 0) {
const normalizedTopics = articleMetadata.topics
.map(topic => normalizeTopic(topic.trim()))
.filter(topic => topic.length > 0)
tags.push(...normalizedTopics.map((topic) => ['t', topic]))
}
}
return tags return tags
}, [emojiTags, highlightTags, pollTags, mediaImetaTags]) }, [emojiTags, highlightTags, pollTags, mediaImetaTags, articleMetadata, kind])
const fakeEvent = useMemo(() => { const fakeEvent = useMemo(() => {
// For voice comments, include the media URL in content if not already there // For voice comments, include the media URL in content if not already there
@ -169,6 +200,58 @@ export default function Preview({
) )
} }
// For LongFormArticle, use MarkdownArticle
if (kind === kinds.LongFormArticle) {
return (
<Card className={cn('p-3', className)}>
<MarkdownArticle
event={fakeEvent}
className="pointer-events-none"
hideMetadata={true}
/>
</Card>
)
}
// For WikiArticle (AsciiDoc), use AsciidocArticle
if (kind === ExtendedKind.WIKI_ARTICLE) {
return (
<Card className={cn('p-3', className)}>
<AsciidocArticle
event={fakeEvent}
className="pointer-events-none"
hideImagesAndInfo={false}
/>
</Card>
)
}
// For WikiArticleMarkdown, use MarkdownArticle
if (kind === ExtendedKind.WIKI_ARTICLE_MARKDOWN) {
return (
<Card className={cn('p-3', className)}>
<MarkdownArticle
event={fakeEvent}
className="pointer-events-none"
hideMetadata={true}
/>
</Card>
)
}
// For PublicationContent, use AsciidocArticle
if (kind === ExtendedKind.PUBLICATION_CONTENT) {
return (
<Card className={cn('p-3', className)}>
<AsciidocArticle
event={fakeEvent}
className="pointer-events-none"
hideImagesAndInfo={false}
/>
</Card>
)
}
return ( return (
<Card className={cn('p-3', className)}> <Card className={cn('p-3', className)}>
<Content <Content

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

@ -51,6 +51,13 @@ const PostTextarea = forwardRef<
getDraftEventJson?: () => Promise<string> getDraftEventJson?: () => Promise<string>
mediaImetaTags?: string[][] mediaImetaTags?: string[][]
mediaUrl?: string mediaUrl?: string
articleMetadata?: {
title?: string
summary?: string
image?: string
dTag?: string
topics?: string[]
}
} }
>( >(
( (
@ -70,7 +77,8 @@ const PostTextarea = forwardRef<
headerActions, headerActions,
getDraftEventJson, getDraftEventJson,
mediaImetaTags, mediaImetaTags,
mediaUrl mediaUrl,
articleMetadata
}, },
ref ref
) => { ) => {
@ -238,7 +246,7 @@ const PostTextarea = forwardRef<
<div className="text-xs text-muted-foreground"> <div className="text-xs text-muted-foreground">
kind {kindDescription.number}: {kindDescription.description} kind {kindDescription.number}: {kindDescription.description}
</div> </div>
<Preview content={text} className={className} kind={kind} highlightData={highlightData} pollCreateData={pollCreateData} mediaImetaTags={mediaImetaTags} mediaUrl={mediaUrl} /> <Preview content={text} className={className} kind={kind} highlightData={highlightData} pollCreateData={pollCreateData} mediaImetaTags={mediaImetaTags} mediaUrl={mediaUrl} articleMetadata={articleMetadata} />
</div> </div>
</TabsContent> </TabsContent>
<TabsContent value="json"> <TabsContent value="json">

1
src/constants.ts

@ -48,7 +48,6 @@ export const StorageKey = {
SHOWN_CREATE_WALLET_GUIDE_TOAST_PUBKEYS: 'shownCreateWalletGuideToastPubkeys', SHOWN_CREATE_WALLET_GUIDE_TOAST_PUBKEYS: 'shownCreateWalletGuideToastPubkeys',
SHOW_RECOMMENDED_RELAYS_PANEL: 'showRecommendedRelaysPanel', SHOW_RECOMMENDED_RELAYS_PANEL: 'showRecommendedRelaysPanel',
DEFAULT_EXPIRATION_ENABLED: 'defaultExpirationEnabled', DEFAULT_EXPIRATION_ENABLED: 'defaultExpirationEnabled',
USE_CACHE_ONLY_FOR_PRIVATE_NOTES: 'useCacheOnlyForPrivateNotes',
DEFAULT_EXPIRATION_MONTHS: 'defaultExpirationMonths', DEFAULT_EXPIRATION_MONTHS: 'defaultExpirationMonths',
DEFAULT_QUIET_ENABLED: 'defaultQuietEnabled', DEFAULT_QUIET_ENABLED: 'defaultQuietEnabled',
DEFAULT_QUIET_DAYS: 'defaultQuietDays', DEFAULT_QUIET_DAYS: 'defaultQuietDays',

12
src/index.css

@ -33,6 +33,18 @@
user-select: none; user-select: none;
} }
/* Allow text selection in note content */
[data-note-content],
.note-content,
article,
.markdown-content,
.prose {
user-select: text;
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
}
.clickable { .clickable {
cursor: pointer; cursor: pointer;
transition: background-color 0.2s ease; transition: background-color 0.2s ease;

34
src/lib/draft-event.ts

@ -3,7 +3,7 @@ import client from '@/services/client.service'
import customEmojiService from '@/services/custom-emoji.service' import customEmojiService from '@/services/custom-emoji.service'
import mediaUpload from '@/services/media-upload.service' import mediaUpload from '@/services/media-upload.service'
import { prefixNostrAddresses } from '@/lib/nostr-address' import { prefixNostrAddresses } from '@/lib/nostr-address'
import { normalizeHashtag } from '@/lib/discussion-topics' import { normalizeHashtag, normalizeTopic } from '@/lib/discussion-topics'
import logger from '@/lib/logger' import logger from '@/lib/logger'
import { import {
TDraftEvent, TDraftEvent,
@ -1418,6 +1418,7 @@ export async function createLongFormArticleDraftEvent(
image?: string image?: string
publishedAt?: number publishedAt?: number
dTag?: string dTag?: string
topics?: string[]
addClientTag?: boolean addClientTag?: boolean
isNsfw?: boolean isNsfw?: boolean
addExpirationTag?: boolean addExpirationTag?: boolean
@ -1447,6 +1448,13 @@ export async function createLongFormArticleDraftEvent(
} }
tags.push(...emojiTags) tags.push(...emojiTags)
tags.push(...hashtags.map((hashtag) => buildTTag(hashtag))) tags.push(...hashtags.map((hashtag) => buildTTag(hashtag)))
// Add topics as t-tags directly
if (options.topics && options.topics.length > 0) {
const normalizedTopics = options.topics
.map(topic => normalizeTopic(topic.trim()))
.filter(topic => topic.length > 0)
tags.push(...normalizedTopics.map((topic) => buildTTag(topic)))
}
tags.push(...mentions.map((pubkey) => buildPTag(pubkey))) tags.push(...mentions.map((pubkey) => buildPTag(pubkey)))
// imeta tags for images in content // imeta tags for images in content
@ -1496,6 +1504,7 @@ export async function createWikiArticleDraftEvent(
title?: string title?: string
summary?: string summary?: string
image?: string image?: string
topics?: string[]
addClientTag?: boolean addClientTag?: boolean
isNsfw?: boolean isNsfw?: boolean
addExpirationTag?: boolean addExpirationTag?: boolean
@ -1520,6 +1529,13 @@ export async function createWikiArticleDraftEvent(
} }
tags.push(...emojiTags) tags.push(...emojiTags)
tags.push(...hashtags.map((hashtag) => buildTTag(hashtag))) tags.push(...hashtags.map((hashtag) => buildTTag(hashtag)))
// Add topics as t-tags directly
if (options.topics && options.topics.length > 0) {
const normalizedTopics = options.topics
.map(topic => normalizeTopic(topic.trim()))
.filter(topic => topic.length > 0)
tags.push(...normalizedTopics.map((topic) => buildTTag(topic)))
}
tags.push(...mentions.map((pubkey) => buildPTag(pubkey))) tags.push(...mentions.map((pubkey) => buildPTag(pubkey)))
if (options.addClientTag) { if (options.addClientTag) {
@ -1554,6 +1570,7 @@ export async function createWikiArticleMarkdownDraftEvent(
title?: string title?: string
summary?: string summary?: string
image?: string image?: string
topics?: string[]
addClientTag?: boolean addClientTag?: boolean
isNsfw?: boolean isNsfw?: boolean
addExpirationTag?: boolean addExpirationTag?: boolean
@ -1578,6 +1595,13 @@ export async function createWikiArticleMarkdownDraftEvent(
} }
tags.push(...emojiTags) tags.push(...emojiTags)
tags.push(...hashtags.map((hashtag) => buildTTag(hashtag))) tags.push(...hashtags.map((hashtag) => buildTTag(hashtag)))
// Add topics as t-tags directly
if (options.topics && options.topics.length > 0) {
const normalizedTopics = options.topics
.map(topic => normalizeTopic(topic.trim()))
.filter(topic => topic.length > 0)
tags.push(...normalizedTopics.map((topic) => buildTTag(topic)))
}
tags.push(...mentions.map((pubkey) => buildPTag(pubkey))) tags.push(...mentions.map((pubkey) => buildPTag(pubkey)))
if (options.addClientTag) { if (options.addClientTag) {
@ -1612,6 +1636,7 @@ export async function createPublicationContentDraftEvent(
title?: string title?: string
summary?: string summary?: string
image?: string image?: string
topics?: string[]
addClientTag?: boolean addClientTag?: boolean
isNsfw?: boolean isNsfw?: boolean
addExpirationTag?: boolean addExpirationTag?: boolean
@ -1636,6 +1661,13 @@ export async function createPublicationContentDraftEvent(
} }
tags.push(...emojiTags) tags.push(...emojiTags)
tags.push(...hashtags.map((hashtag) => buildTTag(hashtag))) tags.push(...hashtags.map((hashtag) => buildTTag(hashtag)))
// Add topics as t-tags directly
if (options.topics && options.topics.length > 0) {
const normalizedTopics = options.topics
.map(topic => normalizeTopic(topic.trim()))
.filter(topic => topic.length > 0)
tags.push(...normalizedTopics.map((topic) => buildTTag(topic)))
}
tags.push(...mentions.map((pubkey) => buildPTag(pubkey))) tags.push(...mentions.map((pubkey) => buildPTag(pubkey)))
if (options.addClientTag) { if (options.addClientTag) {

2
src/lib/kind-description.ts

@ -43,7 +43,7 @@ export function getKindDescription(kind: number): { number: number; description:
case ExtendedKind.POLL: case ExtendedKind.POLL:
return { number: 1068, description: 'Poll' } return { number: 1068, description: 'Poll' }
case ExtendedKind.PUBLIC_MESSAGE: case ExtendedKind.PUBLIC_MESSAGE:
return { number: 14, description: 'Public Message' } return { number: 24, description: 'Public Message' }
default: default:
return { number: kind, description: `Event (kind ${kind})` } return { number: kind, description: `Event (kind ${kind})` }
} }

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

@ -1,92 +0,0 @@
import { Label } from '@/components/ui/label'
import { Switch } from '@/components/ui/switch'
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 { 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(() => {
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 (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)
} 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')
}
}
checkCacheRelays()
}, [cacheRelayListEvent, pubkey])
const handleEnabledChange = (checked: boolean) => {
setEnabled(checked)
window.localStorage.setItem(StorageKey.USE_CACHE_ONLY_FOR_PRIVATE_NOTES, checked.toString())
}
if (!hasCacheRelaysAvailable) {
return null // Don't show if user doesn't have cache relays
}
return (
<div className="space-y-4">
<h3 className="text-lg font-medium">{t('Private Notes')}</h3>
<div className="space-y-2">
<div className="flex items-center space-x-2">
<Label htmlFor="cache-relay-only">{t('Use cache relay only for citations and publication content')}</Label>
<Switch
id="cache-relay-only"
checked={enabled}
onCheckedChange={handleEnabledChange}
/>
</div>
<div className="text-muted-foreground text-xs">
{t('When enabled, citations and publication content (kind 30041) will only be published to your cache relay, not to outbox relays')}
</div>
</div>
</div>
)
}

2
src/pages/secondary/PostSettingsPage/index.tsx

@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next'
import MediaUploadServiceSetting from './MediaUploadServiceSetting' import MediaUploadServiceSetting from './MediaUploadServiceSetting'
import ExpirationSettings from './ExpirationSettings' import ExpirationSettings from './ExpirationSettings'
import QuietSettings from './QuietSettings' import QuietSettings from './QuietSettings'
import CacheRelayOnlySetting from './CacheRelayOnlySetting'
const PostSettingsPage = forwardRef(({ index, hideTitlebar = false }: { index?: number; hideTitlebar?: boolean }, ref) => { const PostSettingsPage = forwardRef(({ index, hideTitlebar = false }: { index?: number; hideTitlebar?: boolean }, ref) => {
const { t } = useTranslation() const { t } = useTranslation()
@ -21,7 +20,6 @@ const PostSettingsPage = forwardRef(({ index, hideTitlebar = false }: { index?:
<h3 className="text-lg font-medium">{t('Quiet Tags')}</h3> <h3 className="text-lg font-medium">{t('Quiet Tags')}</h3>
<QuietSettings /> <QuietSettings />
</div> </div>
<CacheRelayOnlySetting />
</div> </div>
</SecondaryPageLayout> </SecondaryPageLayout>
) )

Loading…
Cancel
Save