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 119 additions and 44 deletions
+1 -2
View File
@@ -45,8 +45,6 @@
"@capacitor/core": "^7.0.1",
"@capacitor/ios": "^7.0.0",
"@capacitor/keyboard": "^7.0.0",
"@noble/curves": "^1.5.0",
"@noble/hashes": "^1.4.0",
"@poppanator/sveltekit-svg": "^4.2.1",
"@sentry/browser": "^8.35.0",
"@sveltejs/adapter-static": "^3.0.4",
@@ -66,6 +64,7 @@
"@welshman/signer": "^0.3.0",
"@welshman/store": "^0.3.0",
"@welshman/util": "^0.3.0",
"compressorjs": "^1.2.1",
"daisyui": "^4.12.10",
"date-picker-svelte": "^2.13.0",
"dotenv": "^16.4.5",
+32 -16
View File
@@ -29,12 +29,6 @@ importers:
'@capacitor/keyboard':
specifier: ^7.0.0
version: 7.0.1(@capacitor/core@7.2.0)
'@noble/curves':
specifier: ^1.5.0
version: 1.8.1
'@noble/hashes':
specifier: ^1.4.0
version: 1.7.1
'@poppanator/sveltekit-svg':
specifier: ^4.2.1
version: 4.2.1(rollup@2.79.2)(svelte@5.25.10)(svgo@3.3.2)(vite@5.4.17(@types/node@22.14.0)(terser@5.39.0))
@@ -92,6 +86,9 @@ importers:
'@welshman/util':
specifier: ^0.3.0
version: 0.3.0(typescript@5.8.3)
compressorjs:
specifier: ^1.2.1
version: 1.2.1
daisyui:
specifier: ^4.12.10
version: 4.12.24(postcss@8.5.3)
@@ -1043,8 +1040,8 @@ packages:
'@noble/curves@1.2.0':
resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==}
'@noble/curves@1.8.1':
resolution: {integrity: sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==}
'@noble/curves@1.9.2':
resolution: {integrity: sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g==}
engines: {node: ^14.21.3 || >=16}
'@noble/hashes@1.3.1':
@@ -1055,8 +1052,8 @@ packages:
resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==}
engines: {node: '>= 16'}
'@noble/hashes@1.7.1':
resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==}
'@noble/hashes@1.8.0':
resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==}
engines: {node: ^14.21.3 || >=16}
'@nodelib/fs.scandir@2.1.5':
@@ -1842,6 +1839,9 @@ packages:
bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
blueimp-canvas-to-blob@3.29.0:
resolution: {integrity: sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg==}
boolbase@1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
@@ -2018,6 +2018,9 @@ packages:
compare-func@2.0.0:
resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==}
compressorjs@1.2.1:
resolution: {integrity: sha512-+geIjeRnPhQ+LLvvA7wxBQE5ddeLU7pJ3FsKFWirDw6veY3s9iLxAQEw7lXGHnhCJvBujEQWuNnGzZcvCvdkLQ==}
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
@@ -2858,6 +2861,10 @@ packages:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
is-blob@2.1.0:
resolution: {integrity: sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==}
engines: {node: '>=6'}
is-boolean-object@1.2.2:
resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
engines: {node: '>= 0.4'}
@@ -5845,15 +5852,15 @@ snapshots:
dependencies:
'@noble/hashes': 1.3.2
'@noble/curves@1.8.1':
'@noble/curves@1.9.2':
dependencies:
'@noble/hashes': 1.7.1
'@noble/hashes': 1.8.0
'@noble/hashes@1.3.1': {}
'@noble/hashes@1.3.2': {}
'@noble/hashes@1.7.1': {}
'@noble/hashes@1.8.0': {}
'@nodelib/fs.scandir@2.1.5':
dependencies:
@@ -6458,7 +6465,7 @@ snapshots:
'@welshman/dvm@0.3.0(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.2)':
dependencies:
'@noble/hashes': 1.7.1
'@noble/hashes': 1.8.0
'@welshman/lib': 0.3.0
'@welshman/net': 0.3.0(typescript@5.8.3)(ws@8.18.2)
'@welshman/signer': 0.3.0(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.2)
@@ -6547,8 +6554,8 @@ snapshots:
'@welshman/signer@0.3.0(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.2)':
dependencies:
'@noble/curves': 1.8.1
'@noble/hashes': 1.7.1
'@noble/curves': 1.9.2
'@noble/hashes': 1.8.0
'@welshman/lib': 0.3.0
'@welshman/net': 0.3.0(typescript@5.8.3)(ws@8.18.2)
'@welshman/util': 0.3.0(typescript@5.8.3)
@@ -6767,6 +6774,8 @@ snapshots:
inherits: 2.0.4
readable-stream: 3.6.2
blueimp-canvas-to-blob@3.29.0: {}
boolbase@1.0.0: {}
bplist-creator@0.1.0:
@@ -6953,6 +6962,11 @@ snapshots:
array-ify: 1.0.0
dot-prop: 5.3.0
compressorjs@1.2.1:
dependencies:
blueimp-canvas-to-blob: 3.29.0
is-blob: 2.1.0
concat-map@0.0.1: {}
consola@3.4.2: {}
@@ -7933,6 +7947,8 @@ snapshots:
dependencies:
binary-extensions: 2.3.0
is-blob@2.1.0: {}
is-boolean-object@1.2.2:
dependencies:
call-bound: 1.0.4
@@ -1,8 +1,7 @@
<script lang="ts">
import {onMount, onDestroy} from "svelte"
import {displayUrl} from "@welshman/lib"
import {getTags, getTagValue, tagsFromIMeta} from "@welshman/util"
import {decryptFile} from "@welshman/editor"
import {getTags, decryptFile, getTagValue, tagsFromIMeta} from "@welshman/util"
import Icon from "@lib/components/Icon.svelte"
import {imgproxy} from "@app/state"
@@ -16,7 +15,7 @@
const key = getTagValue("decryption-key", meta)
const nonce = getTagValue("decryption-nonce", meta)
const encryptionAlgorithm = getTagValue("encryption-algorithm", meta)
const algorithm = getTagValue("encryption-algorithm", meta)
const onError = () => {
hasError = true
@@ -26,14 +25,14 @@
let src = $state(imgproxy(url))
onMount(async () => {
if (encryptionAlgorithm === "aes-gcm" && key && nonce) {
if (algorithm === "aes-gcm" && key && nonce) {
const response = await fetch(url)
if (response.ok) {
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]))
}
}
})
+1 -1
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import {encrypt} from "nostr-tools/nip49"
import {hexToBytes} from "@noble/hashes/utils"
import {hexToBytes} from "@welshman/lib"
import {makeSecret} from "@welshman/signer"
import {preventDefault, downloadText} from "@lib/html"
import Link from "@lib/components/Link.svelte"
+78 -13
View File
@@ -1,15 +1,25 @@
import {mount} from "svelte"
import type {Writable} from "svelte/store"
import {get} from "svelte/store"
import type {StampedEvent} from "@welshman/util"
import {getTagValues, getListTags} from "@welshman/util"
import {sha256} from "@welshman/lib"
import {
getTagValues,
getTagValue,
encryptFile,
uploadBlob,
makeBlossomAuthEvent,
getListTags,
} from "@welshman/util"
import {Router} from "@welshman/router"
import {Nip01Signer} from "@welshman/signer"
import {signer, profileSearch, userBlossomServers} from "@welshman/app"
import type {FileAttributes} from "@welshman/editor"
import {Editor, MentionSuggestion, WelshmanExtension} from "@welshman/editor"
import {makeMentionNodeView} from "./MentionNodeView"
import ProfileSuggestion from "./ProfileSuggestion.svelte"
import {pushToast} from "@app/toast"
export const getUploadUrl = () => {
export const getBlossomServer = () => {
const userUrls = getTagValues("server", getListTags(userBlossomServers.get()))
for (const url of userUrls) {
@@ -19,12 +29,6 @@ export const getUploadUrl = () => {
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,
@@ -53,9 +57,6 @@ export const makeEditor = async ({
extensions: [
WelshmanExtension.configure({
submit,
sign: signWithAssert,
defaultUploadType: "blossom",
defaultUploadUrl: getUploadUrl(),
extensions: {
placeholder: {
config: {
@@ -69,13 +70,77 @@ export const makeEditor = async ({
},
fileUpload: {
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() {
uploading?.set(true)
},
onComplete() {
uploading?.set(false)
},
onUploadError(currentEditor, task) {
currentEditor.commands.removeFailedUploads()
pushToast({theme: "error", message: "Failed to upload file"})
uploading?.set(false)
},
},
},
nprofile: {
+1 -2
View File
@@ -1,6 +1,5 @@
import {hexToBytes, bytesToHex} from "@noble/hashes/utils"
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))
-3
View File
@@ -7,7 +7,6 @@
import {App} from "@capacitor/app"
import {dev} from "$app/environment"
import {goto} from "$app/navigation"
import {bytesToHex, hexToBytes} from "@noble/hashes/utils"
import {identity, memoize, sleep, defer, ago, WEEK, TaskQueue} from "@welshman/lib"
import type {TrustedEvent, StampedEvent} from "@welshman/util"
import {
@@ -80,8 +79,6 @@
Object.assign(window, {
get,
nip19,
bytesToHex,
hexToBytes,
...lib,
...welshmanSigner,
...util,
+1 -1
View File
@@ -1,6 +1,6 @@
<script lang="ts">
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 {pubkey, session, displayNip05, deriveProfile} from "@welshman/app"
import {slideAndFade} from "@lib/transition"