mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-23 04:55:15 +00:00
LibWeb: Implement the "removeFormat" editing command
This commit is contained in:
parent
ae12f7036b
commit
aee8a75c40
Notes:
github-actions[bot]
2025-01-10 22:36:28 +00:00
Author: https://github.com/gmta Commit: https://github.com/LadybirdBrowser/ladybird/commit/aee8a75c400 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3216
6 changed files with 143 additions and 0 deletions
|
@ -1206,6 +1206,81 @@ bool command_italic_action(DOM::Document& document, String const&)
|
|||
return true;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/editing/docs/execCommand/#the-removeformat-command
|
||||
bool command_remove_format_action(DOM::Document& document, String const&)
|
||||
{
|
||||
// 1. Let elements to remove be a list of every removeFormat candidate effectively contained in the active range.
|
||||
Vector<GC::Ref<DOM::Element>> elements_to_remove;
|
||||
for_each_node_effectively_contained_in_range(active_range(document), [&](GC::Ref<DOM::Node> descendant) {
|
||||
if (is_remove_format_candidate(descendant))
|
||||
elements_to_remove.append(static_cast<DOM::Element&>(*descendant));
|
||||
return TraversalDecision::Continue;
|
||||
});
|
||||
|
||||
// 2. For each element in elements to remove:
|
||||
for (auto element : elements_to_remove) {
|
||||
// 1. While element has children, insert the first child of element into the parent of element immediately
|
||||
// before element, preserving ranges.
|
||||
auto element_index = element->index();
|
||||
while (element->has_children())
|
||||
move_node_preserving_ranges(*element->first_child(), *element->parent(), element_index++);
|
||||
|
||||
// 2. Remove element from its parent.
|
||||
element->remove();
|
||||
}
|
||||
|
||||
// 3. If the active range's start node is an editable Text node, and its start offset is neither zero nor its start
|
||||
// node's length, call splitText() on the active range's start node, with argument equal to the active range's
|
||||
// start offset. Then set the active range's start node to the result, and its start offset to zero.
|
||||
auto range = active_range(document);
|
||||
auto start = range->start();
|
||||
if (start.node->is_editable() && is<DOM::Text>(*start.node) && start.offset != 0 && start.offset != start.node->length()) {
|
||||
auto new_node = MUST(static_cast<DOM::Text&>(*start.node).split_text(start.offset));
|
||||
MUST(range->set_start(new_node, 0));
|
||||
}
|
||||
|
||||
// 4. If the active range's end node is an editable Text node, and its end offset is neither zero nor its end node's
|
||||
// length, call splitText() on the active range's end node, with argument equal to the active range's end offset.
|
||||
auto end = range->end();
|
||||
if (end.node->is_editable() && is<DOM::Text>(*end.node) && end.offset != 0 && end.offset != end.node->length())
|
||||
MUST(static_cast<DOM::Text&>(*end.node).split_text(end.offset));
|
||||
|
||||
// 5. Let node list consist of all editable nodes effectively contained in the active range.
|
||||
Vector<GC::Ref<DOM::Node>> node_list;
|
||||
for_each_node_effectively_contained_in_range(active_range(document), [&](GC::Ref<DOM::Node> descendant) {
|
||||
if (descendant->is_editable())
|
||||
node_list.append(descendant);
|
||||
return TraversalDecision::Continue;
|
||||
});
|
||||
|
||||
// 6. For each node in node list, while node's parent is a removeFormat candidate in the same editing host as node,
|
||||
// split the parent of the one-node list consisting of node.
|
||||
for (auto node : node_list) {
|
||||
while (node->parent() && is_remove_format_candidate(*node->parent()) && is_in_same_editing_host(*node->parent(), node))
|
||||
split_the_parent_of_nodes({ node });
|
||||
}
|
||||
|
||||
// 7. For each of the entries in the following list, in the given order, set the selection's value to null, with
|
||||
// command as given.
|
||||
// 1. subscript
|
||||
// 2. bold
|
||||
// 3. fontName
|
||||
// 4. fontSize
|
||||
// 5. foreColor
|
||||
// 6. hiliteColor
|
||||
// 7. italic
|
||||
// 8. strikethrough
|
||||
// 9. underline
|
||||
for (auto command_name : { CommandNames::subscript, CommandNames::bold, CommandNames::fontName,
|
||||
CommandNames::fontSize, CommandNames::foreColor, CommandNames::hiliteColor, CommandNames::italic,
|
||||
CommandNames::strikethrough, CommandNames::underline }) {
|
||||
set_the_selections_value(document, command_name, {});
|
||||
}
|
||||
|
||||
// 8. Return true.
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/editing/docs/execCommand/#the-stylewithcss-command
|
||||
bool command_style_with_css_action(DOM::Document& document, String const& value)
|
||||
{
|
||||
|
@ -1305,6 +1380,11 @@ static Array const commands {
|
|||
.relevant_css_property = CSS::PropertyID::FontStyle,
|
||||
.inline_activated_values = { "italic"sv, "oblique"sv },
|
||||
},
|
||||
// https://w3c.github.io/editing/docs/execCommand/#the-removeformat-command
|
||||
CommandDefinition {
|
||||
.command = CommandNames::removeFormat,
|
||||
.action = command_remove_format_action,
|
||||
},
|
||||
// https://w3c.github.io/editing/docs/execCommand/#the-stylewithcss-command
|
||||
CommandDefinition {
|
||||
.command = CommandNames::styleWithCSS,
|
||||
|
|
|
@ -43,6 +43,7 @@ bool command_forward_delete_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_italic_action(DOM::Document&, String const&);
|
||||
bool command_remove_format_action(DOM::Document&, String const&);
|
||||
bool command_style_with_css_action(DOM::Document&, String const&);
|
||||
bool command_style_with_css_state(DOM::Document const&);
|
||||
|
||||
|
|
|
@ -2246,6 +2246,48 @@ bool is_prohibited_paragraph_child_name(FlyString const& local_name)
|
|||
HTML::TagNames::xmp);
|
||||
}
|
||||
|
||||
// https://w3c.github.io/editing/docs/execCommand/#removeformat-candidate
|
||||
bool is_remove_format_candidate(GC::Ref<DOM::Node> node)
|
||||
{
|
||||
// A removeFormat candidate is an editable HTML element with local name "abbr", "acronym", "b", "bdi", "bdo", "big",
|
||||
// "blink", "cite", "code", "dfn", "em", "font", "i", "ins", "kbd", "mark", "nobr", "q", "s", "samp", "small",
|
||||
// "span", "strike", "strong", "sub", "sup", "tt", "u", or "var".
|
||||
if (!node->is_editable())
|
||||
return false;
|
||||
if (!is<HTML::HTMLElement>(*node))
|
||||
return false;
|
||||
return static_cast<HTML::HTMLElement&>(*node).local_name().is_one_of(
|
||||
HTML::TagNames::abbr,
|
||||
HTML::TagNames::acronym,
|
||||
HTML::TagNames::b,
|
||||
HTML::TagNames::bdi,
|
||||
HTML::TagNames::bdo,
|
||||
HTML::TagNames::big,
|
||||
HTML::TagNames::blink,
|
||||
HTML::TagNames::cite,
|
||||
HTML::TagNames::code,
|
||||
HTML::TagNames::dfn,
|
||||
HTML::TagNames::em,
|
||||
HTML::TagNames::font,
|
||||
HTML::TagNames::i,
|
||||
HTML::TagNames::ins,
|
||||
HTML::TagNames::kbd,
|
||||
HTML::TagNames::mark,
|
||||
HTML::TagNames::nobr,
|
||||
HTML::TagNames::q,
|
||||
HTML::TagNames::s,
|
||||
HTML::TagNames::samp,
|
||||
HTML::TagNames::small,
|
||||
HTML::TagNames::span,
|
||||
HTML::TagNames::strike,
|
||||
HTML::TagNames::strong,
|
||||
HTML::TagNames::sub,
|
||||
HTML::TagNames::sup,
|
||||
HTML::TagNames::tt,
|
||||
HTML::TagNames::u,
|
||||
HTML::TagNames::var);
|
||||
}
|
||||
|
||||
// https://w3c.github.io/editing/docs/execCommand/#simple-indentation-element
|
||||
bool is_simple_indentation_element(GC::Ref<DOM::Node> node)
|
||||
{
|
||||
|
|
|
@ -67,6 +67,7 @@ bool is_name_of_an_element_with_inline_contents(FlyString const&);
|
|||
bool is_non_list_single_line_container(GC::Ref<DOM::Node>);
|
||||
bool is_prohibited_paragraph_child(GC::Ref<DOM::Node>);
|
||||
bool is_prohibited_paragraph_child_name(FlyString const&);
|
||||
bool is_remove_format_candidate(GC::Ref<DOM::Node>);
|
||||
bool is_simple_indentation_element(GC::Ref<DOM::Node>);
|
||||
bool is_simple_modifiable_element(GC::Ref<DOM::Node>);
|
||||
bool is_single_line_container(GC::Ref<DOM::Node>);
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Div contents: "f<b>o</b>ob<i>a</i>r"
|
||||
Div contents: "foobar"
|
|
@ -0,0 +1,17 @@
|
|||
<script src="../include.js"></script>
|
||||
<div contenteditable="true">f<b>o</b>ob<i>a</i>r</div>
|
||||
<script>
|
||||
test(() => {
|
||||
const range = document.createRange();
|
||||
getSelection().addRange(range);
|
||||
|
||||
const divElm = document.querySelector('div');
|
||||
println(`Div contents: "${divElm.innerHTML}"`);
|
||||
|
||||
// Remove all formatting from 'foobar'
|
||||
range.setStart(divElm, 0);
|
||||
range.setEnd(divElm, 5);
|
||||
document.execCommand('removeFormat');
|
||||
println(`Div contents: "${divElm.innerHTML}"`);
|
||||
});
|
||||
</script>
|
Loading…
Add table
Reference in a new issue