Add blossom support
This commit is contained in:
+7
-1
@@ -526,6 +526,12 @@ export declare const hexToBech32: (prefix: string, hex: string) => `${Lowercase<
|
|||||||
export declare const bech32ToHex: (b32: string) => string;
|
export declare const bech32ToHex: (b32: string) => string;
|
||||||
|
|
||||||
// Converts an array buffer to hex format
|
// Converts an array buffer to hex format
|
||||||
export declare const bufferToHex: (buffer: ArrayBuffer) => string;
|
export declare const bytesToHex: (buffer: ArrayBuffer) => string;
|
||||||
|
|
||||||
|
// Converts a hex string to a Uint8Array buffer
|
||||||
|
export declare const hexToBytes: (hex: string) => Uint8Array;
|
||||||
|
|
||||||
|
// Computes SHA-256 hash of binary data
|
||||||
|
export declare const sha256: (data: ArrayBuffer | Uint8Array) => Promise<string>;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
"@tiptap/suggestion": "^2.11.5",
|
"@tiptap/suggestion": "^2.11.5",
|
||||||
"@welshman/lib": "workspace:*",
|
"@welshman/lib": "workspace:*",
|
||||||
"@welshman/util": "workspace:*",
|
"@welshman/util": "workspace:*",
|
||||||
"nostr-editor": "github:cesardeazevedo/nostr-editor#98f579b93bd2b5a3344df42f769d2ea4c5d8cf55",
|
"nostr-editor": "github:cesardeazevedo/nostr-editor#82f37ab",
|
||||||
"nostr-tools": "^2.14.2",
|
"nostr-tools": "^2.14.2",
|
||||||
"tippy.js": "^6.3.7"
|
"tippy.js": "^6.3.7"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,11 +15,13 @@ import {Text} from "@tiptap/extension-text"
|
|||||||
import {Placeholder} from "@tiptap/extension-placeholder"
|
import {Placeholder} from "@tiptap/extension-placeholder"
|
||||||
import type {PlaceholderOptions} from "@tiptap/extension-placeholder"
|
import type {PlaceholderOptions} from "@tiptap/extension-placeholder"
|
||||||
import type {
|
import type {
|
||||||
|
UploadTask,
|
||||||
NostrOptions,
|
NostrOptions,
|
||||||
FileUploadOptions,
|
FileUploadOptions,
|
||||||
ImageOptions,
|
ImageOptions,
|
||||||
LinkOptions,
|
LinkOptions,
|
||||||
NSecRejectOptions,
|
NSecRejectOptions,
|
||||||
|
FileAttributes,
|
||||||
} from "nostr-editor"
|
} from "nostr-editor"
|
||||||
import {
|
import {
|
||||||
NostrExtension,
|
NostrExtension,
|
||||||
@@ -55,7 +57,10 @@ export type WelshmanExtensionOptions = {
|
|||||||
codeBlock?: ChildExtensionOptions<CodeBlockOptions>
|
codeBlock?: ChildExtensionOptions<CodeBlockOptions>
|
||||||
document?: false
|
document?: false
|
||||||
dropcursor?: ChildExtensionOptions<DropcursorOptions>
|
dropcursor?: ChildExtensionOptions<DropcursorOptions>
|
||||||
fileUpload?: ChildExtensionOptions<FileUploadOptions>
|
fileUpload?: {
|
||||||
|
extend?: Partial<any>
|
||||||
|
config?: Partial<FileUploadOptions> & Pick<FileUploadOptions, 'upload'>
|
||||||
|
}
|
||||||
gapcursor?: false
|
gapcursor?: false
|
||||||
history?: ChildExtensionOptions<HistoryOptions>
|
history?: ChildExtensionOptions<HistoryOptions>
|
||||||
image?: ChildExtensionOptions<ImageOptions>
|
image?: ChildExtensionOptions<ImageOptions>
|
||||||
@@ -74,9 +79,6 @@ export type WelshmanExtensionOptions = {
|
|||||||
|
|
||||||
export interface WelshmanOptions extends NostrOptions {
|
export interface WelshmanOptions extends NostrOptions {
|
||||||
submit?: () => void
|
submit?: () => void
|
||||||
sign?: (event: StampedEvent) => Promise<SignedEvent>
|
|
||||||
defaultUploadUrl?: string
|
|
||||||
defaultUploadType?: "nip96" | "blossom"
|
|
||||||
extensions?: WelshmanExtensionOptions
|
extensions?: WelshmanExtensionOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,14 +89,8 @@ export const WelshmanExtension = NostrExtension.extend<WelshmanOptions>({
|
|||||||
},
|
},
|
||||||
|
|
||||||
addExtensions() {
|
addExtensions() {
|
||||||
const {
|
const {submit} = this.options
|
||||||
sign,
|
|
||||||
submit,
|
|
||||||
defaultUploadUrl = "https://nostr.build",
|
|
||||||
defaultUploadType = "nip96",
|
|
||||||
} = this.options
|
|
||||||
|
|
||||||
if (!sign) throw new Error("sign is a required argument to WelshmanExtension")
|
|
||||||
if (!submit) throw new Error("submit is a required argument to WelshmanExtension")
|
if (!submit) throw new Error("submit is a required argument to WelshmanExtension")
|
||||||
|
|
||||||
const extensionOptions = deepMergeLeft(this.options.extensions || {}, {
|
const extensionOptions = deepMergeLeft(this.options.extensions || {}, {
|
||||||
@@ -121,8 +117,6 @@ export const WelshmanExtension = NostrExtension.extend<WelshmanOptions>({
|
|||||||
config: {
|
config: {
|
||||||
inline: true,
|
inline: true,
|
||||||
group: "inline",
|
group: "inline",
|
||||||
defaultUploadUrl,
|
|
||||||
defaultUploadType,
|
|
||||||
},
|
},
|
||||||
extend: {
|
extend: {
|
||||||
addNodeView: () => MediaNodeView,
|
addNodeView: () => MediaNodeView,
|
||||||
@@ -132,8 +126,6 @@ export const WelshmanExtension = NostrExtension.extend<WelshmanOptions>({
|
|||||||
config: {
|
config: {
|
||||||
inline: true,
|
inline: true,
|
||||||
group: "inline",
|
group: "inline",
|
||||||
defaultUploadUrl,
|
|
||||||
defaultUploadType,
|
|
||||||
},
|
},
|
||||||
extend: {
|
extend: {
|
||||||
addNodeView: () => MediaNodeView,
|
addNodeView: () => MediaNodeView,
|
||||||
@@ -169,7 +161,6 @@ export const WelshmanExtension = NostrExtension.extend<WelshmanOptions>({
|
|||||||
},
|
},
|
||||||
fileUpload: {
|
fileUpload: {
|
||||||
config: {
|
config: {
|
||||||
sign,
|
|
||||||
immediateUpload: true,
|
immediateUpload: true,
|
||||||
allowedMimeTypes: [
|
allowedMimeTypes: [
|
||||||
"image/jpeg",
|
"image/jpeg",
|
||||||
@@ -186,7 +177,7 @@ export const WelshmanExtension = NostrExtension.extend<WelshmanOptions>({
|
|||||||
|
|
||||||
const extensions: Extensions = []
|
const extensions: Extensions = []
|
||||||
|
|
||||||
const addExtension = (extension: AnyExtension, options?: ChildExtensionOptions | false) => {
|
const addExtension = (extension: AnyExtension, options?: any) => {
|
||||||
if (options === false) return
|
if (options === false) return
|
||||||
|
|
||||||
if (options?.extend) {
|
if (options?.extend) {
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ export * from "./nodeviews/index.js"
|
|||||||
export * from "./extensions/index.js"
|
export * from "./extensions/index.js"
|
||||||
export * from "./plugins/index.js"
|
export * from "./plugins/index.js"
|
||||||
export {Editor, NodeViewProps} from "@tiptap/core"
|
export {Editor, NodeViewProps} from "@tiptap/core"
|
||||||
export {UploadTask, BlossomOptions, uploadBlossom, encryptFile, decryptFile} from "nostr-editor"
|
export {UploadTask, FileAttributes} from "nostr-editor"
|
||||||
|
|||||||
@@ -1563,11 +1563,35 @@ export const bech32ToHex = (b32: string) =>
|
|||||||
utf8.encode(bech32.fromWords(bech32.decode(b32 as any, false).words))
|
utf8.encode(bech32.fromWords(bech32.decode(b32 as any, false).words))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts an array buffer to hex format
|
* Converts an array buffer or Uint8Array to hex format
|
||||||
* @param buffer - ArrayBuffer string to convert
|
* @param buffer - ArrayBuffer or Uint8Array to convert
|
||||||
* @returns Hex encoded string
|
* @returns Hex encoded string
|
||||||
*/
|
*/
|
||||||
export const bufferToHex = (buffer: ArrayBuffer) =>
|
export const bytesToHex = (buffer: ArrayBuffer | Uint8Array) => {
|
||||||
Array.from(new Uint8Array(buffer))
|
if (buffer instanceof ArrayBuffer) {
|
||||||
|
buffer = new Uint8Array(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(buffer)
|
||||||
.map(b => b.toString(16).padStart(2, "0"))
|
.map(b => b.toString(16).padStart(2, "0"))
|
||||||
.join("")
|
.join("")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a hex string to an array buffer
|
||||||
|
* @param hex - hex string
|
||||||
|
* @returns ArrayBuffer
|
||||||
|
*/
|
||||||
|
export const hexToBytes = (hex: string) =>
|
||||||
|
new Uint8Array(hex.match(/.{2}/g)!.map(hex => parseInt(hex, 16)))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes SHA-256 hash of binary data
|
||||||
|
* @param data - Binary data to hash
|
||||||
|
* @returns Promise resolving to hex-encoded hash string
|
||||||
|
*/
|
||||||
|
export const sha256 = async (data: ArrayBuffer | Uint8Array): Promise<string> => {
|
||||||
|
const hashBuffer = await crypto.subtle.digest("SHA-256", data)
|
||||||
|
const hashArray = Array.from(new Uint8Array(hashBuffer))
|
||||||
|
return hashArray.map(b => b.toString(16).padStart(2, "0")).join("")
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,8 +20,9 @@
|
|||||||
"prepublishOnly": "pnpm run build"
|
"prepublishOnly": "pnpm run build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@welshman/lib": "workspace:*",
|
|
||||||
"@types/ws": "^8.5.13",
|
"@types/ws": "^8.5.13",
|
||||||
|
"@welshman/lib": "workspace:*",
|
||||||
|
"js-base64": "^3.7.7",
|
||||||
"nostr-tools": "^2.14.2",
|
"nostr-tools": "^2.14.2",
|
||||||
"nostr-wasm": "^0.1.0"
|
"nostr-wasm": "^0.1.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,227 @@
|
|||||||
|
import {Base64} from "js-base64"
|
||||||
|
import {now, bytesToHex, hexToBytes} from "@welshman/lib"
|
||||||
|
import {BLOSSOM_AUTH} from "./Kinds.js"
|
||||||
|
import {makeEvent, SignedEvent} from "./Events.js"
|
||||||
|
|
||||||
|
export type BlossomAuthAction = "get" | "upload" | "list" | "delete"
|
||||||
|
|
||||||
|
export type BlossomAuthEventOpts = {
|
||||||
|
action: BlossomAuthAction
|
||||||
|
server: string
|
||||||
|
hashes?: string[]
|
||||||
|
expiration?: number
|
||||||
|
content?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BlossomServer = {
|
||||||
|
url: string
|
||||||
|
pubkey?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BlossomErrorResponse = {
|
||||||
|
message: string
|
||||||
|
reason?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const makeBlossomAuthEvent = ({
|
||||||
|
action,
|
||||||
|
server,
|
||||||
|
hashes = [],
|
||||||
|
expiration = now() + 60,
|
||||||
|
content = `Authorization for ${action} at ${server}`,
|
||||||
|
}: BlossomAuthEventOpts) => {
|
||||||
|
const tags: string[][] = [
|
||||||
|
["t", action],
|
||||||
|
["expiration", expiration.toString()],
|
||||||
|
]
|
||||||
|
|
||||||
|
if (server) {
|
||||||
|
tags.push(["u", server])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hashes) {
|
||||||
|
for (const hash of hashes) {
|
||||||
|
tags.push(["x", hash])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return makeEvent(BLOSSOM_AUTH, {content, tags})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createAuthorizationHeader = (event: SignedEvent): string => {
|
||||||
|
return `Nostr ${Base64.encode(JSON.stringify(event))}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const buildBlobUrl = (server: string, sha256: string, extension?: string): string => {
|
||||||
|
const url = new URL(server)
|
||||||
|
const filename = extension ? `${sha256}.${extension}` : sha256
|
||||||
|
return `${url.origin}/${filename}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const checkBlobExists = async (
|
||||||
|
server: string,
|
||||||
|
sha256: string,
|
||||||
|
options: {
|
||||||
|
authEvent?: SignedEvent
|
||||||
|
} = {},
|
||||||
|
): Promise<{exists: boolean; size?: number}> => {
|
||||||
|
const url = buildBlobUrl(server, sha256)
|
||||||
|
const headers: Record<string, string> = {}
|
||||||
|
|
||||||
|
if (options.authEvent) {
|
||||||
|
headers.Authorization = createAuthorizationHeader(options.authEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {method: "HEAD", headers})
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
const contentLength = response.headers.get("content-length")
|
||||||
|
return {
|
||||||
|
exists: true,
|
||||||
|
size: contentLength ? parseInt(contentLength, 10) : undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {exists: false}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to check blob existence: ${error}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getBlob = async (
|
||||||
|
server: string,
|
||||||
|
sha256: string,
|
||||||
|
options: {
|
||||||
|
authEvent?: SignedEvent
|
||||||
|
range?: {start: number; end?: number}
|
||||||
|
} = {},
|
||||||
|
) => {
|
||||||
|
const url = buildBlobUrl(server, sha256)
|
||||||
|
const headers: Record<string, string> = {}
|
||||||
|
|
||||||
|
if (options.authEvent) {
|
||||||
|
headers.Authorization = createAuthorizationHeader(options.authEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.range) {
|
||||||
|
const {end, start} = options.range
|
||||||
|
|
||||||
|
headers.Range = end !== undefined ? `bytes=${start}-${end}` : `bytes=${start}-`
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(url, {headers})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const uploadBlob = async (
|
||||||
|
server: string,
|
||||||
|
blob: Blob | ArrayBuffer,
|
||||||
|
options: {
|
||||||
|
authEvent?: SignedEvent
|
||||||
|
} = {},
|
||||||
|
) => {
|
||||||
|
const url = new URL(server)
|
||||||
|
const uploadUrl = `${url.origin}/upload`
|
||||||
|
const body = blob instanceof Blob ? blob : new Blob([blob])
|
||||||
|
const headers: Record<string, string> = {}
|
||||||
|
|
||||||
|
if (options.authEvent) {
|
||||||
|
headers.Authorization = createAuthorizationHeader(options.authEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(uploadUrl, {method: "PUT", headers, body})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteBlob = async (
|
||||||
|
server: string,
|
||||||
|
sha256: string,
|
||||||
|
options: {
|
||||||
|
authEvent?: SignedEvent
|
||||||
|
} = {},
|
||||||
|
) => {
|
||||||
|
const url = buildBlobUrl(server, sha256)
|
||||||
|
|
||||||
|
const headers: Record<string, string> = {}
|
||||||
|
|
||||||
|
if (options.authEvent) {
|
||||||
|
headers.Authorization = createAuthorizationHeader(options.authEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(url, {method: "DELETE", headers})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const listBlobs = async (
|
||||||
|
server: string,
|
||||||
|
pubkey: string,
|
||||||
|
options: {
|
||||||
|
authEvent?: SignedEvent
|
||||||
|
since?: number
|
||||||
|
until?: number
|
||||||
|
} = {},
|
||||||
|
) => {
|
||||||
|
const url = new URL(server)
|
||||||
|
const listUrl = `${url.origin}/list/${pubkey}`
|
||||||
|
|
||||||
|
const searchParams = new URLSearchParams()
|
||||||
|
if (options.since !== undefined) {
|
||||||
|
searchParams.append("since", options.since.toString())
|
||||||
|
}
|
||||||
|
if (options.until !== undefined) {
|
||||||
|
searchParams.append("until", options.until.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullUrl = searchParams.toString() ? `${listUrl}?${searchParams.toString()}` : listUrl
|
||||||
|
|
||||||
|
const headers: Record<string, string> = {}
|
||||||
|
|
||||||
|
if (options.authEvent) {
|
||||||
|
headers.Authorization = createAuthorizationHeader(options.authEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(fullUrl, {headers})
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EncryptedFile {
|
||||||
|
key: string
|
||||||
|
nonce: string
|
||||||
|
ciphertext: Uint8Array
|
||||||
|
algorithm: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function encryptFile(file: Blob): Promise<EncryptedFile> {
|
||||||
|
const key = await crypto.subtle.generateKey({name: "AES-GCM", length: 256}, true, [
|
||||||
|
"encrypt",
|
||||||
|
"decrypt",
|
||||||
|
])
|
||||||
|
const iv = crypto.getRandomValues(new Uint8Array(12))
|
||||||
|
const fileBuffer = await file.arrayBuffer()
|
||||||
|
const ciphertext = await crypto.subtle.encrypt({name: "AES-GCM", iv}, key, fileBuffer)
|
||||||
|
const keyBytes = await crypto.subtle.exportKey("raw", key)
|
||||||
|
|
||||||
|
return {
|
||||||
|
ciphertext: new Uint8Array(ciphertext),
|
||||||
|
key: bytesToHex(keyBytes),
|
||||||
|
nonce: bytesToHex(iv),
|
||||||
|
algorithm: "aes-gcm",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function decryptFile({
|
||||||
|
key,
|
||||||
|
nonce,
|
||||||
|
ciphertext,
|
||||||
|
algorithm,
|
||||||
|
}: EncryptedFile): Promise<Uint8Array> {
|
||||||
|
if (algorithm !== "aes-gcm") {
|
||||||
|
throw new Error(`Unknown algorithm ${algorithm}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyBytes = hexToBytes(key)
|
||||||
|
const iv = hexToBytes(nonce)
|
||||||
|
const cryptoKey = await crypto.subtle.importKey("raw", keyBytes, {name: "AES-GCM"}, false, [
|
||||||
|
"decrypt",
|
||||||
|
])
|
||||||
|
const decryptedBuffer = await crypto.subtle.decrypt({name: "AES-GCM", iv}, cryptoKey, ciphertext)
|
||||||
|
|
||||||
|
return new Uint8Array(decryptedBuffer)
|
||||||
|
}
|
||||||
@@ -1,12 +1,26 @@
|
|||||||
import {
|
|
||||||
isRegularKind,
|
|
||||||
isEphemeralKind,
|
|
||||||
isReplaceableKind as isPlainReplaceableKind,
|
|
||||||
isParameterizedReplaceableKind,
|
|
||||||
} from "nostr-tools/kinds"
|
|
||||||
import {between} from "@welshman/lib"
|
import {between} from "@welshman/lib"
|
||||||
|
|
||||||
export {isRegularKind, isEphemeralKind, isPlainReplaceableKind, isParameterizedReplaceableKind}
|
/** Events are **regular**, which means they're all expected to be stored by relays. */
|
||||||
|
export function isRegularKind(kind: number): boolean {
|
||||||
|
return (
|
||||||
|
(1000 <= kind && kind < 10000) || [1, 2, 4, 5, 6, 7, 8, 16, 40, 41, 42, 43, 44].includes(kind)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Events are **replaceable**, which means that, for each combination of `pubkey` and `kind`, only the latest event is expected to (SHOULD) be stored by relays, older versions are expected to be discarded. */
|
||||||
|
export function isPlainReplaceableKind(kind: number): boolean {
|
||||||
|
return [0, 3].includes(kind) || (10000 <= kind && kind < 20000)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Events are **ephemeral**, which means they are not expected to be stored by relays. */
|
||||||
|
export function isEphemeralKind(kind: number): boolean {
|
||||||
|
return 20000 <= kind && kind < 30000
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Events are **parameterized replaceable**, which means that, for each combination of `pubkey`, `kind` and the `d` tag, only the latest event is expected to be stored by relays, older versions are expected to be discarded. */
|
||||||
|
export function isParameterizedReplaceableKind(kind: number): boolean {
|
||||||
|
return 30000 <= kind && kind < 40000
|
||||||
|
}
|
||||||
|
|
||||||
export const isReplaceableKind = (kind: number) =>
|
export const isReplaceableKind = (kind: number) =>
|
||||||
isPlainReplaceableKind(kind) || isParameterizedReplaceableKind(kind)
|
isPlainReplaceableKind(kind) || isParameterizedReplaceableKind(kind)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from "./Address.js"
|
export * from "./Address.js"
|
||||||
|
export * from "./Blossom.js"
|
||||||
export * from "./Encryptable.js"
|
export * from "./Encryptable.js"
|
||||||
export * from "./Events.js"
|
export * from "./Events.js"
|
||||||
export * from "./Filters.js"
|
export * from "./Filters.js"
|
||||||
|
|||||||
Generated
+8
-5
@@ -204,8 +204,8 @@ importers:
|
|||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../util
|
version: link:../util
|
||||||
nostr-editor:
|
nostr-editor:
|
||||||
specifier: github:cesardeazevedo/nostr-editor#98f579b93bd2b5a3344df42f769d2ea4c5d8cf55
|
specifier: github:cesardeazevedo/nostr-editor#82f37ab
|
||||||
version: https://codeload.github.com/cesardeazevedo/nostr-editor/tar.gz/98f579b93bd2b5a3344df42f769d2ea4c5d8cf55(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/extension-image@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)))(@tiptap/extension-link@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)(linkifyjs@4.2.0)(nostr-tools@2.14.2(typescript@5.8.2))(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.0)(prosemirror-state@1.4.3)(tiptap-markdown@0.8.10(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)))
|
version: https://codeload.github.com/cesardeazevedo/nostr-editor/tar.gz/82f37ab(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/extension-image@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)))(@tiptap/extension-link@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)(linkifyjs@4.2.0)(nostr-tools@2.14.2(typescript@5.8.2))(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.0)(prosemirror-state@1.4.3)(tiptap-markdown@0.8.10(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)))
|
||||||
nostr-tools:
|
nostr-tools:
|
||||||
specifier: ^2.14.2
|
specifier: ^2.14.2
|
||||||
version: 2.14.2(typescript@5.8.2)
|
version: 2.14.2(typescript@5.8.2)
|
||||||
@@ -397,6 +397,9 @@ importers:
|
|||||||
'@welshman/lib':
|
'@welshman/lib':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../lib
|
version: link:../lib
|
||||||
|
js-base64:
|
||||||
|
specifier: ^3.7.7
|
||||||
|
version: 3.7.7
|
||||||
nostr-tools:
|
nostr-tools:
|
||||||
specifier: ^2.14.2
|
specifier: ^2.14.2
|
||||||
version: 2.14.2(typescript@5.8.2)
|
version: 2.14.2(typescript@5.8.2)
|
||||||
@@ -2023,8 +2026,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
nostr-editor@https://codeload.github.com/cesardeazevedo/nostr-editor/tar.gz/98f579b93bd2b5a3344df42f769d2ea4c5d8cf55:
|
nostr-editor@https://codeload.github.com/cesardeazevedo/nostr-editor/tar.gz/82f37ab:
|
||||||
resolution: {tarball: https://codeload.github.com/cesardeazevedo/nostr-editor/tar.gz/98f579b93bd2b5a3344df42f769d2ea4c5d8cf55}
|
resolution: {tarball: https://codeload.github.com/cesardeazevedo/nostr-editor/tar.gz/82f37ab}
|
||||||
version: 0.0.4-pre.17
|
version: 0.0.4-pre.17
|
||||||
engines: {node: '>=18.16.1'}
|
engines: {node: '>=18.16.1'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -4210,7 +4213,7 @@ snapshots:
|
|||||||
|
|
||||||
normalize-path@3.0.0: {}
|
normalize-path@3.0.0: {}
|
||||||
|
|
||||||
nostr-editor@https://codeload.github.com/cesardeazevedo/nostr-editor/tar.gz/98f579b93bd2b5a3344df42f769d2ea4c5d8cf55(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/extension-image@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)))(@tiptap/extension-link@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)(linkifyjs@4.2.0)(nostr-tools@2.14.2(typescript@5.8.2))(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.0)(prosemirror-state@1.4.3)(tiptap-markdown@0.8.10(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))):
|
nostr-editor@https://codeload.github.com/cesardeazevedo/nostr-editor/tar.gz/82f37ab(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/extension-image@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)))(@tiptap/extension-link@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)(linkifyjs@4.2.0)(nostr-tools@2.14.2(typescript@5.8.2))(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.0)(prosemirror-state@1.4.3)(tiptap-markdown@0.8.10(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 2.11.7(@tiptap/pm@2.11.7)
|
'@tiptap/core': 2.11.7(@tiptap/pm@2.11.7)
|
||||||
'@tiptap/extension-image': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))
|
'@tiptap/extension-image': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))
|
||||||
|
|||||||
Reference in New Issue
Block a user