Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nostr crossposting all item types #779

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
7852c98
crosspost-item
AustinKelsay Nov 1, 2023
54f4d00
crosspost old items, update with nEventId
AustinKelsay Nov 3, 2023
16f4d5e
Updating noteId encoding, cleaning up a little
AustinKelsay Nov 6, 2023
475a89b
Fixing item-info condition, cleaning up
AustinKelsay Nov 7, 2023
5af11f4
Linting
AustinKelsay Nov 13, 2023
52672e2
Add createdAt variable back
AustinKelsay Nov 13, 2023
6f8f346
Change instances of eventId to noteId
AustinKelsay Nov 20, 2023
aada92c
Adding upsertNoteId mutation
AustinKelsay Nov 20, 2023
f1a3e5d
Cleaning up updateItem, using toasts to communivate success/failure i…
AustinKelsay Nov 22, 2023
54b954c
Linting
AustinKelsay Nov 22, 2023
76750f6
Move crosspost to share button, make sure only OP can crosspost
AustinKelsay Nov 29, 2023
8359688
Lint
AustinKelsay Nov 29, 2023
e03509f
Simplify conditions
AustinKelsay Nov 29, 2023
4437087
user might have no nostr extension installed
AustinKelsay Dec 8, 2023
4c89af5
change upsertNoteId to updateNoteID for resolver and mutations, chang…
AustinKelsay Dec 8, 2023
afee296
Basic setup for crossposting poll / link items
AustinKelsay Nov 14, 2023
44071e1
post rebase fixes and Bounty and job crossposts
AustinKelsay Dec 4, 2023
a47a920
Job crossposting working
AustinKelsay Dec 4, 2023
ac6ca5f
adding back accidentally removed import
AustinKelsay Dec 8, 2023
93d04d2
Lint / rebase
AustinKelsay Dec 31, 2023
1ac2cce
Outsource as much crossposting logic from discussion-form into use-cr…
AustinKelsay Jan 9, 2024
01a24f9
Fix incorrect property for user relays, fix itemId param in updateNoteId
AustinKelsay Jan 9, 2024
9a1fd39
Fix toast messages / error cases in use-crossposter
AustinKelsay Jan 12, 2024
bc214df
Update item forms to for updated use-crossposter hook
AustinKelsay Jan 15, 2024
c486063
CrosspostDropdownItem in share updated to accomodate use-crossposter …
AustinKelsay Jan 17, 2024
ce29a50
Encode paramaterized replacable event id's in naddress format with no…
AustinKelsay Jan 17, 2024
4160b9e
Increase timeout on relay connection / cleaning up
AustinKelsay Jan 19, 2024
34ed45f
No longer crossposting job
AustinKelsay Jan 19, 2024
1c0f78c
Add blastr, fix crosspost button in item-info for polls/discussions, …
AustinKelsay Jan 21, 2024
6e08829
Fix toaster error, create reusable crossposterror function to surface…
AustinKelsay Jan 24, 2024
fc48a85
Cleaning up / comments / linting
AustinKelsay Jan 26, 2024
24c05a3
Update copy
AustinKelsay Jan 30, 2024
0f4f071
Simplify CrosspostdropdownItem, keep replies from being crossposted
AustinKelsay Jan 30, 2024
ee8a58c
Moved query for missing item fields when crossposting to use-crosspos…
AustinKelsay Jan 31, 2024
d2b5913
Remove unneeded param in CrosspostDropdownItem, lint
AustinKelsay Feb 1, 2024
ab05c9f
Small fixes post rebase
AustinKelsay Feb 1, 2024
7cb5329
Remove unused import
AustinKelsay Feb 1, 2024
620b7cc
fix nostr-tools version, fix package-lock.json
AustinKelsay Feb 1, 2024
73357dd
Update components/item-info.js
AustinKelsay Feb 10, 2024
17673ea
Remove unused param, determine poll item type from pollCost field, ad…
AustinKelsay Feb 10, 2024
904188e
Update toaster implementations, use no-cache for item query, restruct…
AustinKelsay Feb 13, 2024
6282502
crosspost info modal that lives under adv-post-form now has dynamic c…
AustinKelsay Feb 13, 2024
a8d265d
Move determineItemType into handleEventCreation, mover item/event han…
AustinKelsay Feb 13, 2024
32f07a3
Lint
AustinKelsay Feb 13, 2024
fa6d6d9
Reconcile skip method with onCancel function in toaster
AustinKelsay Feb 13, 2024
0c71cb2
Handle failedRelays being undefined
AustinKelsay Feb 18, 2024
f92f333
determine item type from router.query.type if available otherwise use…
AustinKelsay Feb 18, 2024
68923eb
Initiliaze failerRelays as undefined but handle error explicitly
AustinKelsay Feb 19, 2024
ae49a42
Lint
AustinKelsay Feb 19, 2024
116edb4
Fix crosspost default value for link, poll, bounty forms
ekzyis Feb 19, 2024
3a01647
Merge branch 'master' into nostr-crossposting-all-item-types
huumn Feb 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 48 additions & 6 deletions components/adv-post-form.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useState, useEffect } from 'react'
import AccordianItem from './accordian-item'
import { Input, InputUserSuggest, VariableInput, Checkbox } from './form'
import InputGroup from 'react-bootstrap/InputGroup'
Expand All @@ -7,8 +8,8 @@ import Info from './info'
import { numWithUnits } from '../lib/format'
import styles from './adv-post-form.module.css'
import { useMe } from './me'
import { useRouter } from 'next/router'
import { useFeeButton } from './fee-button'
import { useRouter } from 'next/router'

