diff --git a/Cargo.lock b/Cargo.lock
index 7a23121..ad00a97 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -781,9 +781,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "js-sys"
-version = "0.3.70"
+version = "0.3.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
+checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
dependencies = [
"wasm-bindgen",
]
@@ -1443,9 +1443,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
-version = "0.2.93"
+version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
+checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
dependencies = [
"cfg-if",
"once_cell",
@@ -1454,9 +1454,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.93"
+version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
+checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
dependencies = [
"bumpalo",
"log",
@@ -1481,9 +1481,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.93"
+version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
+checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1491,9 +1491,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.93"
+version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
+checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
dependencies = [
"proc-macro2",
"quote",
@@ -1504,15 +1504,15 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.93"
+version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
+checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
[[package]]
name = "web-sys"
-version = "0.3.69"
+version = "0.3.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
+checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112"
dependencies = [
"js-sys",
"wasm-bindgen",
diff --git a/Cargo.toml b/Cargo.toml
index a8c4a64..21b74c2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,7 +13,7 @@ piccolo = { git = "https://github.com/kyren/piccolo.git", rev = "fcbaabc92429217
unicode-segmentation = "1.11.0"
unicode-width = "0.1.13"
wasm-bindgen = "0.2.92"
-web-sys = { version = "0.3.69", features = ["ResizeObserver", "DomRect", "CssStyleDeclaration", "HtmlAnchorElement"] }
+web-sys = { version = "0.3.72", features = ["ResizeObserver", "DomRect", "CssStyleDeclaration", "HtmlAnchorElement", "XmlHttpRequest"] }
yew = { version = "0.21.0", features = ["csr"] }
minicrossterm = { git = "https://git.blastmud.org/blasthavers/minicrossterm.git", rev = "0c8c6d4f0cf445adf7bb957811081a1b710bd933" }
thiserror = "1.0.63"
diff --git a/help.json b/help.json
new file mode 100644
index 0000000..f0021ea
--- /dev/null
+++ b/help.json
@@ -0,0 +1,26 @@
+{
+ "_index": "Try #help command_name (leave off the # from command names) for command help.\r\n\r\n\"#help getting started\" has tips to get started.",
+ "act": "Use #act to set a trigger that will automatically run commands based on a match to the MUD's output. Example: #act {^The (.*) attacks you.} {\"Hey $1, you're going to wish you hadn't done that!\"}.\r\nUse #act by itself to list triggers. See #unact for info on how to remove triggers.",
+ "alias": "Use #alias to set a command alias.\r\nFor example, #alias {^hi} {#echo Hello} will make typing 'hi' run the echo command.\r\nUse #alias by itself to list aliases. See also: #unalias",
+ "connect_mud": "Use #connect_mud to connect to a MUD. Provide a name for the connection and the WebSocket server URL, e.g. #connect_mud ex wss://example.com/ws.",
+ "copy_paste": "Use Ctrl Insert to copy and Shift Insert to paste in most browsers.",
+ "create_script": "Use the '+' icon in the script browser to create a new script. You will need to name it first.",
+ "delay": "Use #delay to schedule a command to run after a specified time (in seconds).\r\nFor example, #delay 5 {#echo Ding!} will run the echo command after 5 seconds.\r\nOptionally give it a name with a third argument.\r\nUse #delay by itself to list delays.\r\nSee also: #undelay",
+ "deletelogs": "Use #deletelogs to permanently delete logs.\r\nExample: #deletelogs mylog 2024-10-01 2024-10-10\r\nThe first argument is the log name. Optionally, follow with a second and third argument to give the date range - or just one date to only specify the end of the date range.\r\nSee also: #downloadlogs",
+ "downloadlogs": "Use #downloadlogs to download logs within a date range. Example: #downloadlogs mylog 2024-10-01 2024-10-10.",
+ "editor": "Use #editor to open the script editor where you can manage Lua scripts.",
+ "getting started": "Use #help connect_mud to learn how to connect if you aren't already.\r\nSend basic commands to the current MUD just by typing them in. Use a number like #5 n to repeat a command. Use semicolons to send commands in sequence, e.g. n;#4 e\r\nCopy and paste using Ctrl+Insert and Shift+Insert",
+ "hsplit": "Use #hsplit to split the screen horizontally. For example, #hsplit {} 2 will create a horizontal split with frame 2.",
+ "include": "To run a script, use #include scriptname. Example: #include myscript.lua.",
+ "listlogs": "Use #listlogs to list stored logs and the number of entries for each logname.",
+ "log": "Use #log to start logging a MUD's output. Example: #log mymud mylog will start logging for the 'mymud' connection.",
+ "lua_scripting": "Scripts use Lua code. Use commands like 'commands.command(\"command to send\")' to execute MUD commands. For example, to set an alias, use 'commands.alias(\"^hi\", \"#echo Hello\")'.",
+ "panel_merge": "Use #panel_merge followed by the frame path to merge panels. For example, #panel_merge {} will merge the current panel with the top-level split.",
+ "send_multiple_commands": "To send several commands in quick succession, separate them with a semicolon. For example, use 'n;e' to go north and then east.",
+ "tick": "Use #tick to set a recurring command to run at a specified time interval (in seconds).\r\nFor example, #tick 60 {#echo Cockadoodledoo!} will repeat every 60 seconds.\r\nOptionally give it a name with a third argument.\r\nUse #tick by itself to list active ticks.\r\nSee also: #untick",
+ "unact": "Use #unact followed by the trigger pattern to delete a trigger. Example: #unact {^The (.*) attacks you.}.",
+ "unalias": "Use #unalias followed by the alias pattern to delete it. For example, #unalias {^hi}.",
+ "undelay": "Use #undelay followed by the delay name to cancel it. Example: #undelay ding to cancel a delay named 'ding'.",
+ "untick": "Use #untick followed by the tick name to cancel it. Example: #untick rooster to cancel a tick named 'rooster'.",
+ "vsplit": "Use #vsplit to split the screen vertically. For example, #vsplit {} 2 will create a vertical split with frame 2."
+}
diff --git a/index.html b/index.html
index b50f756..777b2fd 100644
--- a/index.html
+++ b/index.html
@@ -27,6 +27,7 @@
+
diff --git a/src/logging.rs b/src/logging.rs
index 6c54076..ec5cead 100644
--- a/src/logging.rs
+++ b/src/logging.rs
@@ -377,8 +377,8 @@ async fn immediate_download_logs_refutable(
let buf_bytes = buf.as_bytes();
let buf_array = Uint8Array::new_with_length(buf_bytes.len() as u32);
buf_array.copy_from(buf_bytes);
- let mut blobprops: BlobPropertyBag = Default::default();
- blobprops.type_("text/plain");
+ let blobprops: BlobPropertyBag = Default::default();
+ blobprops.set_type("text/plain");
let blob = Blob::new_with_u8_array_sequence_and_options(
&[&buf_array].into_iter().collect::(),
&blobprops,
diff --git a/src/lua_engine.rs b/src/lua_engine.rs
index 575e335..9864ab2 100644
--- a/src/lua_engine.rs
+++ b/src/lua_engine.rs
@@ -1,5 +1,6 @@
use self::{frameroutes::*, frames::*, muds::*, storage::*};
use anyhow::Error;
+use help::{cmd_help, load_help};
use muds::telopt::configure_telopt_table;
use piccolo::{
async_callback::{AsyncSequence, Locals},
@@ -29,7 +30,9 @@ pub struct LuaState {
mod frameroutes;
pub mod frames;
+pub mod help;
pub mod muds;
+pub mod named_closure;
pub mod storage;
impl LuaState {
@@ -217,6 +220,7 @@ pub fn install_lua_globals(
register_command!(echo_frame);
register_command!(echo_frame_raw);
register_command!(editor);
+ register_stateless_command!(cmd_help, "help");
register_command!(hsplit);
register_stateless_command!(cmd_include, "include");
register_command!(cmd_list_logs, "listlogs");
@@ -387,6 +391,7 @@ fn lua_global_initialisation(global_memo: &GlobalMemoCell) -> Result<(), String>
Function::Callback(ensure_frame_instance(ctx, &FrameId(1))),
(),
);
+ let _ = load_help(global_memo, ctx, "help.json");
});
lua_engine_ref
.interp
diff --git a/src/lua_engine/help.rs b/src/lua_engine/help.rs
new file mode 100644
index 0000000..fc787c2
--- /dev/null
+++ b/src/lua_engine/help.rs
@@ -0,0 +1,90 @@
+use std::collections::BTreeMap;
+
+use crate::GlobalMemoCell;
+use anyhow::Result;
+use piccolo::{Callback, CallbackReturn, Context, FromValue, Function, Table, Value, Variadic};
+use std::rc::Rc;
+use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
+use web_sys::{console, XmlHttpRequest};
+
+use super::named_closure::{release_named_global_closure, store_named_global_closure};
+
+pub fn load_help(global_memo: &GlobalMemoCell, ctx: Context, url: &str) -> Result<()> {
+ let xhr: Rc = XmlHttpRequest::new()
+ .map_err(|_e| anyhow::Error::msg("Error creating XmlHttpRequest"))?
+ .into();
+ let global_memo = global_memo.clone();
+ let xhr_cb = xhr.clone();
+ let load_callback: Closure = Closure::new(move || {
+ let _ = global_memo.lua_engine.borrow_mut().interp.try_enter(|ctx| {
+ release_named_global_closure(ctx, "_help_load_callback");
+ if let Ok(Some(txt)) = xhr_cb.response_text() {
+ match serde_json::from_str::>(&txt) {
+ Err(_) => console::log_1(&JsValue::from_str(
+ "Loading help failed because result wasn't a String -> String map",
+ )),
+ Ok(d) => {
+ let help_tbl: Value = ctx.get_global("help")?;
+ let help_tbl = if help_tbl.is_nil() {
+ let tbl = Table::new(&ctx);
+ ctx.set_global("help", tbl);
+ tbl
+ } else {
+ Table::from_value(ctx, help_tbl)?
+ };
+ for (k, v) in d.iter() {
+ help_tbl.set(ctx, k.clone(), v.clone())?;
+ }
+ }
+ }
+ }
+ Ok(())
+ });
+ });
+ xhr.set_onload(Some(load_callback.as_ref().unchecked_ref()));
+ store_named_global_closure(ctx, "_help_load_callback", load_callback);
+ xhr.open("GET", url)
+ .map_err(|_e| anyhow::Error::msg("Error opening XmlHttpRequest"))?;
+ xhr.send()
+ .map_err(|_e| anyhow::Error::msg("Error sending XmlHttpRequest"))?;
+
+ Ok(())
+}
+
+pub(super) fn cmd_help<'gc>(ctx: Context<'gc>) -> Callback<'gc> {
+ Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
+ let help: Value = ctx.get_global("help")?;
+ let echo: Function = ctx.get_global::