Skip to content

Commit df2c5b3

Browse files
authored
Add support for <ol start='n'> in Rust and Android (#66)
1 parent 9db5790 commit df2c5b3

File tree

9 files changed

+93
-9
lines changed

9 files changed

+93
-9
lines changed

crates/wysiwyg/src/dom/dom_list_methods.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ where
9595
}
9696
}
9797

98-
let list = ContainerNode::new_list(list_type, list_items);
98+
let list = ContainerNode::new_list(list_type, list_items, None);
9999
self.insert_at(first_handle, DomNode::Container(list));
100100

101101
if first_handle.has_parent() {

crates/wysiwyg/src/dom/nodes/container_node.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,15 @@ where
121121
}
122122
}
123123

124-
pub fn new_list(list_type: ListType, children: Vec<DomNode<S>>) -> Self {
124+
pub fn new_list(
125+
list_type: ListType,
126+
children: Vec<DomNode<S>>,
127+
attrs: Option<Vec<(S, S)>>,
128+
) -> Self {
125129
Self {
126130
name: list_type.tag().into(),
127131
kind: ContainerNodeKind::List(list_type),
128-
attrs: None,
132+
attrs,
129133
children,
130134
handle: DomHandle::new_unset(),
131135
}

crates/wysiwyg/src/dom/nodes/dom_node.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ where
7575
list_type: ListType,
7676
children: Vec<DomNode<S>>,
7777
) -> DomNode<S> {
78-
DomNode::Container(ContainerNode::new_list(list_type, children))
78+
DomNode::Container(ContainerNode::new_list(list_type, children, None))
7979
}
8080

8181
pub fn new_list_item(children: Vec<DomNode<S>>) -> DomNode<S> {

crates/wysiwyg/src/dom/parser/parse.rs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,14 @@ mod sys {
162162
}
163163
"ol" | "ul" => {
164164
self.current_path.push(DomNodeKind::List);
165-
node.append_child(Self::new_list(tag));
165+
if tag == "ol" {
166+
let custom_start = child
167+
.get_attr("start")
168+
.and_then(|start| start.parse::<usize>().ok());
169+
node.append_child(Self::new_ordered_list(custom_start));
170+
} else {
171+
node.append_child(Self::new_unordered_list());
172+
}
166173
self.convert_children(
167174
padom,
168175
child,
@@ -328,14 +335,29 @@ mod sys {
328335
}
329336
}
330337

331-
/// Create a list node
332-
fn new_list<S>(tag: &str) -> DomNode<S>
338+
/// Create an unordered list node
339+
fn new_unordered_list<S>() -> DomNode<S>
333340
where
334341
S: UnicodeString,
335342
{
336343
DomNode::Container(ContainerNode::new_list(
337-
ListType::from(S::from(tag)),
344+
ListType::Unordered,
338345
Vec::new(),
346+
None,
347+
))
348+
}
349+
350+
/// Create an ordered list node
351+
fn new_ordered_list<S>(custom_start: Option<usize>) -> DomNode<S>
352+
where
353+
S: UnicodeString,
354+
{
355+
DomNode::Container(ContainerNode::new_list(
356+
ListType::Ordered,
357+
Vec::new(),
358+
custom_start.map(|start| {
359+
vec![("start".into(), start.to_string().into())]
360+
}),
339361
))
340362
}
341363

@@ -1300,12 +1322,23 @@ mod js {
13001322
}
13011323

13021324
"OL" => {
1325+
let custom_start = node
1326+
.unchecked_ref::<Element>()
1327+
.get_attribute("start");
13031328
self.current_path.push(DomNodeKind::List);
13041329
dom.append_child(DomNode::Container(
13051330
ContainerNode::new_list(
13061331
ListType::Ordered,
13071332
self.convert(node.child_nodes())?
13081333
.take_children(),
1334+
if let Some(custom_start) = custom_start {
1335+
Some(vec![(
1336+
"start".into(),
1337+
custom_start.into(),
1338+
)])
1339+
} else {
1340+
None
1341+
},
13091342
),
13101343
));
13111344
self.current_path.pop();
@@ -1318,6 +1351,7 @@ mod js {
13181351
ListType::Unordered,
13191352
self.convert(node.child_nodes())?
13201353
.take_children(),
1354+
None,
13211355
),
13221356
));
13231357
self.current_path.pop();

crates/wysiwyg/src/tests/test_set_content.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,3 +229,10 @@ fn set_content_from_markdown_one_new_line() {
229229
.unwrap();
230230
assert_eq!(tx(&model), "<p>test</p><p>test|</p>");
231231
}
232+
233+
#[test]
234+
fn set_content_from_markdown_ordered_list_with_start() {
235+
let mut model = cm("|");
236+
model.set_content_from_markdown(&utf16("3. First")).unwrap();
237+
assert_eq!(tx(&model), "<ol start=\"3\"><li>First|</li></ol>");
238+
}

platforms/android/example-view/src/main/java/io/element/android/wysiwyg/poc/MainActivity.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ package io.element.android.wysiwyg.poc
1111
import android.os.Bundle
1212
import android.view.LayoutInflater
1313
import android.view.View
14+
import androidx.activity.enableEdgeToEdge
1415
import androidx.appcompat.app.AlertDialog
1516
import androidx.appcompat.app.AppCompatActivity
1617
import io.element.android.wysiwyg.poc.databinding.DialogSetLinkBinding
@@ -19,8 +20,11 @@ import io.element.android.wysiwyg.poc.databinding.ActivityMainBinding
1920
class MainActivity : AppCompatActivity() {
2021

2122
override fun onCreate(savedInstanceState: Bundle?) {
23+
enableEdgeToEdge()
24+
2225
super.onCreate(savedInstanceState)
2326
val binding = ActivityMainBinding.inflate(layoutInflater)
27+
2428
setContentView(binding.root)
2529

2630
binding.editor.requestFocus()

platforms/android/example-view/src/main/res/layout/activity_main.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
xmlns:tools="http://schemas.android.com/tools"
55
android:layout_width="match_parent"
66
android:layout_height="match_parent"
7+
android:fitsSystemWindows="true"
78
tools:context="io.element.android.wysiwyg.poc.MainActivity">
89

910
<io.element.android.wysiwyg.poc.RichTextEditor

platforms/android/library/src/main/java/io/element/android/wysiwyg/utils/HtmlToSpansParser.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ internal class HtmlToSpansParser(
6363
"blockquote", "p", "br"
6464
)
6565
.addAttributes("a", "href", "data-mention-type", "contenteditable")
66+
.addAttributes("ol", "start")
6667

6768
/**
6869
* Convert the HTML string into a [Spanned] text.
@@ -216,7 +217,13 @@ internal class HtmlToSpansParser(
216217
"ol" -> {
217218
val typeface = Typeface.defaultFromStyle(Typeface.NORMAL)
218219
val textSize = 16.dpToPx()
219-
val order = (element.parent()?.select("li")?.indexOf(element) ?: 0) + 1
220+
val indexInList = listParent.select("li").indexOf(element)
221+
val customOrder = listParent.attr("start").toIntOrNull()
222+
val order = if (customOrder != null) {
223+
customOrder + indexInList
224+
} else {
225+
indexInList + 1 // Default to 1-based index
226+
}
220227
OrderedListSpan(typeface, textSize, order, gapWidth)
221228
}
222229
else -> return

platforms/android/library/src/test/kotlin/io/element/android/wysiwyg/utils/HtmlToSpansParserTest.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import io.element.android.wysiwyg.display.TextDisplay
1313
import io.element.android.wysiwyg.display.MentionDisplayHandler
1414
import io.element.android.wysiwyg.test.fakes.createFakeStyleConfig
1515
import io.element.android.wysiwyg.test.utils.dumpSpans
16+
import io.element.android.wysiwyg.view.spans.OrderedListSpan
1617
import org.hamcrest.MatcherAssert.assertThat
1718
import org.hamcrest.Matchers.contains
1819
import org.hamcrest.Matchers.equalTo
@@ -76,6 +77,32 @@ class HtmlToSpansParserTest {
7677
)
7778
}
7879

80+
@Test
81+
fun testOrderedListWithStartAttribute() {
82+
val html = """
83+
<ol start="3">
84+
<li>ordered1</li>
85+
<li>ordered2</li>
86+
</ol>
87+
""".trimIndent()
88+
val spanned = convertHtml(html)
89+
90+
assertThat(
91+
spanned.dumpSpans().joinToString(",\n"), equalTo(
92+
"""
93+
ordered1: io.element.android.wysiwyg.view.spans.OrderedListSpan (0-8) fl=#17,
94+
ordered2: io.element.android.wysiwyg.view.spans.OrderedListSpan (9-17) fl=#17
95+
""".trimIndent()
96+
)
97+
)
98+
99+
val listItemSpans = spanned.getSpans(0, spanned.length, OrderedListSpan::class.java)
100+
// The first item should have order 3
101+
assert(listItemSpans.first().order == 3)
102+
// The second item should continue the order
103+
assert(listItemSpans.last().order == 4)
104+
}
105+
79106
@Test
80107
fun testListsWithPreviousText() {
81108
val html = """

0 commit comments

Comments
 (0)