const EMPTY_FORWARD = { nym: '', pct: '' }

Expand All @@ -19,10 +20,51 @@ export function AdvPostInitial ({ forward, boost }) {
}
}

export default function AdvPostForm ({ children }) {
export default function AdvPostForm ({ children, item }) {
const me = useMe()
const router = useRouter()
const { merge } = useFeeButton()
const router = useRouter()
const [itemType, setItemType] = useState()

useEffect(() => {
const determineItemType = () => {
if (router && router.query.type) {
return router.query.type
} else if (item) {
const typeMap = {
url: 'link',
bounty: 'bounty',
pollCost: 'poll'
}

for (const [key, type] of Object.entries(typeMap)) {
if (item[key]) {
return type
}
}

return 'discussion'
}
}

const type = determineItemType()
setItemType(type)
}, [item, router])

function renderCrosspostDetails (itemType) {
switch (itemType) {
case 'discussion':
return <li>crosspost this discussion as a NIP-23 event</li>
case 'link':
return <li>crosspost this link as a NIP-01 event</li>
case 'bounty':
return <li>crosspost this bounty as a NIP-99 event</li>
case 'poll':
return <li>crosspost this poll as a NIP-41 event</li>
default:
return null
}
}

return (
<AccordianItem
Expand Down Expand Up @@ -93,16 +135,16 @@ export default function AdvPostForm ({ children }) {
)
}}
</VariableInput>
{me && router.query.type === 'discussion' &&
{me && itemType &&
ekzyis marked this conversation as resolved.
Show resolved Hide resolved
<Checkbox
label={
<div className='d-flex align-items-center'>crosspost to nostr
<Info>
<ul className='fw-bold'>
<li>crosspost this discussion item to nostr</li>
{renderCrosspostDetails(itemType)}
<li>requires NIP-07 extension for signing</li>
<li>we use your NIP-05 relays if set</li>
<li>otherwise we default to these relays:</li>
<li>we use these relays by default:</li>
<ul>
{DEFAULT_CROSSPOSTING_RELAYS.map((relay, i) => (
<li key={i}>{relay}</li>
Expand Down
13 changes: 11 additions & 2 deletions components/bounty-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { gql, useApolloClient, useMutation } from '@apollo/client'
import Countdown from './countdown'
import AdvPostForm, { AdvPostInitial } from './adv-post-form'
import InputGroup from 'react-bootstrap/InputGroup'
import useCrossposter from './use-crossposter'
import { bountySchema } from '../lib/validate'
import { SubSelectInitial } from './sub-select'
import { useCallback } from 'react'
Expand All @@ -27,6 +28,7 @@ export function BountyForm ({
const client = useApolloClient()
const me = useMe()
const toaster = useToast()
const crossposter = useCrossposter()
const schema = bountySchema({ client, me, existingBoost: item?.boost })
const [upsertBounty] = useMutation(
gql`
Expand Down Expand Up @@ -60,7 +62,7 @@ export function BountyForm ({
)

const onSubmit = useCallback(
async ({ boost, bounty, ...values }) => {
async ({ boost, bounty, crosspost, ...values }) => {
const { data, error } = await upsertBounty({
variables: {
sub: item?.subName || sub?.name,
Expand All @@ -75,6 +77,12 @@ export function BountyForm ({
throw new Error({ message: error.toString() })
}

const bountyId = data?.upsertBounty?.id

if (crosspost && bountyId) {
await crossposter(bountyId)
}

if (item) {
await router.push(`/items/${item.id}`)
} else {
Expand All @@ -90,6 +98,7 @@ export function BountyForm ({
initial={{
title: item?.title || '',
text: item?.text || '',
crosspost: item ? !!item.noteId : me?.privates?.nostrCrossposting,
bounty: item?.bounty || 1000,
...AdvPostInitial({ forward: normalizeForwards(item?.forwards), boost: item?.boost }),
...SubSelectInitial({ sub: item?.subName || sub?.name })
Expand Down Expand Up @@ -134,7 +143,7 @@ export function BountyForm ({
: null
}
/>
<AdvPostForm edit={!!item} />
<AdvPostForm edit={!!item} item={item} />
<ItemButtonBar itemId={item?.id} canDelete={false} />
</Form>
)
Expand Down
42 changes: 3 additions & 39 deletions components/discussion-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { useMe } from './me'
import useCrossposter from './use-crossposter'
import { useToast } from './toast'
import { ItemButtonBar } from './post'
import { callWithTimeout } from '../lib/nostr'

export function DiscussionForm ({
item, sub, editThreshold, titleLabel = 'title',
Expand All @@ -41,28 +40,8 @@ export function DiscussionForm ({
}`
)

const [updateNoteId] = useMutation(
gql`
mutation updateNoteId($id: ID!, $noteId: String!) {
updateNoteId(id: $id, noteId: $noteId) {
id
noteId
}
}`
)

const onSubmit = useCallback(
async ({ boost, crosspost, ...values }) => {
try {
if (crosspost) {
const pubkey = await callWithTimeout(() => window.nostr.getPublicKey(), 5000)
if (!pubkey) throw new Error('failed to get pubkey')
}
} catch (e) {
console.log(e)
throw new Error(`Nostr extension error: ${e.message}`)
}

const { data, error } = await upsertDiscussion({
variables: {
sub: item?.subName || sub?.name,
Expand All @@ -77,25 +56,10 @@ export function DiscussionForm ({
throw new Error({ message: error.toString() })
}

let noteId = null
const discussionId = data?.upsertDiscussion?.id

try {
if (crosspost && discussionId) {
const crosspostResult = await crossposter({ ...values, id: discussionId })
noteId = crosspostResult?.noteId
if (noteId) {
await updateNoteId({
variables: {
id: discussionId,
noteId
}
})
}
}
} catch (e) {
console.error(e)
toaster.danger('Error crossposting to Nostr', e.message)
if (crosspost && discussionId) {
await crossposter(discussionId)
}

if (item) {
Expand Down Expand Up @@ -159,7 +123,7 @@ export function DiscussionForm ({
? <div className='text-muted fw-bold'><Countdown date={editThreshold} /></div>
: null}
/>
<AdvPostForm edit={!!item} />
<AdvPostForm edit={!!item} item={item} />
<ItemButtonBar itemId={item?.id} />
{!item &&
<div className={`mt-3 ${related.length > 0 ? '' : 'invisible'}`}>
Expand Down
4 changes: 2 additions & 2 deletions components/item-info.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ export default function ItemInfo ({
nostr note
</Dropdown.Item>
)}
{item && item.mine && !item.noteId && !item.isJob && !item.parentId &&
<CrosspostDropdownItem item={item} />}
{me && !item.position &&
!item.mine && !item.deletedAt &&
(item.meDontLikeSats > meTotalSats
Expand All @@ -185,8 +187,6 @@ export default function ItemInfo ({
<hr className='dropdown-divider' />
<PinSubDropdownItem item={item} />
</>}
{item?.mine && !item?.noteId &&
<CrosspostDropdownItem item={item} />}
{item.mine && !item.position && !item.deletedAt && !item.bio &&
<>
<hr className='dropdown-divider' />
Expand Down
15 changes: 13 additions & 2 deletions components/link-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { normalizeForwards, toastDeleteScheduled } from '../lib/form'
import { useToast } from './toast'
import { SubSelectInitial } from './sub-select'
import { MAX_TITLE_LENGTH } from '../lib/constants'
import useCrossposter from './use-crossposter'
import { useMe } from './me'
import { ItemButtonBar } from './post'

Expand All @@ -26,6 +27,8 @@ export function LinkForm ({ item, sub, editThreshold, children }) {
const shareUrl = router.query.url
const shareTitle = router.query.title

const crossposter = useCrossposter()

const [getPageTitleAndUnshorted, { data }] = useLazyQuery(gql`
query PageTitleAndUnshorted($url: String!) {
pageTitleAndUnshorted(url: $url) {
Expand Down Expand Up @@ -78,7 +81,7 @@ export function LinkForm ({ item, sub, editThreshold, children }) {
)

const onSubmit = useCallback(
async ({ boost, title, ...values }) => {
async ({ boost, crosspost, title, ...values }) => {
const { data, error } = await upsertLink({
variables: {
sub: item?.subName || sub?.name,
Expand All @@ -92,6 +95,13 @@ export function LinkForm ({ item, sub, editThreshold, children }) {
if (error) {
throw new Error({ message: error.toString() })
}

const linkId = data?.upsertLink?.id

if (crosspost && linkId) {
await crossposter(linkId)
}

if (item) {
await router.push(`/items/${item.id}`)
} else {
Expand Down Expand Up @@ -125,6 +135,7 @@ export function LinkForm ({ item, sub, editThreshold, children }) {
title: item?.title || shareTitle || '',
url: item?.url || shareUrl || '',
text: item?.text || '',
crosspost: item ? !!item.noteId : me?.privates?.nostrCrossposting,
...AdvPostInitial({ forward: normalizeForwards(item?.forwards), boost: item?.boost }),
...SubSelectInitial({ sub: item?.subName || sub?.name })
}}
Expand Down Expand Up @@ -183,7 +194,7 @@ export function LinkForm ({ item, sub, editThreshold, children }) {
}
}}
/>
<AdvPostForm edit={!!item}>
<AdvPostForm edit={!!item} item={item}>
<MarkdownInput
label='context'
name='text'
Expand Down
15 changes: 13 additions & 2 deletions components/poll-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { pollSchema } from '../lib/validate'
import { SubSelectInitial } from './sub-select'
import { useCallback } from 'react'
import { normalizeForwards, toastDeleteScheduled } from '../lib/form'
import useCrossposter from './use-crossposter'
import { useMe } from './me'
import { useToast } from './toast'
import { ItemButtonBar } from './post'
Expand All @@ -20,6 +21,8 @@ export function PollForm ({ item, sub, editThreshold, children }) {
const toaster = useToast()
const schema = pollSchema({ client, me, existingBoost: item?.boost })

const crossposter = useCrossposter()

const [upsertPoll] = useMutation(
gql`
mutation upsertPoll($sub: String, $id: ID, $title: String!, $text: String,
Expand All @@ -33,7 +36,7 @@ export function PollForm ({ item, sub, editThreshold, children }) {
)

const onSubmit = useCallback(
async ({ boost, title, options, ...values }) => {
async ({ boost, title, options, crosspost, ...values }) => {
const optionsFiltered = options.slice(initialOptions?.length).filter(word => word.trim().length > 0)
const { data, error } = await upsertPoll({
variables: {
Expand All @@ -49,6 +52,13 @@ export function PollForm ({ item, sub, editThreshold, children }) {
if (error) {
throw new Error({ message: error.toString() })
}

const pollId = data?.upsertPoll?.id

if (crosspost && pollId) {
await crossposter(pollId)
}

if (item) {
await router.push(`/items/${item.id}`)
} else {
Expand All @@ -67,6 +77,7 @@ export function PollForm ({ item, sub, editThreshold, children }) {
title: item?.title || '',
text: item?.text || '',
options: initialOptions || ['', ''],
crosspost: item ? !!item.noteId : me?.privates?.nostrCrossposting,
pollExpiresAt: item ? item.pollExpiresAt : datePivot(new Date(), { hours: 25 }),
...AdvPostInitial({ forward: normalizeForwards(item?.forwards), boost: item?.boost }),
...SubSelectInitial({ sub: item?.subName || sub?.name })
Expand Down Expand Up @@ -100,7 +111,7 @@ export function PollForm ({ item, sub, editThreshold, children }) {
: null}
maxLength={MAX_POLL_CHOICE_LENGTH}
/>
<AdvPostForm edit={!!item}>
<AdvPostForm edit={!!item} item={item}>
<DateTimeInput
isClearable
label='poll expiration'
Expand Down
Loading
Loading