mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-18 11:13:30 +00:00
update composer with image upload
This commit is contained in:
parent
17d2a8cb56
commit
20a8ce9cba
@ -37,6 +37,7 @@
|
|||||||
"destr": "^1.2.2",
|
"destr": "^1.2.2",
|
||||||
"framer-motion": "^10.13.0",
|
"framer-motion": "^10.13.0",
|
||||||
"get-urls": "^11.0.0",
|
"get-urls": "^11.0.0",
|
||||||
|
"html-to-text": "^9.0.5",
|
||||||
"immer": "^10.0.2",
|
"immer": "^10.0.2",
|
||||||
"light-bolt11-decoder": "^3.0.0",
|
"light-bolt11-decoder": "^3.0.0",
|
||||||
"nostr-fetch": "^0.12.1",
|
"nostr-fetch": "^0.12.1",
|
||||||
@ -55,6 +56,7 @@
|
|||||||
"tauri-plugin-autostart-api": "github:tauri-apps/tauri-plugin-autostart#v1",
|
"tauri-plugin-autostart-api": "github:tauri-apps/tauri-plugin-autostart#v1",
|
||||||
"tauri-plugin-sql-api": "github:tauri-apps/tauri-plugin-sql",
|
"tauri-plugin-sql-api": "github:tauri-apps/tauri-plugin-sql",
|
||||||
"tauri-plugin-stronghold-api": "github:tauri-apps/tauri-plugin-stronghold#v1",
|
"tauri-plugin-stronghold-api": "github:tauri-apps/tauri-plugin-stronghold#v1",
|
||||||
|
"tauri-plugin-upload-api": "github:tauri-apps/tauri-plugin-upload#v1",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
"zustand": "^4.3.9"
|
"zustand": "^4.3.9"
|
||||||
},
|
},
|
||||||
|
@ -64,6 +64,9 @@ dependencies:
|
|||||||
get-urls:
|
get-urls:
|
||||||
specifier: ^11.0.0
|
specifier: ^11.0.0
|
||||||
version: 11.0.0
|
version: 11.0.0
|
||||||
|
html-to-text:
|
||||||
|
specifier: ^9.0.5
|
||||||
|
version: 9.0.5
|
||||||
immer:
|
immer:
|
||||||
specifier: ^10.0.2
|
specifier: ^10.0.2
|
||||||
version: 10.0.2
|
version: 10.0.2
|
||||||
@ -118,6 +121,9 @@ dependencies:
|
|||||||
tauri-plugin-stronghold-api:
|
tauri-plugin-stronghold-api:
|
||||||
specifier: github:tauri-apps/tauri-plugin-stronghold#v1
|
specifier: github:tauri-apps/tauri-plugin-stronghold#v1
|
||||||
version: github.com/tauri-apps/tauri-plugin-stronghold/a2dbe1f937f2f2a1fd27d5a4a7a87ec7b7d1f5d4
|
version: github.com/tauri-apps/tauri-plugin-stronghold/a2dbe1f937f2f2a1fd27d5a4a7a87ec7b7d1f5d4
|
||||||
|
tauri-plugin-upload-api:
|
||||||
|
specifier: github:tauri-apps/tauri-plugin-upload#v1
|
||||||
|
version: github.com/tauri-apps/tauri-plugin-upload/6db7f623b4ba1cd6d25cf7dddb3ec8c442c349c8
|
||||||
tippy.js:
|
tippy.js:
|
||||||
specifier: ^6.3.7
|
specifier: ^6.3.7
|
||||||
version: 6.3.7
|
version: 6.3.7
|
||||||
@ -1702,6 +1708,13 @@ packages:
|
|||||||
'@scure/base': 1.1.1
|
'@scure/base': 1.1.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@selderee/plugin-htmlparser2@0.11.0:
|
||||||
|
resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
|
||||||
|
dependencies:
|
||||||
|
domhandler: 5.0.3
|
||||||
|
selderee: 0.11.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@sinclair/typebox@0.27.8:
|
/@sinclair/typebox@0.27.8:
|
||||||
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
|
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -4161,6 +4174,17 @@ packages:
|
|||||||
lru-cache: 6.0.0
|
lru-cache: 6.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/html-to-text@9.0.5:
|
||||||
|
resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
dependencies:
|
||||||
|
'@selderee/plugin-htmlparser2': 0.11.0
|
||||||
|
deepmerge: 4.3.1
|
||||||
|
dom-serializer: 2.0.0
|
||||||
|
htmlparser2: 8.0.2
|
||||||
|
selderee: 0.11.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/htmlparser2@8.0.2:
|
/htmlparser2@8.0.2:
|
||||||
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
|
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -4571,6 +4595,10 @@ packages:
|
|||||||
language-subtag-registry: 0.3.22
|
language-subtag-registry: 0.3.22
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/leac@0.6.0:
|
||||||
|
resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/levn@0.4.1:
|
/levn@0.4.1:
|
||||||
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
@ -5685,6 +5713,13 @@ packages:
|
|||||||
entities: 4.5.0
|
entities: 4.5.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/parseley@0.12.1:
|
||||||
|
resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==}
|
||||||
|
dependencies:
|
||||||
|
leac: 0.6.0
|
||||||
|
peberminta: 0.9.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/path-exists@4.0.0:
|
/path-exists@4.0.0:
|
||||||
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -5729,6 +5764,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
/peberminta@0.9.0:
|
||||||
|
resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/picocolors@1.0.0:
|
/picocolors@1.0.0:
|
||||||
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
||||||
|
|
||||||
@ -6499,6 +6538,12 @@ packages:
|
|||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/selderee@0.11.0:
|
||||||
|
resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==}
|
||||||
|
dependencies:
|
||||||
|
parseley: 0.12.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/semver@5.7.2:
|
/semver@5.7.2:
|
||||||
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
|
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -7505,3 +7550,11 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@tauri-apps/api': 1.4.0
|
'@tauri-apps/api': 1.4.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
github.com/tauri-apps/tauri-plugin-upload/6db7f623b4ba1cd6d25cf7dddb3ec8c442c349c8:
|
||||||
|
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-upload/tar.gz/6db7f623b4ba1cd6d25cf7dddb3ec8c442c349c8}
|
||||||
|
name: tauri-plugin-upload-api
|
||||||
|
version: 0.0.0
|
||||||
|
dependencies:
|
||||||
|
'@tauri-apps/api': 1.4.0
|
||||||
|
dev: false
|
||||||
|
37
src-tauri/Cargo.lock
generated
37
src-tauri/Cargo.lock
generated
@ -2546,6 +2546,7 @@ dependencies = [
|
|||||||
"tauri-plugin-single-instance",
|
"tauri-plugin-single-instance",
|
||||||
"tauri-plugin-sql",
|
"tauri-plugin-sql",
|
||||||
"tauri-plugin-stronghold",
|
"tauri-plugin-stronghold",
|
||||||
|
"tauri-plugin-upload",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3594,6 +3595,17 @@ dependencies = [
|
|||||||
"num_cpus",
|
"num_cpus",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "read-progress-stream"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6435842fc2fea44b528719eb8c32203bbc1bb2f5b619fbe0c0a3d8350fd8d2a8"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
@ -4912,7 +4924,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-autostart"
|
name = "tauri-plugin-autostart"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#51f20b438e42050cdbfd6c6dc72dbc985a31bbc1"
|
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#0863f800b81925884a6a37c042f3d92d433d4a37"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"auto-launch",
|
"auto-launch",
|
||||||
"log",
|
"log",
|
||||||
@ -4925,7 +4937,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-single-instance"
|
name = "tauri-plugin-single-instance"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#51f20b438e42050cdbfd6c6dc72dbc985a31bbc1"
|
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#0863f800b81925884a6a37c042f3d92d433d4a37"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
@ -4939,7 +4951,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-sql"
|
name = "tauri-plugin-sql"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#51f20b438e42050cdbfd6c6dc72dbc985a31bbc1"
|
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#0863f800b81925884a6a37c042f3d92d433d4a37"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"log",
|
"log",
|
||||||
@ -4955,7 +4967,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-stronghold"
|
name = "tauri-plugin-stronghold"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#51f20b438e42050cdbfd6c6dc72dbc985a31bbc1"
|
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#0863f800b81925884a6a37c042f3d92d433d4a37"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hex",
|
"hex",
|
||||||
"iota-crypto 0.23.0",
|
"iota-crypto 0.23.0",
|
||||||
@ -4968,6 +4980,23 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-upload"
|
||||||
|
version = "0.0.0"
|
||||||
|
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#0863f800b81925884a6a37c042f3d92d433d4a37"
|
||||||
|
dependencies = [
|
||||||
|
"futures-util",
|
||||||
|
"log",
|
||||||
|
"read-progress-stream",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tauri",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
|
@ -20,6 +20,7 @@ tauri = { version = "1.2", features = [ "fs-write-file", "window-create", "path-
|
|||||||
tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||||
tauri-plugin-autostart = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
tauri-plugin-autostart = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||||
tauri-plugin-stronghold = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
tauri-plugin-stronghold = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||||
|
tauri-plugin-upload = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||||
sqlx-cli = {version = "0.7.0", default-features = false, features = ["sqlite"] }
|
sqlx-cli = {version = "0.7.0", default-features = false, features = ["sqlite"] }
|
||||||
rust-argon2 = "1.0"
|
rust-argon2 = "1.0"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
@ -150,6 +150,7 @@ fn main() {
|
|||||||
.emit_all("single-instance", Payload { args: argv, cwd })
|
.emit_all("single-instance", Payload { args: argv, cwd })
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}))
|
}))
|
||||||
|
.plugin(tauri_plugin_upload::init())
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ export function UnlockScreen() {
|
|||||||
<input
|
<input
|
||||||
{...register('password', { required: true })}
|
{...register('password', { required: true })}
|
||||||
type={passwordInput}
|
type={passwordInput}
|
||||||
className="relative w-full rounded-lg bg-zinc-800 py-3 pl-3.5 pr-11 text-zinc-100 !outline-none placeholder:text-zinc-400"
|
className="relative w-full rounded-lg bg-zinc-800 py-3 text-center text-zinc-100 !outline-none placeholder:text-zinc-400"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -15,15 +15,19 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.markdown {
|
.markdown {
|
||||||
@apply prose prose-zinc max-w-none select-text break-words dark:prose-invert prose-p:mb-2 prose-p:mt-0 prose-p:last:mb-0 prose-a:break-all prose-a:font-normal prose-a:leading-tight prose-a:text-fuchsia-400 hover:prose-a:text-fuchsia-500 prose-blockquote:m-0 prose-ol:m-0 prose-ol:mb-1 prose-ul:mb-1 prose-li:leading-tight prose-hr:mx-0 prose-hr:my-2;
|
@apply prose prose-zinc max-w-none select-text break-words dark:prose-invert prose-p:mb-2 prose-p:mt-0 prose-p:last:mb-0 prose-a:break-all prose-a:font-normal prose-a:leading-tight prose-a:text-fuchsia-400 hover:prose-a:text-fuchsia-500 prose-blockquote:m-0 prose-ol:m-0 prose-ol:mb-1 prose-ul:mb-1 prose-li:leading-tight prose-img:mt-1.5 prose-img:mb-1 prose-hr:mx-0 prose-hr:my-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ProseMirror p.is-editor-empty:first-child::before {
|
.ProseMirror p.is-empty::before {
|
||||||
|
@apply text-zinc-400;
|
||||||
content: attr(data-placeholder);
|
content: attr(data-placeholder);
|
||||||
float: left;
|
float: left;
|
||||||
height: 0;
|
height: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
@apply text-zinc-400;
|
}
|
||||||
|
|
||||||
|
.ProseMirror img.ProseMirror-selectednode {
|
||||||
|
@apply outline-fuchsia-500;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* For Webkit-based browsers (Chrome, Safari and Opera) */
|
/* For Webkit-based browsers (Chrome, Safari and Opera) */
|
||||||
|
@ -459,6 +459,7 @@ export async function getAllMetadata() {
|
|||||||
return {
|
return {
|
||||||
pubkey: el.pubkey,
|
pubkey: el.pubkey,
|
||||||
ident: profile.name || profile.display_name || profile.username,
|
ident: profile.name || profile.display_name || profile.username,
|
||||||
|
picture: profile.picture || profile.image,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return users;
|
return users;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
import { NDKProvider } from '@libs/ndk/provider';
|
import { NDKProvider } from '@libs/ndk/provider';
|
||||||
@ -25,6 +24,5 @@ root.render(
|
|||||||
<NDKProvider>
|
<NDKProvider>
|
||||||
<App />
|
<App />
|
||||||
</NDKProvider>
|
</NDKProvider>
|
||||||
<ReactQueryDevtools initialIsOpen={false} position="top-right" />
|
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
|
@ -1,54 +1,80 @@
|
|||||||
|
import { TauriEvent } from '@tauri-apps/api/event';
|
||||||
|
import { getCurrent } from '@tauri-apps/api/window';
|
||||||
|
import Image from '@tiptap/extension-image';
|
||||||
import Mention from '@tiptap/extension-mention';
|
import Mention from '@tiptap/extension-mention';
|
||||||
import Placeholder from '@tiptap/extension-placeholder';
|
import Placeholder from '@tiptap/extension-placeholder';
|
||||||
import { EditorContent, useEditor } from '@tiptap/react';
|
import { EditorContent, useEditor } from '@tiptap/react';
|
||||||
import StarterKit from '@tiptap/starter-kit';
|
import StarterKit from '@tiptap/starter-kit';
|
||||||
|
import { convert } from 'html-to-text';
|
||||||
import { nip19 } from 'nostr-tools';
|
import { nip19 } from 'nostr-tools';
|
||||||
import { useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
import { Button } from '@shared/button';
|
import { Button } from '@shared/button';
|
||||||
import { Suggestion } from '@shared/composer';
|
import { Suggestion } from '@shared/composer';
|
||||||
import { CancelIcon, LoaderIcon } from '@shared/icons';
|
import { CancelIcon, LoaderIcon, PlusCircleIcon } from '@shared/icons';
|
||||||
import { MentionNote } from '@shared/notes';
|
import { MentionNote } from '@shared/notes';
|
||||||
|
|
||||||
import { useComposer } from '@stores/composer';
|
import { useComposer } from '@stores/composer';
|
||||||
import { FULL_RELAYS } from '@stores/constants';
|
import { FULL_RELAYS } from '@stores/constants';
|
||||||
|
|
||||||
import { usePublish } from '@utils/hooks/usePublish';
|
import { usePublish } from '@utils/hooks/usePublish';
|
||||||
|
import { useImageUploader } from '@utils/hooks/useUploader';
|
||||||
|
import { sendNativeNotification } from '@utils/notification';
|
||||||
|
|
||||||
export function Composer() {
|
export function Composer() {
|
||||||
const [loading, setLoading] = useState(false);
|
const [status, setStatus] = useState<null | 'loading' | 'done'>(null);
|
||||||
const [reply, clearReply, toggle] = useComposer((state) => [
|
const [reply, clearReply, toggleModal] = useComposer((state) => [
|
||||||
state.reply,
|
state.reply,
|
||||||
state.clearReply,
|
state.clearReply,
|
||||||
state.toggleModal,
|
state.toggleModal,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const publish = usePublish();
|
|
||||||
const editor = useEditor({
|
const editor = useEditor({
|
||||||
extensions: [
|
extensions: [
|
||||||
StarterKit,
|
StarterKit.configure({
|
||||||
Placeholder.configure({ placeholder: "What's on your mind?" }),
|
dropcursor: {
|
||||||
|
color: '#fff',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Placeholder.configure({ placeholder: 'Type something...' }),
|
||||||
Mention.configure({
|
Mention.configure({
|
||||||
suggestion: Suggestion,
|
suggestion: Suggestion,
|
||||||
renderLabel({ node }) {
|
renderLabel({ node }) {
|
||||||
return `nostr:${nip19.npubEncode(node.attrs.id.pubkey)} `;
|
return `nostr:${nip19.npubEncode(node.attrs.id.pubkey)} `;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
Image.configure({
|
||||||
|
HTMLAttributes: {
|
||||||
|
class:
|
||||||
|
'rounded-lg w-2/3 h-auto border border-zinc-800 outline outline-2 outline-offset-0 outline-zinc-700 ml-1',
|
||||||
|
},
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
content: '',
|
content: '',
|
||||||
editorProps: {
|
editorProps: {
|
||||||
attributes: {
|
attributes: {
|
||||||
class: twMerge(
|
class: twMerge(
|
||||||
'markdown break-all max-h-[500px] overflow-y-auto outline-none',
|
'scrollbar-hide markdown break-all max-h-[500px] overflow-y-auto outline-none pr-2',
|
||||||
`${reply.id ? '!min-h-42' : '!min-h-[86px]'}`
|
`${reply.id ? '!min-h-42' : '!min-h-[100px]'}`
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const upload = useImageUploader();
|
||||||
|
const publish = usePublish();
|
||||||
|
|
||||||
|
const uploadImage = async (file?: string) => {
|
||||||
|
const image = await upload(file);
|
||||||
|
if (image.url) {
|
||||||
|
editor.commands.setImage({ src: image.url });
|
||||||
|
editor.commands.createParagraphNear();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
setLoading(true);
|
setStatus('loading');
|
||||||
try {
|
try {
|
||||||
let tags: string[][] = [];
|
let tags: string[][] = [];
|
||||||
|
|
||||||
@ -65,33 +91,55 @@ export function Composer() {
|
|||||||
['p', reply.pubkey],
|
['p', reply.pubkey],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
tags = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// get plaintext content
|
// get plaintext content
|
||||||
const serializedContent = editor.getText();
|
const html = editor.getHTML();
|
||||||
|
const serializedContent = convert(html, {
|
||||||
|
selectors: [
|
||||||
|
{ selector: 'a', options: { linkBrackets: false } },
|
||||||
|
{ selector: 'img', options: { linkBrackets: false } },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
// publish message
|
// publish message
|
||||||
await publish({ content: serializedContent, kind: 1, tags });
|
await publish({ content: serializedContent, kind: 1, tags });
|
||||||
|
|
||||||
// close modal
|
// send native notifiation
|
||||||
setLoading(false);
|
await sendNativeNotification('Publish post successfully');
|
||||||
toggle(false);
|
|
||||||
|
// update state
|
||||||
|
setStatus('done');
|
||||||
} catch {
|
} catch {
|
||||||
setLoading(false);
|
setStatus(null);
|
||||||
console.log('failed to publish');
|
console.log('failed to publish');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getCurrent().listen(TauriEvent.WINDOW_FILE_DROP, (event) => {
|
||||||
|
const filepath: string = event.payload[0];
|
||||||
|
if (filepath.match(/\.(jpg|jpeg|png|gif)$/gi)) {
|
||||||
|
// open modal
|
||||||
|
toggleModal(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col px-4 pb-4">
|
<div className="flex h-full flex-col px-4 pb-4">
|
||||||
<div className="flex h-full w-full gap-2">
|
<div className="flex h-full w-full gap-3">
|
||||||
<div className="flex w-8 shrink-0 items-center justify-center">
|
<div className="flex w-8 shrink-0 items-center justify-center">
|
||||||
<div className="h-full w-[2px] bg-zinc-800" />
|
<div className="h-full w-[2px] bg-zinc-800" />
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<EditorContent editor={editor} />
|
<EditorContent
|
||||||
|
editor={editor}
|
||||||
|
spellCheck="false"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
/>
|
||||||
{reply.id && (
|
{reply.id && (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<MentionNote id={reply.id} />
|
<MentionNote id={reply.id} />
|
||||||
@ -107,9 +155,15 @@ export function Composer() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 flex items-center justify-between">
|
<div className="mt-4 flex items-center justify-between">
|
||||||
<div />
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => uploadImage()}
|
||||||
|
className="inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-zinc-800"
|
||||||
|
>
|
||||||
|
<PlusCircleIcon className="h-5 w-5 text-zinc-500" />
|
||||||
|
</button>
|
||||||
<Button onClick={() => submit()} preset="publish">
|
<Button onClick={() => submit()} preset="publish">
|
||||||
{loading ? (
|
{status === 'loading' ? (
|
||||||
<LoaderIcon className="h-4 w-4 animate-spin text-zinc-100" />
|
<LoaderIcon className="h-4 w-4 animate-spin text-zinc-100" />
|
||||||
) : (
|
) : (
|
||||||
'Publish'
|
'Publish'
|
||||||
|
@ -23,7 +23,7 @@ export function MentionItem({ profile }: { profile: Profile }) {
|
|||||||
)}
|
)}
|
||||||
</h5>
|
</h5>
|
||||||
<span className="text-sm leading-none text-zinc-400">
|
<span className="text-sm leading-none text-zinc-400">
|
||||||
{profile.nip05 || profile.username || displayNpub(profile.pubkey, 16)}
|
{displayNpub(profile.pubkey, 16)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,8 +8,8 @@ export function ComposerUser({ pubkey }: { pubkey: string }) {
|
|||||||
const { user } = useProfile(pubkey);
|
const { user } = useProfile(pubkey);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-3">
|
||||||
<div className="h-8 w-8 shrink-0 overflow-hidden rounded bg-zinc-900">
|
<div className="h-8 w-8 shrink-0 overflow-hidden rounded-md bg-zinc-900">
|
||||||
<Image
|
<Image
|
||||||
src={user?.picture || user?.image}
|
src={user?.picture || user?.image}
|
||||||
fallback={DEFAULT_AVATAR}
|
fallback={DEFAULT_AVATAR}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { readBinaryFile } from '@tauri-apps/api/fs';
|
import { readBinaryFile } from '@tauri-apps/api/fs';
|
||||||
|
|
||||||
export async function createBlobFromFile(path: string): Promise<Blob> {
|
export async function createBlobFromFile(path: string): Promise<Uint8Array> {
|
||||||
const file = await readBinaryFile(path);
|
const file = await readBinaryFile(path);
|
||||||
return new Blob([file]);
|
return file;
|
||||||
}
|
}
|
||||||
|
82
src/utils/hooks/useUploader.tsx
Normal file
82
src/utils/hooks/useUploader.tsx
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { open } from '@tauri-apps/api/dialog';
|
||||||
|
import { Body, fetch } from '@tauri-apps/api/http';
|
||||||
|
|
||||||
|
import { createBlobFromFile } from '@utils/createBlobFromFile';
|
||||||
|
|
||||||
|
interface UploadResponse {
|
||||||
|
fileID?: string;
|
||||||
|
fileName?: string;
|
||||||
|
imageUrl?: string;
|
||||||
|
lightningDestination?: string;
|
||||||
|
lightningPaymentLink?: string;
|
||||||
|
message?: string;
|
||||||
|
route?: string;
|
||||||
|
status: number;
|
||||||
|
success: boolean;
|
||||||
|
url?: string;
|
||||||
|
data?: {
|
||||||
|
url?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useImageUploader() {
|
||||||
|
const upload = async (file: null | string, nip94?: boolean) => {
|
||||||
|
let filepath = file;
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
const selected = await open({
|
||||||
|
multiple: false,
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
name: 'Image',
|
||||||
|
extensions: ['png', 'jpeg', 'jpg', 'gif'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
if (Array.isArray(selected)) {
|
||||||
|
// user selected multiple files
|
||||||
|
} else if (selected === null) {
|
||||||
|
// user cancelled the selection
|
||||||
|
} else {
|
||||||
|
filepath = selected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const filename = filepath.split('/').pop();
|
||||||
|
const filetype = 'image/' + filename.split('.').pop();
|
||||||
|
|
||||||
|
const blob = await createBlobFromFile(filepath);
|
||||||
|
const res = await fetch('https://nostrimg.com/api/upload', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
body: Body.form({
|
||||||
|
keys: filename,
|
||||||
|
image: {
|
||||||
|
file: blob,
|
||||||
|
mime: filetype,
|
||||||
|
fileName: filename,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
const data = res.data as UploadResponse;
|
||||||
|
if (typeof data?.imageUrl === 'string' && data.success) {
|
||||||
|
if (nip94) {
|
||||||
|
console.log('todo');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
url: new URL(data.imageUrl).toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: 'Upload failed',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return upload;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user