From 03bcfb9b8c044d02d785e0c7e4e7b5d9220df4d1 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Fri, 10 Jan 2025 16:00:52 +0100 Subject: [PATCH] LibWeb: Implement the "outdent" editing command --- Libraries/LibWeb/Editing/Commands.cpp | 97 +++++++++++++++++++ Libraries/LibWeb/Editing/Commands.h | 1 + .../expected/Editing/execCommand-outdent.txt | 7 ++ .../input/Editing/execCommand-outdent.html | 31 ++++++ 4 files changed, 136 insertions(+) create mode 100644 Tests/LibWeb/Text/expected/Editing/execCommand-outdent.txt create mode 100644 Tests/LibWeb/Text/input/Editing/execCommand-outdent.html diff --git a/Libraries/LibWeb/Editing/Commands.cpp b/Libraries/LibWeb/Editing/Commands.cpp index e7e4b855520..7f744f77d77 100644 --- a/Libraries/LibWeb/Editing/Commands.cpp +++ b/Libraries/LibWeb/Editing/Commands.cpp @@ -2062,6 +2062,97 @@ String command_justify_right_value(DOM::Document const& document) return justify_value(document); } +// https://w3c.github.io/editing/docs/execCommand/#the-outdent-command +bool command_outdent_action(DOM::Document& document, String const&) +{ + // 1. Let items be a list of all lis that are inclusive ancestors of the active range's start and/or end node. + Vector> items; + auto add_all_lis = [&items](GC::Ref node) { + node->for_each_inclusive_ancestor([&items](GC::Ref ancestor) { + if (is(*ancestor) && !items.contains_slow(ancestor)) + items.append(ancestor); + return IterationDecision::Continue; + }); + }; + auto range = active_range(document); + add_all_lis(range->start_container()); + add_all_lis(range->end_container()); + + // 2. For each item in items, normalize sublists of item. + for (auto item : items) + normalize_sublists_in_node(item); + + // 3. Block-extend the active range, and let new range be the result. + auto new_range = block_extend_a_range(*active_range(document)); + + // 4. Let node list be a list of nodes, initially empty. + Vector> node_list; + + // 5. For each node node contained in new range, append node to node list if the last member of node list (if any) + // is not an ancestor of node; node is editable; and either node has no editable descendants, or is an ol or ul, + // or is an li whose parent is an ol or ul. + auto is_ol_or_ul = [](GC::Ptr node) { + return is(node.ptr()) || is(node.ptr()); + }; + new_range->for_each_contained([&](GC::Ref node) { + bool has_editable_descendants = false; + node->for_each_in_subtree([&has_editable_descendants](GC::Ref descendant) { + if (descendant->is_editable()) { + has_editable_descendants = true; + return TraversalDecision::Break; + } + return TraversalDecision::Continue; + }); + + if ((node_list.is_empty() || !node_list.last()->is_ancestor_of(node)) + && node->is_editable() + && (!has_editable_descendants || is_ol_or_ul(node) + || (is(*node) && is_ol_or_ul(node->parent())))) + node_list.append(node); + + return IterationDecision::Continue; + }); + + // 6. While node list is not empty: + while (!node_list.is_empty()) { + // 1. While the first member of node list is an ol or ul or is not the child of an ol or ul, outdent it and + // remove it from node list. + while (!node_list.is_empty() && (is_ol_or_ul(node_list.first()) || !is_ol_or_ul(node_list.first()->parent()))) + outdent(node_list.take_first()); + + // 2. If node list is empty, break from these substeps. + if (node_list.is_empty()) + break; + + // 3. Let sublist be a list of nodes, initially empty. + Vector> sublist; + + // 4. Remove the first member of node list and append it to sublist. + sublist.append(node_list.take_first()); + + // 5. While the first member of node list is the nextSibling of the last member of sublist, and the first member + // of node list is not an ol or ul, remove the first member of node list and append it to sublist. + while (!node_list.is_empty() && node_list.first().ptr() == sublist.last()->next_sibling() && !is_ol_or_ul(node_list.first())) + sublist.append(node_list.take_first()); + + // 6. Record the values of sublist, and let values be the result. + auto values = record_the_values_of_nodes(sublist); + + // 7. Split the parent of sublist. + split_the_parent_of_nodes(sublist); + + // 8. Fix disallowed ancestors of each member of sublist. + for (auto member : sublist) + fix_disallowed_ancestors_of_node(member); + + // 9. Restore the values from values. + restore_the_values_of_nodes(values); + } + + // 7. Return true. + return true; +} + // https://w3c.github.io/editing/docs/execCommand/#the-removeformat-command bool command_remove_format_action(DOM::Document& document, String const&) { @@ -2500,6 +2591,12 @@ static Array const commands { .value = command_justify_right_value, .preserves_overrides = true, }, + // https://w3c.github.io/editing/docs/execCommand/#the-outdent-command + CommandDefinition { + .command = CommandNames::outdent, + .action = command_outdent_action, + .preserves_overrides = true, + }, // https://w3c.github.io/editing/docs/execCommand/#the-removeformat-command CommandDefinition { .command = CommandNames::removeFormat, diff --git a/Libraries/LibWeb/Editing/Commands.h b/Libraries/LibWeb/Editing/Commands.h index d3dd36a87b0..eb9ca1c695a 100644 --- a/Libraries/LibWeb/Editing/Commands.h +++ b/Libraries/LibWeb/Editing/Commands.h @@ -73,6 +73,7 @@ bool command_justify_right_action(DOM::Document&, String const&); bool command_justify_right_indeterminate(DOM::Document const&); bool command_justify_right_state(DOM::Document const&); String command_justify_right_value(DOM::Document const&); +bool command_outdent_action(DOM::Document&, String const&); bool command_remove_format_action(DOM::Document&, String const&); bool command_strikethrough_action(DOM::Document&, String const&); bool command_style_with_css_action(DOM::Document&, String const&); diff --git a/Tests/LibWeb/Text/expected/Editing/execCommand-outdent.txt b/Tests/LibWeb/Text/expected/Editing/execCommand-outdent.txt new file mode 100644 index 00000000000..941ab6cf291 --- /dev/null +++ b/Tests/LibWeb/Text/expected/Editing/execCommand-outdent.txt @@ -0,0 +1,7 @@ + +
foobar
+
    +
  • foo
  • +
  • bar
  • +
  • baz
  • +
diff --git a/Tests/LibWeb/Text/input/Editing/execCommand-outdent.html b/Tests/LibWeb/Text/input/Editing/execCommand-outdent.html new file mode 100644 index 00000000000..b169f34ce83 --- /dev/null +++ b/Tests/LibWeb/Text/input/Editing/execCommand-outdent.html @@ -0,0 +1,31 @@ + +
+
  • foobar
+
    +
  • foo
  • +
    • bar
  • +
  • baz
  • +
+
+