mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-06-09 09:34:57 +09:00
LibWeb: Implement the "insertText" editing command
Minus the autolinking algorithm.
This commit is contained in:
parent
199eaf0d3e
commit
26cadf06d2
Notes:
github-actions[bot]
2025-01-10 22:35:07 +00:00
Author: https://github.com/gmta
Commit: 26cadf06d2
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3216
6 changed files with 216 additions and 11 deletions
|
@ -1726,6 +1726,121 @@ bool command_insert_paragraph_action(DOM::Document& document, String const&)
|
|||
return true;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/editing/docs/execCommand/#the-inserttext-command
|
||||
bool command_insert_text_action(DOM::Document& document, String const& value)
|
||||
{
|
||||
// 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 range = active_range(document);
|
||||
if (!range->start_container()->is_editable_or_editing_host())
|
||||
return true;
|
||||
|
||||
// 3. If value's length is greater than one:
|
||||
if (value.code_points().length() > 1) {
|
||||
// 1. For each code unit el in value, take the action for the insertText command, with value equal to el.
|
||||
for (auto el : value.code_points())
|
||||
command_insert_text_action(document, String::from_code_point(el));
|
||||
|
||||
// 2. Return true.
|
||||
return true;
|
||||
}
|
||||
|
||||
// 4. If value is the empty string, return true.
|
||||
if (value.is_empty())
|
||||
return true;
|
||||
|
||||
// 5. If value is a newline (U+000A), take the action for the insertParagraph command and return true.
|
||||
if (value == "\n"sv) {
|
||||
command_insert_paragraph_action(document, {});
|
||||
return true;
|
||||
}
|
||||
|
||||
// 6. Let node and offset be the active range's start node and offset.
|
||||
auto node = range->start_container();
|
||||
auto offset = range->start_offset();
|
||||
|
||||
// 7. If node has a child whose index is offset − 1, and that child is a Text node, set node to that child, then set
|
||||
// offset to node's length.
|
||||
if (is<DOM::Text>(node->child_at_index(offset - 1))) {
|
||||
node = *node->child_at_index(offset - 1);
|
||||
offset = node->length();
|
||||
}
|
||||
|
||||
// 8. If node has a child whose index is offset, and that child is a Text node, set node to that child, then set
|
||||
// offset to zero.
|
||||
if (is<DOM::Text>(node->child_at_index(offset))) {
|
||||
node = *node->child_at_index(offset);
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
// 9. Record current overrides, and let overrides be the result.
|
||||
auto overrides = record_current_overrides(document);
|
||||
|
||||
// 10. Call collapse(node, offset) on the context object's selection.
|
||||
MUST(selection.collapse(node, offset));
|
||||
|
||||
// 11. Canonicalize whitespace at (node, offset).
|
||||
canonicalize_whitespace({ node, offset });
|
||||
|
||||
// 12. Let (node, offset) be the active range's start.
|
||||
range = *active_range(document);
|
||||
node = range->start_container();
|
||||
offset = range->start_offset();
|
||||
|
||||
// 13. If node is a Text node:
|
||||
if (is<DOM::Text>(*node)) {
|
||||
// 1. Call insertData(offset, value) on node.
|
||||
MUST(static_cast<DOM::Text&>(*node).insert_data(offset, value));
|
||||
|
||||
// 2. Call collapse(node, offset) on the context object's selection.
|
||||
MUST(selection.collapse(node, offset));
|
||||
|
||||
// 3. Call extend(node, offset + 1) on the context object's selection.
|
||||
MUST(selection.extend(node, offset + 1));
|
||||
}
|
||||
|
||||
// 14. Otherwise:
|
||||
else {
|
||||
// 1. If node has only one child, which is a collapsed line break, remove its child from it.
|
||||
if (node->child_count() == 1 && is_collapsed_line_break(*node->first_child()))
|
||||
node->first_child()->remove();
|
||||
|
||||
// 2. Let text be the result of calling createTextNode(value) on the context object.
|
||||
auto text = document.create_text_node(value);
|
||||
|
||||
// 3. Call insertNode(text) on the active range.
|
||||
MUST(active_range(document)->insert_node(text));
|
||||
|
||||
// 4. Call collapse(text, 0) on the context object's selection.
|
||||
MUST(selection.collapse(text, 0));
|
||||
|
||||
// 5. Call extend(text, 1) on the context object's selection.
|
||||
MUST(selection.extend(text, 1));
|
||||
}
|
||||
|
||||
// 15. Restore states and values from overrides.
|
||||
restore_states_and_values(document, overrides);
|
||||
|
||||
// 16. Canonicalize whitespace at the active range's start, with fix collapsed space false.
|
||||
canonicalize_whitespace(active_range(document)->start(), false);
|
||||
|
||||
// 17. Canonicalize whitespace at the active range's end, with fix collapsed space false.
|
||||
canonicalize_whitespace(active_range(document)->end(), false);
|
||||
|
||||
// 18. If value is a space character, autolink the active range's start.
|
||||
if (value == " "sv)
|
||||
autolink(active_range(document)->start());
|
||||
|
||||
// 19. Call collapseToEnd() on the context object's selection.
|
||||
MUST(selection.collapse_to_end());
|
||||
|
||||
// 20. Return true.
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/editing/docs/execCommand/#the-italic-command
|
||||
bool command_italic_action(DOM::Document& document, String const&)
|
||||
{
|
||||
|
@ -2125,6 +2240,11 @@ static Array const commands {
|
|||
.action = command_insert_paragraph_action,
|
||||
.preserves_overrides = true,
|
||||
},
|
||||
// https://w3c.github.io/editing/docs/execCommand/#the-inserttext-command
|
||||
CommandDefinition {
|
||||
.command = CommandNames::insertText,
|
||||
.action = command_insert_text_action,
|
||||
},
|
||||
// https://w3c.github.io/editing/docs/execCommand/#the-italic-command
|
||||
CommandDefinition {
|
||||
.command = CommandNames::italic,
|
||||
|
|
|
@ -52,6 +52,7 @@ bool command_insert_ordered_list_action(DOM::Document&, String const&);
|
|||
bool command_insert_ordered_list_indeterminate(DOM::Document const&);
|
||||
bool command_insert_ordered_list_state(DOM::Document const&);
|
||||
bool command_insert_paragraph_action(DOM::Document&, String const&);
|
||||
bool command_insert_text_action(DOM::Document&, String const&);
|
||||
bool command_italic_action(DOM::Document&, String const&);
|
||||
bool command_remove_format_action(DOM::Document&, String const&);
|
||||
bool command_strikethrough_action(DOM::Document&, String const&);
|
||||
|
|
|
@ -55,6 +55,68 @@ GC::Ptr<DOM::Range> active_range(DOM::Document const& document)
|
|||
return selection->range();
|
||||
}
|
||||
|
||||
// https://w3c.github.io/editing/docs/execCommand/#autolink
|
||||
void autolink(DOM::BoundaryPoint point)
|
||||
{
|
||||
// 1. While (node, end offset)'s previous equivalent point is not null, set it to its previous equivalent point.
|
||||
while (true) {
|
||||
auto previous_point = previous_equivalent_point(point);
|
||||
if (!previous_point.has_value())
|
||||
break;
|
||||
point = previous_point.release_value();
|
||||
}
|
||||
|
||||
// 2. If node is not a Text node, or has an a ancestor, do nothing and abort these steps.
|
||||
if (!is<DOM::Text>(*point.node) || point.node->first_ancestor_of_type<HTML::HTMLAnchorElement>())
|
||||
return;
|
||||
|
||||
// FIXME: 3. Let search be the largest substring of node's data whose end is end offset and that contains no space
|
||||
// characters.
|
||||
|
||||
// FIXME: 4. If some substring of search is an autolinkable URL:
|
||||
String href;
|
||||
if (false) {
|
||||
// FIXME: 1. While there is no substring of node's data ending at end offset that is an autolinkable URL, decrement end
|
||||
// offset.
|
||||
|
||||
// FIXME: 2. Let start offset be the start index of the longest substring of node's data that is an autolinkable URL
|
||||
// ending at end offset.
|
||||
|
||||
// FIXME: 3. Let href be the substring of node's data starting at start offset and ending at end offset.
|
||||
}
|
||||
|
||||
// FIXME: 5. Otherwise, if some substring of search is a valid e-mail address:
|
||||
else if (false) {
|
||||
// FIXME: 1. While there is no substring of node's data ending at end offset that is a valid e-mail address, decrement
|
||||
// end offset.
|
||||
|
||||
// FIXME: 2. Let start offset be the start index of the longest substring of node's data that is a valid e-mail address
|
||||
// ending at end offset.
|
||||
|
||||
// FIXME: 3. Let href be "mailto:" concatenated with the substring of node's data starting at start offset and ending
|
||||
// at end offset.
|
||||
}
|
||||
|
||||
// 6. Otherwise, do nothing and abort these steps.
|
||||
else {
|
||||
return;
|
||||
}
|
||||
|
||||
// 7. Let original range be the active range.
|
||||
auto& document = point.node->document();
|
||||
auto original_range = active_range(document);
|
||||
|
||||
// FIXME: 8. Create a new range with start (node, start offset) and end (node, end offset), and set the context object's
|
||||
// selection's range to it.
|
||||
|
||||
// 9. Take the action for "createLink", with value equal to href.
|
||||
take_the_action_for_command(document, CommandNames::createLink, href);
|
||||
|
||||
// 10. Set the context object's selection's range to original range.
|
||||
if (original_range)
|
||||
document.get_selection()->add_range(*original_range);
|
||||
}
|
||||
|
||||
// https://w3c.github.io/editing/docs/execCommand/#block-extend
|
||||
GC::Ref<DOM::Range> block_extend_a_range(GC::Ref<DOM::Range> range)
|
||||
{
|
||||
|
@ -3268,19 +3330,12 @@ void restore_states_and_values(DOM::Document& document, Vector<RecordedOverride>
|
|||
|
||||
// 2. If node is not null,
|
||||
if (node) {
|
||||
auto take_the_action_for_command = [&document](FlyString const& command, String const& value) {
|
||||
auto const& command_definition = find_command_definition(command);
|
||||
// FIXME: replace with VERIFY(command_definition.has_value()) as soon as all command definitions are in place.
|
||||
if (command_definition.has_value())
|
||||
command_definition->action(document, value);
|
||||
};
|
||||
|
||||
// then for each (command, override) pair in overrides, in order:
|
||||
for (auto override : overrides) {
|
||||
// 1. If override is a boolean, and queryCommandState(command) returns something different from override,
|
||||
// take the action for command, with value equal to the empty string.
|
||||
if (override.value.has<bool>() && document.query_command_state(override.command) != override.value.get<bool>()) {
|
||||
take_the_action_for_command(override.command, {});
|
||||
take_the_action_for_command(document, override.command, {});
|
||||
}
|
||||
|
||||
// 2. Otherwise, if override is a string, and command is neither "createLink" nor "fontSize", and
|
||||
|
@ -3288,7 +3343,7 @@ void restore_states_and_values(DOM::Document& document, Vector<RecordedOverride>
|
|||
// with value equal to override.
|
||||
else if (override.value.has<String>() && !override.command.is_one_of(CommandNames::createLink, CommandNames::fontSize)
|
||||
&& document.query_command_value(override.command) != override.value.get<String>()) {
|
||||
take_the_action_for_command(override.command, override.value.get<String>());
|
||||
take_the_action_for_command(document, override.command, override.value.get<String>());
|
||||
}
|
||||
|
||||
// 3. Otherwise, if override is a string; and command is "createLink"; and either there is a value override
|
||||
|
@ -3300,7 +3355,7 @@ void restore_states_and_values(DOM::Document& document, Vector<RecordedOverride>
|
|||
&& ((value_override.has_value() && value_override.value() != override.value.get<String>())
|
||||
|| (!value_override.has_value()
|
||||
&& effective_command_value(node, CommandNames::createLink) != override.value.get<String>()))) {
|
||||
take_the_action_for_command(CommandNames::createLink, override.value.get<String>());
|
||||
take_the_action_for_command(document, CommandNames::createLink, override.value.get<String>());
|
||||
}
|
||||
|
||||
// 4. Otherwise, if override is a string; and command is "fontSize"; and either there is a value override
|
||||
|
@ -3320,7 +3375,7 @@ void restore_states_and_values(DOM::Document& document, Vector<RecordedOverride>
|
|||
override.value = legacy_font_size(override_pixel_size.to_int());
|
||||
|
||||
// 2. Take the action for "fontSize", with value equal to override.
|
||||
take_the_action_for_command(CommandNames::fontSize, override.value.get<String>());
|
||||
take_the_action_for_command(document, CommandNames::fontSize, override.value.get<String>());
|
||||
}
|
||||
|
||||
// 5. Otherwise, continue this loop from the beginning.
|
||||
|
@ -4526,6 +4581,14 @@ Optional<NonnullRefPtr<CSS::CSSStyleValue const>> resolved_value(GC::Ref<DOM::No
|
|||
return optional_style_property.value().value;
|
||||
}
|
||||
|
||||
void take_the_action_for_command(DOM::Document& document, FlyString const& command, String const& value)
|
||||
{
|
||||
auto const& command_definition = find_command_definition(command);
|
||||
// FIXME: replace with VERIFY(command_definition.has_value()) as soon as all command definitions are in place.
|
||||
if (command_definition.has_value())
|
||||
command_definition->action(document, value);
|
||||
}
|
||||
|
||||
bool value_list_contains_keyword(CSS::StyleValueList const& value_list, CSS::Keyword keyword)
|
||||
{
|
||||
for (auto& css_style_value : value_list.values()) {
|
||||
|
|
|
@ -42,6 +42,7 @@ using Selection::Selection;
|
|||
// https://w3c.github.io/editing/docs/execCommand/#assorted-common-algorithms
|
||||
|
||||
GC::Ptr<DOM::Range> active_range(DOM::Document const&);
|
||||
void autolink(DOM::BoundaryPoint);
|
||||
GC::Ref<DOM::Range> block_extend_a_range(GC::Ref<DOM::Range>);
|
||||
GC::Ptr<DOM::Node> block_node_of_node(GC::Ref<DOM::Node>);
|
||||
String canonical_space_sequence(u32 length, bool non_breaking_start, bool non_breaking_end);
|
||||
|
@ -125,6 +126,7 @@ Optional<NonnullRefPtr<CSS::CSSStyleValue const>> property_in_style_attribute(GC
|
|||
Optional<CSS::Display> resolved_display(GC::Ref<DOM::Node>);
|
||||
Optional<CSS::Keyword> resolved_keyword(GC::Ref<DOM::Node>, CSS::PropertyID);
|
||||
Optional<NonnullRefPtr<CSS::CSSStyleValue const>> resolved_value(GC::Ref<DOM::Node>, CSS::PropertyID);
|
||||
void take_the_action_for_command(DOM::Document&, FlyString const&, String const&);
|
||||
bool value_list_contains_keyword(CSS::StyleValueList const&, CSS::Keyword);
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue