mods refactor and added repost tag system #163
@ -7,7 +7,7 @@ import '../styles/styles.css'
|
|||||||
import '../styles/tiptap.scss'
|
import '../styles/tiptap.scss'
|
||||||
|
|
||||||
interface InputFieldProps {
|
interface InputFieldProps {
|
||||||
label: string
|
label: string | React.ReactElement
|
||||||
description?: string
|
description?: string
|
||||||
type?: 'text' | 'textarea' | 'richtext'
|
type?: 'text' | 'textarea' | 'richtext'
|
||||||
placeholder: string
|
placeholder: string
|
||||||
|
@ -57,6 +57,11 @@ export const ModCard = React.memo((props: ModDetails) => {
|
|||||||
<p>NSFW</p>
|
<p>NSFW</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{props.repost && (
|
||||||
|
<div className='IBMSMSMBSSTagsTag IBMSMSMBSSTagsTagRepost IBMSMSMBSSTagsTagRepostCard'>
|
||||||
|
<p>REPOST</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className='cMMBody'>
|
<div className='cMMBody'>
|
||||||
<h3 className='cMMBodyTitle'>{props.title}</h3>
|
<h3 className='cMMBodyTitle'>{props.title}</h3>
|
||||||
|
@ -29,6 +29,7 @@ import {
|
|||||||
import { CheckboxField, InputError, InputField } from './Inputs'
|
import { CheckboxField, InputError, InputField } from './Inputs'
|
||||||
import { LoadingSpinner } from './LoadingSpinner'
|
import { LoadingSpinner } from './LoadingSpinner'
|
||||||
import { NDKEvent } from '@nostr-dev-kit/ndk'
|
import { NDKEvent } from '@nostr-dev-kit/ndk'
|
||||||
|
import { OriginalAuthor } from './OriginalAuthor'
|
||||||
|
|
||||||
interface FormErrors {
|
interface FormErrors {
|
||||||
game?: string
|
game?: string
|
||||||
@ -40,6 +41,8 @@ interface FormErrors {
|
|||||||
screenshotsUrls?: string[]
|
screenshotsUrls?: string[]
|
||||||
tags?: string
|
tags?: string
|
||||||
downloadUrls?: string[]
|
downloadUrls?: string[]
|
||||||
|
author?: string
|
||||||
|
originalAuthor?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GameOption {
|
interface GameOption {
|
||||||
@ -198,36 +201,42 @@ export const ModForm = ({ existingModData }: ModFormProps) => {
|
|||||||
const aTag =
|
const aTag =
|
||||||
formState.aTag || `${kinds.ClassifiedListing}:${hexPubkey}:${uuid}`
|
formState.aTag || `${kinds.ClassifiedListing}:${hexPubkey}:${uuid}`
|
||||||
|
|
||||||
|
const tags = [
|
||||||
|
['d', uuid],
|
||||||
|
['a', aTag],
|
||||||
|
['r', formState.rTag],
|
||||||
|
['t', T_TAG_VALUE],
|
||||||
|
[
|
||||||
|
'published_at',
|
||||||
|
existingModData
|
||||||
|
? existingModData.published_at.toString()
|
||||||
|
: currentTimeStamp.toString()
|
||||||
|
],
|
||||||
|
['game', formState.game],
|
||||||
|
['title', formState.title],
|
||||||
|
['featuredImageUrl', formState.featuredImageUrl],
|
||||||
|
['summary', formState.summary],
|
||||||
|
['nsfw', formState.nsfw.toString()],
|
||||||
|
['repost', formState.repost.toString()],
|
||||||
|
['screenshotsUrls', ...formState.screenshotsUrls],
|
||||||
|
['tags', ...formState.tags.split(',')],
|
||||||
|
[
|
||||||
|
'downloadUrls',
|
||||||
|
...formState.downloadUrls.map((downloadUrl) =>
|
||||||
|
JSON.stringify(downloadUrl)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
if (formState.repost && formState.originalAuthor) {
|
||||||
|
tags.push(['originalAuthor', formState.originalAuthor])
|
||||||
|
}
|
||||||
|
|
||||||
const unsignedEvent: UnsignedEvent = {
|
const unsignedEvent: UnsignedEvent = {
|
||||||
kind: kinds.ClassifiedListing,
|
kind: kinds.ClassifiedListing,
|
||||||
created_at: currentTimeStamp,
|
created_at: currentTimeStamp,
|
||||||
pubkey: hexPubkey,
|
pubkey: hexPubkey,
|
||||||
content: formState.body,
|
content: formState.body,
|
||||||
tags: [
|
tags
|
||||||
['d', uuid],
|
|
||||||
['a', aTag],
|
|
||||||
['r', formState.rTag],
|
|
||||||
['t', T_TAG_VALUE],
|
|
||||||
[
|
|
||||||
'published_at',
|
|
||||||
existingModData
|
|
||||||
? existingModData.published_at.toString()
|
|
||||||
: currentTimeStamp.toString()
|
|
||||||
],
|
|
||||||
['game', formState.game],
|
|
||||||
['title', formState.title],
|
|
||||||
['featuredImageUrl', formState.featuredImageUrl],
|
|
||||||
['summary', formState.summary],
|
|
||||||
['nsfw', formState.nsfw.toString()],
|
|
||||||
['screenshotsUrls', ...formState.screenshotsUrls],
|
|
||||||
['tags', ...formState.tags.split(',')],
|
|
||||||
[
|
|
||||||
'downloadUrls',
|
|
||||||
...formState.downloadUrls.map((downloadUrl) =>
|
|
||||||
JSON.stringify(downloadUrl)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const signedEvent = await window.nostr
|
const signedEvent = await window.nostr
|
||||||
@ -318,6 +327,13 @@ export const ModForm = ({ existingModData }: ModFormProps) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
formState.repost &&
|
||||||
|
(!formState.originalAuthor || formState.originalAuthor === '')
|
||||||
|
) {
|
||||||
|
errors.originalAuthor = 'Original author field can not be empty'
|
||||||
|
}
|
||||||
|
|
||||||
if (formState.tags === '') {
|
if (formState.tags === '') {
|
||||||
errors.tags = 'Tags field can not be empty'
|
errors.tags = 'Tags field can not be empty'
|
||||||
}
|
}
|
||||||
@ -397,6 +413,31 @@ export const ModForm = ({ existingModData }: ModFormProps) => {
|
|||||||
handleChange={handleCheckboxChange}
|
handleChange={handleCheckboxChange}
|
||||||
type='stylized'
|
type='stylized'
|
||||||
/>
|
/>
|
||||||
|
<CheckboxField
|
||||||
|
label='This is a repost of a mod I did not create'
|
||||||
|
name='repost'
|
||||||
|
isChecked={formState.repost}
|
||||||
|
handleChange={handleCheckboxChange}
|
||||||
|
type='stylized'
|
||||||
|
/>
|
||||||
|
{formState.repost && (
|
||||||
|
<>
|
||||||
|
<InputField
|
||||||
|
label={
|
||||||
|
<span>
|
||||||
|
Created by:{' '}
|
||||||
|
{<OriginalAuthor value={formState.originalAuthor || ''} />}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
type='text'
|
||||||
|
placeholder="Original author's name, npub or nprofile"
|
||||||
|
name='originalAuthor'
|
||||||
|
value={formState.originalAuthor || ''}
|
||||||
|
error={formErrors.originalAuthor || ''}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<div className='inputLabelWrapperMain'>
|
<div className='inputLabelWrapperMain'>
|
||||||
<div className='labelWrapperMain'>
|
<div className='labelWrapperMain'>
|
||||||
<label className='form-label labelMain'>Screenshots URLs</label>
|
<label className='form-label labelMain'>Screenshots URLs</label>
|
||||||
|
48
src/components/OriginalAuthor.tsx
Normal file
48
src/components/OriginalAuthor.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { nip19 } from 'nostr-tools'
|
||||||
|
import { appRoutes, getProfilePageRoute } from 'routes'
|
||||||
|
import { npubToHex } from 'utils'
|
||||||
|
import { ProfileLink } from './ProfileLink'
|
||||||
|
|
||||||
|
interface OriginalAuthorProps {
|
||||||
|
value: string
|
||||||
|
fallback?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OriginalAuthor = ({
|
||||||
|
value,
|
||||||
|
fallback = false
|
||||||
|
}: OriginalAuthorProps) => {
|
||||||
|
let profilePubkey
|
||||||
|
let displayName = '[name not set up]'
|
||||||
|
|
||||||
|
// Try to decode/encode depending on what we send to link
|
||||||
|
let profileRoute = appRoutes.home
|
||||||
|
try {
|
||||||
|
if (value.startsWith('nprofile1')) {
|
||||||
|
const decoded = nip19.decode(value as `nprofile1${string}`)
|
||||||
|
profileRoute = getProfilePageRoute(value)
|
||||||
|
profilePubkey = decoded?.data.pubkey
|
||||||
|
} else if (value.startsWith('npub1')) {
|
||||||
|
profilePubkey = npubToHex(value)
|
||||||
|
const nprofile = profilePubkey
|
||||||
|
? nip19.nprofileEncode({
|
||||||
|
pubkey: profilePubkey
|
||||||
|
})
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
if (nprofile) {
|
||||||
|
profileRoute = getProfilePageRoute(nprofile)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
displayName = value
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to create profile link:', error)
|
||||||
|
displayName = value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profileRoute && profilePubkey)
|
||||||
|
return <ProfileLink pubkey={profilePubkey} profileRoute={profileRoute} />
|
||||||
|
|
||||||
|
return fallback ? displayName : null
|
||||||
|
}
|
15
src/components/ProfileLink.tsx
Normal file
15
src/components/ProfileLink.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { useProfile } from 'hooks/useProfile'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
|
interface ProfileLinkProps {
|
||||||
|
pubkey: string
|
||||||
|
profileRoute: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProfileLink = ({ pubkey, profileRoute }: ProfileLinkProps) => {
|
||||||
|
const profile = useProfile(pubkey)
|
||||||
|
const displayName =
|
||||||
|
profile?.displayName || profile?.name || '[name not set up]'
|
||||||
|
|
||||||
|
return <Link to={profileRoute}>{displayName}</Link>
|
||||||
|
}
|
@ -38,6 +38,7 @@ import { Interactions } from 'components/Internal/Interactions'
|
|||||||
import { ReportPopup } from 'components/ReportPopup'
|
import { ReportPopup } from 'components/ReportPopup'
|
||||||
import { Spinner } from 'components/Spinner'
|
import { Spinner } from 'components/Spinner'
|
||||||
import { RouterLoadingSpinner } from 'components/LoadingSpinner'
|
import { RouterLoadingSpinner } from 'components/LoadingSpinner'
|
||||||
|
import { OriginalAuthor } from 'components/OriginalAuthor'
|
||||||
|
|
||||||
const MOD_REPORT_REASONS = [
|
const MOD_REPORT_REASONS = [
|
||||||
{ label: 'Actually CP', key: 'actuallyCP' },
|
{ label: 'Actually CP', key: 'actuallyCP' },
|
||||||
@ -105,6 +106,8 @@ export const ModPage = () => {
|
|||||||
screenshotsUrls={mod.screenshotsUrls}
|
screenshotsUrls={mod.screenshotsUrls}
|
||||||
tags={mod.tags}
|
tags={mod.tags}
|
||||||
nsfw={mod.nsfw}
|
nsfw={mod.nsfw}
|
||||||
|
repost={mod.repost}
|
||||||
|
originalAuthor={mod.originalAuthor}
|
||||||
/>
|
/>
|
||||||
<Interactions
|
<Interactions
|
||||||
addressable={mod}
|
addressable={mod}
|
||||||
@ -424,6 +427,8 @@ type BodyProps = {
|
|||||||
screenshotsUrls: string[]
|
screenshotsUrls: string[]
|
||||||
tags: string[]
|
tags: string[]
|
||||||
nsfw: boolean
|
nsfw: boolean
|
||||||
|
repost: boolean
|
||||||
|
originalAuthor?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const Body = ({
|
const Body = ({
|
||||||
@ -432,7 +437,9 @@ const Body = ({
|
|||||||
body,
|
body,
|
||||||
screenshotsUrls,
|
screenshotsUrls,
|
||||||
tags,
|
tags,
|
||||||
nsfw
|
nsfw,
|
||||||
|
repost,
|
||||||
|
originalAuthor
|
||||||
}: BodyProps) => {
|
}: BodyProps) => {
|
||||||
const postBodyRef = useRef<HTMLDivElement>(null)
|
const postBodyRef = useRef<HTMLDivElement>(null)
|
||||||
const viewFullPostBtnRef = useRef<HTMLDivElement>(null)
|
const viewFullPostBtnRef = useRef<HTMLDivElement>(null)
|
||||||
@ -504,6 +511,17 @@ const Body = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{repost && (
|
||||||
|
<div className='IBMSMSMBSSTagsTag IBMSMSMBSSTagsTagRepost'>
|
||||||
|
<p>
|
||||||
|
REPOST. Original Author:{' '}
|
||||||
|
{!!originalAuthor && (
|
||||||
|
<OriginalAuthor value={originalAuthor} fallback={true} />
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{tags.map((tag, index) => (
|
{tags.map((tag, index) => (
|
||||||
<a className='IBMSMSMBSSTagsTag' href='#' key={`tag-${index}`}>
|
<a className='IBMSMSMBSSTagsTag' href='#' key={`tag-${index}`}>
|
||||||
{tag}
|
{tag}
|
||||||
|
@ -28,6 +28,7 @@ export interface ModFormState {
|
|||||||
summary: string
|
summary: string
|
||||||
nsfw: boolean
|
nsfw: boolean
|
||||||
repost: boolean
|
repost: boolean
|
||||||
|
originalAuthor?: string
|
||||||
screenshotsUrls: string[]
|
screenshotsUrls: string[]
|
||||||
tags: string
|
tags: string
|
||||||
downloadUrls: DownloadUrl[]
|
downloadUrls: DownloadUrl[]
|
||||||
|
@ -40,6 +40,7 @@ export const extractModData = (event: Event | NDKEvent): ModDetails => {
|
|||||||
summary: getFirstTagValue('summary'),
|
summary: getFirstTagValue('summary'),
|
||||||
nsfw: getFirstTagValue('nsfw') === 'true',
|
nsfw: getFirstTagValue('nsfw') === 'true',
|
||||||
repost: getFirstTagValue('repost') === 'true',
|
repost: getFirstTagValue('repost') === 'true',
|
||||||
|
originalAuthor: getFirstTagValue('originalAuthor'),
|
||||||
screenshotsUrls: getTagValue(event, 'screenshotsUrls') || [],
|
screenshotsUrls: getTagValue(event, 'screenshotsUrls') || [],
|
||||||
tags: getTagValue(event, 'tags') || [],
|
tags: getTagValue(event, 'tags') || [],
|
||||||
downloadUrls: (getTagValue(event, 'downloadUrls') || []).map((item) =>
|
downloadUrls: (getTagValue(event, 'downloadUrls') || []).map((item) =>
|
||||||
@ -120,6 +121,7 @@ export const initializeFormState = (
|
|||||||
summary: existingModData?.summary || '',
|
summary: existingModData?.summary || '',
|
||||||
nsfw: existingModData?.nsfw || false,
|
nsfw: existingModData?.nsfw || false,
|
||||||
repost: existingModData?.repost || false,
|
repost: existingModData?.repost || false,
|
||||||
|
originalAuthor: existingModData?.originalAuthor || undefined,
|
||||||
screenshotsUrls: existingModData?.screenshotsUrls || [''],
|
screenshotsUrls: existingModData?.screenshotsUrls || [''],
|
||||||
tags: existingModData?.tags.join(',') || '',
|
tags: existingModData?.tags.join(',') || '',
|
||||||
downloadUrls: existingModData?.downloadUrls || [
|
downloadUrls: existingModData?.downloadUrls || [
|
||||||
|
Loading…
x
Reference in New Issue
Block a user