import {mount} from "svelte" import type {Writable} from "svelte/store" import {get} from "svelte/store" import type {StampedEvent} from "@welshman/util" import {makeEvent, getTagValues, getListTags, BLOSSOM_AUTH} from "@welshman/util" import {simpleCache, normalizeUrl, removeNil, now} from "@welshman/lib" import {Router} from "@welshman/router" import {signer, profileSearch, userBlossomServers} from "@welshman/app" import {Editor, MentionSuggestion, WelshmanExtension} from "@welshman/editor" import {makeMentionNodeView} from "./MentionNodeView" import ProfileSuggestion from "./ProfileSuggestion.svelte" export const hasBlossomSupport = simpleCache(async ([url]: [string]) => { const $signer = signer.get() const headers: Record = { "X-Content-Type": "text/plain", "X-Content-Length": "1", "X-SHA-256": "73cb3858a687a8494ca3323053016282f3dad39d42cf62ca4e79dda2aac7d9ac", } try { if ($signer) { const event = await signer.get().sign( makeEvent(BLOSSOM_AUTH, { tags: [ ["t", "upload"], ["server", url], ["expiration", String(now() + 30)], ], }), ) headers.Authorization = `Nostr ${btoa(JSON.stringify(event))}` } const res = await fetch(normalizeUrl(url) + "/upload", {method: "head", headers}) return res.status === 200 } catch (e) { if (!String(e).includes("Failed to fetch")) { console.error(e) } } return false }) export const getUploadUrl = async (spaceUrl?: string) => { const userUrls = getTagValues("server", getListTags(userBlossomServers.get())) const allUrls = removeNil([spaceUrl, ...userUrls]) for (let url of allUrls) { url = url.replace(/^ws/, "http") if (await hasBlossomSupport(url)) { return url } } return "https://cdn.satellite.earth" } export const signWithAssert = async (template: StampedEvent) => { const event = await signer.get().sign(template) return event! } export const makeEditor = async ({ aggressive = false, autofocus = false, charCount, content = "", placeholder = "", url, submit, uploading, wordCount, disableFileUpload, }: { aggressive?: boolean autofocus?: boolean charCount?: Writable content?: string placeholder?: string url?: string submit: () => void uploading?: Writable wordCount?: Writable disableFileUpload?: boolean }) => { return new Editor({ content, autofocus, element: document.createElement("div"), extensions: [ WelshmanExtension.configure({ submit, sign: signWithAssert, defaultUploadType: "blossom", defaultUploadUrl: await getUploadUrl(url), extensions: { placeholder: { config: { placeholder, }, }, breakOrSubmit: { config: { aggressive, }, }, fileUpload: disableFileUpload ? false : { config: { onDrop() { uploading?.set(true) }, onComplete() { uploading?.set(false) }, }, }, nprofile: { extend: { addNodeView: () => makeMentionNodeView(url), addProseMirrorPlugins() { return [ MentionSuggestion({ editor: (this as any).editor, search: (term: string) => get(profileSearch).searchValues(term), getRelays: (pubkey: string) => Router.get().FromPubkeys([pubkey]).getUrls(), createSuggestion: (value: string) => { const target = document.createElement("div") mount(ProfileSuggestion, {target, props: {value, url}}) return target }, }), ] }, }, }, }, }), ], onUpdate({editor}) { wordCount?.set(editor.storage.wordCount.words) charCount?.set(editor.storage.wordCount.chars) }, }) }