mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-28 19:59:17 +00:00
LibWeb: Implement the "forwardDelete" editing command
This commit is contained in:
parent
a71e999ac8
commit
7736d63290
Notes:
github-actions[bot]
2025-01-10 22:37:23 +00:00
Author: https://github.com/gmta
Commit: 7736d63290
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3216
4 changed files with 218 additions and 0 deletions
|
@ -437,6 +437,197 @@ bool command_delete_action(DOM::Document& document, String const&)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-forwarddelete-command
|
||||||
|
bool command_forward_delete_action(DOM::Document& document, String const&)
|
||||||
|
{
|
||||||
|
// 1. If the active range is not collapsed, delete the selection and return true.
|
||||||
|
auto& selection = *document.get_selection();
|
||||||
|
auto& active_range = *selection.range();
|
||||||
|
if (!active_range.collapsed()) {
|
||||||
|
delete_the_selection(selection);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Canonicalize whitespace at the active range's start.
|
||||||
|
canonicalize_whitespace(active_range.start());
|
||||||
|
|
||||||
|
// 3. Let node and offset be the active range's start node and offset.
|
||||||
|
auto node = active_range.start().node;
|
||||||
|
auto offset = active_range.start().offset;
|
||||||
|
|
||||||
|
// 4. Repeat the following steps:
|
||||||
|
while (true) {
|
||||||
|
// 1. If offset is the length of node and node's nextSibling is an editable invisible node, remove node's
|
||||||
|
// nextSibling from its parent.
|
||||||
|
if (offset == node->length() && node->next_sibling() && node->next_sibling()->is_editable()
|
||||||
|
&& is_invisible_node(*node->next_sibling())) {
|
||||||
|
node->next_sibling()->remove();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Otherwise, if node has a child with index offset and that child is an editable invisible node, remove that
|
||||||
|
// child from node.
|
||||||
|
auto* child_at_offset = node->child_at_index(offset);
|
||||||
|
if (child_at_offset && child_at_offset->is_editable() && is_invisible_node(*child_at_offset)) {
|
||||||
|
child_at_offset->remove();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Otherwise, if offset is the length of node and node is an inline node, or if node is invisible, set offset
|
||||||
|
// to one plus the index of node, then set node to its parent.
|
||||||
|
if (node->parent() && ((offset == node->length() && is_inline_node(node)) || is_invisible_node(node))) {
|
||||||
|
offset = node->index() + 1;
|
||||||
|
node = *node->parent();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Otherwise, if node has a child with index offset and that child is neither a block node nor a br nor an
|
||||||
|
// img nor a collapsed block prop, set node to that child, then set offset to zero.
|
||||||
|
if (child_at_offset && !is_block_node(*child_at_offset) && !is<HTML::HTMLBRElement>(*child_at_offset)
|
||||||
|
&& !is<HTML::HTMLImageElement>(*child_at_offset) && !is_collapsed_block_prop(*child_at_offset)) {
|
||||||
|
node = *child_at_offset;
|
||||||
|
offset = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Otherwise, break from this loop.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. If node is a Text node and offset is not node's length:
|
||||||
|
if (is<DOM::Text>(*node) && offset != node->length()) {
|
||||||
|
// 1. Let end offset be offset plus one.
|
||||||
|
auto end_offset = offset + 1;
|
||||||
|
|
||||||
|
// FIXME: 2. While end offset is not node's length and the end offsetth code unit of node's data has general category M
|
||||||
|
// when interpreted as a Unicode code point, add one to end offset.
|
||||||
|
|
||||||
|
// 3. Call collapse(node, offset) on the context object's selection.
|
||||||
|
MUST(selection.collapse(node, offset));
|
||||||
|
|
||||||
|
// 4. Call extend(node, end offset) on the context object's selection.
|
||||||
|
MUST(selection.extend(node, end_offset));
|
||||||
|
|
||||||
|
// 5. Delete the selection.
|
||||||
|
delete_the_selection(selection);
|
||||||
|
|
||||||
|
// 6. Return true.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. If node is an inline node, return true.
|
||||||
|
if (is_inline_node(node))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// 7. If node has a child with index offset and that child is a br or hr or img, but is not a collapsed block prop:
|
||||||
|
if (auto child_at_offset = node->child_at_index(offset);
|
||||||
|
(is<HTML::HTMLBRElement>(child_at_offset) || is<HTML::HTMLHRElement>(child_at_offset) || is<HTML::HTMLImageElement>(child_at_offset))
|
||||||
|
&& !is_collapsed_block_prop(*child_at_offset)) {
|
||||||
|
// 1. Call collapse(node, offset) on the context object's selection.
|
||||||
|
MUST(selection.collapse(node, offset));
|
||||||
|
|
||||||
|
// 2. Call extend(node, offset + 1) on the context object's selection.
|
||||||
|
MUST(selection.extend(node, offset + 1));
|
||||||
|
|
||||||
|
// 3. Delete the selection.
|
||||||
|
delete_the_selection(selection);
|
||||||
|
|
||||||
|
// 4. Return true.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. Let end node equal node and let end offset equal offset.
|
||||||
|
auto end_node = node;
|
||||||
|
auto end_offset = offset;
|
||||||
|
|
||||||
|
// 9. If end node has a child with index end offset, and that child is a collapsed block prop, add one to end
|
||||||
|
// offset.
|
||||||
|
if (auto child_at_offset = end_node->child_at_index(end_offset); child_at_offset && is_collapsed_block_prop(*child_at_offset))
|
||||||
|
++end_offset;
|
||||||
|
|
||||||
|
// 10. Repeat the following steps:
|
||||||
|
while (true) {
|
||||||
|
// 1. If end offset is the length of end node, set end offset to one plus the index of end node and then set end
|
||||||
|
// node to its parent.
|
||||||
|
if (end_node->parent() && end_offset == end_node->length()) {
|
||||||
|
end_offset = end_node->index() + 1;
|
||||||
|
end_node = *end_node->parent();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Otherwise, if end node has an editable invisible child with index end offset, remove it from end node.
|
||||||
|
if (auto child_at_offset = end_node->child_at_index(end_offset); child_at_offset && child_at_offset->is_editable()
|
||||||
|
&& is_invisible_node(*child_at_offset)) {
|
||||||
|
child_at_offset->remove();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Otherwise, break from this loop.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 11. If the child of end node with index end offset minus one is a table, return true.
|
||||||
|
if (is<HTML::HTMLTableElement>(end_node->child_at_index(end_offset - 1)))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// 12. If the child of end node with index end offset is a table:
|
||||||
|
if (is<HTML::HTMLTableElement>(end_node->child_at_index(end_offset))) {
|
||||||
|
// 1. Call collapse(end node, end offset) on the context object's selection.
|
||||||
|
MUST(selection.collapse(end_node, end_offset));
|
||||||
|
|
||||||
|
// 2. Call extend(end node, end offset + 1) on the context object's selection.
|
||||||
|
MUST(selection.extend(end_node, end_offset + 1));
|
||||||
|
|
||||||
|
// 3. Return true.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 13. If offset is the length of node, and the child of end node with index end offset is an hr or br:
|
||||||
|
if (auto child_at_offset = end_node->child_at_index(end_offset); offset == node->length()
|
||||||
|
&& (is<HTML::HTMLHRElement>(child_at_offset) || is<HTML::HTMLBRElement>(child_at_offset))) {
|
||||||
|
// 1. Call collapse(end node, end offset) on the context object's selection.
|
||||||
|
MUST(selection.collapse(end_node, end_offset));
|
||||||
|
|
||||||
|
// 2. Call extend(end node, end offset + 1) on the context object's selection.
|
||||||
|
MUST(selection.extend(end_node, end_offset + 1));
|
||||||
|
|
||||||
|
// 3. Delete the selection.
|
||||||
|
delete_the_selection(selection);
|
||||||
|
|
||||||
|
// 4. Call collapse(node, offset) on the selection.
|
||||||
|
MUST(selection.collapse(node, offset));
|
||||||
|
|
||||||
|
// 5. Return true.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 14. While end node has a child with index end offset:
|
||||||
|
while (auto child_at_offset = end_node->child_at_index(end_offset)) {
|
||||||
|
// 1. If end node's child with index end offset is editable and invisible, remove it from end node.
|
||||||
|
if (child_at_offset->is_editable() && is_invisible_node(*child_at_offset)) {
|
||||||
|
child_at_offset->remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Otherwise, set end node to its child with index end offset and set end offset to zero.
|
||||||
|
else {
|
||||||
|
end_node = *child_at_offset;
|
||||||
|
end_offset = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 15. Call collapse(node, offset) on the context object's selection.
|
||||||
|
MUST(selection.collapse(node, offset));
|
||||||
|
|
||||||
|
// 16. Call extend(end node, end offset) on the context object's selection.
|
||||||
|
MUST(selection.extend(end_node, end_offset));
|
||||||
|
|
||||||
|
// 17. Delete the selection.
|
||||||
|
delete_the_selection(selection);
|
||||||
|
|
||||||
|
// AD-HOC: Return true.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// https://w3c.github.io/editing/docs/execCommand/#the-insertlinebreak-command
|
// https://w3c.github.io/editing/docs/execCommand/#the-insertlinebreak-command
|
||||||
bool command_insert_linebreak_action(DOM::Document& document, String const&)
|
bool command_insert_linebreak_action(DOM::Document& document, String const&)
|
||||||
{
|
{
|
||||||
|
@ -858,6 +1049,12 @@ static Array const commands {
|
||||||
.action = command_default_paragraph_separator_action,
|
.action = command_default_paragraph_separator_action,
|
||||||
.value = command_default_paragraph_separator_value,
|
.value = command_default_paragraph_separator_value,
|
||||||
},
|
},
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-forwarddelete-command
|
||||||
|
CommandDefinition {
|
||||||
|
.command = CommandNames::forwardDelete,
|
||||||
|
.action = command_forward_delete_action,
|
||||||
|
.preserves_overrides = true,
|
||||||
|
},
|
||||||
// https://w3c.github.io/editing/docs/execCommand/#the-insertlinebreak-command
|
// https://w3c.github.io/editing/docs/execCommand/#the-insertlinebreak-command
|
||||||
CommandDefinition {
|
CommandDefinition {
|
||||||
.command = CommandNames::insertLineBreak,
|
.command = CommandNames::insertLineBreak,
|
||||||
|
|
|
@ -33,6 +33,7 @@ bool command_bold_action(DOM::Document&, String const&);
|
||||||
bool command_default_paragraph_separator_action(DOM::Document&, String const&);
|
bool command_default_paragraph_separator_action(DOM::Document&, String const&);
|
||||||
String command_default_paragraph_separator_value(DOM::Document const&);
|
String command_default_paragraph_separator_value(DOM::Document const&);
|
||||||
bool command_delete_action(DOM::Document&, String const&);
|
bool command_delete_action(DOM::Document&, String const&);
|
||||||
|
bool command_forward_delete_action(DOM::Document&, String const&);
|
||||||
bool command_insert_linebreak_action(DOM::Document&, String const&);
|
bool command_insert_linebreak_action(DOM::Document&, String const&);
|
||||||
bool command_insert_paragraph_action(DOM::Document&, String const&);
|
bool command_insert_paragraph_action(DOM::Document&, String const&);
|
||||||
bool command_style_with_css_action(DOM::Document&, String const&);
|
bool command_style_with_css_action(DOM::Document&, String const&);
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Before: foobar
|
||||||
|
After: fooar
|
|
@ -0,0 +1,18 @@
|
||||||
|
<script src="../include.js"></script>
|
||||||
|
<div contenteditable="true">foobar</div>
|
||||||
|
<script>
|
||||||
|
test(() => {
|
||||||
|
var divElm = document.querySelector('div');
|
||||||
|
println(`Before: ${divElm.textContent}`);
|
||||||
|
|
||||||
|
// Put cursor after 'foo'
|
||||||
|
var range = document.createRange();
|
||||||
|
range.setStart(divElm.childNodes[0], 3);
|
||||||
|
getSelection().addRange(range);
|
||||||
|
|
||||||
|
// Press delete
|
||||||
|
document.execCommand('forwardDelete');
|
||||||
|
|
||||||
|
println(`After: ${divElm.textContent}`);
|
||||||
|
});
|
||||||
|
</script>
|
Loading…
Add table
Add a link
Reference in a new issue