Use space as blossom server if supported

This commit is contained in:
Jon Staab
2025-04-29 12:24:20 -07:00
parent 5a7750a91b
commit 6ddba63ff9
7 changed files with 98 additions and 38 deletions

View File

@@ -36,7 +36,9 @@
const back = () => history.back()
const submit = () => {
const selectFiles = () => editor.then(ed => ed.chain().selectFiles().run())
const submit = async () => {
if ($uploading) return
if (!title) {
@@ -60,8 +62,9 @@
})
}
const ed = await editor
const event = createEvent(EVENT_TIME, {
content: editor.getText({blockSeparator: "\n"}).trim(),
content: ed.getText({blockSeparator: "\n"}).trim(),
tags: [
["d", initialValues?.d || randomId()],
["title", title],
@@ -69,7 +72,7 @@
["start", start.toString()],
["end", end.toString()],
...daysBetween(start, end).map(D => ["D", D.toString()]),
...editor.storage.nostr.getEditorTags(),
...ed.storage.nostr.getEditorTags(),
tagRoom(GENERAL, url),
PROTECTED,
],
@@ -119,10 +122,7 @@
<div class="input-editor flex-grow overflow-hidden">
<EditorContent {editor} />
</div>
<Button
data-tip="Add an image"
class="center btn tooltip"
onclick={() => editor.chain().selectFiles().run()}>
<Button data-tip="Add an image" class="center btn tooltip" onclick={selectFiles}>
{#if $uploading}
<span class="loading loading-spinner loading-xs"></span>
{:else}

View File

@@ -18,21 +18,22 @@
const uploading = writable(false)
export const focus = () => editor.chain().focus().run()
export const focus = () => editor.then(ed => ed.chain().focus().run())
const uploadFiles = () => editor.chain().selectFiles().run()
const uploadFiles = () => editor.then(ed => ed.chain().selectFiles().run())
const submit = () => {
const submit = async () => {
if ($uploading) return
const content = editor.getText({blockSeparator: "\n"}).trim()
const tags = editor.storage.nostr.getEditorTags()
const ed = await editor
const content = ed.getText({blockSeparator: "\n"}).trim()
const tags = ed.storage.nostr.getEditorTags()
if (!content) return
onSubmit({content, tags})
editor.chain().clearContent().run()
ed.chain().clearContent().run()
}
const editor = makeEditor({url, autofocus, submit, uploading, aggressive: true})

View File

@@ -16,11 +16,14 @@
const uploading = writable(false)
const submit = () => {
const selectFiles = () => editor.then(ed => ed.commands.selectFiles())
const submit = async () => {
if ($uploading) return
const content = editor.getText({blockSeparator: "\n"}).trim()
const tags = [...editor.storage.nostr.getEditorTags(), tagRoom(GENERAL, url), PROTECTED]
const ed = await editor
const content = ed.getText({blockSeparator: "\n"}).trim()
const tags = [...ed.storage.nostr.getEditorTags(), tagRoom(GENERAL, url), PROTECTED]
if (!content) {
return pushToast({
@@ -68,7 +71,7 @@
<Button
data-tip="Add an image"
class="tooltip tooltip-left absolute bottom-1 right-2"
onclick={editor.commands.selectFiles}>
onclick={selectFiles}>
{#if $uploading}
<span class="loading loading-spinner loading-xs"></span>
{:else}

View File

@@ -19,7 +19,9 @@
const back = () => history.back()
const submit = () => {
const selectFiles = () => editor.then(ed => ed.commands.selectFiles())
const submit = async () => {
if ($uploading) return
if (!title) {
@@ -29,7 +31,8 @@
})
}
const content = editor.getText({blockSeparator: "\n"}).trim()
const ed = await editor
const content = ed.getText({blockSeparator: "\n"}).trim()
if (!content.trim()) {
return pushToast({
@@ -39,7 +42,7 @@
}
const tags = [
...editor.storage.nostr.getEditorTags(),
...ed.storage.nostr.getEditorTags(),
tagRoom(GENERAL, url),
["title", title],
PROTECTED,
@@ -97,7 +100,7 @@
<Button
data-tip="Add an image"
class="tooltip tooltip-left absolute bottom-1 right-2"
onclick={editor.commands.selectFiles}>
onclick={selectFiles}>
{#if $uploading}
<span class="loading loading-spinner loading-xs"></span>
{:else}

View File

@@ -1,22 +1,29 @@
<script lang="ts">
import {Editor} from "@welshman/editor"
import {onDestroy, onMount} from "svelte"
const {editor} = $props()
type Props = {
editor: Promise<Editor>
}
const {editor}: Props = $props()
let element: HTMLElement
onMount(() => {
if (editor.options.element) {
element?.append(editor.options.element)
}
editor.then(({options}) => {
if (options.element) {
element?.append(options.element)
}
if (editor.options.autofocus) {
;(element?.querySelector("[contenteditable]") as HTMLElement)?.focus()
}
if (options.autofocus) {
;(element?.querySelector("[contenteditable]") as HTMLElement)?.focus()
}
})
})
onDestroy(() => {
editor.destroy()
editor.then($editor => $editor.destroy())
})
</script>

View File

@@ -2,15 +2,58 @@ import {mount} from "svelte"
import type {Writable} from "svelte/store"
import {get} from "svelte/store"
import type {StampedEvent} from "@welshman/util"
import {getTagValue, getListTags} from "@welshman/util"
import {makeEvent, getTagValues, getListTags, BLOSSOM_AUTH} from "@welshman/util"
import {simpleCache, 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 getUploadUrl = () =>
getTagValue("server", getListTags(userBlossomServers.get())) || "https://cdn.satellite.earth"
export const hasBlossomSupport = simpleCache(async ([url]: [string]) => {
try {
const event = await signer.get()!.sign(
makeEvent(BLOSSOM_AUTH, {
tags: [
["t", "upload"],
["server", url],
["expiration", String(now() + 30)],
],
}),
)
const res = await fetch(url + "/upload", {
method: "head",
headers: {
Authorization: `Nostr ${btoa(JSON.stringify(event))}`,
"X-Content-Type": "text/plain",
"X-Content-Length": "1",
"X-SHA-256": "73cb3858a687a8494ca3323053016282f3dad39d42cf62ca4e79dda2aac7d9ac",
},
})
return res.status === 200
} catch (e) {
if (!String(e).includes("Failed to fetch")) {
console.error(e)
}
}
})
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(/wss?:\/\//, "https://")
if (await hasBlossomSupport(url)) {
return url
}
}
return "https://cdn.satellite.earth"
}
export const signWithAssert = async (template: StampedEvent) => {
const event = await signer.get().sign(template)
@@ -18,7 +61,7 @@ export const signWithAssert = async (template: StampedEvent) => {
return event!
}
export const makeEditor = ({
export const makeEditor = async ({
aggressive = false,
autofocus = false,
charCount,
@@ -26,7 +69,6 @@ export const makeEditor = ({
placeholder = "",
url,
submit,
uploadUrl = getUploadUrl(),
uploading,
wordCount,
}: {
@@ -37,11 +79,10 @@ export const makeEditor = ({
placeholder?: string
url?: string
submit: () => void
uploadUrl?: string
uploading?: Writable<boolean>
wordCount?: Writable<number>
}) =>
new Editor({
}) => {
return new Editor({
content,
autofocus,
element: document.createElement("div"),
@@ -50,7 +91,7 @@ export const makeEditor = ({
submit,
sign: signWithAssert,
defaultUploadType: "blossom",
defaultUploadUrl: uploadUrl,
defaultUploadUrl: await getUploadUrl(url),
extensions: {
placeholder: {
config: {
@@ -101,3 +142,4 @@ export const makeEditor = ({
charCount?.set(editor.storage.wordCount.chars)
},
})
}

View File

@@ -10,6 +10,7 @@
import SpaceAuthError from "@app/components/SpaceAuthError.svelte"
import {pushToast} from "@app/toast"
import {pushModal} from "@app/modal"
import {getUploadUrl} from "@app/editor"
import {setChecked} from "@app/notifications"
import {checkRelayConnection, checkRelayAuth, checkRelayAccess} from "@app/commands"
import {decodeRelay, userRoomsByUrl} from "@app/state"
@@ -51,6 +52,9 @@
onMount(() => {
checkConnection()
// Prime our cache so inputs show up quickly
getUploadUrl(url)
const relays = [url]
const since = ago(WEEK)
const controller = new AbortController()