mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-21 03:55:24 +00:00
LibWeb: Implement document.execCommand('insertLinebreak')
This commit is contained in:
parent
ae53059816
commit
495006ddb5
Notes:
github-actions[bot]
2024-12-10 18:35:28 +00:00
Author: https://github.com/gmta Commit: https://github.com/LadybirdBrowser/ladybird/commit/495006ddb58 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2866
4 changed files with 127 additions and 0 deletions
|
@ -19,6 +19,7 @@
|
|||
#include <LibWeb/HTML/HTMLImageElement.h>
|
||||
#include <LibWeb/HTML/HTMLLIElement.h>
|
||||
#include <LibWeb/HTML/HTMLTableElement.h>
|
||||
#include <LibWeb/Layout/Node.h>
|
||||
#include <LibWeb/Namespace.h>
|
||||
|
||||
namespace Web::Editing {
|
||||
|
@ -374,6 +375,81 @@ bool command_delete_action(DOM::Document& document, String const&)
|
|||
return true;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/editing/docs/execCommand/#the-insertlinebreak-command
|
||||
bool command_insert_linebreak_action(DOM::Document& document, String const&)
|
||||
{
|
||||
// 1. Delete the selection, with strip wrappers false.
|
||||
auto& selection = *document.get_selection();
|
||||
delete_the_selection(selection, true, false);
|
||||
|
||||
// 2. If the active range's start node is neither editable nor an editing host, return true.
|
||||
auto& active_range = *selection.range();
|
||||
auto start_node = active_range.start_container();
|
||||
if (!start_node->is_editable_or_editing_host())
|
||||
return true;
|
||||
|
||||
// 3. If the active range's start node is an Element, and "br" is not an allowed child of it, return true.
|
||||
if (is<DOM::Element>(*start_node) && !is_allowed_child_of_node(HTML::TagNames::br, start_node))
|
||||
return true;
|
||||
|
||||
// 4. If the active range's start node is not an Element, and "br" is not an allowed child of the active range's
|
||||
// start node's parent, return true.
|
||||
if (!is<DOM::Element>(*start_node) && start_node->parent() && !is_allowed_child_of_node(HTML::TagNames::br, GC::Ref { *start_node->parent() }))
|
||||
return true;
|
||||
|
||||
// 5. If the active range's start node is a Text node and its start offset is zero, call collapse() on the context
|
||||
// object's selection, with first argument equal to the active range's start node's parent and second argument
|
||||
// equal to the active range's start node's index.
|
||||
if (is<DOM::Text>(*start_node) && active_range.start_offset() == 0)
|
||||
MUST(selection.collapse(start_node->parent(), start_node->index()));
|
||||
|
||||
// 6. If the active range's start node is a Text node and its start offset is the length of its start node, call
|
||||
// collapse() on the context object's selection, with first argument equal to the active range's start node's
|
||||
// parent and second argument equal to one plus the active range's start node's index.
|
||||
if (is<DOM::Text>(*start_node) && active_range.start_offset() == start_node->length())
|
||||
MUST(selection.collapse(start_node->parent(), start_node->index() + 1));
|
||||
|
||||
// AD-HOC: If the active range's start node is a Text node and its resolved value for "white-space" is one of "pre",
|
||||
// "pre-line" or "pre-wrap":
|
||||
// * Insert a newline (\n) character at the active range's start offset;
|
||||
// * Collapse the selection with active range's start node as the first argument and one plus active range's
|
||||
// start offset as the second argument
|
||||
// * Insert another newline (\n) character if the active range's start offset is equal to the length of the
|
||||
// active range's start node.
|
||||
// * Return true.
|
||||
if (is<DOM::Text>(*start_node) && start_node->layout_node()) {
|
||||
auto& text_node = static_cast<DOM::Text&>(*start_node);
|
||||
auto white_space = text_node.layout_node()->computed_values().white_space();
|
||||
if (first_is_one_of(white_space, CSS::WhiteSpace::Pre, CSS::WhiteSpace::PreLine, CSS::WhiteSpace::PreWrap)) {
|
||||
MUST(text_node.insert_data(active_range.start_offset(), "\n"_string));
|
||||
MUST(selection.collapse(start_node, active_range.start_offset() + 1));
|
||||
if (selection.range()->start_offset() == start_node->length())
|
||||
MUST(text_node.insert_data(active_range.start_offset(), "\n"_string));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Let br be the result of calling createElement("br") on the context object.
|
||||
auto br = MUST(DOM::create_element(document, HTML::TagNames::br, Namespace::HTML));
|
||||
|
||||
// 8. Call insertNode(br) on the active range.
|
||||
MUST(active_range.insert_node(br));
|
||||
|
||||
// 9. Call collapse() on the context object's selection, with br's parent as the first argument and one plus br's
|
||||
// index as the second argument.
|
||||
MUST(selection.collapse(br->parent(), br->index() + 1));
|
||||
|
||||
// 10. If br is a collapsed line break, call createElement("br") on the context object and let extra br be the
|
||||
// result, then call insertNode(extra br) on the active range.
|
||||
if (is_collapsed_line_break(br)) {
|
||||
auto extra_br = MUST(DOM::create_element(document, HTML::TagNames::br, Namespace::HTML));
|
||||
MUST(active_range.insert_node(extra_br));
|
||||
}
|
||||
|
||||
// 11. Return true.
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/editing/docs/execCommand/#the-insertparagraph-command
|
||||
bool command_insert_paragraph_action(DOM::Document& document, String const&)
|
||||
{
|
||||
|
@ -705,6 +781,7 @@ bool command_style_with_css_state(DOM::Document const& document)
|
|||
static Array const commands {
|
||||
CommandDefinition { CommandNames::delete_, command_delete_action, {}, {}, {} },
|
||||
CommandDefinition { CommandNames::defaultParagraphSeparator, command_default_paragraph_separator_action, {}, {}, command_default_paragraph_separator_value },
|
||||
CommandDefinition { CommandNames::insertLineBreak, command_insert_linebreak_action, {}, {}, {} },
|
||||
CommandDefinition { CommandNames::insertParagraph, command_insert_paragraph_action, {}, {}, {} },
|
||||
CommandDefinition { CommandNames::styleWithCSS, command_style_with_css_action, {}, command_style_with_css_state, {} },
|
||||
};
|
||||
|
|
|
@ -24,6 +24,7 @@ Optional<CommandDefinition const&> find_command_definition(FlyString const&);
|
|||
bool command_default_paragraph_separator_action(DOM::Document&, String const&);
|
||||
String command_default_paragraph_separator_value(DOM::Document const&);
|
||||
bool command_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_style_with_css_action(DOM::Document&, String const&);
|
||||
bool command_style_with_css_state(DOM::Document const&);
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
Before: "foobar"
|
||||
After: "foo<br>bar"
|
||||
Before: "<p style="white-space: pre">foobar</p>"
|
||||
After: "<p style="white-space: pre">foo
|
||||
bar</p>"
|
||||
Before: "<p style="white-space: pre">foobar</p>"
|
||||
After: "<p style="white-space: pre">foobar
|
||||
|
||||
</p>"
|
|
@ -0,0 +1,40 @@
|
|||
<script src="../include.js"></script>
|
||||
<div id="a" contenteditable>foobar</div>
|
||||
<div id="b" contenteditable><p style="white-space: pre">foobar</p></div>
|
||||
<div id="c" contenteditable><p style="white-space: pre">foobar</p></div>
|
||||
<script>
|
||||
test(() => {
|
||||
let divA = document.querySelector('div#a');
|
||||
let divB = document.querySelector('div#b');
|
||||
let divC = document.querySelector('div#c');
|
||||
|
||||
document.body.offsetWidth // Force a layout
|
||||
|
||||
// A: Insert linebreak after 'foo'
|
||||
println(`Before: "${divA.innerHTML}"`);
|
||||
var range = document.createRange();
|
||||
range.setStart(divA.firstChild, 3);
|
||||
getSelection().addRange(range);
|
||||
document.execCommand('insertLinebreak');
|
||||
println(`After: "${divA.innerHTML}"`);
|
||||
getSelection().empty();
|
||||
|
||||
// B: Insert linebreak after 'foo'
|
||||
println(`Before: "${divB.innerHTML}"`);
|
||||
var range = document.createRange();
|
||||
range.setStart(divB.firstChild.firstChild, 3);
|
||||
getSelection().addRange(range);
|
||||
document.execCommand('insertLinebreak');
|
||||
println(`After: "${divB.innerHTML}"`);
|
||||
getSelection().empty();
|
||||
|
||||
// C: Insert linebreak after 'bar'
|
||||
println(`Before: "${divC.innerHTML}"`);
|
||||
var range = document.createRange();
|
||||
range.setStart(divC.firstChild.firstChild, 6);
|
||||
getSelection().addRange(range);
|
||||
document.execCommand('insertLinebreak');
|
||||
println(`After: "${divC.innerHTML}"`);
|
||||
getSelection().empty();
|
||||
});
|
||||
</script>
|
Loading…
Add table
Reference in a new issue