mirror of
https://github.com/itsjunetime/tdf.git
synced 2026-06-02 08:01:47 -04:00
Make searching work much better and actually make the 'n' input skip to the next page with available results
This commit is contained in:
Generated
+78
-66
@@ -107,9 +107,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.21.7"
|
version = "0.22.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
@@ -188,9 +188,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.97"
|
version = "1.0.98"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4"
|
checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-expr"
|
name = "cfg-expr"
|
||||||
@@ -245,12 +245,6 @@ dependencies = [
|
|||||||
"roff",
|
"roff",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "color_quant"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@@ -272,18 +266,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.4.0"
|
version = "1.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
|
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.5.12"
|
version = "0.5.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95"
|
checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
@@ -309,9 +303,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.8.19"
|
version = "0.8.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
|
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossterm"
|
name = "crossterm"
|
||||||
@@ -347,9 +341,9 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.11.0"
|
version = "1.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2"
|
checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
@@ -526,9 +520,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glib"
|
name = "glib"
|
||||||
version = "0.19.6"
|
version = "0.19.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b0116c428e4841cab183a32a71b900fd6712194c20f9c424f01d2c016c96bd23"
|
checksum = "e52355166df21c7ed16b6a01f615669c7911ed74e27ef60eba339c0d2da12490"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.5.0",
|
"bitflags 2.5.0",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
@@ -548,9 +542,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glib-macros"
|
name = "glib-macros"
|
||||||
version = "0.19.5"
|
version = "0.19.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6ed782fa3e949c31146671da6e7a227a5e7d354660df1db6d0aac4974dc82a3c"
|
checksum = "70025dbfa1275cf7d0531c3317ba6270dae15d87e63342229d638246ff45202e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
@@ -610,17 +604,17 @@ checksum = "86858ae800284d596cfdefcb0ad435c3493c12f35367431bbe9b2b3858c1155b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "image"
|
name = "image"
|
||||||
version = "0.24.9"
|
version = "0.25.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d"
|
checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"color_quant",
|
|
||||||
"jpeg-decoder",
|
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"png",
|
"png",
|
||||||
"rayon",
|
"rayon",
|
||||||
|
"zune-core",
|
||||||
|
"zune-jpeg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -634,12 +628,6 @@ dependencies = [
|
|||||||
"rayon",
|
"rayon",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "indoc"
|
|
||||||
version = "2.0.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "inotify"
|
name = "inotify"
|
||||||
version = "0.9.6"
|
version = "0.9.6"
|
||||||
@@ -675,18 +663,21 @@ dependencies = [
|
|||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.11"
|
version = "1.0.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "jpeg-decoder"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kqueue"
|
name = "kqueue"
|
||||||
version = "1.0.8"
|
version = "1.0.8"
|
||||||
@@ -709,9 +700,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.154"
|
version = "0.2.155"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
|
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libdeflate-sys"
|
name = "libdeflate-sys"
|
||||||
@@ -733,9 +724,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.4.13"
|
version = "0.4.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
|
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
@@ -770,9 +761,9 @@ checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.7.2"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
|
checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"adler",
|
"adler",
|
||||||
"simd-adler32",
|
"simd-adler32",
|
||||||
@@ -854,9 +845,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.2"
|
version = "0.12.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb"
|
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lock_api",
|
"lock_api",
|
||||||
"parking_lot_core",
|
"parking_lot_core",
|
||||||
@@ -956,9 +947,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.82"
|
version = "1.0.84"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
|
checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -1010,29 +1001,25 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ratatui"
|
name = "ratatui"
|
||||||
version = "0.26.2"
|
version = "0.26.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a564a852040e82671dc50a37d88f3aa83bbc690dfc6844cfe7a2591620206a80"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.5.0",
|
"bitflags 2.5.0",
|
||||||
"cassowary",
|
"cassowary",
|
||||||
"compact_str",
|
"compact_str",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"indoc",
|
"itertools 0.13.0",
|
||||||
"itertools",
|
|
||||||
"lru",
|
"lru",
|
||||||
"paste",
|
"paste",
|
||||||
"stability",
|
"stability",
|
||||||
"strum",
|
"strum",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
|
"unicode-truncate",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ratatui-image"
|
name = "ratatui-image"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2264bdb808c89e8395480cfce32c197e75a3d6171063e913bca12e7919a333da"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"dyn-clone",
|
"dyn-clone",
|
||||||
@@ -1165,18 +1152,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.202"
|
version = "1.0.203"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395"
|
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.202"
|
version = "1.0.203"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838"
|
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1289,9 +1276,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.63"
|
version = "2.0.66"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704"
|
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1332,7 +1319,7 @@ dependencies = [
|
|||||||
"futures-util",
|
"futures-util",
|
||||||
"glib",
|
"glib",
|
||||||
"image",
|
"image",
|
||||||
"itertools",
|
"itertools 0.13.0",
|
||||||
"notify",
|
"notify",
|
||||||
"oxipng",
|
"oxipng",
|
||||||
"poppler-rs",
|
"poppler-rs",
|
||||||
@@ -1343,18 +1330,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.60"
|
version = "1.0.61"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"
|
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.60"
|
version = "1.0.61"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"
|
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1440,6 +1427,16 @@ version = "1.11.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-truncate"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a5fbabedabe362c618c714dbefda9927b5afc8e2a8102f47f081089a9019226"
|
||||||
|
dependencies = [
|
||||||
|
"itertools 0.12.1",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
version = "0.1.12"
|
version = "0.1.12"
|
||||||
@@ -1696,3 +1693,18 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zune-core"
|
||||||
|
version = "0.4.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zune-jpeg"
|
||||||
|
version = "0.4.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448"
|
||||||
|
dependencies = [
|
||||||
|
"zune-core",
|
||||||
|
]
|
||||||
|
|||||||
+6
-6
@@ -6,15 +6,15 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
poppler-rs = { version = "0.23.0", features = ["v21_5"] }
|
poppler-rs = { version = "0.23.0", features = ["v21_5"] }
|
||||||
cairo-rs = { version = "0.19.4", features = ["png"] }
|
cairo-rs = { version = "0.19.4", features = ["png"] }
|
||||||
ratatui = "0.26.2"
|
# ratatui = "0.26.3"
|
||||||
# ratatui = { path = "./ratatui" }
|
ratatui = { path = "./ratatui" }
|
||||||
ratatui-image = { version = "1.0.0", features = ["rustix"], default-features = false }
|
# ratatui-image = { version = "1.0.0", features = ["rustix"], default-features = false }
|
||||||
# ratatui-image = { path = "./ratatui-image", features = ["rustix"], default-features = false }
|
ratatui-image = { path = "./ratatui-image", features = ["rustix"], default-features = false }
|
||||||
crossterm = { version = "0.27.0", features = ["event-stream"] }
|
crossterm = { version = "0.27.0", features = ["event-stream"] }
|
||||||
image = { version = "0.24.9", features = ["png", "rayon"], default-features = false }
|
image = { version = "0.25.1", features = ["png", "rayon"], default-features = false }
|
||||||
notify = "6.1.1"
|
notify = "6.1.1"
|
||||||
tokio = { version = "1.37.0", features = ["rt", "sync", "macros"] }
|
tokio = { version = "1.37.0", features = ["rt", "sync", "macros"] }
|
||||||
futures-util = { version = "0.3.30", default-features = false }
|
futures-util = { version = "0.3.30", default-features = false }
|
||||||
glib = "0.19.6"
|
glib = "0.19.6"
|
||||||
itertools = "0.12.1"
|
itertools = "*"
|
||||||
oxipng = { version = "9.1.1", default-features = false, features = ["parallel"] }
|
oxipng = { version = "9.1.1", default-features = false, features = ["parallel"] }
|
||||||
|
|||||||
@@ -1,3 +1 @@
|
|||||||
- Look into a way to tell ratatui to skip diffing and just overwrite the whole thing 'cause we know it won't save time
|
|
||||||
- Look into more efficient 'skip'ing in ratatui so that you don't have to unicode_width::width a bunch of escape codes
|
- Look into more efficient 'skip'ing in ratatui so that you don't have to unicode_width::width a bunch of escape codes
|
||||||
- Render to Box<dyn Protocol> aot but only in the vicinity of the current view to save space
|
|
||||||
|
|||||||
+1
-1
Submodule ratatui updated: 9bd89c218a...fadc73d62e
+4
-6
@@ -63,6 +63,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
let backend = CrosstermBackend::new(std::io::stdout());
|
let backend = CrosstermBackend::new(std::io::stdout());
|
||||||
let mut term = Terminal::new(backend)?;
|
let mut term = Terminal::new(backend)?;
|
||||||
|
term.skip_diff(true);
|
||||||
|
|
||||||
// poppler has some annoying logging (e.g. if you request a page index out-of-bounds of a
|
// poppler has some annoying logging (e.g. if you request a page index out-of-bounds of a
|
||||||
// document's pages, then it will return `None`, but still log to stderr with CRITICAL level),
|
// document's pages, then it will return `None`, but still log to stderr with CRITICAL level),
|
||||||
@@ -117,17 +118,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
Ok(RenderInfo::NumPages(num)) => {
|
Ok(RenderInfo::NumPages(num)) => {
|
||||||
tui.set_n_pages(num);
|
tui.set_n_pages(num);
|
||||||
converter.set_n_pages(num);
|
converter.set_n_pages(num);
|
||||||
true
|
|
||||||
},
|
},
|
||||||
Ok(RenderInfo::Page(info)) => {
|
Ok(RenderInfo::Page(info)) => {
|
||||||
|
tui.got_num_results_on_page(info.page, info.search_results);
|
||||||
converter.add_img(info);
|
converter.add_img(info);
|
||||||
false
|
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => tui.show_error(e),
|
||||||
tui.show_error(e);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
true
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+19
-7
@@ -59,6 +59,10 @@ pub fn start_rendering(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// We want this outside of 'reload so that if the doc reloads, the search term that somebody
|
||||||
|
// set will still get highlighted in the reloaded doc
|
||||||
|
let mut search_term = None;
|
||||||
|
|
||||||
'reload: loop {
|
'reload: loop {
|
||||||
let doc = match Document::from_file(&path, None) {
|
let doc = match Document::from_file(&path, None) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -78,7 +82,6 @@ pub fn start_rendering(
|
|||||||
// then we can split at that page and render at both sides of it
|
// then we can split at that page and render at both sides of it
|
||||||
let mut rendered = vec![false; n_pages];
|
let mut rendered = vec![false; n_pages];
|
||||||
let mut start_point = 0;
|
let mut start_point = 0;
|
||||||
let mut search_term = None;
|
|
||||||
|
|
||||||
// This is kinda a weird way of doing this, but if we get a notification that the area
|
// This is kinda a weird way of doing this, but if we get a notification that the area
|
||||||
// changed, we want to start re-rending all of the pages, but we don't want to reload the
|
// changed, we want to start re-rending all of the pages, but we don't want to reload the
|
||||||
@@ -109,7 +112,11 @@ pub fn start_rendering(
|
|||||||
},
|
},
|
||||||
RenderNotif::Search(term) => {
|
RenderNotif::Search(term) => {
|
||||||
rendered = vec![false; n_pages];
|
rendered = vec![false; n_pages];
|
||||||
search_term = Some(term);
|
if term.is_empty() {
|
||||||
|
search_term = None;
|
||||||
|
} else {
|
||||||
|
search_term = Some(term);
|
||||||
|
}
|
||||||
continue 'render_pages;
|
continue 'render_pages;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,8 +130,9 @@ pub fn start_rendering(
|
|||||||
.map(|(idx, p)| (idx + start_point, p))
|
.map(|(idx, p)| (idx + start_point, p))
|
||||||
.interleave(
|
.interleave(
|
||||||
left.iter_mut()
|
left.iter_mut()
|
||||||
|
.rev()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(idx, p)| (idx - (start_point + 1), p))
|
.map(|(idx, p)| (start_point - (idx + 1), p))
|
||||||
);
|
);
|
||||||
|
|
||||||
for (num, rendered) in page_iter {
|
for (num, rendered) in page_iter {
|
||||||
@@ -141,7 +149,11 @@ pub fn start_rendering(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// We know this is in range 'cause we're iterating over it
|
// We know this is in range 'cause we're iterating over it
|
||||||
let page = doc.page(num as i32).unwrap();
|
let Some(page) = doc.page(num as i32) else {
|
||||||
|
sender.blocking_send(Err(RenderError::Render(format!("Couldn't get page {num} ({}) of doc?? (sp: {start_point}", num as i32))))
|
||||||
|
.unwrap();
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
// render the page
|
// render the page
|
||||||
let to_send = render_single_page(page, area, num, &search_term)
|
let to_send = render_single_page(page, area, num, &search_term)
|
||||||
@@ -239,9 +251,7 @@ fn render_single_page(
|
|||||||
.map(|term| page.find_text_with_options(term, FindFlags::DEFAULT | FindFlags::MULTILINE))
|
.map(|term| page.find_text_with_options(term, FindFlags::DEFAULT | FindFlags::MULTILINE))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let num_results = result_rects.iter()
|
let num_results = result_rects.len();
|
||||||
.filter(|rect| !rect.find_get_match_continued())
|
|
||||||
.count();
|
|
||||||
|
|
||||||
let mut highlight_color = Color::new();
|
let mut highlight_color = Color::new();
|
||||||
highlight_color.set_red((u16::MAX / 5) * 4);
|
highlight_color.set_red((u16::MAX / 5) * 4);
|
||||||
@@ -270,6 +280,8 @@ fn render_single_page(
|
|||||||
ctx.target().write_to_png(&mut img_data)
|
ctx.target().write_to_png(&mut img_data)
|
||||||
.map_err(|e| format!("Couldn't write surface to png: {e}"))?;
|
.map_err(|e| format!("Couldn't write surface to png: {e}"))?;
|
||||||
|
|
||||||
|
// TODO: Maybe cache which pages had no results with the last search term so we don't have to
|
||||||
|
// rerender them when the search term is set to empty and rerenders are requested
|
||||||
Ok(PageInfo {
|
Ok(PageInfo {
|
||||||
img_data: ImageData {
|
img_data: ImageData {
|
||||||
data: img_data,
|
data: img_data,
|
||||||
|
|||||||
+94
-51
@@ -9,10 +9,9 @@ use crate::{renderer::RenderError, skip::Skip};
|
|||||||
pub struct Tui {
|
pub struct Tui {
|
||||||
name: String,
|
name: String,
|
||||||
page: usize,
|
page: usize,
|
||||||
error: Option<String>,
|
|
||||||
input_state: Option<InputCommand>,
|
|
||||||
last_render: LastRender,
|
last_render: LastRender,
|
||||||
rendered: Vec<Option<RenderedInfo>>,
|
bottom_msg: BottomMessage,
|
||||||
|
rendered: Vec<RenderedInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
@@ -24,13 +23,23 @@ struct LastRender {
|
|||||||
unused_width: u16
|
unused_width: u16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
enum BottomMessage {
|
||||||
|
#[default]
|
||||||
|
Help,
|
||||||
|
SearchResults(String),
|
||||||
|
Error(String),
|
||||||
|
Input(InputCommand)
|
||||||
|
}
|
||||||
|
|
||||||
enum InputCommand {
|
enum InputCommand {
|
||||||
GoToPage(usize),
|
GoToPage(usize),
|
||||||
Search(String)
|
Search(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
struct RenderedInfo {
|
struct RenderedInfo {
|
||||||
img: Box<dyn Protocol>,
|
img: Option<Box<dyn Protocol>>,
|
||||||
num_results: usize
|
num_results: usize
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,8 +48,7 @@ impl Tui {
|
|||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
page: 0,
|
page: 0,
|
||||||
error: None,
|
bottom_msg: BottomMessage::Help,
|
||||||
input_state: None,
|
|
||||||
last_render: LastRender::default(),
|
last_render: LastRender::default(),
|
||||||
rendered: vec![],
|
rendered: vec![],
|
||||||
}
|
}
|
||||||
@@ -107,7 +115,7 @@ impl Tui {
|
|||||||
let rendered_str = if !self.rendered.is_empty() {
|
let rendered_str = if !self.rendered.is_empty() {
|
||||||
format!(
|
format!(
|
||||||
"Rendered: {}%",
|
"Rendered: {}%",
|
||||||
(self.rendered.iter().filter(|i| i.is_some()).count() * 100) / self.rendered.len()
|
(self.rendered.iter().filter(|i| i.img.is_some()).count() * 100) / self.rendered.len()
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
@@ -125,22 +133,30 @@ impl Tui {
|
|||||||
);
|
);
|
||||||
frame.render_widget(rendered_span, bottom_layout[1]);
|
frame.render_widget(rendered_span, bottom_layout[1]);
|
||||||
|
|
||||||
if let Some(ref error_str) = self.error {
|
let (msg_str, color) = match self.bottom_msg {
|
||||||
let span = Span::styled(
|
BottomMessage::Help => (
|
||||||
format!("Couldn't render a page: {error_str}"),
|
"/: Search, g: Go To Page".to_string(),
|
||||||
Style::new()
|
Color::Blue
|
||||||
.fg(Color::Red)
|
),
|
||||||
);
|
BottomMessage::Error(ref e) => (
|
||||||
frame.render_widget(span, bottom_layout[0]);
|
format!("Couldn't render a page: {e}"),
|
||||||
} else if let Some(ref cmd) = self.input_state {
|
Color::Red
|
||||||
let cmd_str = match cmd {
|
),
|
||||||
InputCommand::GoToPage(page) => format!("Go to: {page}"),
|
BottomMessage::Input(ref input_state) => (
|
||||||
InputCommand::Search(s) => format!("Search: {s}"),
|
match input_state {
|
||||||
};
|
InputCommand::GoToPage(page) => format!("Go to: {page}"),
|
||||||
|
InputCommand::Search(s) => format!("Search: {s}"),
|
||||||
|
},
|
||||||
|
Color::Blue
|
||||||
|
),
|
||||||
|
BottomMessage::SearchResults(ref term) => (
|
||||||
|
format!("Results for '{term}': {}", self.rendered.iter().map(|r| r.num_results).sum::<usize>()),
|
||||||
|
Color::Blue
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
let span = Span::styled(cmd_str, Style::new().fg(Color::Blue));
|
let span = Span::styled(msg_str, Style::new().fg(color));
|
||||||
frame.render_widget(span, bottom_layout[0]);
|
frame.render_widget(span, bottom_layout[0]);
|
||||||
}
|
|
||||||
|
|
||||||
let mut img_area = main_area[1];
|
let mut img_area = main_area[1];
|
||||||
|
|
||||||
@@ -160,12 +176,12 @@ impl Tui {
|
|||||||
// render each page)
|
// render each page)
|
||||||
.enumerate()
|
.enumerate()
|
||||||
// and only take as many as are ready to be rendered
|
// and only take as many as are ready to be rendered
|
||||||
.take_while(|(_, page)| page.is_some())
|
.take_while(|(_, page)| page.img.is_some())
|
||||||
// and map it to their width (in cells on the terminal, not pixels)
|
// and map it to their width (in cells on the terminal, not pixels)
|
||||||
.flat_map(|(idx, page)|
|
.flat_map(|(idx, page)|
|
||||||
page.as_ref().map(|img| (
|
page.img.as_ref().map(|img| (
|
||||||
idx,
|
idx,
|
||||||
img.img.rect().width,
|
img.rect().width,
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
// and then take them as long as they won't overflow the available area.
|
// and then take them as long as they won't overflow the available area.
|
||||||
@@ -217,8 +233,10 @@ impl Tui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_single_page(&mut self, frame: &mut Frame<'_>, page_idx: usize, img_area: Rect) {
|
fn render_single_page(&mut self, frame: &mut Frame<'_>, page_idx: usize, img_area: Rect) {
|
||||||
match self.rendered[page_idx] {
|
// TODO: Sometimes a page just won't render. But there will be space for it so we clearly
|
||||||
Some(ref page_img) => frame.render_widget(Image::new(&*page_img.img), img_area),
|
// know it should be there. Maybe we're not resetting the last render rect as we should be?
|
||||||
|
match self.rendered[page_idx].img {
|
||||||
|
Some(ref page_img) => frame.render_widget(Image::new(&**page_img), img_area),
|
||||||
None => Self::render_loading_in(frame, img_area)
|
None => Self::render_loading_in(frame, img_area)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -256,7 +274,7 @@ impl Tui {
|
|||||||
pub fn set_n_pages(&mut self, n_pages: usize) {
|
pub fn set_n_pages(&mut self, n_pages: usize) {
|
||||||
self.rendered = Vec::with_capacity(n_pages);
|
self.rendered = Vec::with_capacity(n_pages);
|
||||||
for _ in 0..n_pages {
|
for _ in 0..n_pages {
|
||||||
self.rendered.push(None);
|
self.rendered.push(RenderedInfo::default());
|
||||||
}
|
}
|
||||||
self.page = self.page.min(n_pages - 1);
|
self.page = self.page.min(n_pages - 1);
|
||||||
}
|
}
|
||||||
@@ -281,18 +299,22 @@ impl Tui {
|
|||||||
// We always just set this here because we handle reloading in the `set_n_pages` function.
|
// We always just set this here because we handle reloading in the `set_n_pages` function.
|
||||||
// If the document was reloaded, then It'll have the `set_n_pages` called to set the new
|
// If the document was reloaded, then It'll have the `set_n_pages` called to set the new
|
||||||
// number of pages, so the vec will already be cleared
|
// number of pages, so the vec will already be cleared
|
||||||
self.rendered[page_num] = Some(RenderedInfo { img, num_results })
|
self.rendered[page_num] = RenderedInfo { img: Some(img), num_results };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn got_num_results_on_page(&mut self, page_num: usize, num_results: usize) {
|
||||||
|
self.rendered[page_num].num_results = num_results;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_event(&mut self, ev: Event) -> Option<InputAction> {
|
pub fn handle_event(&mut self, ev: Event) -> Option<InputAction> {
|
||||||
match ev {
|
match ev {
|
||||||
Event::Key(key) => {
|
Event::Key(key) => {
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Char(c) if let Some(InputCommand::Search(ref mut term)) = self.input_state => {
|
KeyCode::Char(c) if let BottomMessage::Input(InputCommand::Search(ref mut term)) = self.bottom_msg => {
|
||||||
term.push(c);
|
term.push(c);
|
||||||
Some(InputAction::Redraw)
|
Some(InputAction::Redraw)
|
||||||
},
|
},
|
||||||
KeyCode::Char(c) if let Some(InputCommand::GoToPage(ref mut page)) = self.input_state => {
|
KeyCode::Char(c) if let BottomMessage::Input(InputCommand::GoToPage(ref mut page)) = self.bottom_msg => {
|
||||||
c.to_digit(10)
|
c.to_digit(10)
|
||||||
.map(|input_num| {
|
.map(|input_num| {
|
||||||
*page = (*page * 10) + input_num as usize;
|
*page = (*page * 10) + input_num as usize;
|
||||||
@@ -303,42 +325,63 @@ impl Tui {
|
|||||||
KeyCode::Down | KeyCode::Char('j') => self.change_page(PageChange::Next, ChangeAmount::WholeScreen),
|
KeyCode::Down | KeyCode::Char('j') => self.change_page(PageChange::Next, ChangeAmount::WholeScreen),
|
||||||
KeyCode::Left | KeyCode::Char('h') => self.change_page(PageChange::Prev, ChangeAmount::Single),
|
KeyCode::Left | KeyCode::Char('h') => self.change_page(PageChange::Prev, ChangeAmount::Single),
|
||||||
KeyCode::Up | KeyCode::Char('k') => self.change_page(PageChange::Prev, ChangeAmount::WholeScreen),
|
KeyCode::Up | KeyCode::Char('k') => self.change_page(PageChange::Prev, ChangeAmount::WholeScreen),
|
||||||
KeyCode::Esc | KeyCode::Char('q') => {
|
KeyCode::Esc | KeyCode::Char('q') => Some(InputAction::QuitApp),
|
||||||
if self.input_state.is_some() {
|
|
||||||
self.input_state = None;
|
|
||||||
Some(InputAction::Redraw)
|
|
||||||
} else {
|
|
||||||
Some(InputAction::QuitApp)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
KeyCode::Char('g') => {
|
KeyCode::Char('g') => {
|
||||||
self.input_state = Some(InputCommand::GoToPage(0));
|
self.bottom_msg = BottomMessage::Input(InputCommand::GoToPage(0));
|
||||||
Some(InputAction::Redraw)
|
Some(InputAction::Redraw)
|
||||||
},
|
},
|
||||||
KeyCode::Char('/') => {
|
KeyCode::Char('/') => {
|
||||||
self.input_state = Some(InputCommand::Search(String::new()));
|
self.bottom_msg = BottomMessage::Input(InputCommand::Search(String::new()));
|
||||||
Some(InputAction::Redraw)
|
Some(InputAction::Redraw)
|
||||||
},
|
},
|
||||||
KeyCode::Char('n') => {
|
KeyCode::Char('n') if self.page < self.rendered.len() - 1 => {
|
||||||
let next_page = self.rendered[self.page..]
|
// TODO: If we can't find one, then maybe like block until we've verified
|
||||||
|
// all the pages have been checked?
|
||||||
|
let next_page = self.rendered[(self.page + 1)..]
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(idx, p)| p.as_ref().map(|p| (idx, p)))
|
.find_map(|(idx, p)| (p.num_results > 0).then_some(self.page + 1 + idx));
|
||||||
.find_map(|(idx, p)| (p.num_results > 0).then_some(idx));
|
|
||||||
if let Some(page) = next_page {
|
if let Some(page) = next_page {
|
||||||
self.page = page;
|
self.page = page;
|
||||||
|
// Make sure we re-render
|
||||||
|
self.last_render.rect = Rect::default();
|
||||||
|
Some(InputAction::JumpingToPage(page))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
next_page.map(|_| InputAction::Redraw)
|
|
||||||
},
|
},
|
||||||
KeyCode::Enter => self.input_state.take()
|
// TODO: Add 'N' key to go back a search page
|
||||||
.and_then(|cmd| match cmd {
|
KeyCode::Enter => {
|
||||||
|
let BottomMessage::Input(_) = self.bottom_msg else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let BottomMessage::Input(cmd) = std::mem::take(&mut self.bottom_msg) else {
|
||||||
|
// We need to verify it's an input msg currently, and only then take it
|
||||||
|
// and replace it by a default Help message. Don't exactly know how to
|
||||||
|
// do this otherwise.
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
|
||||||
|
match cmd {
|
||||||
// Only forward the command if it's within range
|
// Only forward the command if it's within range
|
||||||
InputCommand::GoToPage(page) => (page < self.rendered.len()).then(|| {
|
InputCommand::GoToPage(page) => (page < self.rendered.len()).then(|| {
|
||||||
self.set_page(page);
|
self.set_page(page);
|
||||||
InputAction::JumpingToPage(page)
|
InputAction::JumpingToPage(page)
|
||||||
}),
|
}),
|
||||||
InputCommand::Search(term) => Some(InputAction::Search(term)),
|
InputCommand::Search(term) => {
|
||||||
}),
|
// We only want to show search results if there would actually be
|
||||||
|
// data to show
|
||||||
|
if !term.is_empty() {
|
||||||
|
self.bottom_msg = BottomMessage::SearchResults(term.clone());
|
||||||
|
}
|
||||||
|
// but we still want to tell the rest of the system that we set the
|
||||||
|
// search term to '' so that they can re-render the pages wthout
|
||||||
|
// the highlighting
|
||||||
|
Some(InputAction::Search(term))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -357,7 +400,7 @@ impl Tui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_error(&mut self, err: RenderError) {
|
pub fn show_error(&mut self, err: RenderError) {
|
||||||
self.error = Some(match err {
|
self.bottom_msg = BottomMessage::Error(match err {
|
||||||
RenderError::Doc(e) => format!("Couldn't open document: {e}"),
|
RenderError::Doc(e) => format!("Couldn't open document: {e}"),
|
||||||
RenderError::Render(e) => format!("Couldn't render page: {e}")
|
RenderError::Render(e) => format!("Couldn't render page: {e}")
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user