mirror of
https://github.com/youwen5/iamb.git
synced 2025-06-19 21:29:52 -07:00
Add message slash commands (#317)
This commit is contained in:
parent
653287478e
commit
04480eda1b
7 changed files with 352 additions and 101 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2093,6 +2093,7 @@ dependencies = [
|
||||||
name = "iamb"
|
name = "iamb"
|
||||||
version = "0.0.10-alpha.1"
|
version = "0.0.10-alpha.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"bitflags 2.5.0",
|
"bitflags 2.5.0",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
@ -2114,6 +2115,7 @@ dependencies = [
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
"modalkit",
|
"modalkit",
|
||||||
"modalkit-ratatui",
|
"modalkit-ratatui",
|
||||||
|
"nom",
|
||||||
"notify-rust",
|
"notify-rust",
|
||||||
"open",
|
"open",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
|
|
|
@ -26,6 +26,7 @@ default-features = false
|
||||||
features = ["build", "git", "gitcl",]
|
features = ["build", "git", "gitcl",]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow = "1.0"
|
||||||
bitflags = "^2.3"
|
bitflags = "^2.3"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
clap = {version = "~4.3", features = ["derive"]}
|
clap = {version = "~4.3", features = ["derive"]}
|
||||||
|
@ -41,6 +42,7 @@ libc = "0.2"
|
||||||
markup5ever_rcdom = "0.2.0"
|
markup5ever_rcdom = "0.2.0"
|
||||||
mime = "^0.3.16"
|
mime = "^0.3.16"
|
||||||
mime_guess = "^2.0.4"
|
mime_guess = "^2.0.4"
|
||||||
|
nom = "7.0.0"
|
||||||
notify-rust = { version = "4.10.0", default-features = false, features = ["zbus", "serde"] }
|
notify-rust = { version = "4.10.0", default-features = false, features = ["zbus", "serde"] }
|
||||||
open = "3.2.0"
|
open = "3.2.0"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
|
37
docs/iamb.1
37
docs/iamb.1
|
@ -188,6 +188,43 @@ Close all but one tab.
|
||||||
Go to the preview tab.
|
Go to the preview tab.
|
||||||
.El
|
.El
|
||||||
|
|
||||||
|
.Sh "SLASH COMMANDS"
|
||||||
|
.Bl -tag -width Ds
|
||||||
|
.It Sy "/markdown" , Sy "/md"
|
||||||
|
Interpret the message body as Markdown markup.
|
||||||
|
This is the default behaviour.
|
||||||
|
.It Sy "/html" , Sy "/h"
|
||||||
|
Send the message body as literal HTML.
|
||||||
|
.It Sy "/plaintext" , Sy "/plain" , Sy "/p"
|
||||||
|
Do not interpret any markup in the message body and send it as it is.
|
||||||
|
.It Sy "/me"
|
||||||
|
Send an emote message.
|
||||||
|
.It Sy "/confetti"
|
||||||
|
Produces no effect in
|
||||||
|
.Nm ,
|
||||||
|
but will display confetti in Matrix clients that support doing so.
|
||||||
|
.It Sy "/fireworks"
|
||||||
|
Produces no effect in
|
||||||
|
.Nm ,
|
||||||
|
but will display fireworks in Matrix clients that support doing so.
|
||||||
|
.It Sy "/hearts"
|
||||||
|
Produces no effect in
|
||||||
|
.Nm ,
|
||||||
|
but will display floating hearts in Matrix clients that support doing so.
|
||||||
|
.It Sy "/rainfall"
|
||||||
|
Produces no effect in
|
||||||
|
.Nm ,
|
||||||
|
but will display rainfall in Matrix clients that support doing so.
|
||||||
|
.It Sy "/snowfall"
|
||||||
|
Produces no effect in
|
||||||
|
.Nm ,
|
||||||
|
but will display snowfall in Matrix clients that support doing so.
|
||||||
|
.It Sy "/spaceinvaders"
|
||||||
|
Produces no effect in
|
||||||
|
.Nm ,
|
||||||
|
but will display aliens from Space Invaders in Matrix clients that support doing so.
|
||||||
|
.El
|
||||||
|
|
||||||
.Sh EXAMPLES
|
.Sh EXAMPLES
|
||||||
.Ss Example 1: Starting with a specific profile
|
.Ss Example 1: Starting with a specific profile
|
||||||
To start with a profile named
|
To start with a profile named
|
||||||
|
|
290
src/message/compose.rs
Normal file
290
src/message/compose.rs
Normal file
|
@ -0,0 +1,290 @@
|
||||||
|
//! Code for converting composed messages into content to send to the homeserver.
|
||||||
|
use comrak::{markdown_to_html, ComrakOptions};
|
||||||
|
use nom::{
|
||||||
|
branch::alt,
|
||||||
|
bytes::complete::tag,
|
||||||
|
character::complete::space0,
|
||||||
|
combinator::value,
|
||||||
|
IResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
use matrix_sdk::ruma::events::room::message::{
|
||||||
|
EmoteMessageEventContent,
|
||||||
|
MessageType,
|
||||||
|
RoomMessageEventContent,
|
||||||
|
TextMessageEventContent,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
enum SlashCommand {
|
||||||
|
/// Send an emote message.
|
||||||
|
Emote,
|
||||||
|
|
||||||
|
/// Send a message as literal HTML.
|
||||||
|
Html,
|
||||||
|
|
||||||
|
/// Send a message without parsing any markup.
|
||||||
|
Plaintext,
|
||||||
|
|
||||||
|
/// Send a Markdown message (the default message markup).
|
||||||
|
#[default]
|
||||||
|
Markdown,
|
||||||
|
|
||||||
|
/// Send a message with confetti effects in clients that show them.
|
||||||
|
Confetti,
|
||||||
|
|
||||||
|
/// Send a message with fireworks effects in clients that show them.
|
||||||
|
Fireworks,
|
||||||
|
|
||||||
|
/// Send a message with heart effects in clients that show them.
|
||||||
|
Hearts,
|
||||||
|
|
||||||
|
/// Send a message with rainfall effects in clients that show them.
|
||||||
|
Rainfall,
|
||||||
|
|
||||||
|
/// Send a message with snowfall effects in clients that show them.
|
||||||
|
Snowfall,
|
||||||
|
|
||||||
|
/// Send a message with heart effects in clients that show them.
|
||||||
|
SpaceInvaders,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SlashCommand {
|
||||||
|
fn to_message(&self, input: &str) -> anyhow::Result<MessageType> {
|
||||||
|
let msgtype = match self {
|
||||||
|
SlashCommand::Emote => {
|
||||||
|
let html = text_to_html(input);
|
||||||
|
let msg = EmoteMessageEventContent::html(input, html);
|
||||||
|
MessageType::Emote(msg)
|
||||||
|
},
|
||||||
|
SlashCommand::Html => {
|
||||||
|
let msg = TextMessageEventContent::html(input, input);
|
||||||
|
MessageType::Text(msg)
|
||||||
|
},
|
||||||
|
SlashCommand::Plaintext => {
|
||||||
|
let msg = TextMessageEventContent::plain(input);
|
||||||
|
MessageType::Text(msg)
|
||||||
|
},
|
||||||
|
SlashCommand::Markdown => {
|
||||||
|
let html = text_to_html(input);
|
||||||
|
let msg = TextMessageEventContent::html(input, html);
|
||||||
|
MessageType::Text(msg)
|
||||||
|
},
|
||||||
|
SlashCommand::Confetti => {
|
||||||
|
MessageType::new("nic.custom.confetti", input.into(), Default::default())?
|
||||||
|
},
|
||||||
|
SlashCommand::Fireworks => {
|
||||||
|
MessageType::new("nic.custom.fireworks", input.into(), Default::default())?
|
||||||
|
},
|
||||||
|
SlashCommand::Hearts => {
|
||||||
|
MessageType::new("io.element.effect.hearts", input.into(), Default::default())?
|
||||||
|
},
|
||||||
|
SlashCommand::Rainfall => {
|
||||||
|
MessageType::new("io.element.effect.rainfall", input.into(), Default::default())?
|
||||||
|
},
|
||||||
|
SlashCommand::Snowfall => {
|
||||||
|
MessageType::new("io.element.effect.snowfall", input.into(), Default::default())?
|
||||||
|
},
|
||||||
|
SlashCommand::SpaceInvaders => {
|
||||||
|
MessageType::new(
|
||||||
|
"io.element.effects.space_invaders",
|
||||||
|
input.into(),
|
||||||
|
Default::default(),
|
||||||
|
)?
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(msgtype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_slash_command_inner(input: &str) -> IResult<&str, SlashCommand> {
|
||||||
|
let (input, _) = space0(input)?;
|
||||||
|
let (input, slash) = alt((
|
||||||
|
value(SlashCommand::Emote, tag("/me ")),
|
||||||
|
value(SlashCommand::Html, tag("/h ")),
|
||||||
|
value(SlashCommand::Html, tag("/html ")),
|
||||||
|
value(SlashCommand::Plaintext, tag("/p ")),
|
||||||
|
value(SlashCommand::Plaintext, tag("/plain ")),
|
||||||
|
value(SlashCommand::Plaintext, tag("/plaintext ")),
|
||||||
|
value(SlashCommand::Markdown, tag("/md ")),
|
||||||
|
value(SlashCommand::Markdown, tag("/markdown ")),
|
||||||
|
value(SlashCommand::Confetti, tag("/confetti ")),
|
||||||
|
value(SlashCommand::Fireworks, tag("/fireworks ")),
|
||||||
|
value(SlashCommand::Hearts, tag("/hearts ")),
|
||||||
|
value(SlashCommand::Rainfall, tag("/rainfall ")),
|
||||||
|
value(SlashCommand::Snowfall, tag("/snowfall ")),
|
||||||
|
value(SlashCommand::SpaceInvaders, tag("/spaceinvaders ")),
|
||||||
|
))(input)?;
|
||||||
|
let (input, _) = space0(input)?;
|
||||||
|
|
||||||
|
Ok((input, slash))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_slash_command(input: &str) -> anyhow::Result<(&str, SlashCommand)> {
|
||||||
|
match parse_slash_command_inner(input) {
|
||||||
|
Ok(input) => Ok(input),
|
||||||
|
Err(e) => Err(anyhow::anyhow!("Failed to parse slash command: {e}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_to_html(input: &str) -> String {
|
||||||
|
let mut options = ComrakOptions::default();
|
||||||
|
options.extension.autolink = true;
|
||||||
|
options.extension.shortcodes = true;
|
||||||
|
options.extension.strikethrough = true;
|
||||||
|
options.render.hardbreaks = true;
|
||||||
|
markdown_to_html(input, &options)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_to_message_content(input: String) -> TextMessageEventContent {
|
||||||
|
let html = text_to_html(input.as_str());
|
||||||
|
TextMessageEventContent::html(input, html)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn text_to_message(input: String) -> RoomMessageEventContent {
|
||||||
|
let msg = parse_slash_command(input.as_str())
|
||||||
|
.and_then(|(input, slash)| slash.to_message(input))
|
||||||
|
.unwrap_or_else(|_| MessageType::Text(text_to_message_content(input)));
|
||||||
|
|
||||||
|
RoomMessageEventContent::new(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_markdown_autolink() {
|
||||||
|
let input = "http://example.com\n";
|
||||||
|
let content = text_to_message_content(input.into());
|
||||||
|
assert_eq!(content.body, input);
|
||||||
|
assert_eq!(
|
||||||
|
content.formatted.unwrap().body,
|
||||||
|
"<p><a href=\"http://example.com\">http://example.com</a></p>\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
let input = "www.example.com\n";
|
||||||
|
let content = text_to_message_content(input.into());
|
||||||
|
assert_eq!(content.body, input);
|
||||||
|
assert_eq!(
|
||||||
|
content.formatted.unwrap().body,
|
||||||
|
"<p><a href=\"http://www.example.com\">www.example.com</a></p>\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
let input = "See docs (they're at https://iamb.chat)\n";
|
||||||
|
let content = text_to_message_content(input.into());
|
||||||
|
assert_eq!(content.body, input);
|
||||||
|
assert_eq!(
|
||||||
|
content.formatted.unwrap().body,
|
||||||
|
"<p>See docs (they're at <a href=\"https://iamb.chat\">https://iamb.chat</a>)</p>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_markdown_message() {
|
||||||
|
let input = "**bold**\n";
|
||||||
|
let content = text_to_message_content(input.into());
|
||||||
|
assert_eq!(content.body, input);
|
||||||
|
assert_eq!(content.formatted.unwrap().body, "<p><strong>bold</strong></p>\n");
|
||||||
|
|
||||||
|
let input = "*emphasis*\n";
|
||||||
|
let content = text_to_message_content(input.into());
|
||||||
|
assert_eq!(content.body, input);
|
||||||
|
assert_eq!(content.formatted.unwrap().body, "<p><em>emphasis</em></p>\n");
|
||||||
|
|
||||||
|
let input = "`code`\n";
|
||||||
|
let content = text_to_message_content(input.into());
|
||||||
|
assert_eq!(content.body, input);
|
||||||
|
assert_eq!(content.formatted.unwrap().body, "<p><code>code</code></p>\n");
|
||||||
|
|
||||||
|
let input = "```rust\nconst A: usize = 1;\n```\n";
|
||||||
|
let content = text_to_message_content(input.into());
|
||||||
|
assert_eq!(content.body, input);
|
||||||
|
assert_eq!(
|
||||||
|
content.formatted.unwrap().body,
|
||||||
|
"<pre><code class=\"language-rust\">const A: usize = 1;\n</code></pre>\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
let input = ":heart:\n";
|
||||||
|
let content = text_to_message_content(input.into());
|
||||||
|
assert_eq!(content.body, input);
|
||||||
|
assert_eq!(content.formatted.unwrap().body, "<p>\u{2764}\u{FE0F}</p>\n");
|
||||||
|
|
||||||
|
let input = "para 1\n\npara 2\n";
|
||||||
|
let content = text_to_message_content(input.into());
|
||||||
|
assert_eq!(content.body, input);
|
||||||
|
assert_eq!(content.formatted.unwrap().body, "<p>para 1</p>\n<p>para 2</p>\n");
|
||||||
|
|
||||||
|
let input = "line 1\nline 2\n";
|
||||||
|
let content = text_to_message_content(input.into());
|
||||||
|
assert_eq!(content.body, input);
|
||||||
|
assert_eq!(content.formatted.unwrap().body, "<p>line 1<br />\nline 2</p>\n");
|
||||||
|
|
||||||
|
let input = "# Heading\n## Subheading\n\ntext\n";
|
||||||
|
let content = text_to_message_content(input.into());
|
||||||
|
assert_eq!(content.body, input);
|
||||||
|
assert_eq!(
|
||||||
|
content.formatted.unwrap().body,
|
||||||
|
"<h1>Heading</h1>\n<h2>Subheading</h2>\n<p>text</p>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn text_to_message_slash_commands() {
|
||||||
|
let MessageType::Text(content) = text_to_message("/html <b>bold</b>".into()).msgtype else {
|
||||||
|
panic!("Expected MessageType::Text");
|
||||||
|
};
|
||||||
|
assert_eq!(content.body, "<b>bold</b>");
|
||||||
|
assert_eq!(content.formatted.unwrap().body, "<b>bold</b>");
|
||||||
|
|
||||||
|
let MessageType::Text(content) = text_to_message("/h <b>bold</b>".into()).msgtype else {
|
||||||
|
panic!("Expected MessageType::Text");
|
||||||
|
};
|
||||||
|
assert_eq!(content.body, "<b>bold</b>");
|
||||||
|
assert_eq!(content.formatted.unwrap().body, "<b>bold</b>");
|
||||||
|
|
||||||
|
let MessageType::Text(content) = text_to_message("/plain <b>bold</b>".into()).msgtype
|
||||||
|
else {
|
||||||
|
panic!("Expected MessageType::Text");
|
||||||
|
};
|
||||||
|
assert_eq!(content.body, "<b>bold</b>");
|
||||||
|
assert!(content.formatted.is_none(), "{:?}", content.formatted);
|
||||||
|
|
||||||
|
let MessageType::Text(content) = text_to_message("/p <b>bold</b>".into()).msgtype else {
|
||||||
|
panic!("Expected MessageType::Text");
|
||||||
|
};
|
||||||
|
assert_eq!(content.body, "<b>bold</b>");
|
||||||
|
assert!(content.formatted.is_none(), "{:?}", content.formatted);
|
||||||
|
|
||||||
|
let MessageType::Emote(content) = text_to_message("/me *bold*".into()).msgtype else {
|
||||||
|
panic!("Expected MessageType::Emote");
|
||||||
|
};
|
||||||
|
assert_eq!(content.body, "*bold*");
|
||||||
|
assert_eq!(content.formatted.unwrap().body, "<p><em>bold</em></p>\n");
|
||||||
|
|
||||||
|
let content = text_to_message("/confetti hello".into()).msgtype;
|
||||||
|
assert_eq!(content.msgtype(), "nic.custom.confetti");
|
||||||
|
assert_eq!(content.body(), "hello");
|
||||||
|
|
||||||
|
let content = text_to_message("/fireworks hello".into()).msgtype;
|
||||||
|
assert_eq!(content.msgtype(), "nic.custom.fireworks");
|
||||||
|
assert_eq!(content.body(), "hello");
|
||||||
|
|
||||||
|
let content = text_to_message("/hearts hello".into()).msgtype;
|
||||||
|
assert_eq!(content.msgtype(), "io.element.effect.hearts");
|
||||||
|
assert_eq!(content.body(), "hello");
|
||||||
|
|
||||||
|
let content = text_to_message("/rainfall hello".into()).msgtype;
|
||||||
|
assert_eq!(content.msgtype(), "io.element.effect.rainfall");
|
||||||
|
assert_eq!(content.body(), "hello");
|
||||||
|
|
||||||
|
let content = text_to_message("/snowfall hello".into()).msgtype;
|
||||||
|
assert_eq!(content.msgtype(), "io.element.effect.snowfall");
|
||||||
|
assert_eq!(content.body(), "hello");
|
||||||
|
|
||||||
|
let content = text_to_message("/spaceinvaders hello".into()).msgtype;
|
||||||
|
assert_eq!(content.msgtype(), "io.element.effects.space_invaders");
|
||||||
|
assert_eq!(content.body(), "hello");
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,6 @@ use std::hash::{Hash, Hasher};
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use chrono::{DateTime, Local as LocalTz, NaiveDateTime, TimeZone};
|
use chrono::{DateTime, Local as LocalTz, NaiveDateTime, TimeZone};
|
||||||
use comrak::{markdown_to_html, ComrakOptions};
|
|
||||||
use humansize::{format_size, DECIMAL};
|
use humansize::{format_size, DECIMAL};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
@ -33,7 +32,6 @@ use matrix_sdk::ruma::{
|
||||||
Relation,
|
Relation,
|
||||||
RoomMessageEvent,
|
RoomMessageEvent,
|
||||||
RoomMessageEventContent,
|
RoomMessageEventContent,
|
||||||
TextMessageEventContent,
|
|
||||||
},
|
},
|
||||||
redaction::SyncRoomRedactionEvent,
|
redaction::SyncRoomRedactionEvent,
|
||||||
},
|
},
|
||||||
|
@ -66,9 +64,12 @@ use crate::{
|
||||||
util::{replace_emojis_in_str, space, space_span, take_width, wrapped_text},
|
util::{replace_emojis_in_str, space, space_span, take_width, wrapped_text},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod compose;
|
||||||
mod html;
|
mod html;
|
||||||
mod printer;
|
mod printer;
|
||||||
|
|
||||||
|
pub use self::compose::text_to_message;
|
||||||
|
|
||||||
pub type MessageKey = (MessageTimeStamp, OwnedEventId);
|
pub type MessageKey = (MessageTimeStamp, OwnedEventId);
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -129,22 +130,6 @@ const MIN_MSG_LEN: usize = 30;
|
||||||
const TIME_GUTTER_EMPTY: &str = " ";
|
const TIME_GUTTER_EMPTY: &str = " ";
|
||||||
const TIME_GUTTER_EMPTY_SPAN: Span<'static> = span_static(TIME_GUTTER_EMPTY);
|
const TIME_GUTTER_EMPTY_SPAN: Span<'static> = span_static(TIME_GUTTER_EMPTY);
|
||||||
|
|
||||||
fn text_to_message_content(input: String) -> TextMessageEventContent {
|
|
||||||
let mut options = ComrakOptions::default();
|
|
||||||
options.extension.autolink = true;
|
|
||||||
options.extension.shortcodes = true;
|
|
||||||
options.extension.strikethrough = true;
|
|
||||||
options.render.hardbreaks = true;
|
|
||||||
let html = markdown_to_html(input.as_str(), &options);
|
|
||||||
|
|
||||||
TextMessageEventContent::html(input, html)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text_to_message(input: String) -> RoomMessageEventContent {
|
|
||||||
let msg = MessageType::Text(text_to_message_content(input));
|
|
||||||
RoomMessageEventContent::new(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Before the image is loaded, already display a placeholder frame of the image size.
|
/// Before the image is loaded, already display a placeholder frame of the image size.
|
||||||
fn placeholder_frame(
|
fn placeholder_frame(
|
||||||
text: Option<&str>,
|
text: Option<&str>,
|
||||||
|
@ -553,7 +538,18 @@ fn body_cow_content(content: &RoomMessageEventContent) -> Cow<'_, str> {
|
||||||
display_file_to_text!(Video, content);
|
display_file_to_text!(Video, content);
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
return Cow::Owned(format!("[Unknown message type: {:?}]", content.msgtype()));
|
match content.msgtype() {
|
||||||
|
// Just show the body text for the special Element messages.
|
||||||
|
"nic.custom.confetti" |
|
||||||
|
"nic.custom.fireworks" |
|
||||||
|
"io.element.effect.hearts" |
|
||||||
|
"io.element.effect.rainfall" |
|
||||||
|
"io.element.effect.snowfall" |
|
||||||
|
"io.element.effects.space_invaders" => content.body(),
|
||||||
|
other => {
|
||||||
|
return Cow::Owned(format!("[Unknown message type: {other:?}]"));
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1274,82 +1270,6 @@ pub mod tests {
|
||||||
assert_eq!(identity(&mc6), mc1);
|
assert_eq!(identity(&mc6), mc1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_markdown_autolink() {
|
|
||||||
let input = "http://example.com\n";
|
|
||||||
let content = text_to_message_content(input.into());
|
|
||||||
assert_eq!(content.body, input);
|
|
||||||
assert_eq!(
|
|
||||||
content.formatted.unwrap().body,
|
|
||||||
"<p><a href=\"http://example.com\">http://example.com</a></p>\n"
|
|
||||||
);
|
|
||||||
|
|
||||||
let input = "www.example.com\n";
|
|
||||||
let content = text_to_message_content(input.into());
|
|
||||||
assert_eq!(content.body, input);
|
|
||||||
assert_eq!(
|
|
||||||
content.formatted.unwrap().body,
|
|
||||||
"<p><a href=\"http://www.example.com\">www.example.com</a></p>\n"
|
|
||||||
);
|
|
||||||
|
|
||||||
let input = "See docs (they're at https://iamb.chat)\n";
|
|
||||||
let content = text_to_message_content(input.into());
|
|
||||||
assert_eq!(content.body, input);
|
|
||||||
assert_eq!(
|
|
||||||
content.formatted.unwrap().body,
|
|
||||||
"<p>See docs (they're at <a href=\"https://iamb.chat\">https://iamb.chat</a>)</p>\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_markdown_message() {
|
|
||||||
let input = "**bold**\n";
|
|
||||||
let content = text_to_message_content(input.into());
|
|
||||||
assert_eq!(content.body, input);
|
|
||||||
assert_eq!(content.formatted.unwrap().body, "<p><strong>bold</strong></p>\n");
|
|
||||||
|
|
||||||
let input = "*emphasis*\n";
|
|
||||||
let content = text_to_message_content(input.into());
|
|
||||||
assert_eq!(content.body, input);
|
|
||||||
assert_eq!(content.formatted.unwrap().body, "<p><em>emphasis</em></p>\n");
|
|
||||||
|
|
||||||
let input = "`code`\n";
|
|
||||||
let content = text_to_message_content(input.into());
|
|
||||||
assert_eq!(content.body, input);
|
|
||||||
assert_eq!(content.formatted.unwrap().body, "<p><code>code</code></p>\n");
|
|
||||||
|
|
||||||
let input = "```rust\nconst A: usize = 1;\n```\n";
|
|
||||||
let content = text_to_message_content(input.into());
|
|
||||||
assert_eq!(content.body, input);
|
|
||||||
assert_eq!(
|
|
||||||
content.formatted.unwrap().body,
|
|
||||||
"<pre><code class=\"language-rust\">const A: usize = 1;\n</code></pre>\n"
|
|
||||||
);
|
|
||||||
|
|
||||||
let input = ":heart:\n";
|
|
||||||
let content = text_to_message_content(input.into());
|
|
||||||
assert_eq!(content.body, input);
|
|
||||||
assert_eq!(content.formatted.unwrap().body, "<p>\u{2764}\u{FE0F}</p>\n");
|
|
||||||
|
|
||||||
let input = "para 1\n\npara 2\n";
|
|
||||||
let content = text_to_message_content(input.into());
|
|
||||||
assert_eq!(content.body, input);
|
|
||||||
assert_eq!(content.formatted.unwrap().body, "<p>para 1</p>\n<p>para 2</p>\n");
|
|
||||||
|
|
||||||
let input = "line 1\nline 2\n";
|
|
||||||
let content = text_to_message_content(input.into());
|
|
||||||
assert_eq!(content.body, input);
|
|
||||||
assert_eq!(content.formatted.unwrap().body, "<p>line 1<br />\nline 2</p>\n");
|
|
||||||
|
|
||||||
let input = "# Heading\n## Subheading\n\ntext\n";
|
|
||||||
let content = text_to_message_content(input.into());
|
|
||||||
assert_eq!(content.body, input);
|
|
||||||
assert_eq!(
|
|
||||||
content.formatted.unwrap().body,
|
|
||||||
"<h1>Heading</h1>\n<h2>Subheading</h2>\n<p>text</p>\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_placeholder_frame() {
|
fn test_placeholder_frame() {
|
||||||
fn pretty_frame_test(str: &str) -> Option<String> {
|
fn pretty_frame_test(str: &str) -> Option<String> {
|
||||||
|
|
|
@ -228,7 +228,9 @@ pub fn event_notification_body(
|
||||||
MessageType::VerificationRequest(_) => {
|
MessageType::VerificationRequest(_) => {
|
||||||
format!("{sender_name} sent a verification request.")
|
format!("{sender_name} sent a verification request.")
|
||||||
},
|
},
|
||||||
_ => unimplemented!(),
|
_ => {
|
||||||
|
format!("[Unknown message type: {:?}]", &message.msgtype)
|
||||||
|
},
|
||||||
};
|
};
|
||||||
Some(body)
|
Some(body)
|
||||||
},
|
},
|
||||||
|
|
|
@ -401,7 +401,7 @@ impl RoomState {
|
||||||
},
|
},
|
||||||
RoomField::CanonicalAlias => {
|
RoomField::CanonicalAlias => {
|
||||||
let Some(alias_to_destroy) = room.canonical_alias() else {
|
let Some(alias_to_destroy) = room.canonical_alias() else {
|
||||||
let msg = format!("This room has no canonical alias to unset");
|
let msg = "This room has no canonical alias to unset";
|
||||||
|
|
||||||
return Ok(vec![(Action::ShowInfoMessage(msg.into()), ctx)]);
|
return Ok(vec![(Action::ShowInfoMessage(msg.into()), ctx)]);
|
||||||
};
|
};
|
||||||
|
@ -500,11 +500,9 @@ impl RoomState {
|
||||||
Some(can) => format!("Canonical alias: {can}"),
|
Some(can) => format!("Canonical alias: {can}"),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
RoomField::Tag(_) => {
|
RoomField::Tag(_) => "Cannot currently show value for a tag".into(),
|
||||||
format!("Cannot currently show value for a tag")
|
|
||||||
},
|
|
||||||
RoomField::Alias(_) => {
|
RoomField::Alias(_) => {
|
||||||
format!("Cannot show a single alias; use `:room aliases show` instead.")
|
"Cannot show a single alias; use `:room aliases show` instead.".into()
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue