Use new editor uploads

This commit is contained in:
Jon Staab 2025-06-06 16:38:11 -07:00
parent 664f3c01e0
commit 18ae6f6044
8 changed files with 87 additions and 28 deletions

View file

@ -45,8 +45,6 @@
"@capacitor/core": "^7.0.1", "@capacitor/core": "^7.0.1",
"@capacitor/ios": "^7.0.0", "@capacitor/ios": "^7.0.0",
"@capacitor/keyboard": "^7.0.0", "@capacitor/keyboard": "^7.0.0",
"@noble/curves": "^1.5.0",
"@noble/hashes": "^1.4.0",
"@poppanator/sveltekit-svg": "^4.2.1", "@poppanator/sveltekit-svg": "^4.2.1",
"@sentry/browser": "^8.35.0", "@sentry/browser": "^8.35.0",
"@sveltejs/adapter-static": "^3.0.4", "@sveltejs/adapter-static": "^3.0.4",
@ -66,6 +64,7 @@
"@welshman/signer": "^0.3.0", "@welshman/signer": "^0.3.0",
"@welshman/store": "^0.3.0", "@welshman/store": "^0.3.0",
"@welshman/util": "^0.3.0", "@welshman/util": "^0.3.0",
"compressorjs": "^1.2.1",
"daisyui": "^4.12.10", "daisyui": "^4.12.10",
"date-picker-svelte": "^2.13.0", "date-picker-svelte": "^2.13.0",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",

Binary file not shown.

View file

