mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-30 04:39:06 +00:00
LibWeb: Implement the "justifyCenter/Full/Left/Right" editing commands
This commit is contained in:
parent
1c3251e2d5
commit
fbc0d40d2c
Notes:
github-actions[bot]
2025-01-10 22:34:54 +00:00
Author: https://github.com/gmta
Commit: fbc0d40d2c
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3216
12 changed files with 524 additions and 0 deletions
|
@ -1880,6 +1880,188 @@ bool command_italic_action(DOM::Document& document, String const&)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-justifycenter-command
|
||||||
|
static bool justify_indeterminate(DOM::Document const& document, JustifyAlignment alignment)
|
||||||
|
{
|
||||||
|
// NOTE: This definition is taken from the "justifyCenter" spec and was made generic.
|
||||||
|
|
||||||
|
// Return false if the active range is null. Otherwise, block-extend the active range.
|
||||||
|
auto range = active_range(document);
|
||||||
|
if (!range)
|
||||||
|
return false;
|
||||||
|
range = block_extend_a_range(*range);
|
||||||
|
|
||||||
|
// Return true if among visible editable nodes that are contained in the result and have no children, at least one
|
||||||
|
// has alignment value "[alignment]" and at least one does not. Otherwise return false.
|
||||||
|
Vector<GC::Ref<DOM::Node>> matching_nodes;
|
||||||
|
range->for_each_contained([&matching_nodes](GC::Ref<DOM::Node> node) {
|
||||||
|
if (is_visible_node(node) && node->is_editable() && !node->has_children())
|
||||||
|
matching_nodes.append(node);
|
||||||
|
return IterationDecision::Continue;
|
||||||
|
});
|
||||||
|
return any_of(matching_nodes, [&](GC::Ref<DOM::Node> node) {
|
||||||
|
return alignment_value_of_node(node) == alignment;
|
||||||
|
}) && any_of(matching_nodes, [&](GC::Ref<DOM::Node> node) {
|
||||||
|
return alignment_value_of_node(node) != alignment;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-justifycenter-command
|
||||||
|
static bool justify_state(DOM::Document const& document, JustifyAlignment alignment)
|
||||||
|
{
|
||||||
|
// NOTE: This definition is taken from the "justifyCenter" spec and was made generic.
|
||||||
|
|
||||||
|
// Return false if the active range is null. Otherwise, block-extend the active range.
|
||||||
|
auto range = active_range(document);
|
||||||
|
if (!range)
|
||||||
|
return false;
|
||||||
|
range = block_extend_a_range(*range);
|
||||||
|
|
||||||
|
// Return true if there is at least one visible editable node that is contained in the result and has no children,
|
||||||
|
// and all such nodes have alignment value "[alignment]". Otherwise return false.
|
||||||
|
Vector<GC::Ref<DOM::Node>> matching_nodes;
|
||||||
|
range->for_each_contained([&matching_nodes](GC::Ref<DOM::Node> node) {
|
||||||
|
if (is_visible_node(node) && node->is_editable() && !node->has_children())
|
||||||
|
matching_nodes.append(node);
|
||||||
|
return IterationDecision::Continue;
|
||||||
|
});
|
||||||
|
return !matching_nodes.is_empty() && all_of(matching_nodes, [&](GC::Ref<DOM::Node> node) {
|
||||||
|
return alignment_value_of_node(node) == alignment;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-justifycenter-command
|
||||||
|
static String justify_value(DOM::Document const& document)
|
||||||
|
{
|
||||||
|
// NOTE: This definition is taken from the "justifyCenter" spec and was made generic.
|
||||||
|
|
||||||
|
// Return the empty string if the active range is null. Otherwise, block-extend the active range,
|
||||||
|
auto range = active_range(document);
|
||||||
|
if (!range)
|
||||||
|
return {};
|
||||||
|
range = block_extend_a_range(*range);
|
||||||
|
|
||||||
|
// and return the alignment value of the first visible editable node that is contained in the result and has no
|
||||||
|
// children.
|
||||||
|
GC::Ptr<DOM::Node> first_match;
|
||||||
|
range->for_each_contained([&first_match](GC::Ref<DOM::Node> node) {
|
||||||
|
if (is_visible_node(node) && node->is_editable() && !node->has_children()) {
|
||||||
|
first_match = node;
|
||||||
|
return IterationDecision::Break;
|
||||||
|
}
|
||||||
|
return IterationDecision::Continue;
|
||||||
|
});
|
||||||
|
if (first_match)
|
||||||
|
return justify_alignment_to_string(alignment_value_of_node(first_match));
|
||||||
|
|
||||||
|
// If there is no such node, return "left".
|
||||||
|
return justify_alignment_to_string(JustifyAlignment::Left);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-justifycenter-command
|
||||||
|
bool command_justify_center_action(DOM::Document& document, String const&)
|
||||||
|
{
|
||||||
|
// Justify the selection with alignment "center", then return true.
|
||||||
|
justify_the_selection(document, JustifyAlignment::Center);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-justifycenter-command
|
||||||
|
bool command_justify_center_indeterminate(DOM::Document const& document)
|
||||||
|
{
|
||||||
|
return justify_indeterminate(document, JustifyAlignment::Center);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-justifycenter-command
|
||||||
|
bool command_justify_center_state(DOM::Document const& document)
|
||||||
|
{
|
||||||
|
return justify_state(document, JustifyAlignment::Center);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-justifycenter-command
|
||||||
|
String command_justify_center_value(DOM::Document const& document)
|
||||||
|
{
|
||||||
|
return justify_value(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-justifyfull-command
|
||||||
|
bool command_justify_full_action(DOM::Document& document, String const&)
|
||||||
|
{
|
||||||
|
// Justify the selection with alignment "justify", then return true.
|
||||||
|
justify_the_selection(document, JustifyAlignment::Justify);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-justifyfull-command
|
||||||
|
bool command_justify_full_indeterminate(DOM::Document const& document)
|
||||||
|
{
|
||||||
|
return justify_indeterminate(document, JustifyAlignment::Justify);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-justifyfull-command
|
||||||
|
bool command_justify_full_state(DOM::Document const& document)
|
||||||
|
{
|
||||||
|
return justify_state(document, JustifyAlignment::Justify);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-justifyfull-command
|
||||||
|
String command_justify_full_value(DOM::Document const& document)
|
||||||
|
{
|
||||||
|
return justify_value(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-justifyleft-command
|
||||||
|
bool command_justify_left_action(DOM::Document& document, String const&)
|
||||||
|
{
|
||||||
|
// Justify the selection with alignment "left", then return true.
|
||||||
|
justify_the_selection(document, JustifyAlignment::Left);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-justifyleft-command
|
||||||
|
bool command_justify_left_indeterminate(DOM::Document const& document)
|
||||||
|
{
|
||||||
|
return justify_indeterminate(document, JustifyAlignment::Left);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-justifyleft-command
|
||||||
|
bool command_justify_left_state(DOM::Document const& document)
|
||||||
|
{
|
||||||
|
return justify_state(document, JustifyAlignment::Left);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-justifyleft-command
|
||||||
|
String command_justify_left_value(DOM::Document const& document)
|
||||||
|
{
|
||||||
|
return justify_value(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-justifyright-command
|
||||||
|
bool command_justify_right_action(DOM::Document& document, String const&)
|
||||||
|
{
|
||||||
|
// Justify the selection with alignment "right", then return true.
|
||||||
|
justify_the_selection(document, JustifyAlignment::Right);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-justifyright-command
|
||||||
|
bool command_justify_right_indeterminate(DOM::Document const& document)
|
||||||
|
{
|
||||||
|
return justify_indeterminate(document, JustifyAlignment::Right);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-justifyright-command
|
||||||
|
bool command_justify_right_state(DOM::Document const& document)
|
||||||
|
{
|
||||||
|
return justify_state(document, JustifyAlignment::Right);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-justifyright-command
|
||||||
|
String command_justify_right_value(DOM::Document const& document)
|
||||||
|
{
|
||||||
|
return justify_value(document);
|
||||||
|
}
|
||||||
|
|
||||||
// https://w3c.github.io/editing/docs/execCommand/#the-removeformat-command
|
// https://w3c.github.io/editing/docs/execCommand/#the-removeformat-command
|
||||||
bool command_remove_format_action(DOM::Document& document, String const&)
|
bool command_remove_format_action(DOM::Document& document, String const&)
|
||||||
{
|
{
|
||||||
|
@ -2282,6 +2464,42 @@ static Array const commands {
|
||||||
.relevant_css_property = CSS::PropertyID::FontStyle,
|
.relevant_css_property = CSS::PropertyID::FontStyle,
|
||||||
.inline_activated_values = { "italic"sv, "oblique"sv },
|
.inline_activated_values = { "italic"sv, "oblique"sv },
|
||||||
},
|
},
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-justifycenter-command
|
||||||
|
CommandDefinition {
|
||||||
|
.command = CommandNames::justifyCenter,
|
||||||
|
.action = command_justify_center_action,
|
||||||
|
.indeterminate = command_justify_center_indeterminate,
|
||||||
|
.state = command_justify_center_state,
|
||||||
|
.value = command_justify_center_value,
|
||||||
|
.preserves_overrides = true,
|
||||||
|
},
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-justifyfull-command
|
||||||
|
CommandDefinition {
|
||||||
|
.command = CommandNames::justifyFull,
|
||||||
|
.action = command_justify_full_action,
|
||||||
|
.indeterminate = command_justify_full_indeterminate,
|
||||||
|
.state = command_justify_full_state,
|
||||||
|
.value = command_justify_full_value,
|
||||||
|
.preserves_overrides = true,
|
||||||
|
},
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-justifyleft-command
|
||||||
|
CommandDefinition {
|
||||||
|
.command = CommandNames::justifyLeft,
|
||||||
|
.action = command_justify_left_action,
|
||||||
|
.indeterminate = command_justify_left_indeterminate,
|
||||||
|
.state = command_justify_left_state,
|
||||||
|
.value = command_justify_left_value,
|
||||||
|
.preserves_overrides = true,
|
||||||
|
},
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#the-justifyright-command
|
||||||
|
CommandDefinition {
|
||||||
|
.command = CommandNames::justifyRight,
|
||||||
|
.action = command_justify_right_action,
|
||||||
|
.indeterminate = command_justify_right_indeterminate,
|
||||||
|
.state = command_justify_right_state,
|
||||||
|
.value = command_justify_right_value,
|
||||||
|
.preserves_overrides = true,
|
||||||
|
},
|
||||||
// https://w3c.github.io/editing/docs/execCommand/#the-removeformat-command
|
// https://w3c.github.io/editing/docs/execCommand/#the-removeformat-command
|
||||||
CommandDefinition {
|
CommandDefinition {
|
||||||
.command = CommandNames::removeFormat,
|
.command = CommandNames::removeFormat,
|
||||||
|
|
|
@ -57,6 +57,22 @@ bool command_insert_unordered_list_action(DOM::Document&, String const&);
|
||||||
bool command_insert_unordered_list_indeterminate(DOM::Document const&);
|
bool command_insert_unordered_list_indeterminate(DOM::Document const&);
|
||||||
bool command_insert_unordered_list_state(DOM::Document const&);
|
bool command_insert_unordered_list_state(DOM::Document const&);
|
||||||
bool command_italic_action(DOM::Document&, String const&);
|
bool command_italic_action(DOM::Document&, String const&);
|
||||||
|
bool command_justify_center_action(DOM::Document&, String const&);
|
||||||
|
bool command_justify_center_indeterminate(DOM::Document const&);
|
||||||
|
bool command_justify_center_state(DOM::Document const&);
|
||||||
|
String command_justify_center_value(DOM::Document const&);
|
||||||
|
bool command_justify_full_action(DOM::Document&, String const&);
|
||||||
|
bool command_justify_full_indeterminate(DOM::Document const&);
|
||||||
|
bool command_justify_full_state(DOM::Document const&);
|
||||||
|
String command_justify_full_value(DOM::Document const&);
|
||||||
|
bool command_justify_left_action(DOM::Document&, String const&);
|
||||||
|
bool command_justify_left_indeterminate(DOM::Document const&);
|
||||||
|
bool command_justify_left_state(DOM::Document const&);
|
||||||
|
String command_justify_left_value(DOM::Document const&);
|
||||||
|
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_remove_format_action(DOM::Document&, String const&);
|
bool command_remove_format_action(DOM::Document&, String const&);
|
||||||
bool command_strikethrough_action(DOM::Document&, String const&);
|
bool command_strikethrough_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&);
|
||||||
|
|
|
@ -55,6 +55,54 @@ GC::Ptr<DOM::Range> active_range(DOM::Document const& document)
|
||||||
return selection->range();
|
return selection->range();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#alignment-value
|
||||||
|
JustifyAlignment alignment_value_of_node(GC::Ptr<DOM::Node> node)
|
||||||
|
{
|
||||||
|
// 1. While node is neither null nor an Element, or it is an Element but its "display" property has resolved value
|
||||||
|
// "inline" or "none", set node to its parent.
|
||||||
|
auto is_display_inline_or_none = [](GC::Ref<DOM::Node> node) {
|
||||||
|
auto display = resolved_display(node);
|
||||||
|
if (!display.has_value())
|
||||||
|
return false;
|
||||||
|
return (display.value().is_inline_outside() && display.value().is_flow_inside()) || display.value().is_none();
|
||||||
|
};
|
||||||
|
while ((node && !is<DOM::Element>(node.ptr())) || (is<DOM::Element>(node.ptr()) && is_display_inline_or_none(*node)))
|
||||||
|
node = node->parent();
|
||||||
|
|
||||||
|
// 2. If node is not an Element, return "left".
|
||||||
|
if (!is<DOM::Element>(node.ptr()))
|
||||||
|
return JustifyAlignment::Left;
|
||||||
|
GC::Ref<DOM::Element> element = static_cast<DOM::Element&>(*node);
|
||||||
|
|
||||||
|
// 3. If node's "text-align" property has resolved value "start", return "left" if the directionality of node is
|
||||||
|
// "ltr", "right" if it is "rtl".
|
||||||
|
auto text_align_value = resolved_keyword(*node, CSS::PropertyID::TextAlign);
|
||||||
|
if (!text_align_value.has_value())
|
||||||
|
return JustifyAlignment::Left;
|
||||||
|
if (text_align_value.value() == CSS::Keyword::Start)
|
||||||
|
return element->directionality() == DOM::Element::Directionality::Ltr ? JustifyAlignment::Left : JustifyAlignment::Right;
|
||||||
|
|
||||||
|
// 4. If node's "text-align" property has resolved value "end", return "right" if the directionality of node is
|
||||||
|
// "ltr", "left" if it is "rtl".
|
||||||
|
if (text_align_value.value() == CSS::Keyword::End)
|
||||||
|
return element->directionality() == DOM::Element::Directionality::Ltr ? JustifyAlignment::Right : JustifyAlignment::Left;
|
||||||
|
|
||||||
|
// 5. If node's "text-align" property has resolved value "center", "justify", "left", or "right", return that value.
|
||||||
|
switch (text_align_value.value()) {
|
||||||
|
case CSS::Keyword::Center:
|
||||||
|
return JustifyAlignment::Center;
|
||||||
|
case CSS::Keyword::Justify:
|
||||||
|
return JustifyAlignment::Justify;
|
||||||
|
case CSS::Keyword::Left:
|
||||||
|
return JustifyAlignment::Left;
|
||||||
|
case CSS::Keyword::Right:
|
||||||
|
return JustifyAlignment::Right;
|
||||||
|
default:
|
||||||
|
// 6. Return "left".
|
||||||
|
return JustifyAlignment::Left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// https://w3c.github.io/editing/docs/execCommand/#autolink
|
// https://w3c.github.io/editing/docs/execCommand/#autolink
|
||||||
void autolink(DOM::BoundaryPoint point)
|
void autolink(DOM::BoundaryPoint point)
|
||||||
{
|
{
|
||||||
|
@ -2595,6 +2643,130 @@ bool is_whitespace_node(GC::Ref<DOM::Node> node)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#justify-the-selection
|
||||||
|
void justify_the_selection(DOM::Document& document, JustifyAlignment alignment)
|
||||||
|
{
|
||||||
|
// 1. Block-extend the active range, and let new range be the result.
|
||||||
|
auto new_range = block_extend_a_range(*active_range(document));
|
||||||
|
|
||||||
|
// 2. Let element list be a list of all editable Elements contained in new range that either has an attribute in the
|
||||||
|
// HTML namespace whose local name is "align", or has a style attribute that sets "text-align", or is a center.
|
||||||
|
Vector<GC::Ref<DOM::Element>> element_list;
|
||||||
|
new_range->for_each_contained([&element_list](GC::Ref<DOM::Node> node) {
|
||||||
|
if (!node->is_editable() || !is<DOM::Element>(*node))
|
||||||
|
return IterationDecision::Continue;
|
||||||
|
|
||||||
|
auto& element = static_cast<DOM::Element&>(*node);
|
||||||
|
if (element.has_attribute_ns(Namespace::HTML, HTML::AttributeNames::align)
|
||||||
|
|| property_in_style_attribute(element, CSS::PropertyID::TextAlign).has_value()
|
||||||
|
|| element.local_name() == HTML::TagNames::center)
|
||||||
|
element_list.append(element);
|
||||||
|
|
||||||
|
return IterationDecision::Continue;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. For each element in element list:
|
||||||
|
for (auto element : element_list) {
|
||||||
|
// 1. If element has an attribute in the HTML namespace whose local name is "align", remove that attribute.
|
||||||
|
if (element->has_attribute_ns(Namespace::HTML, HTML::AttributeNames::align))
|
||||||
|
element->remove_attribute_ns(Namespace::HTML, HTML::AttributeNames::align);
|
||||||
|
|
||||||
|
// 2. Unset the CSS property "text-align" on element, if it's set by a style attribute.
|
||||||
|
auto* inline_style = element->style_for_bindings();
|
||||||
|
MUST(inline_style->remove_property(CSS::PropertyID::TextAlign));
|
||||||
|
|
||||||
|
// 3. If element is a div or span or center with no attributes, remove it, preserving its descendants.
|
||||||
|
if (element->local_name().is_one_of(HTML::TagNames::div, HTML::TagNames::span, HTML::TagNames::center)
|
||||||
|
&& !element->has_attributes())
|
||||||
|
remove_node_preserving_its_descendants(element);
|
||||||
|
|
||||||
|
// 4. If element is a center with one or more attributes, set the tag name of element to "div".
|
||||||
|
if (element->local_name() == HTML::TagNames::center && element->has_attributes())
|
||||||
|
set_the_tag_name(element, HTML::TagNames::div);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Block-extend the active range, and let new range be the result.
|
||||||
|
new_range = block_extend_a_range(*active_range(document));
|
||||||
|
|
||||||
|
// 5. Let node list be a list of nodes, initially empty.
|
||||||
|
Vector<GC::Ref<DOM::Node>> node_list;
|
||||||
|
|
||||||
|
// 6. 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; node is an allowed child of "div"; and node's alignment value is
|
||||||
|
// not alignment.
|
||||||
|
new_range->for_each_contained([&](GC::Ref<DOM::Node> node) {
|
||||||
|
if ((node_list.is_empty() || !node_list.last()->is_ancestor_of(node))
|
||||||
|
&& node->is_editable()
|
||||||
|
&& is_allowed_child_of_node(node, HTML::TagNames::div)
|
||||||
|
&& alignment_value_of_node(node) != alignment)
|
||||||
|
node_list.append(node);
|
||||||
|
return IterationDecision::Continue;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 7. While node list is not empty:
|
||||||
|
while (!node_list.is_empty()) {
|
||||||
|
// 1. Let sublist be a list of nodes, initially empty.
|
||||||
|
Vector<GC::Ref<DOM::Node>> sublist;
|
||||||
|
|
||||||
|
// 2. Remove the first member of node list and append it to sublist.
|
||||||
|
sublist.append(node_list.take_first());
|
||||||
|
|
||||||
|
// 3. While node list is not empty, and the first member of node list is the nextSibling of the last member of
|
||||||
|
// sublist, 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())
|
||||||
|
sublist.append(node_list.take_first());
|
||||||
|
|
||||||
|
// 4. Wrap sublist. Sibling criteria returns true for any div that has one or both of the following two
|
||||||
|
// attributes and no other attributes, and false otherwise:
|
||||||
|
// * An align attribute whose value is an ASCII case-insensitive match for alignment.
|
||||||
|
// * A style attribute which sets exactly one CSS property (including unrecognized or invalid attributes),
|
||||||
|
// which is "text-align", which is set to alignment.
|
||||||
|
//
|
||||||
|
// New parent instructions are to call createElement("div") on the context object, then set its CSS property
|
||||||
|
// "text-align" to alignment and return the result.
|
||||||
|
auto alignment_keyword = string_from_keyword([&alignment] {
|
||||||
|
switch (alignment) {
|
||||||
|
case JustifyAlignment::Center:
|
||||||
|
return CSS::Keyword::Center;
|
||||||
|
case JustifyAlignment::Justify:
|
||||||
|
return CSS::Keyword::Justify;
|
||||||
|
case JustifyAlignment::Left:
|
||||||
|
return CSS::Keyword::Left;
|
||||||
|
case JustifyAlignment::Right:
|
||||||
|
return CSS::Keyword::Right;
|
||||||
|
}
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}());
|
||||||
|
|
||||||
|
wrap(
|
||||||
|
sublist,
|
||||||
|
[&](GC::Ref<DOM::Node> sibling) {
|
||||||
|
if (!is<HTML::HTMLDivElement>(*sibling))
|
||||||
|
return false;
|
||||||
|
GC::Ref<DOM::Element> element = static_cast<DOM::Element&>(*sibling);
|
||||||
|
u8 number_of_matching_attributes = 0;
|
||||||
|
if (element->get_attribute_value(HTML::AttributeNames::align).equals_ignoring_ascii_case(alignment_keyword))
|
||||||
|
++number_of_matching_attributes;
|
||||||
|
if (element->has_attribute(HTML::AttributeNames::style) && element->inline_style()
|
||||||
|
&& element->inline_style()->length() == 1) {
|
||||||
|
auto text_align = element->inline_style()->property(CSS::PropertyID::TextAlign);
|
||||||
|
if (text_align.has_value()) {
|
||||||
|
auto align_value = text_align.value().value->to_string(CSS::CSSStyleValue::SerializationMode::Normal);
|
||||||
|
if (align_value.equals_ignoring_ascii_case(alignment_keyword))
|
||||||
|
++number_of_matching_attributes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return element->attribute_list_size() == number_of_matching_attributes;
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
auto div = MUST(DOM::create_element(document, HTML::TagNames::div, Namespace::HTML));
|
||||||
|
auto inline_style = div->style_for_bindings();
|
||||||
|
MUST(inline_style->set_property(CSS::PropertyID::TextAlign, alignment_keyword));
|
||||||
|
return div;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// https://w3c.github.io/editing/docs/execCommand/#last-equivalent-point
|
// https://w3c.github.io/editing/docs/execCommand/#last-equivalent-point
|
||||||
DOM::BoundaryPoint last_equivalent_point(DOM::BoundaryPoint boundary_point)
|
DOM::BoundaryPoint last_equivalent_point(DOM::BoundaryPoint boundary_point)
|
||||||
{
|
{
|
||||||
|
@ -4530,6 +4702,21 @@ bool is_heading(FlyString const& local_name)
|
||||||
HTML::TagNames::h6);
|
HTML::TagNames::h6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String justify_alignment_to_string(JustifyAlignment alignment)
|
||||||
|
{
|
||||||
|
switch (alignment) {
|
||||||
|
case JustifyAlignment::Center:
|
||||||
|
return "center"_string;
|
||||||
|
case JustifyAlignment::Justify:
|
||||||
|
return "justify"_string;
|
||||||
|
case JustifyAlignment::Left:
|
||||||
|
return "left"_string;
|
||||||
|
case JustifyAlignment::Right:
|
||||||
|
return "right"_string;
|
||||||
|
}
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
Array<StringView, 7> named_font_sizes()
|
Array<StringView, 7> named_font_sizes()
|
||||||
{
|
{
|
||||||
return { "x-small"sv, "small"sv, "medium"sv, "large"sv, "x-large"sv, "xx-large"sv, "xxx-large"sv };
|
return { "x-small"sv, "small"sv, "medium"sv, "large"sv, "x-large"sv, "xx-large"sv, "xxx-large"sv };
|
||||||
|
|
|
@ -36,12 +36,21 @@ enum class SelectionsListState : u8 {
|
||||||
None,
|
None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// https://w3c.github.io/editing/docs/execCommand/#justify-the-selection
|
||||||
|
enum class JustifyAlignment : u8 {
|
||||||
|
Center,
|
||||||
|
Justify,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
};
|
||||||
|
|
||||||
using Selection::Selection;
|
using Selection::Selection;
|
||||||
|
|
||||||
// Below algorithms are specified here:
|
// Below algorithms are specified here:
|
||||||
// https://w3c.github.io/editing/docs/execCommand/#assorted-common-algorithms
|
// https://w3c.github.io/editing/docs/execCommand/#assorted-common-algorithms
|
||||||
|
|
||||||
GC::Ptr<DOM::Range> active_range(DOM::Document const&);
|
GC::Ptr<DOM::Range> active_range(DOM::Document const&);
|
||||||
|
JustifyAlignment alignment_value_of_node(GC::Ptr<DOM::Node>);
|
||||||
void autolink(DOM::BoundaryPoint);
|
void autolink(DOM::BoundaryPoint);
|
||||||
GC::Ref<DOM::Range> block_extend_a_range(GC::Ref<DOM::Range>);
|
GC::Ref<DOM::Range> block_extend_a_range(GC::Ref<DOM::Range>);
|
||||||
GC::Ptr<DOM::Node> block_node_of_node(GC::Ref<DOM::Node>);
|
GC::Ptr<DOM::Node> block_node_of_node(GC::Ref<DOM::Node>);
|
||||||
|
@ -85,6 +94,7 @@ bool is_simple_modifiable_element(GC::Ref<DOM::Node>);
|
||||||
bool is_single_line_container(GC::Ref<DOM::Node>);
|
bool is_single_line_container(GC::Ref<DOM::Node>);
|
||||||
bool is_visible_node(GC::Ref<DOM::Node>);
|
bool is_visible_node(GC::Ref<DOM::Node>);
|
||||||
bool is_whitespace_node(GC::Ref<DOM::Node>);
|
bool is_whitespace_node(GC::Ref<DOM::Node>);
|
||||||
|
void justify_the_selection(DOM::Document&, JustifyAlignment);
|
||||||
DOM::BoundaryPoint last_equivalent_point(DOM::BoundaryPoint);
|
DOM::BoundaryPoint last_equivalent_point(DOM::BoundaryPoint);
|
||||||
String legacy_font_size(int);
|
String legacy_font_size(int);
|
||||||
void move_node_preserving_ranges(GC::Ref<DOM::Node>, GC::Ref<DOM::Node> new_parent, u32 new_index);
|
void move_node_preserving_ranges(GC::Ref<DOM::Node>, GC::Ref<DOM::Node> new_parent, u32 new_index);
|
||||||
|
@ -121,6 +131,7 @@ CSSPixels font_size_to_pixel_size(StringView);
|
||||||
void for_each_node_effectively_contained_in_range(GC::Ptr<DOM::Range>, Function<TraversalDecision(GC::Ref<DOM::Node>)>);
|
void for_each_node_effectively_contained_in_range(GC::Ptr<DOM::Range>, Function<TraversalDecision(GC::Ref<DOM::Node>)>);
|
||||||
bool has_visible_children(GC::Ref<DOM::Node>);
|
bool has_visible_children(GC::Ref<DOM::Node>);
|
||||||
bool is_heading(FlyString const&);
|
bool is_heading(FlyString const&);
|
||||||
|
String justify_alignment_to_string(JustifyAlignment);
|
||||||
Array<StringView, 7> named_font_sizes();
|
Array<StringView, 7> named_font_sizes();
|
||||||
Optional<NonnullRefPtr<CSS::CSSStyleValue const>> property_in_style_attribute(GC::Ref<DOM::Element>, CSS::PropertyID);
|
Optional<NonnullRefPtr<CSS::CSSStyleValue const>> property_in_style_attribute(GC::Ref<DOM::Element>, CSS::PropertyID);
|
||||||
Optional<CSS::Display> resolved_display(GC::Ref<DOM::Node>);
|
Optional<CSS::Display> resolved_display(GC::Ref<DOM::Node>);
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
<div style="text-align: center;">foobar</div>
|
||||||
|
<div style="text-align: center;"><div style="">foobar</div></div>
|
|
@ -0,0 +1,2 @@
|
||||||
|
<div style="text-align: justify;">foobar</div>
|
||||||
|
<div style="text-align: justify;"><div style="">foobar</div></div>
|
|
@ -0,0 +1,2 @@
|
||||||
|
foobar
|
||||||
|
<div style="">foobar</div>
|
|
@ -0,0 +1,2 @@
|
||||||
|
<div style="text-align: right;">foobar</div>
|
||||||
|
<div style="text-align: right;"><div style="">foobar</div></div>
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script src="../include.js"></script>
|
||||||
|
<div contenteditable="true" id="d1">foobar</div>
|
||||||
|
<div contenteditable="true" id="d2"><div style="text-align: right">foobar</div></div>
|
||||||
|
<script>
|
||||||
|
test(() => {
|
||||||
|
const range = document.createRange();
|
||||||
|
getSelection().addRange(range);
|
||||||
|
|
||||||
|
const div1 = document.querySelector('#d1');
|
||||||
|
range.setStart(div1.firstChild, 0);
|
||||||
|
range.setEnd(div1.firstChild, 6);
|
||||||
|
document.execCommand('justifyCenter');
|
||||||
|
println(div1.innerHTML);
|
||||||
|
|
||||||
|
const div2 = document.querySelector('#d2');
|
||||||
|
range.setStart(div2.firstChild, 0);
|
||||||
|
range.setEnd(div2.firstChild, 1);
|
||||||
|
document.execCommand('justifyCenter');
|
||||||
|
println(div2.innerHTML);
|
||||||
|
});
|
||||||
|
</script>
|
21
Tests/LibWeb/Text/input/Editing/execCommand-justifyFull.html
Normal file
21
Tests/LibWeb/Text/input/Editing/execCommand-justifyFull.html
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<script src="../include.js"></script>
|
||||||
|
<div contenteditable="true" id="d1">foobar</div>
|
||||||
|
<div contenteditable="true" id="d2"><div style="text-align: right">foobar</div></div>
|
||||||
|
<script>
|
||||||
|
test(() => {
|
||||||
|
const range = document.createRange();
|
||||||
|
getSelection().addRange(range);
|
||||||
|
|
||||||
|
const div1 = document.querySelector('#d1');
|
||||||
|
range.setStart(div1.firstChild, 0);
|
||||||
|
range.setEnd(div1.firstChild, 6);
|
||||||
|
document.execCommand('justifyFull');
|
||||||
|
println(div1.innerHTML);
|
||||||
|
|
||||||
|
const div2 = document.querySelector('#d2');
|
||||||
|
range.setStart(div2.firstChild, 0);
|
||||||
|
range.setEnd(div2.firstChild, 1);
|
||||||
|
document.execCommand('justifyFull');
|
||||||
|
println(div2.innerHTML);
|
||||||
|
});
|
||||||
|
</script>
|
21
Tests/LibWeb/Text/input/Editing/execCommand-justifyLeft.html
Normal file
21
Tests/LibWeb/Text/input/Editing/execCommand-justifyLeft.html
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<script src="../include.js"></script>
|
||||||
|
<div contenteditable="true" id="d1">foobar</div>
|
||||||
|
<div contenteditable="true" id="d2"><div style="text-align: right">foobar</div></div>
|
||||||
|
<script>
|
||||||
|
test(() => {
|
||||||
|
const range = document.createRange();
|
||||||
|
getSelection().addRange(range);
|
||||||
|
|
||||||
|
const div1 = document.querySelector('#d1');
|
||||||
|
range.setStart(div1.firstChild, 0);
|
||||||
|
range.setEnd(div1.firstChild, 6);
|
||||||
|
document.execCommand('justifyLeft');
|
||||||
|
println(div1.innerHTML);
|
||||||
|
|
||||||
|
const div2 = document.querySelector('#d2');
|
||||||
|
range.setStart(div2.firstChild, 0);
|
||||||
|
range.setEnd(div2.firstChild, 1);
|
||||||
|
document.execCommand('justifyLeft');
|
||||||
|
println(div2.innerHTML);
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script src="../include.js"></script>
|
||||||
|
<div contenteditable="true" id="d1">foobar</div>
|
||||||
|
<div contenteditable="true" id="d2"><div style="text-align: left">foobar</div></div>
|
||||||
|
<script>
|
||||||
|
test(() => {
|
||||||
|
const range = document.createRange();
|
||||||
|
getSelection().addRange(range);
|
||||||
|
|
||||||
|
const div1 = document.querySelector('#d1');
|
||||||
|
range.setStart(div1.firstChild, 0);
|
||||||
|
range.setEnd(div1.firstChild, 6);
|
||||||
|
document.execCommand('justifyRight');
|
||||||
|
println(div1.innerHTML);
|
||||||
|
|
||||||
|
const div2 = document.querySelector('#d2');
|
||||||
|
range.setStart(div2.firstChild, 0);
|
||||||
|
range.setEnd(div2.firstChild, 1);
|
||||||
|
document.execCommand('justifyRight');
|
||||||
|
println(div2.innerHTML);
|
||||||
|
});
|
||||||
|
</script>
|
Loading…
Add table
Add a link
Reference in a new issue