refactor: post editor
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { BIG_RELAY_URLS, ExtendedKind } from '@/constants'
|
||||
import { getProfileFromProfileEvent, getRelayListFromRelayListEvent } from '@/lib/event'
|
||||
import { formatPubkey, userIdToPubkey } from '@/lib/pubkey'
|
||||
import { formatPubkey, pubkeyToNpub, userIdToPubkey } from '@/lib/pubkey'
|
||||
import { extractPubkeysFromEventTags } from '@/lib/tag'
|
||||
import { isLocalNetworkUrl, isWebsocketUrl, normalizeUrl } from '@/lib/url'
|
||||
import { ISigner, TProfile, TRelayList } from '@/types'
|
||||
@@ -693,7 +693,7 @@ class ClientService extends EventTarget {
|
||||
|
||||
try {
|
||||
const pubkey = userIdToPubkey(id)
|
||||
return { pubkey, username: formatPubkey(pubkey) }
|
||||
return { pubkey, npub: pubkeyToNpub(pubkey) ?? '', username: formatPubkey(pubkey) }
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
@@ -833,11 +833,9 @@ class ClientService extends EventTarget {
|
||||
this.relayListEventDataLoader.prime(event.pubkey, Promise.resolve(event))
|
||||
}
|
||||
|
||||
async searchProfilesFromIndex(query: string, limit: number = 100) {
|
||||
async searchNpubs(query: string, limit: number = 100) {
|
||||
const result = await this.userIndex.searchAsync(query, { limit })
|
||||
return Promise.all(result.map((pubkey) => this.fetchProfile(pubkey as string))).then(
|
||||
(profiles) => profiles.filter(Boolean) as TProfile[]
|
||||
)
|
||||
return result.map((pubkey) => pubkeyToNpub(pubkey as string)).filter(Boolean) as string[]
|
||||
}
|
||||
|
||||
async initUserIndexFromFollowings(pubkey: string, signal: AbortSignal) {
|
||||
|
||||
99
src/services/media-upload.service.ts
Normal file
99
src/services/media-upload.service.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { simplifyUrl } from '@/lib/url'
|
||||
import dayjs from 'dayjs'
|
||||
import { kinds } from 'nostr-tools'
|
||||
import { z } from 'zod'
|
||||
import client from './client.service'
|
||||
import storage from './local-storage.service'
|
||||
|
||||
class MediaUploadService {
|
||||
static instance: MediaUploadService
|
||||
|
||||
private service: string = storage.getMediaUploadService()
|
||||
private serviceUploadUrlMap = new Map<string, string | undefined>()
|
||||
private imetaTagMap = new Map<string, string[]>()
|
||||
|
||||
constructor() {
|
||||
if (!MediaUploadService.instance) {
|
||||
MediaUploadService.instance = this
|
||||
}
|
||||
return MediaUploadService.instance
|
||||
}
|
||||
|
||||
getService() {
|
||||
return this.service
|
||||
}
|
||||
|
||||
setService(service: string) {
|
||||
this.service = service
|
||||
storage.setMediaUploadService(service)
|
||||
}
|
||||
|
||||
async upload(file: File) {
|
||||
let uploadUrl = this.serviceUploadUrlMap.get(this.service)
|
||||
if (!uploadUrl) {
|
||||
const response = await fetch(`${this.service}/.well-known/nostr/nip96.json`)
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`${simplifyUrl(this.service)} does not work, please try another service in your settings`
|
||||
)
|
||||
}
|
||||
const data = await response.json()
|
||||
uploadUrl = data?.api_url
|
||||
if (!uploadUrl) {
|
||||
throw new Error(
|
||||
`${simplifyUrl(this.service)} does not work, please try another service in your settings`
|
||||
)
|
||||
}
|
||||
this.serviceUploadUrlMap.set(this.service, uploadUrl)
|
||||
}
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
|
||||
const auth = await this.signHttpAuth(uploadUrl, 'POST')
|
||||
const response = await fetch(uploadUrl, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
Authorization: auth
|
||||
}
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(response.status.toString() + ' ' + response.statusText)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const tags = z.array(z.array(z.string())).parse(data.nip94_event?.tags ?? [])
|
||||
const url = tags.find(([tagName]) => tagName === 'url')?.[1]
|
||||
if (url) {
|
||||
this.imetaTagMap.set(url, ['imeta', ...tags.map(([n, v]) => `${n} ${v}`)])
|
||||
return { url: url, tags }
|
||||
} else {
|
||||
throw new Error('No url found')
|
||||
}
|
||||
}
|
||||
|
||||
getImetaTagByUrl(url: string) {
|
||||
return this.imetaTagMap.get(url)
|
||||
}
|
||||
|
||||
async signHttpAuth(url: string, method: string) {
|
||||
if (!client.signer) {
|
||||
throw new Error('No signer found')
|
||||
}
|
||||
const event = await client.signer.signEvent({
|
||||
content: '',
|
||||
kind: kinds.HTTPAuth,
|
||||
created_at: dayjs().unix(),
|
||||
tags: [
|
||||
['u', url],
|
||||
['method', method]
|
||||
]
|
||||
})
|
||||
return 'Nostr ' + btoa(JSON.stringify(event))
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new MediaUploadService()
|
||||
export default instance
|
||||
@@ -1,19 +1,10 @@
|
||||
import { Content } from '@tiptap/react'
|
||||
import { Event } from 'nostr-tools'
|
||||
|
||||
class PostContentCacheService {
|
||||
static instance: PostContentCacheService
|
||||
|
||||
private normalPostCache: Map<
|
||||
string,
|
||||
{
|
||||
content: string
|
||||
pictureInfos: { url: string; tags: string[][] }[]
|
||||
}
|
||||
> = new Map()
|
||||
private picturePostCache: {
|
||||
content: string
|
||||
pictureInfos: { url: string; tags: string[][] }[]
|
||||
} = { content: '', pictureInfos: [] }
|
||||
private normalPostCache: Map<string, Content> = new Map()
|
||||
|
||||
constructor() {
|
||||
if (!PostContentCacheService.instance) {
|
||||
@@ -22,35 +13,30 @@ class PostContentCacheService {
|
||||
return PostContentCacheService.instance
|
||||
}
|
||||
|
||||
getNormalPostCache({
|
||||
getPostCache({
|
||||
defaultContent,
|
||||
parentEvent
|
||||
}: { defaultContent?: string; parentEvent?: Event } = {}) {
|
||||
return (
|
||||
this.normalPostCache.get(this.generateCacheKey(defaultContent, parentEvent)) ?? {
|
||||
content: defaultContent,
|
||||
pictureInfos: [] as { url: string; tags: string[][] }[]
|
||||
}
|
||||
this.normalPostCache.get(this.generateCacheKey(defaultContent, parentEvent)) ?? defaultContent
|
||||
)
|
||||
}
|
||||
|
||||
setNormalPostCache(
|
||||
setPostCache(
|
||||
{ defaultContent, parentEvent }: { defaultContent?: string; parentEvent?: Event },
|
||||
content: string,
|
||||
pictureInfos: { url: string; tags: string[][] }[]
|
||||
content: Content
|
||||
) {
|
||||
this.normalPostCache.set(this.generateCacheKey(defaultContent, parentEvent), {
|
||||
content,
|
||||
pictureInfos
|
||||
})
|
||||
this.normalPostCache.set(this.generateCacheKey(defaultContent, parentEvent), content)
|
||||
}
|
||||
|
||||
getPicturePostCache() {
|
||||
return this.picturePostCache
|
||||
}
|
||||
|
||||
setPicturePostCache(content: string, pictureInfos: { url: string; tags: string[][] }[]) {
|
||||
this.picturePostCache = { content, pictureInfos }
|
||||
clearPostCache({
|
||||
defaultContent,
|
||||
parentEvent
|
||||
}: {
|
||||
defaultContent?: string
|
||||
parentEvent?: Event
|
||||
}) {
|
||||
this.normalPostCache.delete(this.generateCacheKey(defaultContent, parentEvent))
|
||||
}
|
||||
|
||||
generateCacheKey(defaultContent: string = '', parentEvent?: Event): string {
|
||||
|
||||
23
src/services/post-editor.service.ts
Normal file
23
src/services/post-editor.service.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
class PostEditorService extends EventTarget {
|
||||
static instance: PostEditorService
|
||||
|
||||
isSuggestionPopupOpen = false
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
if (!PostEditorService.instance) {
|
||||
PostEditorService.instance = this
|
||||
}
|
||||
return PostEditorService.instance
|
||||
}
|
||||
|
||||
closeSuggestionPopup() {
|
||||
if (this.isSuggestionPopupOpen) {
|
||||
this.isSuggestionPopupOpen = false
|
||||
this.dispatchEvent(new CustomEvent('closeSuggestionPopup'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new PostEditorService()
|
||||
export default instance
|
||||
Reference in New Issue
Block a user