forked from coracle/flotilla
Use space as blossom server if supported
This commit is contained in:
@@ -36,7 +36,9 @@
|
|||||||
|
|
||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
|
|
||||||
const submit = () => {
|
const selectFiles = () => editor.then(ed => ed.chain().selectFiles().run())
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
if ($uploading) return
|
if ($uploading) return
|
||||||
|
|
||||||
if (!title) {
|
if (!title) {
|
||||||
@@ -60,8 +62,9 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ed = await editor
|
||||||
const event = createEvent(EVENT_TIME, {
|
const event = createEvent(EVENT_TIME, {
|
||||||
content: editor.getText({blockSeparator: "\n"}).trim(),
|
content: ed.getText({blockSeparator: "\n"}).trim(),
|
||||||
tags: [
|
tags: [
|
||||||
["d", initialValues?.d || randomId()],
|
["d", initialValues?.d || randomId()],
|
||||||
["title", title],
|
["title", title],
|
||||||
@@ -69,7 +72,7 @@
|
|||||||
["start", start.toString()],
|
["start", start.toString()],
|
||||||
["end", end.toString()],
|
["end", end.toString()],
|
||||||
...daysBetween(start, end).map(D => ["D", D.toString()]),
|
...daysBetween(start, end).map(D => ["D", D.toString()]),
|
||||||
...editor.storage.nostr.getEditorTags(),
|
...ed.storage.nostr.getEditorTags(),
|
||||||
tagRoom(GENERAL, url),
|
tagRoom(GENERAL, url),
|
||||||
PROTECTED,
|
PROTECTED,
|
||||||
],
|
],
|
||||||
@@ -119,10 +122,7 @@
|
|||||||
<div class="input-editor flex-grow overflow-hidden">
|
<div class="input-editor flex-grow overflow-hidden">
|
||||||
<EditorContent {editor} />
|
<EditorContent {editor} />
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button data-tip="Add an image" class="center btn tooltip" onclick={selectFiles}>
|
||||||
data-tip="Add an image"
|
|
||||||
class="center btn tooltip"
|
|
||||||
onclick={() => editor.chain().selectFiles().run()}>
|
|
||||||
{#if $uploading}
|
{#if $uploading}
|
||||||
<span class="loading loading-spinner loading-xs"></span>
|
<span class="loading loading-spinner loading-xs"></span>
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
@@ -18,21 +18,22 @@
|
|||||||
|
|
||||||
const uploading = writable(false)
|
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
|
if ($uploading) return
|
||||||
|
|
||||||
const content = editor.getText({blockSeparator: "\n"}).trim()
|
const ed = await editor
|
||||||
const tags = editor.storage.nostr.getEditorTags()
|
const content = ed.getText({blockSeparator: "\n"}).trim()
|
||||||
|
const tags = ed.storage.nostr.getEditorTags()
|
||||||
|
|
||||||
if (!content) return
|
if (!content) return
|
||||||
|
|
||||||
onSubmit({content, tags})
|
onSubmit({content, tags})
|
||||||
|
|
||||||
editor.chain().clearContent().run()
|
ed.chain().clearContent().run()
|
||||||
}
|
}
|
||||||
|
|
||||||
const editor = makeEditor({url, autofocus, submit, uploading, aggressive: true})
|
const editor = makeEditor({url, autofocus, submit, uploading, aggressive: true})
|
||||||
|
|||||||
@@ -16,11 +16,14 @@
|
|||||||
|
|
||||||
const uploading = writable(false)
|
const uploading = writable(false)
|
||||||
|
|
||||||
const submit = () => {
|
const selectFiles = () => editor.then(ed => ed.commands.selectFiles())
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
if ($uploading) return
|
if ($uploading) return
|
||||||
|
|
||||||
const content = editor.getText({blockSeparator: "\n"}).trim()
|
const ed = await editor
|
||||||
const tags = [...editor.storage.nostr.getEditorTags(), tagRoom(GENERAL, url), PROTECTED]
|
const content = ed.getText({blockSeparator: "\n"}).trim()
|
||||||
|
const tags = [...ed.storage.nostr.getEditorTags(), tagRoom(GENERAL, url), PROTECTED]
|
||||||
|
|
||||||
if (!content) {
|
if (!content) {
|
||||||
return pushToast({
|
return pushToast({
|
||||||
@@ -68,7 +71,7 @@
|
|||||||
<Button
|
<Button
|
||||||
data-tip="Add an image"
|
data-tip="Add an image"
|
||||||
class="tooltip tooltip-left absolute bottom-1 right-2"
|
class="tooltip tooltip-left absolute bottom-1 right-2"
|
||||||
onclick={editor.commands.selectFiles}>
|
onclick={selectFiles}>
|
||||||
{#if $uploading}
|
{#if $uploading}
|
||||||
<span class="loading loading-spinner loading-xs"></span>
|
<span class="loading loading-spinner loading-xs"></span>
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
@@ -19,7 +19,9 @@
|
|||||||
|
|
||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
|
|
||||||
const submit = () => {
|
const selectFiles = () => editor.then(ed => ed.commands.selectFiles())
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
if ($uploading) return
|
if ($uploading) return
|
||||||
|
|
||||||
if (!title) {
|
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()) {
|
if (!content.trim()) {
|
||||||
return pushToast({
|
return pushToast({
|
||||||
@@ -39,7 +42,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tags = [
|
const tags = [
|
||||||
...editor.storage.nostr.getEditorTags(),
|
...ed.storage.nostr.getEditorTags(),
|
||||||
tagRoom(GENERAL, url),
|
tagRoom(GENERAL, url),
|
||||||
["title", title],
|
["title", title],
|
||||||
PROTECTED,
|
PROTECTED,
|
||||||
@@ -97,7 +100,7 @@
|
|||||||
<Button
|
<Button
|
||||||
data-tip="Add an image"
|
data-tip="Add an image"
|
||||||
class="tooltip tooltip-left absolute bottom-1 right-2"
|
class="tooltip tooltip-left absolute bottom-1 right-2"
|
||||||
onclick={editor.commands.selectFiles}>
|
onclick={selectFiles}>
|
||||||
{#if $uploading}
|
{#if $uploading}
|
||||||
<span class="loading loading-spinner loading-xs"></span>
|
<span class="loading loading-spinner loading-xs"></span>
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
@@ -1,22 +1,29 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import {Editor} from "@welshman/editor"
|
||||||
import {onDestroy, onMount} from "svelte"
|
import {onDestroy, onMount} from "svelte"
|
||||||
|
|
||||||
const {editor} = $props()
|
type Props = {
|
||||||
|
editor: Promise<Editor>
|
||||||
|
}
|
||||||
|
|
||||||
|
const {editor}: Props = $props()
|
||||||
|
|
||||||
let element: HTMLElement
|
let element: HTMLElement
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (editor.options.element) {
|
editor.then(({options}) => {
|
||||||
element?.append(editor.options.element)
|
if (options.element) {
|
||||||
}
|
element?.append(options.element)
|
||||||
|
}
|
||||||
|
|
||||||
if (editor.options.autofocus) {
|
if (options.autofocus) {
|
||||||
;(element?.querySelector("[contenteditable]") as HTMLElement)?.focus()
|
;(element?.querySelector("[contenteditable]") as HTMLElement)?.focus()
|
||||||
}
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
editor.destroy()
|
editor.then($editor => $editor.destroy())
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
+51
-9
@@ -2,15 +2,58 @@ import {mount} from "svelte"
|
|||||||
import type {Writable} from "svelte/store"
|
import type {Writable} from "svelte/store"
|
||||||
import {get} from "svelte/store"
|
import {get} from "svelte/store"
|
||||||
import type {StampedEvent} from "@welshman/util"
|
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 {Router} from "@welshman/router"
|
||||||
import {signer, profileSearch, userBlossomServers} from "@welshman/app"
|
import {signer, profileSearch, userBlossomServers} from "@welshman/app"
|
||||||
import {Editor, MentionSuggestion, WelshmanExtension} from "@welshman/editor"
|
import {Editor, MentionSuggestion, WelshmanExtension} from "@welshman/editor"
|
||||||
import {makeMentionNodeView} from "./MentionNodeView"
|
import {makeMentionNodeView} from "./MentionNodeView"
|
||||||
import ProfileSuggestion from "./ProfileSuggestion.svelte"
|
import ProfileSuggestion from "./ProfileSuggestion.svelte"
|
||||||
|
|
||||||
export const getUploadUrl = () =>
|
export const hasBlossomSupport = simpleCache(async ([url]: [string]) => {
|
||||||
getTagValue("server", getListTags(userBlossomServers.get())) || "https://cdn.satellite.earth"
|
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) => {
|
export const signWithAssert = async (template: StampedEvent) => {
|
||||||
const event = await signer.get().sign(template)
|
const event = await signer.get().sign(template)
|
||||||
@@ -18,7 +61,7 @@ export const signWithAssert = async (template: StampedEvent) => {
|
|||||||
return event!
|
return event!
|
||||||
}
|
}
|
||||||
|
|
||||||
export const makeEditor = ({
|
export const makeEditor = async ({
|
||||||
aggressive = false,
|
aggressive = false,
|
||||||
autofocus = false,
|
autofocus = false,
|
||||||
charCount,
|
charCount,
|
||||||
@@ -26,7 +69,6 @@ export const makeEditor = ({
|
|||||||
placeholder = "",
|
placeholder = "",
|
||||||
url,
|
url,
|
||||||
submit,
|
submit,
|
||||||
uploadUrl = getUploadUrl(),
|
|
||||||
uploading,
|
uploading,
|
||||||
wordCount,
|
wordCount,
|
||||||
}: {
|
}: {
|
||||||
@@ -37,11 +79,10 @@ export const makeEditor = ({
|
|||||||
placeholder?: string
|
placeholder?: string
|
||||||
url?: string
|
url?: string
|
||||||
submit: () => void
|
submit: () => void
|
||||||
uploadUrl?: string
|
|
||||||
uploading?: Writable<boolean>
|
uploading?: Writable<boolean>
|
||||||
wordCount?: Writable<number>
|
wordCount?: Writable<number>
|
||||||
}) =>
|
}) => {
|
||||||
new Editor({
|
return new Editor({
|
||||||
content,
|
content,
|
||||||
autofocus,
|
autofocus,
|
||||||
element: document.createElement("div"),
|
element: document.createElement("div"),
|
||||||
@@ -50,7 +91,7 @@ export const makeEditor = ({
|
|||||||
submit,
|
submit,
|
||||||
sign: signWithAssert,
|
sign: signWithAssert,
|
||||||
defaultUploadType: "blossom",
|
defaultUploadType: "blossom",
|
||||||
defaultUploadUrl: uploadUrl,
|
defaultUploadUrl: await getUploadUrl(url),
|
||||||
extensions: {
|
extensions: {
|
||||||
placeholder: {
|
placeholder: {
|
||||||
config: {
|
config: {
|
||||||
@@ -101,3 +142,4 @@ export const makeEditor = ({
|
|||||||
charCount?.set(editor.storage.wordCount.chars)
|
charCount?.set(editor.storage.wordCount.chars)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
import SpaceAuthError from "@app/components/SpaceAuthError.svelte"
|
import SpaceAuthError from "@app/components/SpaceAuthError.svelte"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/toast"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
|
import {getUploadUrl} from "@app/editor"
|
||||||
import {setChecked} from "@app/notifications"
|
import {setChecked} from "@app/notifications"
|
||||||
import {checkRelayConnection, checkRelayAuth, checkRelayAccess} from "@app/commands"
|
import {checkRelayConnection, checkRelayAuth, checkRelayAccess} from "@app/commands"
|
||||||
import {decodeRelay, userRoomsByUrl} from "@app/state"
|
import {decodeRelay, userRoomsByUrl} from "@app/state"
|
||||||
@@ -51,6 +52,9 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
checkConnection()
|
checkConnection()
|
||||||
|
|
||||||
|
// Prime our cache so inputs show up quickly
|
||||||
|
getUploadUrl(url)
|
||||||
|
|
||||||
const relays = [url]
|
const relays = [url]
|
||||||
const since = ago(WEEK)
|
const since = ago(WEEK)
|
||||||
const controller = new AbortController()
|
const controller = new AbortController()
|
||||||
|
|||||||
Reference in New Issue
Block a user