Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f0a472e5fe | |||
| a40bf3a2c2 | |||
|
dbb407606b |
|||
| f763af13be |
38 changed files with 1513 additions and 2918 deletions
187
apps/tauri/src-tauri/Cargo.lock
generated
187
apps/tauri/src-tauri/Cargo.lock
generated
|
|
@ -924,7 +924,23 @@ dependencies = [
|
||||||
"cpufeatures 0.2.17",
|
"cpufeatures 0.2.17",
|
||||||
"curve25519-dalek-derive",
|
"curve25519-dalek-derive",
|
||||||
"digest 0.10.7",
|
"digest 0.10.7",
|
||||||
"fiat-crypto",
|
"fiat-crypto 0.2.9",
|
||||||
|
"rustc_version",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "curve25519-dalek"
|
||||||
|
version = "5.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures 0.3.0",
|
||||||
|
"curve25519-dalek-derive",
|
||||||
|
"digest 0.11.3",
|
||||||
|
"fiat-crypto 0.3.0",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
"subtle",
|
"subtle",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
|
|
@ -999,7 +1015,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"const-oid 0.9.6",
|
"const-oid 0.9.6",
|
||||||
"pem-rfc7468",
|
"pem-rfc7468 0.7.0",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1010,6 +1026,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d"
|
checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"const-oid 0.10.2",
|
"const-oid 0.10.2",
|
||||||
|
"pem-rfc7468 1.0.0",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1246,20 +1263,45 @@ dependencies = [
|
||||||
"signature 2.2.0",
|
"signature 2.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ed25519"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a"
|
||||||
|
dependencies = [
|
||||||
|
"pkcs8 0.11.0",
|
||||||
|
"signature 3.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ed25519-dalek"
|
name = "ed25519-dalek"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
|
checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"curve25519-dalek",
|
"curve25519-dalek 4.1.3",
|
||||||
"ed25519",
|
"ed25519 2.2.3",
|
||||||
"serde",
|
"serde",
|
||||||
"sha2 0.10.9",
|
"sha2 0.10.9",
|
||||||
"subtle",
|
"subtle",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ed25519-dalek"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de"
|
||||||
|
dependencies = [
|
||||||
|
"curve25519-dalek 5.0.0",
|
||||||
|
"ed25519 3.0.0",
|
||||||
|
"serde",
|
||||||
|
"sha2 0.11.0",
|
||||||
|
"signature 3.0.0",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "embed-resource"
|
name = "embed-resource"
|
||||||
version = "3.0.11"
|
version = "3.0.11"
|
||||||
|
|
@ -1406,6 +1448,12 @@ version = "0.2.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fiat-crypto"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "field-offset"
|
name = "field-offset"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
|
|
@ -2822,12 +2870,12 @@ version = "0.2.0"
|
||||||
source = "git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22#7182272e3edb0079d7b74b45b2f62a298d2fee22"
|
source = "git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22#7182272e3edb0079d7b74b45b2f62a298d2fee22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"mtp-client",
|
"mtp-client",
|
||||||
"mtp-codec",
|
"mtp-codec 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||||
"mtp-common",
|
"mtp-common 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||||
"mtp-crypto",
|
"mtp-crypto 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||||
"mtp-host",
|
"mtp-host",
|
||||||
"mtp-transport",
|
"mtp-transport 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||||
"mtp-type-map",
|
"mtp-type-map 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||||
"mtp-webserver",
|
"mtp-webserver",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2836,10 +2884,10 @@ name = "mtp-client"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22#7182272e3edb0079d7b74b45b2f62a298d2fee22"
|
source = "git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22#7182272e3edb0079d7b74b45b2f62a298d2fee22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"mtp-codec",
|
"mtp-codec 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||||
"mtp-common",
|
"mtp-common 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||||
"mtp-crypto",
|
"mtp-crypto 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||||
"mtp-transport",
|
"mtp-transport 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||||
"rand 0.10.2",
|
"rand 0.10.2",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
@ -2851,9 +2899,21 @@ source = "git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.23.1",
|
"base64 0.23.1",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"mtp-common",
|
"mtp-common 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||||
"mtp-crypto",
|
"mtp-crypto 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||||
"mtp-type-map",
|
"mtp-type-map 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||||
|
"rand 0.10.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mtp-codec"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "git+https://git.methanium.net/methanium/mtp.git?rev=b067614a684eb1856bc5db7b3fd82148c036ce6b#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.23.1",
|
||||||
|
"byteorder",
|
||||||
|
"mtp-common 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=b067614a684eb1856bc5db7b3fd82148c036ce6b)",
|
||||||
|
"mtp-type-map 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=b067614a684eb1856bc5db7b3fd82148c036ce6b)",
|
||||||
"rand 0.10.2",
|
"rand 0.10.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2868,6 +2928,17 @@ dependencies = [
|
||||||
"wtransport",
|
"wtransport",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mtp-common"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "git+https://git.methanium.net/methanium/mtp.git?rev=b067614a684eb1856bc5db7b3fd82148c036ce6b#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||||
|
dependencies = [
|
||||||
|
"quinn",
|
||||||
|
"rustls",
|
||||||
|
"thiserror 2.0.19",
|
||||||
|
"wtransport",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mtp-crypto"
|
name = "mtp-crypto"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
@ -2875,7 +2946,7 @@ source = "git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.23.1",
|
"base64 0.23.1",
|
||||||
"chacha20poly1305",
|
"chacha20poly1305",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek 2.2.0",
|
||||||
"getrandom 0.4.3",
|
"getrandom 0.4.3",
|
||||||
"hkdf",
|
"hkdf",
|
||||||
"ml-dsa",
|
"ml-dsa",
|
||||||
|
|
@ -2890,15 +2961,35 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mtp-crypto"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "git+https://git.methanium.net/methanium/mtp.git?rev=b067614a684eb1856bc5db7b3fd82148c036ce6b#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.23.1",
|
||||||
|
"chacha20poly1305",
|
||||||
|
"ed25519-dalek 3.0.0",
|
||||||
|
"getrandom 0.4.3",
|
||||||
|
"hkdf",
|
||||||
|
"ml-dsa",
|
||||||
|
"rand 0.10.2",
|
||||||
|
"rand_core 0.10.1",
|
||||||
|
"rustls",
|
||||||
|
"sha2 0.11.0",
|
||||||
|
"thiserror 2.0.19",
|
||||||
|
"tokio",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mtp-host"
|
name = "mtp-host"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22#7182272e3edb0079d7b74b45b2f62a298d2fee22"
|
source = "git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22#7182272e3edb0079d7b74b45b2f62a298d2fee22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"mtp-codec",
|
"mtp-codec 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||||
"mtp-common",
|
"mtp-common 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||||
"mtp-crypto",
|
"mtp-crypto 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||||
"mtp-transport",
|
"mtp-transport 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||||
"rand 0.10.2",
|
"rand 0.10.2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
@ -2911,9 +3002,27 @@ version = "0.2.0"
|
||||||
source = "git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22#7182272e3edb0079d7b74b45b2f62a298d2fee22"
|
source = "git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22#7182272e3edb0079d7b74b45b2f62a298d2fee22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"mtp-codec",
|
"mtp-codec 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||||
"mtp-common",
|
"mtp-common 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||||
"mtp-crypto",
|
"mtp-crypto 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||||
|
"rcgen",
|
||||||
|
"rustls",
|
||||||
|
"rustls-native-certs",
|
||||||
|
"sha2 0.11.0",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
"wtransport",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mtp-transport"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "git+https://git.methanium.net/methanium/mtp.git?rev=b067614a684eb1856bc5db7b3fd82148c036ce6b#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"mtp-codec 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=b067614a684eb1856bc5db7b3fd82148c036ce6b)",
|
||||||
|
"mtp-common 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=b067614a684eb1856bc5db7b3fd82148c036ce6b)",
|
||||||
|
"mtp-crypto 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=b067614a684eb1856bc5db7b3fd82148c036ce6b)",
|
||||||
"rcgen",
|
"rcgen",
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-native-certs",
|
"rustls-native-certs",
|
||||||
|
|
@ -2932,6 +3041,15 @@ dependencies = [
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mtp-type-map"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "git+https://git.methanium.net/methanium/mtp.git?rev=b067614a684eb1856bc5db7b3fd82148c036ce6b#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_yaml",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mtp-webserver"
|
name = "mtp-webserver"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
@ -2946,11 +3064,11 @@ dependencies = [
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper",
|
"hyper",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"mtp-codec",
|
"mtp-codec 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||||
"mtp-common",
|
"mtp-common 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||||
"mtp-crypto",
|
"mtp-crypto 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||||
"mtp-host",
|
"mtp-host",
|
||||||
"mtp-transport",
|
"mtp-transport 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||||
"quinn",
|
"quinn",
|
||||||
"rand 0.10.2",
|
"rand 0.10.2",
|
||||||
"rustls",
|
"rustls",
|
||||||
|
|
@ -3463,6 +3581,15 @@ dependencies = [
|
||||||
"base64ct",
|
"base64ct",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pem-rfc7468"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
|
|
@ -5375,7 +5502,7 @@ dependencies = [
|
||||||
"base64 0.23.1",
|
"base64 0.23.1",
|
||||||
"jni 0.22.4",
|
"jni 0.22.4",
|
||||||
"mtp",
|
"mtp",
|
||||||
"mtp-transport",
|
"mtp-transport 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=b067614a684eb1856bc5db7b3fd82148c036ce6b)",
|
||||||
"reqwest 0.12.28",
|
"reqwest 0.12.28",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
@ -6701,7 +6828,7 @@ version = "2.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
|
checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"curve25519-dalek",
|
"curve25519-dalek 4.1.3",
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
"serde",
|
"serde",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ base64 = "0.23"
|
||||||
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
|
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
|
||||||
tokio = { version = "1", features = ["rt-multi-thread", "sync", "time"] }
|
tokio = { version = "1", features = ["rt-multi-thread", "sync", "time"] }
|
||||||
mtp = { git = "https://git.methanium.net/methanium/mtp.git", rev = "7182272e3edb0079d7b74b45b2f62a298d2fee22", features = ["client", "crypto"] }
|
mtp = { git = "https://git.methanium.net/methanium/mtp.git", rev = "7182272e3edb0079d7b74b45b2f62a298d2fee22", features = ["client", "crypto"] }
|
||||||
mtp-transport = { git = "https://git.methanium.net/methanium/mtp.git", rev = "7182272e3edb0079d7b74b45b2f62a298d2fee22" }
|
mtp-transport = { git = "https://git.methanium.net/methanium/mtp.git", rev = "b067614a684eb1856bc5db7b3fd82148c036ce6b" }
|
||||||
webpki-root-certs = "1"
|
webpki-root-certs = "1"
|
||||||
tauri-plugin-deep-link = "2"
|
tauri-plugin-deep-link = "2"
|
||||||
tauri-plugin-notification = "2"
|
tauri-plugin-notification = "2"
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@
|
||||||
"@tensamin/chat": "workspace:*",
|
"@tensamin/chat": "workspace:*",
|
||||||
"@tensamin/crypto": "workspace:*",
|
"@tensamin/crypto": "workspace:*",
|
||||||
"@tensamin/hotkeys": "workspace:*",
|
"@tensamin/hotkeys": "workspace:*",
|
||||||
"@tensamin/markdown": "workspace:*",
|
|
||||||
"@tensamin/mtp": "workspace:*",
|
"@tensamin/mtp": "workspace:*",
|
||||||
"@tensamin/notifications": "workspace:*",
|
"@tensamin/notifications": "workspace:*",
|
||||||
"@tensamin/onboarding": "workspace:*",
|
"@tensamin/onboarding": "workspace:*",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import type { User } from "@tensamin/user/context";
|
import type { User } from "@tensamin/user/context";
|
||||||
import { Avatar, AvatarFallback, AvatarImage, Button } from "@methanium/ui";
|
import { Avatar, AvatarFallback, AvatarImage, Button } from "@methanium/ui";
|
||||||
import Text from "@tensamin/markdown/text";
|
import { Text } from "@methanium/ui/markdown";
|
||||||
import { ChevronDown, ChevronUp } from "lucide-react";
|
import { ChevronDown, ChevronUp } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { createReadStream, realpathSync, statSync } from "node:fs";
|
import { createReadStream, statSync } from "node:fs";
|
||||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||||
import { dirname, resolve } from "node:path";
|
import { dirname, resolve } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
@ -11,11 +11,6 @@ import { methaniumUi } from "@methanium/ui/vite";
|
||||||
|
|
||||||
const host = process.env.TAURI_DEV_HOST;
|
const host = process.env.TAURI_DEV_HOST;
|
||||||
const appDir = dirname(fileURLToPath(import.meta.url));
|
const appDir = dirname(fileURLToPath(import.meta.url));
|
||||||
const markdownPackageDir = resolve(appDir, "../../packages/markdown");
|
|
||||||
|
|
||||||
function resolveMarkdownDependency(packageName: string): string {
|
|
||||||
return realpathSync(resolve(markdownPackageDir, "node_modules", packageName));
|
|
||||||
}
|
|
||||||
|
|
||||||
function deepFilterAssetHeaders(rootDir: string): Plugin {
|
function deepFilterAssetHeaders(rootDir: string): Plugin {
|
||||||
const serveModel = (
|
const serveModel = (
|
||||||
|
|
@ -57,24 +52,6 @@ export default defineConfig({
|
||||||
clearScreen: false,
|
clearScreen: false,
|
||||||
resolve: {
|
resolve: {
|
||||||
tsconfigPaths: true,
|
tsconfigPaths: true,
|
||||||
alias: [
|
|
||||||
{
|
|
||||||
find: "@codemirror/commands",
|
|
||||||
replacement: resolveMarkdownDependency("@codemirror/commands"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
find: "@codemirror/lang-markdown",
|
|
||||||
replacement: resolveMarkdownDependency("@codemirror/lang-markdown"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
find: "@codemirror/state",
|
|
||||||
replacement: resolveMarkdownDependency("@codemirror/state"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
find: "@codemirror/view",
|
|
||||||
replacement: resolveMarkdownDependency("@codemirror/view"),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
dedupe: [
|
dedupe: [
|
||||||
"react",
|
"react",
|
||||||
"react-dom",
|
"react-dom",
|
||||||
|
|
@ -90,10 +67,6 @@ export default defineConfig({
|
||||||
"@tensamin/user",
|
"@tensamin/user",
|
||||||
"@tensamin/tauri",
|
"@tensamin/tauri",
|
||||||
"@tensamin/chat",
|
"@tensamin/chat",
|
||||||
"@codemirror/commands",
|
|
||||||
"@codemirror/lang-markdown",
|
|
||||||
"@codemirror/state",
|
|
||||||
"@codemirror/view",
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
|
|
@ -133,7 +106,6 @@ export default defineConfig({
|
||||||
"@tensamin/crypto",
|
"@tensamin/crypto",
|
||||||
"@tensamin/crypto/context",
|
"@tensamin/crypto/context",
|
||||||
"@tensamin/hotkeys",
|
"@tensamin/hotkeys",
|
||||||
"@tensamin/markdown",
|
|
||||||
"@tensamin/mtp",
|
"@tensamin/mtp",
|
||||||
"@tensamin/notifications",
|
"@tensamin/notifications",
|
||||||
"@tensamin/onboarding",
|
"@tensamin/onboarding",
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2022–present Jason Sofonia & Justine De Caires
|
|
||||||
Copyright (c) 2014–2021 Twitter
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
@ -2,60 +2,6 @@
|
||||||
|
|
||||||
Generated from pnpm-lock.yaml and installed packages in workspace node_modules folders.
|
Generated from pnpm-lock.yaml and installed packages in workspace node_modules folders.
|
||||||
|
|
||||||
## @codemirror/autocomplete@6.20.3
|
|
||||||
|
|
||||||
- License: MIT
|
|
||||||
- Repository: git+https://code.haverbeke.berlin/codemirror/autocomplete.git
|
|
||||||
- Description: Autocompletion for the CodeMirror code editor
|
|
||||||
- Included files: LICENSE
|
|
||||||
- Folder: `licenses/@codemirror_autocomplete@6.20.3`
|
|
||||||
- Source package dir: `packages/markdown/node_modules/@codemirror/autocomplete`
|
|
||||||
|
|
||||||
## @codemirror/commands@6.10.4
|
|
||||||
|
|
||||||
- License: MIT
|
|
||||||
- Repository: git+https://code.haverbeke.berlin/codemirror/commands.git
|
|
||||||
- Description: Collection of editing commands for the CodeMirror code editor
|
|
||||||
- Included files: LICENSE
|
|
||||||
- Folder: `licenses/@codemirror_commands@6.10.4`
|
|
||||||
- Source package dir: `packages/markdown/node_modules/@codemirror/commands`
|
|
||||||
|
|
||||||
## @codemirror/lang-markdown@6.5.2
|
|
||||||
|
|
||||||
- License: MIT
|
|
||||||
- Repository: git+https://code.haverbeke.berlin/codemirror/lang-markdown.git
|
|
||||||
- Description: Markdown language support for the CodeMirror code editor
|
|
||||||
- Included files: LICENSE
|
|
||||||
- Folder: `licenses/@codemirror_lang-markdown@6.5.2`
|
|
||||||
- Source package dir: `packages/markdown/node_modules/@codemirror/lang-markdown`
|
|
||||||
|
|
||||||
## @codemirror/language@6.12.4
|
|
||||||
|
|
||||||
- License: MIT
|
|
||||||
- Repository: git+https://code.haverbeke.berlin/codemirror/language.git
|
|
||||||
- Description: Language support infrastructure for the CodeMirror code editor
|
|
||||||
- Included files: LICENSE
|
|
||||||
- Folder: `licenses/@codemirror_language@6.12.4`
|
|
||||||
- Source package dir: `packages/markdown/node_modules/@codemirror/language`
|
|
||||||
|
|
||||||
## @codemirror/state@6.7.1
|
|
||||||
|
|
||||||
- License: MIT
|
|
||||||
- Repository: git+https://code.haverbeke.berlin/codemirror/state.git
|
|
||||||
- Description: Editor state data structures for the CodeMirror code editor
|
|
||||||
- Included files: LICENSE
|
|
||||||
- Folder: `licenses/@codemirror_state@6.7.1`
|
|
||||||
- Source package dir: `packages/markdown/node_modules/@codemirror/state`
|
|
||||||
|
|
||||||
## @codemirror/view@6.43.8
|
|
||||||
|
|
||||||
- License: MIT
|
|
||||||
- Repository: git+https://code.haverbeke.berlin/codemirror/view.git
|
|
||||||
- Description: DOM view component for the CodeMirror code editor
|
|
||||||
- Included files: LICENSE
|
|
||||||
- Folder: `licenses/@codemirror_view@6.43.8`
|
|
||||||
- Source package dir: `packages/markdown/node_modules/@codemirror/view`
|
|
||||||
|
|
||||||
## @eslint/js@10.0.1
|
## @eslint/js@10.0.1
|
||||||
|
|
||||||
- License: MIT
|
- License: MIT
|
||||||
|
|
@ -84,11 +30,11 @@ Generated from pnpm-lock.yaml and installed packages in workspace node_modules f
|
||||||
- Folder: `licenses/@livekit_components-react@2.9.23`
|
- Folder: `licenses/@livekit_components-react@2.9.23`
|
||||||
- Source package dir: `packages/call/node_modules/@livekit/components-react`
|
- Source package dir: `packages/call/node_modules/@livekit/components-react`
|
||||||
|
|
||||||
## @methanium/ui@0.0.22
|
## @methanium/ui@0.0.28
|
||||||
|
|
||||||
- License: UNKNOWN
|
- License: UNKNOWN
|
||||||
- Included files: LICENSE
|
- Included files: LICENSE
|
||||||
- Folder: `licenses/@methanium_ui@0.0.22`
|
- Folder: `licenses/@methanium_ui@0.0.28`
|
||||||
- Source package dir: `apps/tauri/node_modules/@methanium/ui`
|
- Source package dir: `apps/tauri/node_modules/@methanium/ui`
|
||||||
|
|
||||||
## @tailwindcss/vite@4.3.3
|
## @tailwindcss/vite@4.3.3
|
||||||
|
|
@ -197,16 +143,6 @@ Generated from pnpm-lock.yaml and installed packages in workspace node_modules f
|
||||||
- Folder: `licenses/@tauri-apps_plugin-notification@2.3.3`
|
- Folder: `licenses/@tauri-apps_plugin-notification@2.3.3`
|
||||||
- Source package dir: `packages/notifications/node_modules/@tauri-apps/plugin-notification`
|
- Source package dir: `packages/notifications/node_modules/@tauri-apps/plugin-notification`
|
||||||
|
|
||||||
## @twemoji/api@17.0.3
|
|
||||||
|
|
||||||
- License: MIT AND CC-BY-4.0
|
|
||||||
- Homepage: https://github.com/jdecked/twemoji
|
|
||||||
- Repository: git://github.com/jdecked/twemoji.git
|
|
||||||
- Description: A Unicode standard based way to implement emoji across all platforms.
|
|
||||||
- Included files: LICENSE
|
|
||||||
- Folder: `licenses/@twemoji_api@17.0.3`
|
|
||||||
- Source package dir: `packages/markdown/node_modules/@twemoji/api`
|
|
||||||
|
|
||||||
## @types/node@26.2.0
|
## @types/node@26.2.0
|
||||||
|
|
||||||
- License: MIT
|
- License: MIT
|
||||||
|
|
@ -305,15 +241,6 @@ Generated from pnpm-lock.yaml and installed packages in workspace node_modules f
|
||||||
- Folder: `licenses/electron-builder@26.15.3`
|
- Folder: `licenses/electron-builder@26.15.3`
|
||||||
- Source package dir: `apps/electron/node_modules/electron-builder`
|
- Source package dir: `apps/electron/node_modules/electron-builder`
|
||||||
|
|
||||||
## emojibase-data@17.0.0
|
|
||||||
|
|
||||||
- License: MIT
|
|
||||||
- Repository: git@github.com:milesj/emojibase.git
|
|
||||||
- Description: Evergreen emoji datasets.
|
|
||||||
- Included files: LICENSE
|
|
||||||
- Folder: `licenses/emojibase-data@17.0.0`
|
|
||||||
- Source package dir: `packages/markdown/node_modules/emojibase-data`
|
|
||||||
|
|
||||||
## esbuild@0.28.1
|
## esbuild@0.28.1
|
||||||
|
|
||||||
- License: MIT
|
- License: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2017-2019 Miles Johnson
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
"specVersion": "1.5",
|
"specVersion": "1.5",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"timestamp": "2026-08-09T18:16:10.177Z",
|
"timestamp": "2026-08-09T22:09:04.469Z",
|
||||||
"tools": [
|
"tools": [
|
||||||
{
|
{
|
||||||
"vendor": "OpenAI",
|
"vendor": "OpenAI",
|
||||||
|
|
@ -16,192 +16,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"components": [
|
"components": [
|
||||||
{
|
|
||||||
"type": "library",
|
|
||||||
"bomRef": "pkg:npm/%40codemirror/autocomplete@6.20.3",
|
|
||||||
"name": "@codemirror/autocomplete",
|
|
||||||
"version": "6.20.3",
|
|
||||||
"purl": "pkg:npm/%40codemirror/autocomplete@6.20.3",
|
|
||||||
"description": "Autocompletion for the CodeMirror code editor",
|
|
||||||
"licenses": [
|
|
||||||
{
|
|
||||||
"license": {
|
|
||||||
"id": "MIT"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"externalReferences": [
|
|
||||||
{
|
|
||||||
"type": "vcs",
|
|
||||||
"url": "git+https://code.haverbeke.berlin/codemirror/autocomplete.git"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": [
|
|
||||||
{
|
|
||||||
"name": "local:licenseFolder",
|
|
||||||
"value": "licenses/@codemirror_autocomplete@6.20.3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "local:sourcePackageDir",
|
|
||||||
"value": "packages/markdown/node_modules/@codemirror/autocomplete"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "library",
|
|
||||||
"bomRef": "pkg:npm/%40codemirror/commands@6.10.4",
|
|
||||||
"name": "@codemirror/commands",
|
|
||||||
"version": "6.10.4",
|
|
||||||
"purl": "pkg:npm/%40codemirror/commands@6.10.4",
|
|
||||||
"description": "Collection of editing commands for the CodeMirror code editor",
|
|
||||||
"licenses": [
|
|
||||||
{
|
|
||||||
"license": {
|
|
||||||
"id": "MIT"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"externalReferences": [
|
|
||||||
{
|
|
||||||
"type": "vcs",
|
|
||||||
"url": "git+https://code.haverbeke.berlin/codemirror/commands.git"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": [
|
|
||||||
{
|
|
||||||
"name": "local:licenseFolder",
|
|
||||||
"value": "licenses/@codemirror_commands@6.10.4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "local:sourcePackageDir",
|
|
||||||
"value": "packages/markdown/node_modules/@codemirror/commands"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "library",
|
|
||||||
"bomRef": "pkg:npm/%40codemirror/lang-markdown@6.5.2",
|
|
||||||
"name": "@codemirror/lang-markdown",
|
|
||||||
"version": "6.5.2",
|
|
||||||
"purl": "pkg:npm/%40codemirror/lang-markdown@6.5.2",
|
|
||||||
"description": "Markdown language support for the CodeMirror code editor",
|
|
||||||
"licenses": [
|
|
||||||
{
|
|
||||||
"license": {
|
|
||||||
"id": "MIT"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"externalReferences": [
|
|
||||||
{
|
|
||||||
"type": "vcs",
|
|
||||||
"url": "git+https://code.haverbeke.berlin/codemirror/lang-markdown.git"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": [
|
|
||||||
{
|
|
||||||
"name": "local:licenseFolder",
|
|
||||||
"value": "licenses/@codemirror_lang-markdown@6.5.2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "local:sourcePackageDir",
|
|
||||||
"value": "packages/markdown/node_modules/@codemirror/lang-markdown"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "library",
|
|
||||||
"bomRef": "pkg:npm/%40codemirror/language@6.12.4",
|
|
||||||
"name": "@codemirror/language",
|
|
||||||
"version": "6.12.4",
|
|
||||||
"purl": "pkg:npm/%40codemirror/language@6.12.4",
|
|
||||||
"description": "Language support infrastructure for the CodeMirror code editor",
|
|
||||||
"licenses": [
|
|
||||||
{
|
|
||||||
"license": {
|
|
||||||
"id": "MIT"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"externalReferences": [
|
|
||||||
{
|
|
||||||
"type": "vcs",
|
|
||||||
"url": "git+https://code.haverbeke.berlin/codemirror/language.git"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": [
|
|
||||||
{
|
|
||||||
"name": "local:licenseFolder",
|
|
||||||
"value": "licenses/@codemirror_language@6.12.4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "local:sourcePackageDir",
|
|
||||||
"value": "packages/markdown/node_modules/@codemirror/language"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "library",
|
|
||||||
"bomRef": "pkg:npm/%40codemirror/state@6.7.1",
|
|
||||||
"name": "@codemirror/state",
|
|
||||||
"version": "6.7.1",
|
|
||||||
"purl": "pkg:npm/%40codemirror/state@6.7.1",
|
|
||||||
"description": "Editor state data structures for the CodeMirror code editor",
|
|
||||||
"licenses": [
|
|
||||||
{
|
|
||||||
"license": {
|
|
||||||
"id": "MIT"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"externalReferences": [
|
|
||||||
{
|
|
||||||
"type": "vcs",
|
|
||||||
"url": "git+https://code.haverbeke.berlin/codemirror/state.git"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": [
|
|
||||||
{
|
|
||||||
"name": "local:licenseFolder",
|
|
||||||
"value": "licenses/@codemirror_state@6.7.1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "local:sourcePackageDir",
|
|
||||||
"value": "packages/markdown/node_modules/@codemirror/state"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "library",
|
|
||||||
"bomRef": "pkg:npm/%40codemirror/view@6.43.8",
|
|
||||||
"name": "@codemirror/view",
|
|
||||||
"version": "6.43.8",
|
|
||||||
"purl": "pkg:npm/%40codemirror/view@6.43.8",
|
|
||||||
"description": "DOM view component for the CodeMirror code editor",
|
|
||||||
"licenses": [
|
|
||||||
{
|
|
||||||
"license": {
|
|
||||||
"id": "MIT"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"externalReferences": [
|
|
||||||
{
|
|
||||||
"type": "vcs",
|
|
||||||
"url": "git+https://code.haverbeke.berlin/codemirror/view.git"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": [
|
|
||||||
{
|
|
||||||
"name": "local:licenseFolder",
|
|
||||||
"value": "licenses/@codemirror_view@6.43.8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "local:sourcePackageDir",
|
|
||||||
"value": "packages/markdown/node_modules/@codemirror/view"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/%40eslint/js@10.0.1",
|
"bomRef": "pkg:npm/%40eslint/js@10.0.1",
|
||||||
|
|
@ -304,15 +118,15 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/%40methanium/ui@0.0.22",
|
"bomRef": "pkg:npm/%40methanium/ui@0.0.28",
|
||||||
"name": "@methanium/ui",
|
"name": "@methanium/ui",
|
||||||
"version": "0.0.22",
|
"version": "0.0.28",
|
||||||
"purl": "pkg:npm/%40methanium/ui@0.0.22",
|
"purl": "pkg:npm/%40methanium/ui@0.0.28",
|
||||||
"externalReferences": [],
|
"externalReferences": [],
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "local:licenseFolder",
|
"name": "local:licenseFolder",
|
||||||
"value": "licenses/@methanium_ui@0.0.22"
|
"value": "licenses/@methanium_ui@0.0.28"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "local:sourcePackageDir",
|
"name": "local:sourcePackageDir",
|
||||||
|
|
@ -692,41 +506,6 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "library",
|
|
||||||
"bomRef": "pkg:npm/%40twemoji/api@17.0.3",
|
|
||||||
"name": "@twemoji/api",
|
|
||||||
"version": "17.0.3",
|
|
||||||
"purl": "pkg:npm/%40twemoji/api@17.0.3",
|
|
||||||
"description": "A Unicode standard based way to implement emoji across all platforms.",
|
|
||||||
"licenses": [
|
|
||||||
{
|
|
||||||
"license": {
|
|
||||||
"name": "MIT AND CC-BY-4.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"externalReferences": [
|
|
||||||
{
|
|
||||||
"type": "website",
|
|
||||||
"url": "https://github.com/jdecked/twemoji"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "vcs",
|
|
||||||
"url": "git://github.com/jdecked/twemoji.git"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": [
|
|
||||||
{
|
|
||||||
"name": "local:licenseFolder",
|
|
||||||
"value": "licenses/@twemoji_api@17.0.3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "local:sourcePackageDir",
|
|
||||||
"value": "packages/markdown/node_modules/@twemoji/api"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/%40types/node@26.2.0",
|
"bomRef": "pkg:npm/%40types/node@26.2.0",
|
||||||
|
|
@ -1069,37 +848,6 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "library",
|
|
||||||
"bomRef": "pkg:npm/emojibase-data@17.0.0",
|
|
||||||
"name": "emojibase-data",
|
|
||||||
"version": "17.0.0",
|
|
||||||
"purl": "pkg:npm/emojibase-data@17.0.0",
|
|
||||||
"description": "Evergreen emoji datasets.",
|
|
||||||
"licenses": [
|
|
||||||
{
|
|
||||||
"license": {
|
|
||||||
"id": "MIT"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"externalReferences": [
|
|
||||||
{
|
|
||||||
"type": "vcs",
|
|
||||||
"url": "git@github.com:milesj/emojibase.git"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": [
|
|
||||||
{
|
|
||||||
"name": "local:licenseFolder",
|
|
||||||
"value": "licenses/emojibase-data@17.0.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "local:sourcePackageDir",
|
|
||||||
"value": "packages/markdown/node_modules/emojibase-data"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/esbuild@0.28.1",
|
"bomRef": "pkg:npm/esbuild@0.28.1",
|
||||||
|
|
|
||||||
|
|
@ -1,85 +1,7 @@
|
||||||
{
|
{
|
||||||
"generatedAt": "2026-08-09T18:16:10.172Z",
|
"generatedAt": "2026-08-09T22:09:04.466Z",
|
||||||
"packageCount": 60,
|
"packageCount": 52,
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
|
||||||
"name": "@codemirror/autocomplete",
|
|
||||||
"version": "6.20.3",
|
|
||||||
"license": "MIT",
|
|
||||||
"homepage": null,
|
|
||||||
"repository": "git+https://code.haverbeke.berlin/codemirror/autocomplete.git",
|
|
||||||
"description": "Autocompletion for the CodeMirror code editor",
|
|
||||||
"files": [
|
|
||||||
"LICENSE"
|
|
||||||
],
|
|
||||||
"licenseFolder": "licenses/@codemirror_autocomplete@6.20.3",
|
|
||||||
"sourcePackageDir": "packages/markdown/node_modules/@codemirror/autocomplete"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@codemirror/commands",
|
|
||||||
"version": "6.10.4",
|
|
||||||
"license": "MIT",
|
|
||||||
"homepage": null,
|
|
||||||
"repository": "git+https://code.haverbeke.berlin/codemirror/commands.git",
|
|
||||||
"description": "Collection of editing commands for the CodeMirror code editor",
|
|
||||||
"files": [
|
|
||||||
"LICENSE"
|
|
||||||
],
|
|
||||||
"licenseFolder": "licenses/@codemirror_commands@6.10.4",
|
|
||||||
"sourcePackageDir": "packages/markdown/node_modules/@codemirror/commands"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@codemirror/lang-markdown",
|
|
||||||
"version": "6.5.2",
|
|
||||||
"license": "MIT",
|
|
||||||
"homepage": null,
|
|
||||||
"repository": "git+https://code.haverbeke.berlin/codemirror/lang-markdown.git",
|
|
||||||
"description": "Markdown language support for the CodeMirror code editor",
|
|
||||||
"files": [
|
|
||||||
"LICENSE"
|
|
||||||
],
|
|
||||||
"licenseFolder": "licenses/@codemirror_lang-markdown@6.5.2",
|
|
||||||
"sourcePackageDir": "packages/markdown/node_modules/@codemirror/lang-markdown"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@codemirror/language",
|
|
||||||
"version": "6.12.4",
|
|
||||||
"license": "MIT",
|
|
||||||
"homepage": null,
|
|
||||||
"repository": "git+https://code.haverbeke.berlin/codemirror/language.git",
|
|
||||||
"description": "Language support infrastructure for the CodeMirror code editor",
|
|
||||||
"files": [
|
|
||||||
"LICENSE"
|
|
||||||
],
|
|
||||||
"licenseFolder": "licenses/@codemirror_language@6.12.4",
|
|
||||||
"sourcePackageDir": "packages/markdown/node_modules/@codemirror/language"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@codemirror/state",
|
|
||||||
"version": "6.7.1",
|
|
||||||
"license": "MIT",
|
|
||||||
"homepage": null,
|
|
||||||
"repository": "git+https://code.haverbeke.berlin/codemirror/state.git",
|
|
||||||
"description": "Editor state data structures for the CodeMirror code editor",
|
|
||||||
"files": [
|
|
||||||
"LICENSE"
|
|
||||||
],
|
|
||||||
"licenseFolder": "licenses/@codemirror_state@6.7.1",
|
|
||||||
"sourcePackageDir": "packages/markdown/node_modules/@codemirror/state"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@codemirror/view",
|
|
||||||
"version": "6.43.8",
|
|
||||||
"license": "MIT",
|
|
||||||
"homepage": null,
|
|
||||||
"repository": "git+https://code.haverbeke.berlin/codemirror/view.git",
|
|
||||||
"description": "DOM view component for the CodeMirror code editor",
|
|
||||||
"files": [
|
|
||||||
"LICENSE"
|
|
||||||
],
|
|
||||||
"licenseFolder": "licenses/@codemirror_view@6.43.8",
|
|
||||||
"sourcePackageDir": "packages/markdown/node_modules/@codemirror/view"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "@eslint/js",
|
"name": "@eslint/js",
|
||||||
"version": "10.0.1",
|
"version": "10.0.1",
|
||||||
|
|
@ -121,7 +43,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "@methanium/ui",
|
"name": "@methanium/ui",
|
||||||
"version": "0.0.22",
|
"version": "0.0.28",
|
||||||
"license": "UNKNOWN",
|
"license": "UNKNOWN",
|
||||||
"homepage": null,
|
"homepage": null,
|
||||||
"repository": null,
|
"repository": null,
|
||||||
|
|
@ -129,7 +51,7 @@
|
||||||
"files": [
|
"files": [
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"licenseFolder": "licenses/@methanium_ui@0.0.22",
|
"licenseFolder": "licenses/@methanium_ui@0.0.28",
|
||||||
"sourcePackageDir": "apps/tauri/node_modules/@methanium/ui"
|
"sourcePackageDir": "apps/tauri/node_modules/@methanium/ui"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -277,19 +199,6 @@
|
||||||
"licenseFolder": "licenses/@tauri-apps_plugin-notification@2.3.3",
|
"licenseFolder": "licenses/@tauri-apps_plugin-notification@2.3.3",
|
||||||
"sourcePackageDir": "packages/notifications/node_modules/@tauri-apps/plugin-notification"
|
"sourcePackageDir": "packages/notifications/node_modules/@tauri-apps/plugin-notification"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "@twemoji/api",
|
|
||||||
"version": "17.0.3",
|
|
||||||
"license": "MIT AND CC-BY-4.0",
|
|
||||||
"homepage": "https://github.com/jdecked/twemoji",
|
|
||||||
"repository": "git://github.com/jdecked/twemoji.git",
|
|
||||||
"description": "A Unicode standard based way to implement emoji across all platforms.",
|
|
||||||
"files": [
|
|
||||||
"LICENSE"
|
|
||||||
],
|
|
||||||
"licenseFolder": "licenses/@twemoji_api@17.0.3",
|
|
||||||
"sourcePackageDir": "packages/markdown/node_modules/@twemoji/api"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "@types/node",
|
"name": "@types/node",
|
||||||
"version": "26.2.0",
|
"version": "26.2.0",
|
||||||
|
|
@ -421,19 +330,6 @@
|
||||||
"licenseFolder": "licenses/electron-builder@26.15.3",
|
"licenseFolder": "licenses/electron-builder@26.15.3",
|
||||||
"sourcePackageDir": "apps/electron/node_modules/electron-builder"
|
"sourcePackageDir": "apps/electron/node_modules/electron-builder"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "emojibase-data",
|
|
||||||
"version": "17.0.0",
|
|
||||||
"license": "MIT",
|
|
||||||
"homepage": null,
|
|
||||||
"repository": "git@github.com:milesj/emojibase.git",
|
|
||||||
"description": "Evergreen emoji datasets.",
|
|
||||||
"files": [
|
|
||||||
"LICENSE"
|
|
||||||
],
|
|
||||||
"licenseFolder": "licenses/emojibase-data@17.0.0",
|
|
||||||
"sourcePackageDir": "packages/markdown/node_modules/emojibase-data"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "esbuild",
|
"name": "esbuild",
|
||||||
"version": "0.28.1",
|
"version": "0.28.1",
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@
|
||||||
"@tensamin/cache": "workspace:*",
|
"@tensamin/cache": "workspace:*",
|
||||||
"@tensamin/crypto": "workspace:*",
|
"@tensamin/crypto": "workspace:*",
|
||||||
"@tensamin/hotkeys": "workspace:*",
|
"@tensamin/hotkeys": "workspace:*",
|
||||||
"@tensamin/markdown": "workspace:*",
|
|
||||||
"@tensamin/mtp": "workspace:*",
|
"@tensamin/mtp": "workspace:*",
|
||||||
"@tensamin/shared": "workspace:*",
|
"@tensamin/shared": "workspace:*",
|
||||||
"@tensamin/storage": "workspace:*",
|
"@tensamin/storage": "workspace:*",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Button } from "@methanium/ui";
|
import { Button } from "@methanium/ui";
|
||||||
import Emoji from "@tensamin/markdown/emoji";
|
import { Emoji } from "@methanium/ui/markdown";
|
||||||
import { getRecentEmojis, useEmojiRanks } from "./emojiRanks";
|
import { getRecentEmojis, useEmojiRanks } from "./emojiRanks";
|
||||||
|
|
||||||
export default function EmojiPicker({
|
export default function EmojiPicker({
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useStorage } from "@tensamin/storage/context";
|
import { useStorage } from "@tensamin/storage/context";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { normalizeShortcode } from "@tensamin/markdown/emoji";
|
import { normalizeShortcode } from "@methanium/ui/markdown";
|
||||||
|
|
||||||
const RANKS_CHANGED_EVENT = "tensamin-reaction-ranks-changed";
|
const RANKS_CHANGED_EVENT = "tensamin-reaction-ranks-changed";
|
||||||
let recordQueue = Promise.resolve();
|
let recordQueue = Promise.resolve();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import Input, { type InputController } from "@tensamin/markdown/input";
|
import { Input, type InputController } from "@methanium/ui/markdown";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
|
|
@ -276,7 +276,7 @@ export default function InputComponent({
|
||||||
<CardHeader className="relative p-0 flex flex-col">
|
<CardHeader className="relative p-0 flex flex-col">
|
||||||
<div className="w-full flex items-center">
|
<div className="w-full flex items-center">
|
||||||
<Input
|
<Input
|
||||||
className="w-full"
|
className="w-full p-2"
|
||||||
onControllerChange={setComposer}
|
onControllerChange={setComposer}
|
||||||
paddingY="13px"
|
paddingY="13px"
|
||||||
paddingX="13px"
|
paddingX="13px"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import Text from "@tensamin/markdown/text";
|
import { Text } from "@methanium/ui/markdown";
|
||||||
import { useStorage } from "@tensamin/storage/context";
|
import { useStorage } from "@tensamin/storage/context";
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
|
|
@ -22,9 +22,9 @@ import {
|
||||||
X,
|
X,
|
||||||
ZoomIn,
|
ZoomIn,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useState, useMemo, useEffect, useRef, WheelEvent } from "react";
|
import { useState, useMemo, useEffect, useRef, type WheelEvent } from "react";
|
||||||
import MediaSaveButton from "./mediaSaveButton";
|
import MediaSaveButton from "./mediaSaveButton";
|
||||||
import { User, useUser } from "@tensamin/user/context";
|
import { type User, useUser } from "@tensamin/user/context";
|
||||||
|
|
||||||
const zoomLevels = [1, 1.5, 2, 3];
|
const zoomLevels = [1, 1.5, 2, 3];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import type { RawMessage } from "../values";
|
import type { RawMessage } from "../values";
|
||||||
import Text from "@tensamin/markdown/text";
|
|
||||||
import { AlertTriangle, Check, CheckLine, RefreshCw } from "lucide-react";
|
import { AlertTriangle, Check, CheckLine, RefreshCw } from "lucide-react";
|
||||||
import { memo, useCallback, useEffect, useRef, useState } from "react";
|
import { memo, useCallback, useEffect, useRef, useState } from "react";
|
||||||
import type { User } from "@tensamin/user/context";
|
import type { User } from "@tensamin/user/context";
|
||||||
|
|
@ -9,6 +8,7 @@ import {
|
||||||
AvatarFallback,
|
AvatarFallback,
|
||||||
AvatarImage,
|
AvatarImage,
|
||||||
Button,
|
Button,
|
||||||
|
Card,
|
||||||
cn,
|
cn,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
} from "@methanium/ui";
|
} from "@methanium/ui";
|
||||||
|
|
@ -16,11 +16,10 @@ import MessageContextMenu from "./messageContextMenu";
|
||||||
import Media from "./media";
|
import Media from "./media";
|
||||||
import { useStorage } from "@tensamin/storage/context";
|
import { useStorage } from "@tensamin/storage/context";
|
||||||
import { useMTP } from "@tensamin/mtp";
|
import { useMTP } from "@tensamin/mtp";
|
||||||
import Input from "@tensamin/markdown/input";
|
|
||||||
import { getMessage, useChat } from "../context";
|
import { getMessage, useChat } from "../context";
|
||||||
import { decryptChatText, encryptChatText } from "@tensamin/crypto/chatSecret";
|
import { decryptChatText, encryptChatText } from "@tensamin/crypto/chatSecret";
|
||||||
import { log, toast } from "@tensamin/shared/log";
|
import { log, toast } from "@tensamin/shared/log";
|
||||||
import Emoji, { normalizeShortcode } from "@tensamin/markdown/emoji";
|
import { Emoji, Input, normalizeShortcode, Text } from "@methanium/ui/markdown";
|
||||||
import { useRecordEmojiUse } from "./emojiRanks";
|
import { useRecordEmojiUse } from "./emojiRanks";
|
||||||
import { useUser } from "@tensamin/user/context";
|
import { useUser } from "@tensamin/user/context";
|
||||||
import ReplyBox from "./replyBox";
|
import ReplyBox from "./replyBox";
|
||||||
|
|
@ -347,7 +346,7 @@ function MessageComponent({
|
||||||
</Avatar>
|
</Avatar>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
className={cn("flex flex-col group", {
|
className={cn("flex flex-col group w-full", {
|
||||||
"min-w-0 flex-1": editing,
|
"min-w-0 flex-1": editing,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
|
@ -388,20 +387,21 @@ function MessageComponent({
|
||||||
)}
|
)}
|
||||||
{editing ? (
|
{editing ? (
|
||||||
<div className="flex w-full flex-col gap-1">
|
<div className="flex w-full flex-col gap-1">
|
||||||
<Input
|
<Card className="p-2!">
|
||||||
autoFocus
|
<Input
|
||||||
className="w-full"
|
autoFocus
|
||||||
styled
|
className="w-full"
|
||||||
setValue={setEditDraft}
|
setValue={setEditDraft}
|
||||||
value={editDraft}
|
value={editDraft}
|
||||||
onSubmit={() => {
|
onSubmit={() => {
|
||||||
if (!editDraft.trim()) return;
|
if (!editDraft.trim()) return;
|
||||||
if (editDraft !== message.Content) {
|
if (editDraft !== message.Content) {
|
||||||
void submitEditMessage(editDraft);
|
void submitEditMessage(editDraft);
|
||||||
}
|
}
|
||||||
onSetEditing(false);
|
onSetEditing(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</Card>
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
|
|
@ -429,7 +429,7 @@ function MessageComponent({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1 w-full!">
|
||||||
{isValidURL ? (
|
{isValidURL ? (
|
||||||
<Media
|
<Media
|
||||||
link={message.Content}
|
link={message.Content}
|
||||||
|
|
@ -440,12 +440,12 @@ function MessageComponent({
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Text value={message.Content} />
|
<div className="min-w-0 flex-1">
|
||||||
)}
|
<Text
|
||||||
{message.Edited && (
|
showEditedIndicator={message.Edited}
|
||||||
<p className="text-xs text-muted-foreground self-center">
|
value={message.Content}
|
||||||
(edited)
|
/>
|
||||||
</p>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ import type {
|
||||||
Ref,
|
Ref,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { useChat } from "../context";
|
import { useChat } from "../context";
|
||||||
import Emoji from "@tensamin/markdown/emoji";
|
import { Emoji } from "@methanium/ui/markdown";
|
||||||
import EmojiPicker from "./emojiPicker";
|
import EmojiPicker from "./emojiPicker";
|
||||||
import { getRecentEmojis, useEmojiRanks } from "./emojiRanks";
|
import { getRecentEmojis, useEmojiRanks } from "./emojiRanks";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import {
|
||||||
cn,
|
cn,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
} from "@methanium/ui";
|
} from "@methanium/ui";
|
||||||
import Text from "@tensamin/markdown/text";
|
import { Text } from "@methanium/ui/markdown";
|
||||||
import type { User } from "@tensamin/user/context";
|
import type { User } from "@tensamin/user/context";
|
||||||
import Wrapper from "@tensamin/user/wrapper";
|
import Wrapper from "@tensamin/user/wrapper";
|
||||||
import { Forward, X } from "lucide-react";
|
import { Forward, X } from "lucide-react";
|
||||||
|
|
@ -77,12 +77,11 @@ export default function ReplyBox({
|
||||||
) : null}
|
) : null}
|
||||||
{content !== undefined && (
|
{content !== undefined && (
|
||||||
<div className="h-5.5 min-w-0 flex-1 truncate flex gap-1">
|
<div className="h-5.5 min-w-0 flex-1 truncate flex gap-1">
|
||||||
<Text fontSize="0.88rem" value={content} />
|
<Text
|
||||||
{edited && (
|
fontSize="0.88rem"
|
||||||
<p className="text-xs text-muted-foreground self-center">
|
showEditedIndicator={edited}
|
||||||
(edited)
|
value={content}
|
||||||
</p>
|
/>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@tensamin/markdown",
|
|
||||||
"private": true,
|
|
||||||
"version": "0.0.0",
|
|
||||||
"type": "module",
|
|
||||||
"exports": {
|
|
||||||
"./text": "./src/text.tsx",
|
|
||||||
"./input": "./src/input.tsx",
|
|
||||||
"./emoji": "./src/emoji.tsx"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"format": "pnpm exec prettier --write .",
|
|
||||||
"lint": "eslint src",
|
|
||||||
"build": "tsc -p tsconfig.json --noEmit"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@codemirror/autocomplete": "^6.20.3",
|
|
||||||
"@codemirror/commands": "^6.10.4",
|
|
||||||
"@codemirror/lang-markdown": "^6.5.2",
|
|
||||||
"@codemirror/language": "^6.12.4",
|
|
||||||
"@codemirror/state": "^6.7.1",
|
|
||||||
"@codemirror/view": "^6.43.8",
|
|
||||||
"@methanium/ui": "*",
|
|
||||||
"@twemoji/api": "^17.0.3",
|
|
||||||
"emojibase-data": "^17.0.0",
|
|
||||||
"lucide-react": "^1.30.0",
|
|
||||||
"react": "^19.2.8",
|
|
||||||
"react-dom": "^19.2.8"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,138 +0,0 @@
|
||||||
import { describe, expect, it, vi } from "vitest";
|
|
||||||
import type { ReactNode } from "react";
|
|
||||||
|
|
||||||
vi.mock("@methanium/ui", () => ({
|
|
||||||
Tooltip: ({ children }: { children: ReactNode }) => children,
|
|
||||||
TooltipContent: ({ children }: { children: ReactNode }) => children,
|
|
||||||
TooltipTrigger: ({ render }: { render: ReactNode }) => render,
|
|
||||||
}));
|
|
||||||
import { EditorState } from "@codemirror/state";
|
|
||||||
import { CompletionContext } from "@codemirror/autocomplete";
|
|
||||||
import { markdown } from "@codemirror/lang-markdown";
|
|
||||||
import { normalizeShortcode, resolveEmoji, searchEmojis } from "./emojiData";
|
|
||||||
import { parseEmojiText, parseInlineNodes } from "./markdown";
|
|
||||||
import {
|
|
||||||
createEmojiCompletionSource,
|
|
||||||
findEmojiRanges,
|
|
||||||
MAX_RENDERED_EMOJI_OPTIONS,
|
|
||||||
} from "./input";
|
|
||||||
|
|
||||||
describe("emoji shortcodes", () => {
|
|
||||||
it("normalizes aliases to their canonical shortcode", () => {
|
|
||||||
expect(normalizeShortcode(":flame:")).toBe(":fire:");
|
|
||||||
expect(normalizeShortcode("+1")).toBe(":thumbsup:");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("resolves every search result to a Twemoji hexcode", () => {
|
|
||||||
const results = searchEmojis("fire");
|
|
||||||
expect(results[0]?.shortcode).toBe(":fire:");
|
|
||||||
expect(results.every((emoji) => emoji.hexcode.length > 0)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows all emojis for an empty query", () => {
|
|
||||||
expect(searchEmojis("").length).toBeGreaterThan(1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("bounds the number of mounted autocomplete rows", () => {
|
|
||||||
expect(MAX_RENDERED_EMOJI_OPTIONS).toBeLessThanOrEqual(100);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("parses known shortcodes and preserves unknown ones", () => {
|
|
||||||
expect(parseEmojiText("a :fire: b :not_an_emoji:")).toEqual([
|
|
||||||
{ type: "text", value: "a " },
|
|
||||||
{ type: "emoji", shortcode: ":fire:" },
|
|
||||||
{ type: "text", value: " b :not_an_emoji:" },
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("recognizes a valid shortcode sharing an unknown closing colon", () => {
|
|
||||||
expect(parseEmojiText(":bla:thumbsup:")).toEqual([
|
|
||||||
{ type: "text", value: ":bla" },
|
|
||||||
{ type: "emoji", shortcode: ":thumbsup:" },
|
|
||||||
]);
|
|
||||||
|
|
||||||
const state = EditorState.create({
|
|
||||||
doc: ":bla:thumbsup:",
|
|
||||||
extensions: [markdown()],
|
|
||||||
});
|
|
||||||
expect(findEmojiRanges(state)[0]?.from).toBe(4);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does not parse underscores inside emoji shortcodes as emphasis", () => {
|
|
||||||
expect(parseInlineNodes("before :white_check_mark: after")).toEqual([
|
|
||||||
{ type: "text", value: "before " },
|
|
||||||
{ type: "emoji", shortcode: ":white_check_mark:" },
|
|
||||||
{ type: "text", value: " after" },
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("contains the picker defaults", () => {
|
|
||||||
expect(resolveEmoji(":thumbsup:")).toBeDefined();
|
|
||||||
expect(resolveEmoji(":white_check_mark:")).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("finds completed emoji shortcodes in editor state", () => {
|
|
||||||
const state = EditorState.create({
|
|
||||||
doc: "before :fire: after :not_an_emoji:",
|
|
||||||
extensions: [markdown()],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
findEmojiRanges(state).map(({ from, shortcode, to }) => ({
|
|
||||||
from,
|
|
||||||
shortcode,
|
|
||||||
to,
|
|
||||||
})),
|
|
||||||
).toEqual([{ from: 7, shortcode: ":fire:", to: 13 }]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does not replace emoji shortcodes inside code", () => {
|
|
||||||
const state = EditorState.create({
|
|
||||||
doc: "`:fire:`\n\n```\n:fire:\n```\n\n:fire:",
|
|
||||||
extensions: [markdown()],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(findEmojiRanges(state)).toHaveLength(1);
|
|
||||||
expect(findEmojiRanges(state)[0]?.from).toBe(26);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("ranks frequently used emojis first for a bare colon", async () => {
|
|
||||||
const state = EditorState.create({ doc: ":", extensions: [markdown()] });
|
|
||||||
const result = await createEmojiCompletionSource({
|
|
||||||
":fire:": 50,
|
|
||||||
":thumbsup:": 2,
|
|
||||||
})(new CompletionContext(state, 1, false));
|
|
||||||
|
|
||||||
expect(result?.options[0]?.displayLabel).toBe(":fire:");
|
|
||||||
expect(result?.options[1]?.displayLabel).toBe(":thumbsup:");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("keeps typed relevance above usage frequency", async () => {
|
|
||||||
const state = EditorState.create({
|
|
||||||
doc: ":fire",
|
|
||||||
extensions: [markdown()],
|
|
||||||
});
|
|
||||||
const result = await createEmojiCompletionSource({
|
|
||||||
":fire_engine:": 10000,
|
|
||||||
":fire:": 1,
|
|
||||||
})(new CompletionContext(state, 5, false));
|
|
||||||
|
|
||||||
expect(result?.options[0]?.displayLabel).toBe(":fire:");
|
|
||||||
expect(result?.options[0]?.boost).toBeGreaterThan(
|
|
||||||
result?.options[1]?.boost ?? 0,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("merges alias frequencies into canonical completions", async () => {
|
|
||||||
const state = EditorState.create({ doc: ":", extensions: [markdown()] });
|
|
||||||
const result = await createEmojiCompletionSource({
|
|
||||||
":fire:": 2,
|
|
||||||
":flame:": 3,
|
|
||||||
})(new CompletionContext(state, 1, false));
|
|
||||||
const fire = result?.options.find(
|
|
||||||
(option) => option.displayLabel === ":fire:",
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(fire?.boost).toBe(15);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
import twemoji from "@twemoji/api";
|
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@methanium/ui";
|
|
||||||
import { resolveEmoji } from "./emojiData";
|
|
||||||
|
|
||||||
export {
|
|
||||||
emojis,
|
|
||||||
findEmojiShortcodes,
|
|
||||||
normalizeShortcode,
|
|
||||||
resolveEmoji,
|
|
||||||
searchEmojis,
|
|
||||||
} from "./emojiData";
|
|
||||||
export type { EmojiDefinition } from "./emojiData";
|
|
||||||
|
|
||||||
export function getEmojiUrl(shortcode: string): string | undefined {
|
|
||||||
const emoji = resolveEmoji(shortcode);
|
|
||||||
return emoji ? `${twemoji.base}svg/${emoji.hexcode}.svg` : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Emoji({
|
|
||||||
className = "h-6 w-6",
|
|
||||||
shortcode,
|
|
||||||
tooltip = true,
|
|
||||||
}: {
|
|
||||||
className?: string;
|
|
||||||
shortcode: string;
|
|
||||||
tooltip?: boolean;
|
|
||||||
}) {
|
|
||||||
const emoji = resolveEmoji(shortcode);
|
|
||||||
if (!emoji) return <span>{shortcode}</span>;
|
|
||||||
|
|
||||||
const image = (
|
|
||||||
<img
|
|
||||||
alt={emoji.shortcode}
|
|
||||||
className={className}
|
|
||||||
decoding="async"
|
|
||||||
draggable={false}
|
|
||||||
loading="lazy"
|
|
||||||
src={`${twemoji.base}svg/${emoji.hexcode}.svg`}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!tooltip) return image;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger render={image} />
|
|
||||||
<TooltipContent sideOffset={8}>{emoji.shortcode}</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
import shortcodeData from "emojibase-data/en/shortcodes/joypixels.json";
|
|
||||||
|
|
||||||
export type EmojiDefinition = {
|
|
||||||
aliases: readonly string[];
|
|
||||||
hexcode: string;
|
|
||||||
name: string;
|
|
||||||
shortcode: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function normalizeName(value: string) {
|
|
||||||
return value
|
|
||||||
.trim()
|
|
||||||
.replace(/^:+|:+$/g, "")
|
|
||||||
.toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
export const emojis: readonly EmojiDefinition[] = Object.entries(
|
|
||||||
shortcodeData as Record<string, string | string[]>,
|
|
||||||
).map(([hexcode, value]) => {
|
|
||||||
const aliases = Array.isArray(value) ? value : [value];
|
|
||||||
const name = aliases[0];
|
|
||||||
|
|
||||||
return {
|
|
||||||
aliases,
|
|
||||||
hexcode: hexcode.toLowerCase().replaceAll("_", "-"),
|
|
||||||
name,
|
|
||||||
shortcode: `:${name}:`,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const emojiByName = new Map<string, EmojiDefinition>();
|
|
||||||
for (const emoji of emojis) {
|
|
||||||
for (const alias of emoji.aliases) {
|
|
||||||
emojiByName.set(normalizeName(alias), emoji);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveEmoji(value: string): EmojiDefinition | undefined {
|
|
||||||
return emojiByName.get(normalizeName(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normalizeShortcode(value: string): string | undefined {
|
|
||||||
return resolveEmoji(value)?.shortcode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function findEmojiShortcodes(value: string) {
|
|
||||||
const matches: Array<{
|
|
||||||
emoji: EmojiDefinition;
|
|
||||||
from: number;
|
|
||||||
to: number;
|
|
||||||
}> = [];
|
|
||||||
let searchFrom = 0;
|
|
||||||
|
|
||||||
while (searchFrom < value.length) {
|
|
||||||
const from = value.indexOf(":", searchFrom);
|
|
||||||
if (from === -1) break;
|
|
||||||
|
|
||||||
const candidate = value.slice(from).match(/^:([a-z0-9_+-]+):/i);
|
|
||||||
if (!candidate) {
|
|
||||||
searchFrom = from + 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const emoji = resolveEmoji(candidate[1]);
|
|
||||||
if (!emoji) {
|
|
||||||
// The closing colon may also open the next valid shortcode.
|
|
||||||
searchFrom = from + candidate[0].length - 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const to = from + candidate[0].length;
|
|
||||||
matches.push({ emoji, from, to });
|
|
||||||
searchFrom = to;
|
|
||||||
}
|
|
||||||
|
|
||||||
return matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function searchEmojis(query: string): EmojiDefinition[] {
|
|
||||||
const normalizedQuery = normalizeName(query);
|
|
||||||
if (!normalizedQuery) return [...emojis];
|
|
||||||
|
|
||||||
return emojis
|
|
||||||
.map((emoji) => {
|
|
||||||
const names = emoji.aliases.map(normalizeName);
|
|
||||||
const exact = names.includes(normalizedQuery);
|
|
||||||
const prefix = names.some((name) => name.startsWith(normalizedQuery));
|
|
||||||
const contains = names.some((name) => name.includes(normalizedQuery));
|
|
||||||
return { emoji, rank: exact ? 0 : prefix ? 1 : contains ? 2 : 3 };
|
|
||||||
})
|
|
||||||
.filter(({ rank }) => rank < 3)
|
|
||||||
.sort((a, b) => a.rank - b.rank || a.emoji.name.localeCompare(b.emoji.name))
|
|
||||||
.map(({ emoji }) => emoji);
|
|
||||||
}
|
|
||||||
|
|
@ -1,850 +0,0 @@
|
||||||
import { markdown } from "@codemirror/lang-markdown";
|
|
||||||
import { syntaxTree } from "@codemirror/language";
|
|
||||||
import {
|
|
||||||
acceptCompletion,
|
|
||||||
autocompletion,
|
|
||||||
completionStatus,
|
|
||||||
pickedCompletion,
|
|
||||||
startCompletion,
|
|
||||||
type Completion,
|
|
||||||
type CompletionContext,
|
|
||||||
type CompletionResult,
|
|
||||||
} from "@codemirror/autocomplete";
|
|
||||||
import {
|
|
||||||
EditorState,
|
|
||||||
EditorSelection,
|
|
||||||
Annotation,
|
|
||||||
Compartment,
|
|
||||||
Prec,
|
|
||||||
Transaction,
|
|
||||||
type Extension,
|
|
||||||
type Range,
|
|
||||||
type SelectionRange,
|
|
||||||
} from "@codemirror/state";
|
|
||||||
import {
|
|
||||||
Decoration,
|
|
||||||
EditorView,
|
|
||||||
keymap,
|
|
||||||
placeholder,
|
|
||||||
ViewPlugin,
|
|
||||||
WidgetType,
|
|
||||||
type DecorationSet,
|
|
||||||
type KeyBinding,
|
|
||||||
type ViewUpdate,
|
|
||||||
} from "@codemirror/view";
|
|
||||||
import {
|
|
||||||
defaultKeymap,
|
|
||||||
history,
|
|
||||||
historyKeymap,
|
|
||||||
indentWithTab,
|
|
||||||
} from "@codemirror/commands";
|
|
||||||
import { useEffect, useRef } from "react";
|
|
||||||
import type { CSSProperties } from "react";
|
|
||||||
import { createRoot, type Root } from "react-dom/client";
|
|
||||||
|
|
||||||
import { collectInlineRanges, ensureMarkdownStyles } from "./markdown";
|
|
||||||
import Emoji, {
|
|
||||||
findEmojiShortcodes,
|
|
||||||
getEmojiUrl,
|
|
||||||
resolveEmoji,
|
|
||||||
searchEmojis,
|
|
||||||
} from "./emoji";
|
|
||||||
|
|
||||||
export const MAX_RENDERED_EMOJI_OPTIONS = 100;
|
|
||||||
|
|
||||||
export type InputController = {
|
|
||||||
focus: () => void;
|
|
||||||
hasFocus: () => boolean;
|
|
||||||
insertText: (text: string) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type InputProps = {
|
|
||||||
ref?: HTMLDivElement;
|
|
||||||
placeholder?: string;
|
|
||||||
value: string;
|
|
||||||
setValue: (value: string) => void;
|
|
||||||
onSubmit?: () => void;
|
|
||||||
invertEnterBehavior?: boolean;
|
|
||||||
styled?: boolean;
|
|
||||||
fontSize?: CSSProperties["fontSize"];
|
|
||||||
paddingX?: CSSProperties["padding"];
|
|
||||||
paddingY?: CSSProperties["padding"];
|
|
||||||
className?: string;
|
|
||||||
emojiFrequencies?: Readonly<Record<string, number>>;
|
|
||||||
onEmojiSelect?: (shortcode: string) => void;
|
|
||||||
autoFocus?: boolean;
|
|
||||||
onControllerChange?: (controller: InputController | null) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
function toCssLength(value: CSSProperties["padding"]): string | undefined {
|
|
||||||
if (value === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return typeof value === "number" ? `${value}px` : value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toCssPadding(
|
|
||||||
vertical: CSSProperties["padding"],
|
|
||||||
horizontal: CSSProperties["padding"],
|
|
||||||
styled: boolean,
|
|
||||||
): string {
|
|
||||||
const defaultVertical = styled ? "0.25rem" : "0";
|
|
||||||
const defaultHorizontal = styled ? "0.625rem" : "0";
|
|
||||||
|
|
||||||
return `${toCssLength(vertical) ?? defaultVertical} ${toCssLength(horizontal) ?? defaultHorizontal}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hiddenTokenDecoration = Decoration.mark({ class: "tm-md-hidden-token" });
|
|
||||||
const strongDecoration = Decoration.mark({ class: "tm-md-strong" });
|
|
||||||
const emDecoration = Decoration.mark({ class: "tm-md-em" });
|
|
||||||
const delDecoration = Decoration.mark({ class: "tm-md-del" });
|
|
||||||
const codeDecoration = Decoration.mark({ class: "tm-md-code" });
|
|
||||||
const linkDecoration = Decoration.mark({ class: "tm-md-link" });
|
|
||||||
const codeLineDecoration = Decoration.line({ class: "tm-md-code-line" });
|
|
||||||
const externalValueSync = Annotation.define<boolean>();
|
|
||||||
const widgetRoots = new WeakMap<HTMLElement, Root>();
|
|
||||||
|
|
||||||
type EmojiRange = {
|
|
||||||
from: number;
|
|
||||||
shortcode: string;
|
|
||||||
to: number;
|
|
||||||
url: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
class EmojiWidget extends WidgetType {
|
|
||||||
readonly shortcode: string;
|
|
||||||
readonly url: string;
|
|
||||||
|
|
||||||
constructor(shortcode: string, url: string) {
|
|
||||||
super();
|
|
||||||
this.shortcode = shortcode;
|
|
||||||
this.url = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
eq(other: EmojiWidget) {
|
|
||||||
return other.shortcode === this.shortcode && other.url === this.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
toDOM() {
|
|
||||||
const container = document.createElement("span");
|
|
||||||
const root = createRoot(container);
|
|
||||||
root.render(
|
|
||||||
<Emoji className="tm-md-editor-emoji" shortcode={this.shortcode} />,
|
|
||||||
);
|
|
||||||
widgetRoots.set(container, root);
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy(dom: HTMLElement) {
|
|
||||||
widgetRoots.get(dom)?.unmount();
|
|
||||||
widgetRoots.delete(dom);
|
|
||||||
}
|
|
||||||
|
|
||||||
ignoreEvent() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function codeRanges(state: EditorState) {
|
|
||||||
const ranges: Array<{ from: number; to: number }> = [];
|
|
||||||
|
|
||||||
syntaxTree(state).iterate({
|
|
||||||
enter(node) {
|
|
||||||
if (
|
|
||||||
node.name === "InlineCode" ||
|
|
||||||
node.name === "FencedCode" ||
|
|
||||||
node.name === "CodeBlock"
|
|
||||||
) {
|
|
||||||
ranges.push({ from: node.from, to: node.to });
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return ranges;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function findEmojiRanges(state: EditorState): EmojiRange[] {
|
|
||||||
const document = state.doc.toString();
|
|
||||||
const excluded = codeRanges(state);
|
|
||||||
const ranges: EmojiRange[] = [];
|
|
||||||
|
|
||||||
for (const match of findEmojiShortcodes(document)) {
|
|
||||||
const { from, to } = match;
|
|
||||||
const inCode = excluded.some((range) => from < range.to && to > range.from);
|
|
||||||
const emoji = inCode ? undefined : match.emoji;
|
|
||||||
const url = emoji ? getEmojiUrl(emoji.shortcode) : undefined;
|
|
||||||
|
|
||||||
if (emoji && url) {
|
|
||||||
ranges.push({ from, shortcode: emoji.shortcode, to, url });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ranges;
|
|
||||||
}
|
|
||||||
|
|
||||||
class EmojiPluginValue {
|
|
||||||
decorations: DecorationSet;
|
|
||||||
ranges: EmojiRange[];
|
|
||||||
|
|
||||||
constructor(view: EditorView) {
|
|
||||||
this.ranges = findEmojiRanges(view.state);
|
|
||||||
this.decorations = this.buildDecorations();
|
|
||||||
}
|
|
||||||
|
|
||||||
update(update: ViewUpdate) {
|
|
||||||
if (
|
|
||||||
update.docChanged ||
|
|
||||||
syntaxTree(update.startState) !== syntaxTree(update.state)
|
|
||||||
) {
|
|
||||||
this.ranges = findEmojiRanges(update.state);
|
|
||||||
this.decorations = this.buildDecorations();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildDecorations() {
|
|
||||||
return Decoration.set(
|
|
||||||
this.ranges.map((range) =>
|
|
||||||
Decoration.replace({
|
|
||||||
inclusive: false,
|
|
||||||
widget: new EmojiWidget(range.shortcode, range.url),
|
|
||||||
}).range(range.from, range.to),
|
|
||||||
),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const emojiDecorations = ViewPlugin.fromClass(EmojiPluginValue, {
|
|
||||||
decorations: (instance) => instance.decorations,
|
|
||||||
provide: (plugin) =>
|
|
||||||
EditorView.atomicRanges.of(
|
|
||||||
(view) => view.plugin(plugin)?.decorations ?? Decoration.none,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
function deleteEmoji(view: EditorView, direction: "backward" | "forward") {
|
|
||||||
const ranges = view.plugin(emojiDecorations)?.ranges ?? [];
|
|
||||||
const deletions: Array<{ from: number; to: number }> = [];
|
|
||||||
|
|
||||||
for (const selection of view.state.selection.ranges) {
|
|
||||||
if (selection.empty) {
|
|
||||||
const emoji = ranges.find((range) =>
|
|
||||||
direction === "backward"
|
|
||||||
? selection.from > range.from && selection.from <= range.to
|
|
||||||
: selection.from >= range.from && selection.from < range.to,
|
|
||||||
);
|
|
||||||
if (emoji) deletions.push({ from: emoji.from, to: emoji.to });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let from = selection.from;
|
|
||||||
let to = selection.to;
|
|
||||||
let changed = false;
|
|
||||||
|
|
||||||
for (const emoji of ranges) {
|
|
||||||
if (from < emoji.to && to > emoji.from) {
|
|
||||||
from = Math.min(from, emoji.from);
|
|
||||||
to = Math.max(to, emoji.to);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changed) deletions.push({ from, to });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deletions.length === 0) return false;
|
|
||||||
|
|
||||||
const merged = deletions
|
|
||||||
.sort((a, b) => a.from - b.from)
|
|
||||||
.reduce<Array<{ from: number; to: number }>>((result, deletion) => {
|
|
||||||
const previous = result.at(-1);
|
|
||||||
if (previous && deletion.from <= previous.to) {
|
|
||||||
previous.to = Math.max(previous.to, deletion.to);
|
|
||||||
} else {
|
|
||||||
result.push({ ...deletion });
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
view.dispatch({
|
|
||||||
changes: merged.map((range) => ({ from: range.from, to: range.to })),
|
|
||||||
selection: EditorSelection.cursor(merged[0].from),
|
|
||||||
scrollIntoView: true,
|
|
||||||
userEvent: direction === "backward" ? "delete.backward" : "delete.forward",
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds markdown styling decorations every time the document or cursor selection changes.
|
|
||||||
* Token delimiters are hidden unless the cursor is currently intersecting that token range.
|
|
||||||
*/
|
|
||||||
const markdownDecorations = ViewPlugin.fromClass(
|
|
||||||
class {
|
|
||||||
decorations: DecorationSet;
|
|
||||||
|
|
||||||
constructor(view: EditorView) {
|
|
||||||
this.decorations = buildDecorations(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
update(update: ViewUpdate) {
|
|
||||||
if (update.docChanged || update.selectionSet || update.viewportChanged) {
|
|
||||||
this.decorations = buildDecorations(update.view);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
decorations: (instance: { decorations: DecorationSet }) =>
|
|
||||||
instance.decorations,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes Input.
|
|
||||||
* @param props Parameter props.
|
|
||||||
* @returns unknown.
|
|
||||||
*/
|
|
||||||
export default function Input(props: InputProps) {
|
|
||||||
ensureMarkdownStyles();
|
|
||||||
|
|
||||||
const shellClassName = props.styled
|
|
||||||
? "min-h-8 w-full min-w-0 rounded-lg border border-input bg-transparent text-base transition-colors outline-none placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40"
|
|
||||||
: "";
|
|
||||||
|
|
||||||
const elementRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
const viewRef = useRef<EditorView | undefined>(undefined);
|
|
||||||
const setValueRef = useRef(props.setValue);
|
|
||||||
const onSubmitRef = useRef<InputProps["onSubmit"]>(props.onSubmit);
|
|
||||||
const onEmojiSelectRef = useRef<InputProps["onEmojiSelect"]>(
|
|
||||||
props.onEmojiSelect,
|
|
||||||
);
|
|
||||||
const invertEnterBehaviorRef = useRef(Boolean(props.invertEnterBehavior));
|
|
||||||
const completionCompartmentRef = useRef<Compartment | null>(null);
|
|
||||||
completionCompartmentRef.current ??= new Compartment();
|
|
||||||
const completionCompartment = completionCompartmentRef.current;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setValueRef.current = props.setValue;
|
|
||||||
onSubmitRef.current = props.onSubmit;
|
|
||||||
onEmojiSelectRef.current = props.onEmojiSelect;
|
|
||||||
invertEnterBehaviorRef.current = Boolean(props.invertEnterBehavior);
|
|
||||||
}, [
|
|
||||||
props.onEmojiSelect,
|
|
||||||
props.onSubmit,
|
|
||||||
props.invertEnterBehavior,
|
|
||||||
props.setValue,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!elementRef.current) return;
|
|
||||||
|
|
||||||
const state = EditorState.create({
|
|
||||||
doc: props.value,
|
|
||||||
extensions: createEditorExtensions(
|
|
||||||
(value) => {
|
|
||||||
setValueRef.current(value);
|
|
||||||
},
|
|
||||||
() => props.placeholder,
|
|
||||||
() => invertEnterBehaviorRef.current,
|
|
||||||
() => onSubmitRef.current?.(),
|
|
||||||
completionCompartment,
|
|
||||||
props.emojiFrequencies,
|
|
||||||
(shortcode) => onEmojiSelectRef.current?.(shortcode),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
viewRef.current = new EditorView({
|
|
||||||
state,
|
|
||||||
parent: elementRef.current,
|
|
||||||
});
|
|
||||||
props.onControllerChange?.({
|
|
||||||
focus: () => viewRef.current?.contentDOM.focus({ preventScroll: true }),
|
|
||||||
hasFocus: () => viewRef.current?.hasFocus ?? false,
|
|
||||||
insertText: (text) => {
|
|
||||||
const editor = viewRef.current;
|
|
||||||
if (!editor) return;
|
|
||||||
editor.dispatch({
|
|
||||||
...editor.state.replaceSelection(text),
|
|
||||||
annotations: Transaction.userEvent.of("input.type"),
|
|
||||||
scrollIntoView: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (props.autoFocus) viewRef.current.focus();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
props.onControllerChange?.(null);
|
|
||||||
viewRef.current?.destroy();
|
|
||||||
viewRef.current = undefined;
|
|
||||||
};
|
|
||||||
// Run once to initialize/destroy the editor instance.
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const editor = viewRef.current;
|
|
||||||
if (!editor) return;
|
|
||||||
|
|
||||||
const next = props.value;
|
|
||||||
const current = editor.state.doc.toString();
|
|
||||||
|
|
||||||
if (next === current) return;
|
|
||||||
|
|
||||||
editor.dispatch({
|
|
||||||
changes: {
|
|
||||||
from: 0,
|
|
||||||
to: current.length,
|
|
||||||
insert: next,
|
|
||||||
},
|
|
||||||
annotations: [
|
|
||||||
externalValueSync.of(true),
|
|
||||||
Transaction.addToHistory.of(false),
|
|
||||||
],
|
|
||||||
filter: false,
|
|
||||||
});
|
|
||||||
}, [props.value]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const editor = viewRef.current;
|
|
||||||
const compartment = completionCompartmentRef.current;
|
|
||||||
if (!editor || !compartment) return;
|
|
||||||
|
|
||||||
const wasActive = completionStatus(editor.state) === "active";
|
|
||||||
editor.dispatch({
|
|
||||||
effects: compartment.reconfigure(
|
|
||||||
createEmojiAutocomplete(props.emojiFrequencies, (shortcode) =>
|
|
||||||
onEmojiSelectRef.current?.(shortcode),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
if (wasActive) startCompletion(editor);
|
|
||||||
}, [props.emojiFrequencies]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={elementRef}
|
|
||||||
className={`tm-md-root ${shellClassName} ${props.className ?? ""}`}
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
fontSize: props.fontSize ?? "1rem",
|
|
||||||
"--tm-md-content-padding": toCssPadding(
|
|
||||||
props.paddingY,
|
|
||||||
props.paddingX,
|
|
||||||
Boolean(props.styled),
|
|
||||||
),
|
|
||||||
} as CSSProperties & {
|
|
||||||
"--tm-md-content-padding"?: string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes createEditorExtensions.
|
|
||||||
* @param onChange Parameter onChange.
|
|
||||||
* @param getPlaceholder Parameter getPlaceholder.
|
|
||||||
* @param getInvertEnterBehavior Parameter getInvertEnterBehavior.
|
|
||||||
* @param onSubmit Parameter onSubmit.
|
|
||||||
* @returns Extension[].
|
|
||||||
*/
|
|
||||||
function createEditorExtensions(
|
|
||||||
onChange: (value: string) => void,
|
|
||||||
getPlaceholder: () => string | undefined,
|
|
||||||
getInvertEnterBehavior: () => boolean,
|
|
||||||
onSubmit: () => void,
|
|
||||||
completionCompartment: Compartment,
|
|
||||||
emojiFrequencies: Readonly<Record<string, number>> | undefined,
|
|
||||||
onEmojiSelect: (shortcode: string) => void,
|
|
||||||
): Extension[] {
|
|
||||||
const editorKeymap = [
|
|
||||||
...defaultKeymap,
|
|
||||||
...historyKeymap,
|
|
||||||
indentWithTab,
|
|
||||||
] as unknown as readonly KeyBinding[];
|
|
||||||
|
|
||||||
const customEnterKeymap = keymap.of([
|
|
||||||
{
|
|
||||||
key: "Shift-Enter",
|
|
||||||
run: (view) => {
|
|
||||||
if (completionStatus(view.state) === "active") {
|
|
||||||
return acceptCompletion(view);
|
|
||||||
}
|
|
||||||
if (!getInvertEnterBehavior()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
onSubmit();
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "Enter",
|
|
||||||
run: (view) => {
|
|
||||||
if (completionStatus(view.state) === "active") {
|
|
||||||
return acceptCompletion(view);
|
|
||||||
}
|
|
||||||
if (getInvertEnterBehavior()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
onSubmit();
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
const completionTabKeymap = keymap.of([
|
|
||||||
{
|
|
||||||
key: "Tab",
|
|
||||||
run: (view) =>
|
|
||||||
completionStatus(view.state) === "active"
|
|
||||||
? acceptCompletion(view)
|
|
||||||
: false,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
const emojiDeletionKeymap = keymap.of([
|
|
||||||
{
|
|
||||||
key: "Backspace",
|
|
||||||
run: (view) => deleteEmoji(view, "backward"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "Delete",
|
|
||||||
run: (view) => deleteEmoji(view, "forward"),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
return [
|
|
||||||
history(),
|
|
||||||
markdown(),
|
|
||||||
completionCompartment.of(
|
|
||||||
createEmojiAutocomplete(emojiFrequencies, onEmojiSelect),
|
|
||||||
),
|
|
||||||
emojiDecorations,
|
|
||||||
keymap.of(editorKeymap),
|
|
||||||
Prec.highest(completionTabKeymap),
|
|
||||||
Prec.highest(emojiDeletionKeymap),
|
|
||||||
Prec.highest(customEnterKeymap),
|
|
||||||
EditorView.lineWrapping,
|
|
||||||
placeholder(getPlaceholder() ?? ""),
|
|
||||||
EditorView.updateListener.of((update: ViewUpdate) => {
|
|
||||||
if (!update.docChanged) return;
|
|
||||||
if (
|
|
||||||
update.transactions.some(
|
|
||||||
(transaction) => transaction.annotation(externalValueSync) === true,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onChange(update.state.doc.toString());
|
|
||||||
}),
|
|
||||||
EditorView.theme({
|
|
||||||
"&": {
|
|
||||||
fontSize: "inherit",
|
|
||||||
},
|
|
||||||
"&.cm-editor": {
|
|
||||||
width: "100%",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
EditorView.editorAttributes.of({
|
|
||||||
class: "tm-md-editor",
|
|
||||||
spellcheck: "true",
|
|
||||||
"aria-label": "Markdown input",
|
|
||||||
}),
|
|
||||||
markdownDecorations,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
function createEmojiAutocomplete(
|
|
||||||
frequencies: Readonly<Record<string, number>> | undefined,
|
|
||||||
onEmojiSelect: (shortcode: string) => void,
|
|
||||||
) {
|
|
||||||
return autocompletion({
|
|
||||||
activateOnTyping: true,
|
|
||||||
addToOptions: [
|
|
||||||
{
|
|
||||||
position: 20,
|
|
||||||
render(completion) {
|
|
||||||
const container = document.createElement("span");
|
|
||||||
createRoot(container).render(
|
|
||||||
<Emoji
|
|
||||||
className="tm-md-completion-emoji"
|
|
||||||
shortcode={completion.label}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
return container;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
maxRenderedOptions: MAX_RENDERED_EMOJI_OPTIONS,
|
|
||||||
override: [createEmojiCompletionSource(frequencies, onEmojiSelect)],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizedFrequencies(
|
|
||||||
frequencies: Readonly<Record<string, number>> | undefined,
|
|
||||||
) {
|
|
||||||
const normalized = new Map<string, number>();
|
|
||||||
for (const [value, frequency] of Object.entries(frequencies ?? {})) {
|
|
||||||
const shortcode = resolveEmoji(value)?.shortcode;
|
|
||||||
if (!shortcode || !Number.isFinite(frequency) || frequency <= 0) continue;
|
|
||||||
normalized.set(shortcode, (normalized.get(shortcode) ?? 0) + frequency);
|
|
||||||
}
|
|
||||||
return normalized;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createEmojiCompletionSource(
|
|
||||||
frequencies?: Readonly<Record<string, number>>,
|
|
||||||
onEmojiSelect: (shortcode: string) => void = () => undefined,
|
|
||||||
) {
|
|
||||||
const normalized = normalizedFrequencies(frequencies);
|
|
||||||
const maxFrequency = Math.max(0, ...normalized.values());
|
|
||||||
|
|
||||||
return (context: CompletionContext): CompletionResult | null => {
|
|
||||||
const token = context.matchBefore(/:[a-z0-9_+-]*$/i);
|
|
||||||
if (!token) return null;
|
|
||||||
if (
|
|
||||||
codeRanges(context.state).some(
|
|
||||||
(range) => token.from < range.to && token.to > range.from,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const characterBefore = context.state.sliceDoc(
|
|
||||||
Math.max(0, token.from - 1),
|
|
||||||
token.from,
|
|
||||||
);
|
|
||||||
if (characterBefore && /[a-z0-9_]/i.test(characterBefore)) return null;
|
|
||||||
|
|
||||||
const query = token.text.slice(1).toLowerCase();
|
|
||||||
const options: Completion[] = searchEmojis(query)
|
|
||||||
.map((emoji) => {
|
|
||||||
const aliases = emoji.aliases.map((alias) => alias.toLowerCase());
|
|
||||||
const matchedAlias =
|
|
||||||
aliases.find((alias) => alias === query) ??
|
|
||||||
aliases.find((alias) => alias.startsWith(query)) ??
|
|
||||||
aliases.find((alias) => alias.includes(query)) ??
|
|
||||||
emoji.name;
|
|
||||||
const relevance = !query
|
|
||||||
? 0
|
|
||||||
: matchedAlias === query
|
|
||||||
? 80
|
|
||||||
: matchedAlias.startsWith(query)
|
|
||||||
? 40
|
|
||||||
: 0;
|
|
||||||
const frequency = normalized.get(emoji.shortcode) ?? 0;
|
|
||||||
const usage =
|
|
||||||
maxFrequency > 0
|
|
||||||
? (15 * Math.log1p(frequency)) / Math.log1p(maxFrequency)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
return {
|
|
||||||
apply(view, completion, from, to) {
|
|
||||||
view.dispatch({
|
|
||||||
annotations: pickedCompletion.of(completion),
|
|
||||||
changes: { from, insert: `${emoji.shortcode} `, to },
|
|
||||||
selection: EditorSelection.cursor(
|
|
||||||
from + emoji.shortcode.length + 1,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
onEmojiSelect(emoji.shortcode);
|
|
||||||
},
|
|
||||||
boost: relevance + usage,
|
|
||||||
displayLabel: emoji.shortcode,
|
|
||||||
label: `:${matchedAlias}:`,
|
|
||||||
type: "text",
|
|
||||||
frequency,
|
|
||||||
relevance,
|
|
||||||
} satisfies Completion & { frequency: number; relevance: number };
|
|
||||||
})
|
|
||||||
.sort(
|
|
||||||
(a, b) =>
|
|
||||||
b.relevance - a.relevance ||
|
|
||||||
b.frequency - a.frequency ||
|
|
||||||
(a.displayLabel ?? a.label).localeCompare(b.displayLabel ?? b.label),
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
from: token.from,
|
|
||||||
options,
|
|
||||||
validFor: /^:[a-z0-9_+-]*$/i,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const emojiCompletionSource = createEmojiCompletionSource();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes buildDecorations.
|
|
||||||
* @param view Parameter view.
|
|
||||||
* @returns DecorationSet.
|
|
||||||
*/
|
|
||||||
function buildDecorations(view: EditorView): DecorationSet {
|
|
||||||
const builder: Range<Decoration>[] = [];
|
|
||||||
const selections = view.state.selection.ranges.map(
|
|
||||||
(range: SelectionRange) => ({
|
|
||||||
from: range.from,
|
|
||||||
to: range.to,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
let codeFenceOpen = false;
|
|
||||||
|
|
||||||
for (
|
|
||||||
let lineNumber = 1;
|
|
||||||
lineNumber <= view.state.doc.lines;
|
|
||||||
lineNumber += 1
|
|
||||||
) {
|
|
||||||
const line = view.state.doc.line(lineNumber);
|
|
||||||
const text = line.text;
|
|
||||||
const lineFrom = line.from;
|
|
||||||
const trimmed = text.trim();
|
|
||||||
|
|
||||||
const fence = text.match(/^```\s*([^`]*)$/);
|
|
||||||
if (fence) {
|
|
||||||
const ticksStart = lineFrom + text.indexOf("```");
|
|
||||||
const ticksEnd = ticksStart + 3;
|
|
||||||
addHiddenToken(builder, selections, { from: ticksStart, to: ticksEnd });
|
|
||||||
if (trimmed.length > 3) {
|
|
||||||
addHiddenToken(builder, selections, {
|
|
||||||
from: ticksEnd,
|
|
||||||
to: line.to,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
codeFenceOpen = !codeFenceOpen;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (codeFenceOpen) {
|
|
||||||
builder.push(codeLineDecoration.range(lineFrom));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const heading = text.match(/^(#{1,6})\s+/);
|
|
||||||
if (heading) {
|
|
||||||
const markerLength = heading[0].length;
|
|
||||||
addHiddenToken(builder, selections, {
|
|
||||||
from: lineFrom,
|
|
||||||
to: lineFrom + markerLength,
|
|
||||||
});
|
|
||||||
|
|
||||||
const level = heading[1].length;
|
|
||||||
const headingClass = Decoration.mark({
|
|
||||||
class: `tm-md-heading tm-md-h${String(level)}`,
|
|
||||||
});
|
|
||||||
const contentFrom = lineFrom + markerLength;
|
|
||||||
if (contentFrom < line.to) {
|
|
||||||
builder.push(headingClass.range(contentFrom, line.to));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const quote = text.match(/^>\s?/);
|
|
||||||
if (quote) {
|
|
||||||
addHiddenToken(builder, selections, {
|
|
||||||
from: lineFrom,
|
|
||||||
to: lineFrom + quote[0].length,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const unordered = text.match(/^(\s*)([-+*])\s+(?:\[( |x|X)\]\s+)?/);
|
|
||||||
if (unordered) {
|
|
||||||
const markerStart = lineFrom + unordered[1].length;
|
|
||||||
const markerEnd = markerStart + unordered[2].length + 1;
|
|
||||||
addHiddenToken(builder, selections, { from: markerStart, to: markerEnd });
|
|
||||||
|
|
||||||
const checkbox = unordered[0].match(/\[( |x|X)\]\s+$/);
|
|
||||||
if (checkbox) {
|
|
||||||
const checkboxStart = lineFrom + unordered[0].lastIndexOf("[");
|
|
||||||
addHiddenToken(builder, selections, {
|
|
||||||
from: checkboxStart,
|
|
||||||
to: checkboxStart + checkbox[0].length,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ordered = text.match(/^(\s*)(\d+\.)\s+/);
|
|
||||||
if (ordered) {
|
|
||||||
const markerStart = lineFrom + ordered[1].length;
|
|
||||||
addHiddenToken(builder, selections, {
|
|
||||||
from: markerStart,
|
|
||||||
to: markerStart + ordered[2].length + 1,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (/^(?:\*\s*){3,}$|^(?:-\s*){3,}$|^(?:_\s*){3,}$/.test(trimmed)) {
|
|
||||||
if (lineFrom < line.to) {
|
|
||||||
builder.push(
|
|
||||||
Decoration.mark({ class: "tm-md-hr" }).range(lineFrom, line.to),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tableSeparator = /^\|?\s*:?-{3,}:?\s*(?:\|\s*:?-{3,}:?\s*)+\|?$/.test(
|
|
||||||
text,
|
|
||||||
);
|
|
||||||
if (tableSeparator) {
|
|
||||||
if (lineFrom < line.to) {
|
|
||||||
builder.push(
|
|
||||||
Decoration.mark({ class: "tm-md-del" }).range(lineFrom, line.to),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { styleRanges, tokenRanges } = collectInlineRanges(text, lineFrom);
|
|
||||||
|
|
||||||
for (const range of styleRanges) {
|
|
||||||
if (range.from >= range.to) continue;
|
|
||||||
|
|
||||||
if (range.className === "tm-md-strong") {
|
|
||||||
builder.push(strongDecoration.range(range.from, range.to));
|
|
||||||
} else if (range.className === "tm-md-em") {
|
|
||||||
builder.push(emDecoration.range(range.from, range.to));
|
|
||||||
} else if (range.className === "tm-md-del") {
|
|
||||||
builder.push(delDecoration.range(range.from, range.to));
|
|
||||||
} else if (range.className === "tm-md-code") {
|
|
||||||
builder.push(codeDecoration.range(range.from, range.to));
|
|
||||||
} else if (range.className === "tm-md-link") {
|
|
||||||
builder.push(linkDecoration.range(range.from, range.to));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const token of tokenRanges) {
|
|
||||||
addHiddenToken(builder, selections, token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Decoration.set(builder, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keeps markdown syntax visible only when user selection intersects the token.
|
|
||||||
* This preserves cursor predictability and cross-token selection while still hiding syntax during reading.
|
|
||||||
*/
|
|
||||||
function addHiddenToken(
|
|
||||||
builder: Range<Decoration>[],
|
|
||||||
selections: ReadonlyArray<{ from: number; to: number }>,
|
|
||||||
token: {
|
|
||||||
from: number;
|
|
||||||
to: number;
|
|
||||||
},
|
|
||||||
): void {
|
|
||||||
if (token.from >= token.to) return;
|
|
||||||
|
|
||||||
const overlapsSelection = selections.some((selection) => {
|
|
||||||
const selectionFrom = Math.min(selection.from, selection.to);
|
|
||||||
const selectionTo = Math.max(selection.from, selection.to);
|
|
||||||
|
|
||||||
if (selectionFrom === selectionTo) {
|
|
||||||
return selectionFrom >= token.from && selectionFrom <= token.to;
|
|
||||||
}
|
|
||||||
|
|
||||||
return selectionFrom < token.to && selectionTo > token.from;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (overlapsSelection) return;
|
|
||||||
builder.push(hiddenTokenDecoration.range(token.from, token.to));
|
|
||||||
}
|
|
||||||
|
|
@ -1,815 +0,0 @@
|
||||||
import {
|
|
||||||
Fragment,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
type ReactElement,
|
|
||||||
type ReactNode,
|
|
||||||
} from "react";
|
|
||||||
import Emoji from "./emoji";
|
|
||||||
import { findEmojiShortcodes } from "./emojiData";
|
|
||||||
import { Check } from "lucide-react";
|
|
||||||
|
|
||||||
type InlineNode =
|
|
||||||
| { type: "text"; value: string }
|
|
||||||
| { type: "emoji"; shortcode: string }
|
|
||||||
| { type: "strong"; value: string }
|
|
||||||
| { type: "em"; value: string }
|
|
||||||
| { type: "del"; value: string }
|
|
||||||
| { type: "code"; value: string }
|
|
||||||
| { type: "link"; label: string; href: string }
|
|
||||||
| { type: "image"; alt: string; src: string };
|
|
||||||
|
|
||||||
type InlineDecorationRange = {
|
|
||||||
from: number;
|
|
||||||
to: number;
|
|
||||||
className: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type InlineTokenRange = {
|
|
||||||
from: number;
|
|
||||||
to: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ListItem = {
|
|
||||||
text: string;
|
|
||||||
checked: boolean | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TableBlock = {
|
|
||||||
type: "table";
|
|
||||||
headers: string[];
|
|
||||||
rows: string[][];
|
|
||||||
};
|
|
||||||
|
|
||||||
type MarkdownBlock =
|
|
||||||
| {
|
|
||||||
type: "paragraph";
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "heading";
|
|
||||||
level: number;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "hr";
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "blockquote";
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "code";
|
|
||||||
language: string;
|
|
||||||
code: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "list";
|
|
||||||
ordered: boolean;
|
|
||||||
items: ListItem[];
|
|
||||||
}
|
|
||||||
| TableBlock;
|
|
||||||
|
|
||||||
const INLINE_TOKEN_REGEX =
|
|
||||||
/!\[([^\]]*)\]\(([^)\s]+(?:\s+"[^"]*")?)\)|\[([^\]]+)\]\(([^)\s]+(?:\s+"[^"]*")?)\)|`([^`\n]+)`|~~([^~\n]+)~~|\*\*([^*\n]+)\*\*|__([^_\n]+)__|\*([^*\n]+)\*|(?<![a-zA-Z0-9:])_([^_\n]+)_(?![a-zA-Z0-9:])/g;
|
|
||||||
|
|
||||||
function CopiedIndicator({
|
|
||||||
block,
|
|
||||||
visible,
|
|
||||||
}: {
|
|
||||||
block: boolean;
|
|
||||||
visible: boolean;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
className={`pointer-events-none inline-flex align-middle text-foreground transition-opacity duration-200 ease-out ${block ? "mt-3 shrink-0" : "ml-1"} ${visible ? "opacity-100" : "opacity-0"}`}
|
|
||||||
aria-live="polite"
|
|
||||||
aria-hidden={!visible}
|
|
||||||
>
|
|
||||||
<Check className="size-3.5" />
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CopyableCode({
|
|
||||||
block = false,
|
|
||||||
language,
|
|
||||||
value,
|
|
||||||
}: {
|
|
||||||
block?: boolean;
|
|
||||||
language?: string;
|
|
||||||
value: string;
|
|
||||||
}) {
|
|
||||||
const [copied, setCopied] = useState(false);
|
|
||||||
const copiedTimer = useRef<ReturnType<typeof setTimeout> | undefined>(
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => () => {
|
|
||||||
clearTimeout(copiedTimer.current);
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
async function copy() {
|
|
||||||
await navigator.clipboard.writeText(value);
|
|
||||||
setCopied(true);
|
|
||||||
clearTimeout(copiedTimer.current);
|
|
||||||
copiedTimer.current = setTimeout(() => setCopied(false), 1200);
|
|
||||||
}
|
|
||||||
|
|
||||||
const code = (
|
|
||||||
<code
|
|
||||||
className={block ? "" : "bg-card border p-0.5 rounded text-sm"}
|
|
||||||
data-language={language}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
onClick={() => void copy()}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
if (event.key !== "Enter" && event.key !== " ") return;
|
|
||||||
event.preventDefault();
|
|
||||||
void copy();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{value}
|
|
||||||
</code>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (block) {
|
|
||||||
return (
|
|
||||||
<div className="flex min-w-0 items-start gap-1">
|
|
||||||
<pre className="tm-md-pre min-w-0 flex-1">{code}</pre>
|
|
||||||
<CopiedIndicator block visible={copied} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{code}
|
|
||||||
<CopiedIndicator block={false} visible={copied} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes parseInlineNodes.
|
|
||||||
* @param input Parameter input.
|
|
||||||
* @returns InlineNode[].
|
|
||||||
*/
|
|
||||||
export function parseInlineNodes(input: string): InlineNode[] {
|
|
||||||
const nodes: InlineNode[] = [];
|
|
||||||
|
|
||||||
let cursor = 0;
|
|
||||||
let match = INLINE_TOKEN_REGEX.exec(input);
|
|
||||||
|
|
||||||
while (match) {
|
|
||||||
const index = match.index;
|
|
||||||
const raw = match[0];
|
|
||||||
|
|
||||||
if (index > cursor) {
|
|
||||||
nodes.push(...parseEmojiText(input.slice(cursor, index)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (match[1] !== undefined && match[2] !== undefined) {
|
|
||||||
nodes.push({ type: "image", alt: match[1], src: normalizeUrl(match[2]) });
|
|
||||||
} else if (match[3] !== undefined && match[4] !== undefined) {
|
|
||||||
nodes.push({
|
|
||||||
type: "link",
|
|
||||||
label: match[3],
|
|
||||||
href: normalizeUrl(match[4]),
|
|
||||||
});
|
|
||||||
} else if (match[5] !== undefined) {
|
|
||||||
nodes.push({ type: "code", value: match[5] });
|
|
||||||
} else if (match[6] !== undefined) {
|
|
||||||
nodes.push({ type: "del", value: match[6] });
|
|
||||||
} else if (match[7] !== undefined || match[8] !== undefined) {
|
|
||||||
nodes.push({ type: "strong", value: match[7] ?? match[8] ?? "" });
|
|
||||||
} else if (match[9] !== undefined || match[10] !== undefined) {
|
|
||||||
nodes.push({ type: "em", value: match[9] ?? match[10] ?? "" });
|
|
||||||
} else {
|
|
||||||
nodes.push(...parseEmojiText(raw));
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor = index + raw.length;
|
|
||||||
match = INLINE_TOKEN_REGEX.exec(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cursor < input.length) {
|
|
||||||
nodes.push(...parseEmojiText(input.slice(cursor)));
|
|
||||||
}
|
|
||||||
|
|
||||||
INLINE_TOKEN_REGEX.lastIndex = 0;
|
|
||||||
return nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseEmojiText(input: string): InlineNode[] {
|
|
||||||
const nodes: InlineNode[] = [];
|
|
||||||
let cursor = 0;
|
|
||||||
|
|
||||||
for (const match of findEmojiShortcodes(input)) {
|
|
||||||
if (match.from > cursor) {
|
|
||||||
nodes.push({ type: "text", value: input.slice(cursor, match.from) });
|
|
||||||
}
|
|
||||||
|
|
||||||
nodes.push({ type: "emoji", shortcode: match.emoji.shortcode });
|
|
||||||
cursor = match.to;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cursor < input.length) {
|
|
||||||
nodes.push({ type: "text", value: input.slice(cursor) });
|
|
||||||
}
|
|
||||||
|
|
||||||
return nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes collectInlineRanges.
|
|
||||||
* @param input Parameter input.
|
|
||||||
* @param offset Parameter offset.
|
|
||||||
* @returns {
|
|
||||||
styleRanges: InlineDecorationRange[];
|
|
||||||
tokenRanges: InlineTokenRange[];
|
|
||||||
}.
|
|
||||||
*/
|
|
||||||
export function collectInlineRanges(
|
|
||||||
input: string,
|
|
||||||
offset = 0,
|
|
||||||
): {
|
|
||||||
styleRanges: InlineDecorationRange[];
|
|
||||||
tokenRanges: InlineTokenRange[];
|
|
||||||
} {
|
|
||||||
const styleRanges: InlineDecorationRange[] = [];
|
|
||||||
const tokenRanges: InlineTokenRange[] = [];
|
|
||||||
|
|
||||||
let match = INLINE_TOKEN_REGEX.exec(input);
|
|
||||||
|
|
||||||
while (match) {
|
|
||||||
const raw = match[0];
|
|
||||||
const start = offset + match.index;
|
|
||||||
const end = start + raw.length;
|
|
||||||
|
|
||||||
if (match[1] !== undefined && match[2] !== undefined) {
|
|
||||||
const openLength = 2;
|
|
||||||
const closeLength = raw.endsWith(")") ? 1 : 0;
|
|
||||||
|
|
||||||
const imageEnd = start + openLength + match[1].length;
|
|
||||||
tokenRanges.push({ from: start, to: start + openLength });
|
|
||||||
tokenRanges.push({ from: imageEnd, to: imageEnd + 1 });
|
|
||||||
|
|
||||||
const srcStart = imageEnd + 1;
|
|
||||||
const srcEnd = end - closeLength;
|
|
||||||
tokenRanges.push({ from: srcStart, to: srcStart + 1 });
|
|
||||||
tokenRanges.push({ from: srcEnd, to: srcEnd + closeLength });
|
|
||||||
} else if (match[3] !== undefined && match[4] !== undefined) {
|
|
||||||
const label = match[3];
|
|
||||||
const labelStart = start + 1;
|
|
||||||
const labelEnd = labelStart + label.length;
|
|
||||||
|
|
||||||
tokenRanges.push({ from: start, to: start + 1 });
|
|
||||||
tokenRanges.push({ from: labelEnd, to: labelEnd + 1 });
|
|
||||||
tokenRanges.push({ from: labelEnd + 1, to: labelEnd + 2 });
|
|
||||||
tokenRanges.push({ from: end - 1, to: end });
|
|
||||||
|
|
||||||
styleRanges.push({
|
|
||||||
from: labelStart,
|
|
||||||
to: labelEnd,
|
|
||||||
className: "tm-md-link",
|
|
||||||
});
|
|
||||||
} else if (match[5] !== undefined) {
|
|
||||||
const codeStart = start + 1;
|
|
||||||
const codeEnd = end - 1;
|
|
||||||
|
|
||||||
tokenRanges.push({ from: start, to: start + 1 });
|
|
||||||
tokenRanges.push({ from: end - 1, to: end });
|
|
||||||
styleRanges.push({
|
|
||||||
from: codeStart,
|
|
||||||
to: codeEnd,
|
|
||||||
className: "tm-md-code",
|
|
||||||
});
|
|
||||||
} else if (match[6] !== undefined) {
|
|
||||||
const contentStart = start + 2;
|
|
||||||
const contentEnd = end - 2;
|
|
||||||
|
|
||||||
tokenRanges.push({ from: start, to: start + 2 });
|
|
||||||
tokenRanges.push({ from: end - 2, to: end });
|
|
||||||
styleRanges.push({
|
|
||||||
from: contentStart,
|
|
||||||
to: contentEnd,
|
|
||||||
className: "tm-md-del",
|
|
||||||
});
|
|
||||||
} else if (match[7] !== undefined || match[8] !== undefined) {
|
|
||||||
const contentStart = start + 2;
|
|
||||||
const contentEnd = end - 2;
|
|
||||||
|
|
||||||
tokenRanges.push({ from: start, to: start + 2 });
|
|
||||||
tokenRanges.push({ from: end - 2, to: end });
|
|
||||||
styleRanges.push({
|
|
||||||
from: contentStart,
|
|
||||||
to: contentEnd,
|
|
||||||
className: "tm-md-strong",
|
|
||||||
});
|
|
||||||
} else if (match[9] !== undefined || match[10] !== undefined) {
|
|
||||||
const contentStart = start + 1;
|
|
||||||
const contentEnd = end - 1;
|
|
||||||
|
|
||||||
tokenRanges.push({ from: start, to: start + 1 });
|
|
||||||
tokenRanges.push({ from: end - 1, to: end });
|
|
||||||
styleRanges.push({
|
|
||||||
from: contentStart,
|
|
||||||
to: contentEnd,
|
|
||||||
className: "tm-md-em",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
match = INLINE_TOKEN_REGEX.exec(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
INLINE_TOKEN_REGEX.lastIndex = 0;
|
|
||||||
return { styleRanges, tokenRanges };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes parseMarkdownBlocks.
|
|
||||||
* @param markdown Parameter markdown.
|
|
||||||
* @returns MarkdownBlock[].
|
|
||||||
*/
|
|
||||||
export function parseMarkdownBlocks(markdown: string): MarkdownBlock[] {
|
|
||||||
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
|
|
||||||
const blocks: MarkdownBlock[] = [];
|
|
||||||
|
|
||||||
let index = 0;
|
|
||||||
|
|
||||||
while (index < lines.length) {
|
|
||||||
const line = lines[index];
|
|
||||||
|
|
||||||
if (!line.trim()) {
|
|
||||||
index += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const codeFence = line.match(/^```\s*([^`]*)$/);
|
|
||||||
if (codeFence) {
|
|
||||||
const language = (codeFence[1] ?? "").trim();
|
|
||||||
const codeLines: string[] = [];
|
|
||||||
index += 1;
|
|
||||||
|
|
||||||
while (index < lines.length && !/^```\s*$/.test(lines[index])) {
|
|
||||||
codeLines.push(lines[index]);
|
|
||||||
index += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index < lines.length) {
|
|
||||||
index += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
blocks.push({ type: "code", language, code: codeLines.join("\n") });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (/^(?:\*\s*){3,}$|^(?:-\s*){3,}$|^(?:_\s*){3,}$/.test(line.trim())) {
|
|
||||||
blocks.push({ type: "hr" });
|
|
||||||
index += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const heading = line.match(/^(#{1,6})\s+(.+)$/);
|
|
||||||
if (heading) {
|
|
||||||
blocks.push({
|
|
||||||
type: "heading",
|
|
||||||
level: heading[1].length,
|
|
||||||
text: heading[2],
|
|
||||||
});
|
|
||||||
index += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const quote = line.match(/^>\s?(.*)$/);
|
|
||||||
if (quote) {
|
|
||||||
const quoteLines: string[] = [quote[1]];
|
|
||||||
index += 1;
|
|
||||||
|
|
||||||
while (index < lines.length) {
|
|
||||||
const next = lines[index].match(/^>\s?(.*)$/);
|
|
||||||
if (!next) break;
|
|
||||||
quoteLines.push(next[1]);
|
|
||||||
index += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
blocks.push({ type: "blockquote", text: quoteLines.join("\n") });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tableCandidate = readTable(lines, index);
|
|
||||||
if (tableCandidate) {
|
|
||||||
blocks.push(tableCandidate.block);
|
|
||||||
index = tableCandidate.nextIndex;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const unordered = line.match(/^\s*[-*+]\s+(.*)$/);
|
|
||||||
const ordered = line.match(/^\s*\d+\.\s+(.*)$/);
|
|
||||||
if (unordered || ordered) {
|
|
||||||
const orderedList = Boolean(ordered);
|
|
||||||
const items: ListItem[] = [];
|
|
||||||
|
|
||||||
while (index < lines.length) {
|
|
||||||
const current = lines[index];
|
|
||||||
const match = orderedList
|
|
||||||
? current.match(/^\s*\d+\.\s+(.*)$/)
|
|
||||||
: current.match(/^\s*[-*+]\s+(.*)$/);
|
|
||||||
|
|
||||||
if (!match) break;
|
|
||||||
|
|
||||||
const task = match[1].match(/^\[( |x|X)\]\s+(.*)$/);
|
|
||||||
if (task) {
|
|
||||||
items.push({
|
|
||||||
text: task[2],
|
|
||||||
checked: task[1].toLowerCase() === "x",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
items.push({ text: match[1], checked: null });
|
|
||||||
}
|
|
||||||
|
|
||||||
index += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
blocks.push({ type: "list", ordered: orderedList, items });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const paragraphLines = [line];
|
|
||||||
index += 1;
|
|
||||||
|
|
||||||
while (
|
|
||||||
index < lines.length &&
|
|
||||||
lines[index].trim() &&
|
|
||||||
!/^(#{1,6})\s+/.test(lines[index]) &&
|
|
||||||
!/^```\s*/.test(lines[index]) &&
|
|
||||||
!/^>\s?/.test(lines[index]) &&
|
|
||||||
!/^\s*[-*+]\s+/.test(lines[index]) &&
|
|
||||||
!/^\s*\d+\.\s+/.test(lines[index]) &&
|
|
||||||
!/^(?:\*\s*){3,}$|^(?:-\s*){3,}$|^(?:_\s*){3,}$/.test(lines[index].trim())
|
|
||||||
) {
|
|
||||||
paragraphLines.push(lines[index]);
|
|
||||||
index += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
blocks.push({ type: "paragraph", text: paragraphLines.join("\n") });
|
|
||||||
}
|
|
||||||
|
|
||||||
return blocks;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes renderInline.
|
|
||||||
* @param nodes Parameter nodes.
|
|
||||||
* @returns React.ReactNode[].
|
|
||||||
*/
|
|
||||||
function renderInline(nodes: InlineNode[]): ReactNode[] {
|
|
||||||
return nodes.map((node, index) => {
|
|
||||||
if (node.type === "text") {
|
|
||||||
return node.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.type === "emoji") {
|
|
||||||
return (
|
|
||||||
<Emoji key={index} className="tm-md-emoji" shortcode={node.shortcode} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.type === "strong") {
|
|
||||||
return (
|
|
||||||
<strong key={index} className="tm-md-strong">
|
|
||||||
{renderInline(parseEmojiText(node.value))}
|
|
||||||
</strong>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.type === "em") {
|
|
||||||
return (
|
|
||||||
<em key={index} className="tm-md-em">
|
|
||||||
{renderInline(parseEmojiText(node.value))}
|
|
||||||
</em>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.type === "del") {
|
|
||||||
return (
|
|
||||||
<del key={index} className="tm-md-del">
|
|
||||||
{renderInline(parseEmojiText(node.value))}
|
|
||||||
</del>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.type === "code") {
|
|
||||||
return <CopyableCode key={index} value={node.value} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.type === "link") {
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
key={index}
|
|
||||||
className="tm-md-link"
|
|
||||||
href={node.href}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
{renderInline(parseEmojiText(node.label))}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
key={index}
|
|
||||||
className="tm-md-image"
|
|
||||||
src={node.src}
|
|
||||||
alt={node.alt}
|
|
||||||
loading="lazy"
|
|
||||||
decoding="async"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes renderBlocks.
|
|
||||||
* @param blocks Parameter blocks.
|
|
||||||
* @returns React.ReactElement.
|
|
||||||
*/
|
|
||||||
export function renderBlocks(blocks: MarkdownBlock[]): ReactElement {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{blocks.map((block, blockIndex) => {
|
|
||||||
if (block.type === "heading") {
|
|
||||||
const className = `tm-md-heading tm-md-h${String(block.level)}`;
|
|
||||||
if (block.level === 1)
|
|
||||||
return (
|
|
||||||
<h1 key={blockIndex} className={className}>
|
|
||||||
{renderInline(parseInlineNodes(block.text))}
|
|
||||||
</h1>
|
|
||||||
);
|
|
||||||
if (block.level === 2)
|
|
||||||
return (
|
|
||||||
<h2 key={blockIndex} className={className}>
|
|
||||||
{renderInline(parseInlineNodes(block.text))}
|
|
||||||
</h2>
|
|
||||||
);
|
|
||||||
if (block.level === 3)
|
|
||||||
return (
|
|
||||||
<h3 key={blockIndex} className={className}>
|
|
||||||
{renderInline(parseInlineNodes(block.text))}
|
|
||||||
</h3>
|
|
||||||
);
|
|
||||||
if (block.level === 4)
|
|
||||||
return (
|
|
||||||
<h4 key={blockIndex} className={className}>
|
|
||||||
{renderInline(parseInlineNodes(block.text))}
|
|
||||||
</h4>
|
|
||||||
);
|
|
||||||
if (block.level === 5)
|
|
||||||
return (
|
|
||||||
<h5 key={blockIndex} className={className}>
|
|
||||||
{renderInline(parseInlineNodes(block.text))}
|
|
||||||
</h5>
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<h6 key={blockIndex} className={className}>
|
|
||||||
{renderInline(parseInlineNodes(block.text))}
|
|
||||||
</h6>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (block.type === "blockquote") {
|
|
||||||
return (
|
|
||||||
<blockquote key={blockIndex} className="tm-md-blockquote">
|
|
||||||
{block.text.split("\n").map((line, lineIndex) => (
|
|
||||||
<p key={lineIndex}>{renderInline(parseInlineNodes(line))}</p>
|
|
||||||
))}
|
|
||||||
</blockquote>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (block.type === "code") {
|
|
||||||
return (
|
|
||||||
<CopyableCode
|
|
||||||
key={blockIndex}
|
|
||||||
block
|
|
||||||
language={block.language}
|
|
||||||
value={block.code}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (block.type === "list") {
|
|
||||||
const Tag = block.ordered ? "ol" : "ul";
|
|
||||||
return (
|
|
||||||
<Tag
|
|
||||||
key={blockIndex}
|
|
||||||
className={block.ordered ? "tm-md-ol" : "tm-md-ul"}
|
|
||||||
>
|
|
||||||
{block.items.map((item, itemIndex) => (
|
|
||||||
<li key={itemIndex} className="tm-md-li">
|
|
||||||
{item.checked !== null ? (
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={item.checked}
|
|
||||||
disabled
|
|
||||||
className="tm-md-checkbox"
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<span>{renderInline(parseInlineNodes(item.text))}</span>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</Tag>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (block.type === "table") {
|
|
||||||
return (
|
|
||||||
<div key={blockIndex} className="tm-md-table-wrap">
|
|
||||||
<table className="tm-md-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
{block.headers.map((header, headerIndex) => (
|
|
||||||
<th key={headerIndex}>
|
|
||||||
{renderInline(parseInlineNodes(header))}
|
|
||||||
</th>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{block.rows.map((row, rowIndex) => (
|
|
||||||
<tr key={rowIndex}>
|
|
||||||
{row.map((cell, cellIndex) => (
|
|
||||||
<td key={cellIndex}>
|
|
||||||
{renderInline(parseInlineNodes(cell))}
|
|
||||||
</td>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (block.type === "hr") {
|
|
||||||
return <hr key={blockIndex} className="tm-md-hr" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<p key={blockIndex}>
|
|
||||||
{block.text.split("\n").map((line, lineIndex) => (
|
|
||||||
<Fragment key={lineIndex}>
|
|
||||||
{lineIndex > 0 ? <br /> : null}
|
|
||||||
{renderInline(parseInlineNodes(line))}
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes normalizeUrl.
|
|
||||||
* @param input Parameter input.
|
|
||||||
* @returns string.
|
|
||||||
*/
|
|
||||||
function normalizeUrl(input: string): string {
|
|
||||||
const value = input.trim();
|
|
||||||
if (/^(https?:|mailto:|tel:|\/)/i.test(value)) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return "#";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes splitTableRow.
|
|
||||||
* @param row Parameter row.
|
|
||||||
* @returns string[].
|
|
||||||
*/
|
|
||||||
function splitTableRow(row: string): string[] {
|
|
||||||
const cleaned = row.trim().replace(/^\|/, "").replace(/\|$/, "");
|
|
||||||
return cleaned.split("|").map((cell) => cell.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes readTable.
|
|
||||||
* @param lines Parameter lines.
|
|
||||||
* @param index Parameter index.
|
|
||||||
* @returns { block: TableBlock; nextIndex: number } | null.
|
|
||||||
*/
|
|
||||||
function readTable(
|
|
||||||
lines: string[],
|
|
||||||
index: number,
|
|
||||||
): { block: TableBlock; nextIndex: number } | null {
|
|
||||||
const header = lines[index] ?? "";
|
|
||||||
const separator = lines[index + 1] ?? "";
|
|
||||||
|
|
||||||
if (!header.includes("|") || !separator.includes("|")) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const separatorCells = splitTableRow(separator);
|
|
||||||
const isSeparator = separatorCells.every((cell) => /^:?-{3,}:?$/.test(cell));
|
|
||||||
if (!isSeparator) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const headers = splitTableRow(header);
|
|
||||||
const rows: string[][] = [];
|
|
||||||
let cursor = index + 2;
|
|
||||||
|
|
||||||
while (cursor < lines.length && lines[cursor].includes("|")) {
|
|
||||||
rows.push(splitTableRow(lines[cursor]));
|
|
||||||
cursor += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
block: { type: "table", headers, rows },
|
|
||||||
nextIndex: cursor,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const markdownStyles = `
|
|
||||||
.tm-md-root { color: var(--foreground); line-height: 1.65; font-size: 1rem; }
|
|
||||||
.tm-md-heading { margin: 0.2rem 0 0.35rem; font-weight: 700; line-height: 1.25; }
|
|
||||||
.tm-md-h1 { font-size: 1.65rem; }
|
|
||||||
.tm-md-h2 { font-size: 1.45rem; }
|
|
||||||
.tm-md-h3 { font-size: 1.25rem; }
|
|
||||||
.tm-md-h4 { font-size: 1.1rem; }
|
|
||||||
.tm-md-h5 { font-size: 1rem; }
|
|
||||||
.tm-md-h6 { font-size: 0.95rem; opacity: 0.9; }
|
|
||||||
.tm-md-blockquote { margin: 0.45rem 0; padding-left: 0.75rem; opacity: 0.95; }
|
|
||||||
.tm-md-blockquote p { margin: 0.2rem 0; }
|
|
||||||
.tm-md-pre { margin: 0.45rem 0; padding: 0.65rem 0.75rem; border-radius: 0.5rem; background: var(--muted); overflow-x: auto; }
|
|
||||||
.tm-md-code, .tm-md-codeblock { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 0.87em; cursor: pointer; }
|
|
||||||
.tm-md-code { padding: 0.08rem 0.32rem; border: 1px solid var(--border); border-radius: 0.28rem; background: var(--muted); }
|
|
||||||
.tm-md-codeblock { display: block; }
|
|
||||||
.tm-md-code:focus-visible, .tm-md-codeblock:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
|
|
||||||
.tm-md-strong { font-weight: 700; }
|
|
||||||
.tm-md-em { font-style: italic; }
|
|
||||||
.tm-md-del { text-decoration: line-through; }
|
|
||||||
.tm-md-link { color: var(--primary); text-decoration: underline; text-underline-offset: 0.14rem; }
|
|
||||||
.tm-md-image { display: block; max-width: 100%; border-radius: 0.4rem; margin: 0.5rem 0; }
|
|
||||||
.tm-md-emoji { display: inline-block; width: 1.15em; height: 1.15em; vertical-align: -0.18em; }
|
|
||||||
.tm-md-ul, .tm-md-ol { margin: 0.3rem 0 0.35rem 1.2rem; padding: 0; }
|
|
||||||
.tm-md-li { margin: 0.2rem 0; }
|
|
||||||
.tm-md-checkbox { margin-right: 0.5rem; vertical-align: middle; }
|
|
||||||
.tm-md-table-wrap { overflow-x: auto; margin: 0.45rem 0; }
|
|
||||||
.tm-md-table { border-collapse: collapse; width: 100%; min-width: 16rem; }
|
|
||||||
.tm-md-table th, .tm-md-table td { padding: 0.4rem 0.5rem; text-align: left; }
|
|
||||||
.tm-md-table th { background: var(--muted); font-weight: 600; }
|
|
||||||
.tm-md-hr { margin: 0.55rem 0; }
|
|
||||||
|
|
||||||
.cm-editor.tm-md-editor { border-radius: inherit; background: transparent; caret-color: var(--foreground); }
|
|
||||||
.cm-editor.tm-md-editor.cm-focused { outline: none; box-shadow: none; }
|
|
||||||
.cm-editor.tm-md-editor .cm-scroller { font-family: inherit; line-height: 1.55; max-height: 30vh; overflow-y: auto; overflow-x: hidden; }
|
|
||||||
.cm-editor.tm-md-editor .cm-content { caret-color: var(--foreground); }
|
|
||||||
.cm-editor.tm-md-editor .cm-content { padding: var(--tm-md-content-padding, 0.25rem 0.625rem); min-height: 2rem; }
|
|
||||||
.cm-editor.tm-md-editor .cm-line { padding: 0; color: var(--foreground); }
|
|
||||||
.cm-editor.tm-md-editor .tm-md-editor-emoji { display: inline-block; width: 1.15em; height: 1.15em; vertical-align: -0.18em; object-fit: contain; pointer-events: none; }
|
|
||||||
.cm-editor.tm-md-editor .tm-md-hidden-token { color: transparent; opacity: 0; font-size: inherit; }
|
|
||||||
.cm-editor.tm-md-editor .tm-md-code-line { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; background: var(--muted); border-radius: 0.3rem; }
|
|
||||||
.cm-tooltip.cm-tooltip-autocomplete { min-width: 18rem; max-width: min(26rem, calc(100vw - 1rem)); overflow: hidden; border: 1px solid var(--border); border-radius: var(--radius); background: var(--popover); color: var(--popover-foreground); box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); font-family: "Public Sans Variable", sans-serif; font-size: 0.875rem; }
|
|
||||||
.cm-editor.tm-md-editor .cm-tooltip.cm-tooltip-autocomplete > ul { max-height: min(20rem, 45vh); padding: 0.25rem; font-family: "Public Sans Variable", sans-serif; scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
|
|
||||||
.cm-tooltip.cm-tooltip-autocomplete > ul::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
||||||
.cm-tooltip.cm-tooltip-autocomplete > ul::-webkit-scrollbar-track { background: transparent; }
|
|
||||||
.cm-tooltip.cm-tooltip-autocomplete > ul::-webkit-scrollbar-thumb { border-radius: 9999px; background: var(--border); }
|
|
||||||
.cm-tooltip.cm-tooltip-autocomplete > ul > li { display: flex; min-height: 2.25rem; align-items: center; border-radius: calc(var(--radius) * 0.8); padding: 0.3rem 0.5rem; color: var(--popover-foreground); }
|
|
||||||
.cm-tooltip.cm-tooltip-autocomplete > ul > li:hover,
|
|
||||||
.cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected] { background: var(--accent); color: var(--accent-foreground); }
|
|
||||||
.cm-tooltip.cm-tooltip-autocomplete .cm-completionIcon { display: none; }
|
|
||||||
.cm-tooltip.cm-tooltip-autocomplete .cm-completionLabel { overflow: hidden; text-overflow: ellipsis; }
|
|
||||||
.cm-tooltip.cm-tooltip-autocomplete .cm-completionMatchedText { color: inherit; text-decoration: none; font-weight: 600; }
|
|
||||||
.cm-tooltip-autocomplete .tm-md-completion-emoji { display: inline-block; width: 1.35rem; height: 1.35rem; flex: 0 0 auto; margin-right: 0.5rem; vertical-align: middle; }
|
|
||||||
`;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes ensureMarkdownStyles.
|
|
||||||
* @param none This function has no parameters.
|
|
||||||
* @returns void.
|
|
||||||
*/
|
|
||||||
export function ensureMarkdownStyles(): void {
|
|
||||||
if (typeof document === "undefined") return;
|
|
||||||
|
|
||||||
const styleId = "tensamin-markdown-styles";
|
|
||||||
let style = document.getElementById(styleId) as HTMLStyleElement | null;
|
|
||||||
|
|
||||||
if (!style) {
|
|
||||||
style = document.createElement("style");
|
|
||||||
style.id = styleId;
|
|
||||||
document.head.appendChild(style);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (style.textContent !== markdownStyles) {
|
|
||||||
style.textContent = markdownStyles;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
import { useMemo, type CSSProperties } from "react";
|
|
||||||
|
|
||||||
import {
|
|
||||||
ensureMarkdownStyles,
|
|
||||||
parseMarkdownBlocks,
|
|
||||||
renderBlocks,
|
|
||||||
} from "./markdown";
|
|
||||||
|
|
||||||
export type TextProps = {
|
|
||||||
value: string;
|
|
||||||
fontSize?: CSSProperties["fontSize"];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes Text.
|
|
||||||
* @param props Parameter props.
|
|
||||||
* @returns unknown.
|
|
||||||
*/
|
|
||||||
export default function Text(props: TextProps) {
|
|
||||||
ensureMarkdownStyles();
|
|
||||||
|
|
||||||
const blocks = useMemo(() => parseMarkdownBlocks(props.value), [props.value]);
|
|
||||||
const renderedBlocks = useMemo(() => renderBlocks(blocks), [blocks]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="tm-md-root" style={{ fontSize: props.fontSize }}>
|
|
||||||
{renderedBlocks}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
- Improve the Input box
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ES2022",
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
"strict": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"noEmit": true
|
|
||||||
},
|
|
||||||
"include": ["src"]
|
|
||||||
}
|
|
||||||
|
|
@ -16,7 +16,6 @@
|
||||||
"@tanstack/react-router": "^1.170.21",
|
"@tanstack/react-router": "^1.170.21",
|
||||||
"@tensamin/cache": "workspace:*",
|
"@tensamin/cache": "workspace:*",
|
||||||
"@tensamin/hotkeys": "workspace:*",
|
"@tensamin/hotkeys": "workspace:*",
|
||||||
"@tensamin/markdown": "workspace:*",
|
|
||||||
"@tensamin/mtp": "workspace:*",
|
"@tensamin/mtp": "workspace:*",
|
||||||
"@tensamin/shared": "workspace:*",
|
"@tensamin/shared": "workspace:*",
|
||||||
"@tensamin/storage": "workspace:*",
|
"@tensamin/storage": "workspace:*",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import MDInput from "@tensamin/markdown/input";
|
import { Input as MDInput } from "@methanium/ui/markdown";
|
||||||
import { useMTP } from "@tensamin/mtp";
|
import { useMTP } from "@tensamin/mtp";
|
||||||
import { mtp } from "@tensamin/shared/data";
|
import { mtp } from "@tensamin/shared/data";
|
||||||
import { useStorage } from "@tensamin/storage/context";
|
import { useStorage } from "@tensamin/storage/context";
|
||||||
|
|
@ -145,7 +145,6 @@ export default function Page() {
|
||||||
value={draftUser.Username || ""}
|
value={draftUser.Username || ""}
|
||||||
/>
|
/>
|
||||||
<MDInput
|
<MDInput
|
||||||
styled
|
|
||||||
paddingY="4px"
|
paddingY="4px"
|
||||||
paddingX="10px"
|
paddingX="10px"
|
||||||
fontSize=".875rem"
|
fontSize=".875rem"
|
||||||
|
|
|
||||||
1483
pnpm-lock.yaml
generated
1483
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -6,5 +6,5 @@ allowBuilds:
|
||||||
electron-winstaller: true
|
electron-winstaller: true
|
||||||
esbuild: true
|
esbuild: true
|
||||||
overrides:
|
overrides:
|
||||||
"@methanium/ui": "https://git.methanium.net/methanium/ui/releases/download/0.0.22/methanium-ui.tgz"
|
"@methanium/ui": "https://git.methanium.net/methanium/ui/releases/download/0.0.28/methanium-ui.tgz"
|
||||||
mtp: "https://git.methanium.net/methanium/mtp/releases/download/0.2.0-dev-a692bed/mtp-0.2.0.tgz"
|
mtp: "https://git.methanium.net/methanium/mtp/releases/download/0.2.0-dev-a692bed/mtp-0.2.0.tgz"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue