Merge remote-tracking branch 'upstream/main' into conduwuit-changes
Signed-off-by: strawberry <strawberry@puppygock.gay>
This commit is contained in:
commit
c8a2d06e24
@ -27,6 +27,9 @@ Improvements:
|
||||
to send `Future` events and update `Future` events with `future_tokens`.
|
||||
(`Future` events are scheduled messages that can be controlled
|
||||
with `future_tokens` to send on demand or restart the timeout)
|
||||
- Change types of `SyncRequestListFilters::{room_types,not_room_types}` to
|
||||
`Vec<RoomTypeFilter>` instead of a vector of strings
|
||||
- This is a breaking change, but only for users of `unstable-msc3575`
|
||||
|
||||
Bug fixes:
|
||||
|
||||
|
@ -10,6 +10,7 @@ use js_int::UInt;
|
||||
use js_option::JsOption;
|
||||
use ruma_common::{
|
||||
api::{request, response, Metadata},
|
||||
directory::RoomTypeFilter,
|
||||
metadata,
|
||||
serde::{deserialize_cow_str, duration::opt_ms, Raw},
|
||||
DeviceKeyAlgorithm, MilliSecondsSinceUnixEpoch, OwnedMxcUri, OwnedRoomId, OwnedUserId, RoomId, room::RoomType
|
||||
|
@ -223,12 +223,32 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<RoomType>> for RoomTypeFilter {
|
||||
fn from(t: Option<RoomType>) -> Self {
|
||||
match t {
|
||||
None => Self::Default,
|
||||
Some(s) => match s {
|
||||
RoomType::Space => Self::Space,
|
||||
_ => Self::from(Some(s.as_str())),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use assert_matches2::assert_matches;
|
||||
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
|
||||
|
||||
use super::{Filter, RoomNetwork, RoomTypeFilter};
|
||||
use crate::room::RoomType;
|
||||
|
||||
#[test]
|
||||
fn test_from_room_type() {
|
||||
let test = RoomType::Space;
|
||||
let other: RoomTypeFilter = RoomTypeFilter::from(Some(test));
|
||||
assert_eq!(other, RoomTypeFilter::Space);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_matrix_network_only() {
|
||||
|
@ -7,6 +7,8 @@ Bug fixes:
|
||||
- Fix serialization of `room::message::Relation` and `room::encrypted::Relation`
|
||||
which could cause duplicate `rel_type` keys.
|
||||
- `Restricted` no longer fails to deserialize when the `allow` field is missing
|
||||
- Markdown text constructors now also detect markdown syntax like backslash
|
||||
escapes and entity references to decide if the text should be sent as HTML.
|
||||
|
||||
Improvements:
|
||||
|
||||
|
@ -65,7 +65,7 @@ indexmap = { version = "2.0.0", features = ["serde"] }
|
||||
js_int = { workspace = true, features = ["serde"] }
|
||||
js_option = "0.1.0"
|
||||
percent-encoding = "2.1.0"
|
||||
pulldown-cmark = { version = "0.11.0", optional = true, default-features = false, features = ["html"] }
|
||||
pulldown-cmark = { version = "0.12.1", optional = true, default-features = false, features = ["html"] }
|
||||
regex = { version = "1.5.6", default-features = false, features = ["std", "perf"] }
|
||||
ruma-common = { workspace = true }
|
||||
ruma-html = { workspace = true, optional = true }
|
||||
|
@ -72,6 +72,7 @@ impl CallMemberEventContent {
|
||||
pub fn new_empty(leave_reason: Option<LeaveReason>) -> Self {
|
||||
Self::Empty(EmptyMembershipData { leave_reason })
|
||||
}
|
||||
|
||||
/// All non expired memberships in this member event.
|
||||
///
|
||||
/// In most cases you want to use this method instead of the public memberships field.
|
||||
@ -268,7 +269,7 @@ mod tests {
|
||||
}),
|
||||
"ABCDE".to_owned(),
|
||||
ActiveFocus::Livekit(ActiveLivekitFocus {
|
||||
focus_select: FocusSelection::OldestMembership,
|
||||
focus_selection: FocusSelection::OldestMembership,
|
||||
}),
|
||||
vec![Focus::Livekit(LivekitFocus {
|
||||
alias: "1".to_owned(),
|
||||
@ -294,7 +295,7 @@ mod tests {
|
||||
],
|
||||
"focus_active":{
|
||||
"type":"livekit",
|
||||
"focus_select":"oldest_membership"
|
||||
"focus_selection":"oldest_membership"
|
||||
}
|
||||
});
|
||||
assert_eq!(
|
||||
@ -348,7 +349,7 @@ mod tests {
|
||||
}),
|
||||
"THIS_DEVICE".to_owned(),
|
||||
ActiveFocus::Livekit(ActiveLivekitFocus {
|
||||
focus_select: FocusSelection::OldestMembership,
|
||||
focus_selection: FocusSelection::OldestMembership,
|
||||
}),
|
||||
vec![Focus::Livekit(LivekitFocus {
|
||||
alias: "room1".to_owned(),
|
||||
@ -364,7 +365,7 @@ mod tests {
|
||||
"device_id": "THIS_DEVICE",
|
||||
"focus_active":{
|
||||
"type": "livekit",
|
||||
"focus_select": "oldest_membership"
|
||||
"focus_selection": "oldest_membership"
|
||||
},
|
||||
"foci_preferred": [
|
||||
{
|
||||
@ -473,7 +474,7 @@ mod tests {
|
||||
"device_id": "THIS_DEVICE",
|
||||
"focus_active":{
|
||||
"type": "livekit",
|
||||
"focus_select": "oldest_membership"
|
||||
"focus_selection": "oldest_membership"
|
||||
},
|
||||
"foci_preferred": [
|
||||
{
|
||||
@ -509,26 +510,32 @@ mod tests {
|
||||
assert_eq!(member_event.sender, sender);
|
||||
assert_eq!(member_event.room_id, room_id);
|
||||
assert_eq!(member_event.origin_server_ts, TS(js_int::UInt::new(111).unwrap()));
|
||||
let membership = SessionMembershipData {
|
||||
application: Application::Call(CallApplicationContent {
|
||||
call_id: "".to_owned(),
|
||||
scope: CallScope::Room,
|
||||
}),
|
||||
device_id: "THIS_DEVICE".to_owned(),
|
||||
foci_preferred: [Focus::Livekit(LivekitFocus {
|
||||
alias: "room1".to_owned(),
|
||||
service_url: "https://livekit1.com".to_owned(),
|
||||
})]
|
||||
.to_vec(),
|
||||
focus_active: ActiveFocus::Livekit(ActiveLivekitFocus {
|
||||
focus_selection: FocusSelection::OldestMembership,
|
||||
}),
|
||||
created_ts: None,
|
||||
};
|
||||
assert_eq!(
|
||||
member_event.content,
|
||||
CallMemberEventContent::SessionContent(SessionMembershipData {
|
||||
application: Application::Call(CallApplicationContent {
|
||||
call_id: "".to_owned(),
|
||||
scope: CallScope::Room
|
||||
}),
|
||||
device_id: "THIS_DEVICE".to_owned(),
|
||||
foci_preferred: [Focus::Livekit(LivekitFocus {
|
||||
alias: "room1".to_owned(),
|
||||
service_url: "https://livekit1.com".to_owned()
|
||||
})]
|
||||
.to_vec(),
|
||||
focus_active: ActiveFocus::Livekit(ActiveLivekitFocus {
|
||||
focus_select: FocusSelection::OldestMembership
|
||||
}),
|
||||
created_ts: None
|
||||
})
|
||||
CallMemberEventContent::SessionContent(membership.clone())
|
||||
);
|
||||
|
||||
// Correctly computes the active_memberships array.
|
||||
assert_eq!(
|
||||
member_event.content.active_memberships(None)[0],
|
||||
vec![MembershipData::Session(&membership)][0]
|
||||
);
|
||||
assert_eq!(js_int::Int::new(10), member_event.unsigned.age);
|
||||
assert_eq!(
|
||||
CallMemberEventContent::Empty(EmptyMembershipData { leave_reason: None }),
|
||||
@ -568,7 +575,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memberships_do_expire() {
|
||||
fn legacy_memberships_do_expire() {
|
||||
let content_legacy = create_call_member_legacy_event_content();
|
||||
let (now, one_second_ago, two_hours_ago) = timestamps();
|
||||
|
||||
|
@ -59,7 +59,7 @@ pub enum ActiveFocus {
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct ActiveLivekitFocus {
|
||||
/// The selection method used to select the LiveKit focus for the rtc session.
|
||||
pub focus_select: FocusSelection,
|
||||
pub focus_selection: FocusSelection,
|
||||
}
|
||||
|
||||
impl ActiveLivekitFocus {
|
||||
@ -67,10 +67,10 @@ impl ActiveLivekitFocus {
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `focus_select` - The selection method used to select the LiveKit focus for the rtc
|
||||
/// * `focus_selection` - The selection method used to select the LiveKit focus for the rtc
|
||||
/// session.
|
||||
pub fn new() -> Self {
|
||||
Self { focus_select: FocusSelection::OldestMembership }
|
||||
Self { focus_selection: FocusSelection::OldestMembership }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ impl<'a> MembershipData<'a> {
|
||||
pub fn focus_active(&self) -> &ActiveFocus {
|
||||
match self {
|
||||
MembershipData::Legacy(_) => &ActiveFocus::Livekit(ActiveLivekitFocus {
|
||||
focus_select: super::focus::FocusSelection::OldestMembership,
|
||||
focus_selection: super::focus::FocusSelection::OldestMembership,
|
||||
}),
|
||||
MembershipData::Session(data) => &data.focus_active,
|
||||
}
|
||||
|
@ -858,11 +858,12 @@ pub struct CustomEventContent {
|
||||
|
||||
#[cfg(feature = "markdown")]
|
||||
pub(crate) fn parse_markdown(text: &str) -> Option<String> {
|
||||
use pulldown_cmark::{Event, Options, Parser, Tag, TagEnd};
|
||||
use pulldown_cmark::{CowStr, Event, Options, Parser, Tag, TagEnd};
|
||||
|
||||
const OPTIONS: Options = Options::ENABLE_TABLES.union(Options::ENABLE_STRIKETHROUGH);
|
||||
|
||||
let mut found_first_paragraph = false;
|
||||
let mut previous_event_was_text = false;
|
||||
|
||||
let parser_events: Vec<_> = Parser::new_ext(text, OPTIONS)
|
||||
.map(|event| match event {
|
||||
@ -871,8 +872,29 @@ pub(crate) fn parse_markdown(text: &str) -> Option<String> {
|
||||
})
|
||||
.collect();
|
||||
let has_markdown = parser_events.iter().any(|ref event| {
|
||||
let is_text = matches!(event, Event::Text(_));
|
||||
// Numeric references should be replaced by their UTF-8 equivalent, so encountering a
|
||||
// non-borrowed string means that there is markdown syntax.
|
||||
let is_borrowed_text = matches!(event, Event::Text(CowStr::Borrowed(_)));
|
||||
|
||||
if is_borrowed_text {
|
||||
if previous_event_was_text {
|
||||
// The text was split, so a character was likely removed, like in the case of
|
||||
// backslash escapes, or replaced by a static string, like for entity references, so
|
||||
// there is markdown syntax.
|
||||
return true;
|
||||
} else {
|
||||
previous_event_was_text = true;
|
||||
}
|
||||
} else {
|
||||
previous_event_was_text = false;
|
||||
}
|
||||
|
||||
// A hard break happens when a newline is encountered, which is not necessarily markdown
|
||||
// syntax.
|
||||
let is_break = matches!(event, Event::HardBreak);
|
||||
|
||||
// The parser always wraps the string into a paragraph, so the first paragraph should be
|
||||
// ignored, it is not due to markdown syntax.
|
||||
let is_first_paragraph_start = if matches!(event, Event::Start(Tag::Paragraph)) {
|
||||
if found_first_paragraph {
|
||||
false
|
||||
@ -885,7 +907,7 @@ pub(crate) fn parse_markdown(text: &str) -> Option<String> {
|
||||
};
|
||||
let is_paragraph_end = matches!(event, Event::End(TagEnd::Paragraph));
|
||||
|
||||
!is_text && !is_break && !is_first_paragraph_start && !is_paragraph_end
|
||||
!is_borrowed_text && !is_break && !is_first_paragraph_start && !is_paragraph_end
|
||||
});
|
||||
|
||||
if !has_markdown {
|
||||
@ -897,3 +919,41 @@ pub(crate) fn parse_markdown(text: &str) -> Option<String> {
|
||||
|
||||
Some(html_body)
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "markdown"))]
|
||||
mod tests {
|
||||
use assert_matches2::assert_matches;
|
||||
|
||||
use super::parse_markdown;
|
||||
|
||||
#[test]
|
||||
fn detect_markdown() {
|
||||
// Simple single-line text.
|
||||
let text = "Hello world.";
|
||||
assert_matches!(parse_markdown(text), None);
|
||||
|
||||
// Simple double-line text.
|
||||
let text = "Hello\nworld.";
|
||||
assert_matches!(parse_markdown(text), None);
|
||||
|
||||
// With new paragraph.
|
||||
let text = "Hello\n\nworld.";
|
||||
assert_matches!(parse_markdown(text), Some(_));
|
||||
|
||||
// With tagged element.
|
||||
let text = "Hello **world**.";
|
||||
assert_matches!(parse_markdown(text), Some(_));
|
||||
|
||||
// With backslash escapes.
|
||||
let text = r#"Hello \<world\>."#;
|
||||
assert_matches!(parse_markdown(text), Some(_));
|
||||
|
||||
// With entity reference.
|
||||
let text = r#"Hello <world>."#;
|
||||
assert_matches!(parse_markdown(text), Some(_));
|
||||
|
||||
// With numeric reference.
|
||||
let text = "Hello w⊕rld.";
|
||||
assert_matches!(parse_markdown(text), Some(_));
|
||||
}
|
||||
}
|
||||
|
@ -201,9 +201,9 @@ fn markdown_detection() {
|
||||
let formatted_body = FormattedBody::markdown("A message\nwith\n\nmultiple\n\nparagraphs");
|
||||
formatted_body.unwrap();
|
||||
|
||||
// HTML entities don't trigger markdown.
|
||||
// "Less than" symbol triggers markdown.
|
||||
let formatted_body = FormattedBody::markdown("A message with & HTML < entities");
|
||||
assert_matches!(formatted_body, None);
|
||||
assert_matches!(formatted_body, Some(_));
|
||||
|
||||
// HTML triggers markdown.
|
||||
let formatted_body = FormattedBody::markdown("<span>An HTML message</span>");
|
||||
|
@ -3,6 +3,7 @@
|
||||
Breaking Changes:
|
||||
|
||||
- `MatrixElement::Div` is now a newtype variant.
|
||||
- `AnchorData`'s `name` field was removed, according to MSC4159.
|
||||
|
||||
Improvements:
|
||||
|
||||
|
@ -337,9 +337,6 @@ impl PartialEq<u8> for HeadingLevel {
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub struct AnchorData {
|
||||
/// The name of the anchor.
|
||||
pub name: Option<StrTendril>,
|
||||
|
||||
/// Where to display the linked URL.
|
||||
pub target: Option<StrTendril>,
|
||||
|
||||
@ -350,7 +347,7 @@ pub struct AnchorData {
|
||||
impl AnchorData {
|
||||
/// Construct an empty `AnchorData`.
|
||||
fn new() -> Self {
|
||||
Self { name: None, target: None, href: None }
|
||||
Self { target: None, href: None }
|
||||
}
|
||||
|
||||
/// Parse the given attributes to construct a new `AnchorData`.
|
||||
@ -368,9 +365,6 @@ impl AnchorData {
|
||||
}
|
||||
|
||||
match attr.name.local.as_bytes() {
|
||||
b"name" => {
|
||||
data.name = Some(attr.value.clone());
|
||||
}
|
||||
b"target" => {
|
||||
data.target = Some(attr.value.clone());
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ static ALLOWED_ATTRIBUTES_STRICT: Map<&str, &Set<&str>> = phf_map! {
|
||||
};
|
||||
static ALLOWED_ATTRIBUTES_SPAN_STRICT: Set<&str> =
|
||||
phf_set! { "data-mx-bg-color", "data-mx-color", "data-mx-spoiler", "data-mx-maths" };
|
||||
static ALLOWED_ATTRIBUTES_A_STRICT: Set<&str> = phf_set! { "name", "target", "href" };
|
||||
static ALLOWED_ATTRIBUTES_A_STRICT: Set<&str> = phf_set! { "target", "href" };
|
||||
static ALLOWED_ATTRIBUTES_IMG_STRICT: Set<&str> =
|
||||
phf_set! { "width", "height", "alt", "title", "src" };
|
||||
static ALLOWED_ATTRIBUTES_OL_STRICT: Set<&str> = phf_set! { "start" };
|
||||
|
@ -84,11 +84,7 @@ fn span_attributes() {
|
||||
#[test]
|
||||
fn a_attributes() {
|
||||
let raw_html = "\
|
||||
<a \
|
||||
name=\"my_anchor\" \
|
||||
target=\"_blank\" \
|
||||
href=\"https://localhost/\"\
|
||||
>\
|
||||
<a target=\"_blank\" href=\"https://localhost/\">\
|
||||
Link with all supported attributes\
|
||||
</a>\
|
||||
<a href=\"matrix:r/somewhere:localhost\">Link with valid matrix scheme URI</a>\
|
||||
@ -105,7 +101,6 @@ fn a_attributes() {
|
||||
let element = node.as_element().unwrap().to_matrix();
|
||||
|
||||
assert_matches!(element.element, MatrixElement::A(anchor));
|
||||
assert_eq!(anchor.name.unwrap().as_ref(), "my_anchor");
|
||||
assert_eq!(anchor.target.unwrap().as_ref(), "_blank");
|
||||
assert_matches!(anchor.href.unwrap(), AnchorUri::Other(uri));
|
||||
assert_eq!(uri.as_ref(), "https://localhost/");
|
||||
@ -116,7 +111,6 @@ fn a_attributes() {
|
||||
let element = node.as_element().unwrap().to_matrix();
|
||||
|
||||
assert_matches!(element.element, MatrixElement::A(anchor));
|
||||
assert!(anchor.name.is_none());
|
||||
assert!(anchor.target.is_none());
|
||||
assert_matches!(anchor.href.unwrap(), AnchorUri::Matrix(uri));
|
||||
assert_eq!(uri.to_string(), "matrix:r/somewhere:localhost");
|
||||
@ -127,7 +121,6 @@ fn a_attributes() {
|
||||
let element = node.as_element().unwrap().to_matrix();
|
||||
|
||||
assert_matches!(element.element, MatrixElement::A(anchor));
|
||||
assert!(anchor.name.is_none());
|
||||
assert!(anchor.target.is_none());
|
||||
assert!(anchor.href.is_none());
|
||||
// The `href` attribute is in the unsupported attributes.
|
||||
@ -138,7 +131,6 @@ fn a_attributes() {
|
||||
let element = node.as_element().unwrap().to_matrix();
|
||||
|
||||
assert_matches!(element.element, MatrixElement::A(anchor));
|
||||
assert!(anchor.name.is_none());
|
||||
assert!(anchor.target.is_none());
|
||||
assert_matches!(anchor.href.unwrap(), AnchorUri::MatrixTo(uri));
|
||||
assert_eq!(uri.to_string(), "https://matrix.to/#/%23somewhere:example.org");
|
||||
@ -149,7 +141,6 @@ fn a_attributes() {
|
||||
let element = node.as_element().unwrap().to_matrix();
|
||||
|
||||
assert_matches!(element.element, MatrixElement::A(anchor));
|
||||
assert!(anchor.name.is_none());
|
||||
assert!(anchor.target.is_none());
|
||||
assert!(anchor.href.is_none());
|
||||
// The `href` attribute is in the unsupported attributes.
|
||||
@ -160,7 +151,6 @@ fn a_attributes() {
|
||||
let element = node.as_element().unwrap().to_matrix();
|
||||
|
||||
assert_matches!(element.element, MatrixElement::A(anchor));
|
||||
assert!(anchor.name.is_none());
|
||||
assert!(anchor.target.is_none());
|
||||
assert!(anchor.href.is_none());
|
||||
// The `href` attribute is in the unsupported attributes.
|
||||
|
Loading…
x
Reference in New Issue
Block a user