feat: add option to add client tag

This commit is contained in:
codytseng
2024-12-24 12:13:39 +08:00
parent 234319ef50
commit 31f70c2ab1
8 changed files with 117 additions and 12 deletions

23
package-lock.json generated
View File

@@ -15,6 +15,7 @@
"@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-dialog": "^1.1.4",
"@radix-ui/react-dropdown-menu": "^2.1.4", "@radix-ui/react-dropdown-menu": "^2.1.4",
"@radix-ui/react-hover-card": "^1.1.4", "@radix-ui/react-hover-card": "^1.1.4",
"@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-popover": "^1.1.4", "@radix-ui/react-popover": "^1.1.4",
"@radix-ui/react-scroll-area": "1.2.0", "@radix-ui/react-scroll-area": "1.2.0",
"@radix-ui/react-separator": "^1.1.1", "@radix-ui/react-separator": "^1.1.1",
@@ -2832,6 +2833,28 @@
} }
} }
}, },
"node_modules/@radix-ui/react-label": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.1.tgz",
"integrity": "sha512-UUw5E4e/2+4kFMH7+YxORXGWggtY6sM8WIwh5RZchhLuUg2H1hc98Py+pr8HMz6rdaYrK2t296ZEjYLOCO5uUw==",
"dependencies": {
"@radix-ui/react-primitive": "2.0.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-menu": { "node_modules/@radix-ui/react-menu": {
"version": "2.1.4", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.4.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.4.tgz",

View File

@@ -25,6 +25,7 @@
"@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-dialog": "^1.1.4",
"@radix-ui/react-dropdown-menu": "^2.1.4", "@radix-ui/react-dropdown-menu": "^2.1.4",
"@radix-ui/react-hover-card": "^1.1.4", "@radix-ui/react-hover-card": "^1.1.4",
"@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-popover": "^1.1.4", "@radix-ui/react-popover": "^1.1.4",
"@radix-ui/react-scroll-area": "1.2.0", "@radix-ui/react-scroll-area": "1.2.0",
"@radix-ui/react-separator": "^1.1.1", "@radix-ui/react-separator": "^1.1.1",

View File

@@ -6,20 +6,23 @@ import {
DialogHeader, DialogHeader,
DialogTitle DialogTitle
} from '@/components/ui/dialog' } from '@/components/ui/dialog'
import { Label } from '@/components/ui/label'
import { ScrollArea } from '@/components/ui/scroll-area' import { ScrollArea } from '@/components/ui/scroll-area'
import { Switch } from '@/components/ui/switch'
import { Textarea } from '@/components/ui/textarea' import { Textarea } from '@/components/ui/textarea'
import { StorageKey } from '@/constants'
import { useToast } from '@/hooks/use-toast' import { useToast } from '@/hooks/use-toast'
import { createShortTextNoteDraftEvent } from '@/lib/draft-event' import { createShortTextNoteDraftEvent } from '@/lib/draft-event'
import { useNostr } from '@/providers/NostrProvider' import { useNostr } from '@/providers/NostrProvider'
import client from '@/services/client.service' import client from '@/services/client.service'
import { LoaderCircle } from 'lucide-react' import { ChevronDown, LoaderCircle } from 'lucide-react'
import { Event } from 'nostr-tools' import { Event } from 'nostr-tools'
import { Dispatch, useState } from 'react' import { Dispatch, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import UserAvatar from '../UserAvatar' import UserAvatar from '../UserAvatar'
import Mentions from './Metions' import Mentions from './Metions'
import Preview from './Preview' import Preview from './Preview'
import Uploader from './Uploader' import Uploader from './Uploader'
import { useTranslation } from 'react-i18next'
export default function PostDialog({ export default function PostDialog({
defaultContent = '', defaultContent = '',
@@ -37,8 +40,14 @@ export default function PostDialog({
const { publish, checkLogin } = useNostr() const { publish, checkLogin } = useNostr()
const [content, setContent] = useState(defaultContent) const [content, setContent] = useState(defaultContent)
const [posting, setPosting] = useState(false) const [posting, setPosting] = useState(false)
const [showMoreOptions, setShowMoreOptions] = useState(false)
const [addClientTag, setAddClientTag] = useState(false)
const canPost = !!content && !posting const canPost = !!content && !posting
useEffect(() => {
setAddClientTag(window.localStorage.getItem(StorageKey.ADD_CLIENT_TAG) === 'true')
}, [])
const handleTextareaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { const handleTextareaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setContent(e.target.value) setContent(e.target.value)
} }
@@ -58,7 +67,10 @@ export default function PostDialog({
const relayList = await client.fetchRelayList(parentEvent.pubkey) const relayList = await client.fetchRelayList(parentEvent.pubkey)
additionalRelayUrls.push(...relayList.read.slice(0, 5)) additionalRelayUrls.push(...relayList.read.slice(0, 5))
} }
const draftEvent = await createShortTextNoteDraftEvent(content, parentEvent) const draftEvent = await createShortTextNoteDraftEvent(content, {
parentEvent,
addClientTag
})
await publish(draftEvent, additionalRelayUrls) await publish(draftEvent, additionalRelayUrls)
setContent('') setContent('')
setOpen(false) setOpen(false)
@@ -90,6 +102,11 @@ export default function PostDialog({
}) })
} }
const onAddClientTagChange = (checked: boolean) => {
setAddClientTag(checked)
window.localStorage.setItem(StorageKey.ADD_CLIENT_TAG, checked.toString())
}
return ( return (
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="p-0" withoutClose> <DialogContent className="p-0" withoutClose>
@@ -117,8 +134,20 @@ export default function PostDialog({
/> />
{content && <Preview content={content} />} {content && <Preview content={content} />}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex gap-2 items-center">
<Uploader setContent={setContent} /> <Uploader setContent={setContent} />
<div className="flex gap-2"> <Button
variant="link"
className="text-foreground gap-0 px-0"
onClick={() => setShowMoreOptions((pre) => !pre)}
>
{t('More options')}
<ChevronDown
className={`transition-transform ${showMoreOptions ? 'rotate-180' : ''}`}
/>
</Button>
</div>
<div className="flex gap-2 items-center">
<Mentions content={content} parentEvent={parentEvent} /> <Mentions content={content} parentEvent={parentEvent} />
<Button <Button
variant="secondary" variant="secondary"
@@ -135,6 +164,21 @@ export default function PostDialog({
</Button> </Button>
</div> </div>
</div> </div>
{showMoreOptions && (
<div className="space-y-2">
<div className="flex items-center space-x-2">
<Label htmlFor="add-client-tag">{t('Add client tag')}</Label>
<Switch
id="add-client-tag"
checked={addClientTag}
onCheckedChange={onAddClientTagChange}
/>
</div>
<div className="text-muted-foreground text-xs">
{t('Show others this was sent via Jumble')}
</div>
</div>
)}
</div> </div>
</ScrollArea> </ScrollArea>
</DialogContent> </DialogContent>

View File

@@ -0,0 +1,24 @@
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }

View File

@@ -2,7 +2,8 @@ export const StorageKey = {
THEME_SETTING: 'themeSetting', THEME_SETTING: 'themeSetting',
RELAY_GROUPS: 'relayGroups', RELAY_GROUPS: 'relayGroups',
ACCOUNTS: 'accounts', ACCOUNTS: 'accounts',
CURRENT_ACCOUNT: 'currentAccount' CURRENT_ACCOUNT: 'currentAccount',
ADD_CLIENT_TAG: 'addClientTag'
} }
export const BIG_RELAY_URLS = [ export const BIG_RELAY_URLS = [

View File

@@ -87,6 +87,9 @@ export default {
'reload notes': 'reload notes', 'reload notes': 'reload notes',
'Logged in Accounts': 'Logged in Accounts', 'Logged in Accounts': 'Logged in Accounts',
'Add an Account': 'Add an Account', 'Add an Account': 'Add an Account',
Accounts: 'Accounts' Accounts: 'Accounts',
'More options': 'More options',
'Add client tag': 'Add client tag',
'Show others this was sent via Jumble': 'Show others this was sent via Jumble'
} }
} }

View File

@@ -86,6 +86,9 @@ export default {
'reload notes': '重新加载笔记', 'reload notes': '重新加载笔记',
'Logged in Accounts': '已登录账户', 'Logged in Accounts': '已登录账户',
'Add an Account': '添加账户', 'Add an Account': '添加账户',
Accounts: '多帐户' Accounts: '多帐户',
'More options': '更多选项',
'Add client tag': '添加客户端标签',
'Show others this was sent via Jumble': '告诉别人这是通过 Jumble 发送的'
} }
} }

View File

@@ -39,10 +39,13 @@ export function createRepostDraftEvent(event: Event): TDraftEvent {
export async function createShortTextNoteDraftEvent( export async function createShortTextNoteDraftEvent(
content: string, content: string,
options: {
parentEvent?: Event parentEvent?: Event
addClientTag?: boolean
} = {}
): Promise<TDraftEvent> { ): Promise<TDraftEvent> {
const { pubkeys, otherRelatedEventIds, quoteEventIds, rootEventId, parentEventId } = const { pubkeys, otherRelatedEventIds, quoteEventIds, rootEventId, parentEventId } =
await extractMentions(content, parentEvent) await extractMentions(content, options.parentEvent)
const hashtags = extractHashtags(content) const hashtags = extractHashtags(content)
const tags = pubkeys const tags = pubkeys
@@ -50,7 +53,6 @@ export async function createShortTextNoteDraftEvent(
.concat(otherRelatedEventIds.map((eventId) => ['e', eventId])) .concat(otherRelatedEventIds.map((eventId) => ['e', eventId]))
.concat(quoteEventIds.map((eventId) => ['q', eventId])) .concat(quoteEventIds.map((eventId) => ['q', eventId]))
.concat(hashtags.map((hashtag) => ['t', hashtag])) .concat(hashtags.map((hashtag) => ['t', hashtag]))
.concat([['client', 'jumble']])
if (rootEventId) { if (rootEventId) {
tags.push(['e', rootEventId, '', 'root']) tags.push(['e', rootEventId, '', 'root'])
@@ -60,6 +62,10 @@ export async function createShortTextNoteDraftEvent(
tags.push(['e', parentEventId, '', 'reply']) tags.push(['e', parentEventId, '', 'reply'])
} }
if (options.addClientTag) {
tags.push(['client', 'jumble'])
}
return { return {
kind: kinds.ShortTextNote, kind: kinds.ShortTextNote,
content, content,