@ -1,8 +1,7 @@
<script lang="ts"> <script lang="ts">
import {onMount, onDestroy} from "svelte" import {onMount, onDestroy} from "svelte"
import {displayUrl} from "@welshman/lib" import {displayUrl} from "@welshman/lib"
import {getTags, getTagValue, tagsFromIMeta} from "@welshman/util" import {getTags, decryptFile, getTagValue, tagsFromIMeta} from "@welshman/util"
import {decryptFile} from "@welshman/editor"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import {imgproxy} from "@app/state" import {imgproxy} from "@app/state"
@ -16,7 +15,7 @@
const key = getTagValue("decryption-key", meta) const key = getTagValue("decryption-key", meta)
const nonce = getTagValue("decryption-nonce", meta) const nonce = getTagValue("decryption-nonce", meta)
const encryptionAlgorithm = getTagValue("encryption-algorithm", meta) const algorithm = getTagValue("encryption-algorithm", meta)
const onError = () => { const onError = () => {
hasError = true hasError = true
@ -26,14 +25,14 @@
let src = $state(imgproxy(url)) let src = $state(imgproxy(url))
onMount(async () => { onMount(async () => {
if (encryptionAlgorithm === "aes-gcm" && key && nonce) { if (algorithm === "aes-gcm" && key && nonce) {
const response = await fetch(url) const response = await fetch(url)
if (response.ok) { if (response.ok) {
const ciphertext = new Uint8Array(await response.arrayBuffer()) const ciphertext = new Uint8Array(await response.arrayBuffer())
const decryptedData = decryptFile({ciphertext, key, nonce, encryptionAlgorithm}) const decryptedData = await decryptFile({ciphertext, key, nonce, algorithm})
src = URL.createObjectURL(new Blob([new Uint8Array(decryptedData)])) src = URL.createObjectURL(new Blob([decryptedData]))
} }
} }
}) })

View file

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import {encrypt} from "nostr-tools/nip49" import {encrypt} from "nostr-tools/nip49"
import {hexToBytes} from "@noble/hashes/utils" import {hexToBytes} from "@welshman/lib"
import {makeSecret} from "@welshman/signer" import {makeSecret} from "@welshman/signer"
import {preventDefault, downloadText} from "@lib/html" import {preventDefault, downloadText} from "@lib/html"
import Link from "@lib/components/Link.svelte" import Link from "@lib/components/Link.svelte"

View file

@ -1,15 +1,25 @@
import {mount} from "svelte" 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 {sha256} from "@welshman/lib"
import {getTagValues, getListTags} from "@welshman/util" import {
getTagValues,
getTagValue,
encryptFile,
uploadBlob,
makeBlossomAuthEvent,
getListTags,
} from "@welshman/util"
import {Router} from "@welshman/router" import {Router} from "@welshman/router"
import {Nip01Signer} from "@welshman/signer"
import {signer, profileSearch, userBlossomServers} from "@welshman/app" import {signer, profileSearch, userBlossomServers} from "@welshman/app"
import type {FileAttributes} from "@welshman/editor"
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"
import {pushToast} from "@app/toast"
export const getUploadUrl = () => { export const getBlossomServer = () => {
const userUrls = getTagValues("server", getListTags(userBlossomServers.get())) const userUrls = getTagValues("server", getListTags(userBlossomServers.get()))
for (const url of userUrls) { for (const url of userUrls) {
@ -19,12 +29,6 @@ export const getUploadUrl = () => {
return "https://cdn.satellite.earth" return "https://cdn.satellite.earth"
} }
export const signWithAssert = async (template: StampedEvent) => {
const event = await signer.get().sign(template)
return event!
}
export const makeEditor = async ({ export const makeEditor = async ({
aggressive = false, aggressive = false,
autofocus = false, autofocus = false,
@ -53,9 +57,6 @@ export const makeEditor = async ({
extensions: [ extensions: [
WelshmanExtension.configure({ WelshmanExtension.configure({
submit, submit,
sign: signWithAssert,
defaultUploadType: "blossom",
defaultUploadUrl: getUploadUrl(),
extensions: { extensions: {
placeholder: { placeholder: {
config: { config: {
@ -69,13 +70,77 @@ export const makeEditor = async ({
}, },
fileUpload: { fileUpload: {
config: { config: {
encryptionAlgorithm: "aes-gcm", upload: async (attrs: FileAttributes) => {
let file: Blob = attrs.file
if (!file.type.match("image/(webp|gif)")) {
const {default: Compressor} = await import("compressorjs")
file = await new Promise((resolve, _reject) => {
new Compressor(file, {
maxWidth: 1024,
maxHeight: 1024,
convertSize: 2 * 1024 * 1024,
success: resolve,
error: e => {
// Non-images break compressor
if (e.toString().includes("File or Blob")) {
return resolve(file)
}
_reject(e)
},
})
})
}
const {ciphertext, key, nonce, algorithm} = await encryptFile(file)
const tags = [
["decryption-key", key],
["decryption-nonce", nonce],
["encryption-algorithm", algorithm],
]
file = new File([new Blob([ciphertext])], attrs.file.name, {type: attrs.file.type})
const server = getBlossomServer()
const hashes = [await sha256(await file.arrayBuffer())]
const $signer = signer.get() || Nip01Signer.ephemeral()
const authTemplate = makeBlossomAuthEvent({action: "upload", server, hashes})
const authEvent = await $signer.sign(authTemplate)
try {
const res = await uploadBlob(server, file, {authEvent})
let {uploaded, url, ...task} = await res.json()
if (!uploaded) {
return {error: "Server refused to process the file"}
}
// Always append file extension if missing
if (new URL(url).pathname.split(".").length === 1) {
url += "." + attrs.file.type.split("/")[1]
}
const result = {...task, tags, url}
return {result}
} catch (e: any) {
console.error(e)
return {error: e.toString()}
}
},
onDrop() { onDrop() {
uploading?.set(true) uploading?.set(true)
}, },
onComplete() { onComplete() {
uploading?.set(false) uploading?.set(false)
}, },
onUploadError(currentEditor, task) {
currentEditor.commands.removeFailedUploads()
pushToast({theme: "error", message: "Failed to upload file"})
uploading?.set(false)
},
}, },
}, },
nprofile: { nprofile: {

View file

@ -1,6 +1,5 @@
import {hexToBytes, bytesToHex} from "@noble/hashes/utils"
import * as nip19 from "nostr-tools/nip19" import * as nip19 from "nostr-tools/nip19"
import {range, DAY} from "@welshman/lib" import {range, DAY, hexToBytes, bytesToHex} from "@welshman/lib"
export const nsecEncode = (secret: string) => nip19.nsecEncode(hexToBytes(secret)) export const nsecEncode = (secret: string) => nip19.nsecEncode(hexToBytes(secret))

View file

@ -7,7 +7,6 @@
import {App} from "@capacitor/app" import {App} from "@capacitor/app"
import {dev} from "$app/environment" import {dev} from "$app/environment"
import {goto} from "$app/navigation" import {goto} from "$app/navigation"
import {bytesToHex, hexToBytes} from "@noble/hashes/utils"
import {identity, memoize, sleep, defer, ago, WEEK, TaskQueue} from "@welshman/lib" import {identity, memoize, sleep, defer, ago, WEEK, TaskQueue} from "@welshman/lib"
import type {TrustedEvent, StampedEvent} from "@welshman/util" import type {TrustedEvent, StampedEvent} from "@welshman/util"
import { import {
@ -80,8 +79,6 @@
Object.assign(window, { Object.assign(window, {
get, get,
nip19, nip19,
bytesToHex,
hexToBytes,
...lib, ...lib,
...welshmanSigner, ...welshmanSigner,
...util, ...util,

View file

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import * as nip19 from "nostr-tools/nip19" import * as nip19 from "nostr-tools/nip19"
import {hexToBytes} from "@noble/hashes/utils" import {hexToBytes} from "@welshman/lib"
import {displayPubkey, displayProfile} from "@welshman/util" import {displayPubkey, displayProfile} from "@welshman/util"
import {pubkey, session, displayNip05, deriveProfile} from "@welshman/app" import {pubkey, session, displayNip05, deriveProfile} from "@welshman/app"
import {slideAndFade} from "@lib/transition" import {slideAndFade} from "@lib/transition"