123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 |
- /*
- * Copyright (c) 2020-2021, the SerenityOS developers.
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
- #include <AK/StringBuilder.h>
- #include <AK/Utf8View.h>
- #include <LibUnicode/Segmentation.h>
- #include <LibWeb/DOM/Document.h>
- #include <LibWeb/DOM/Position.h>
- #include <LibWeb/DOM/Range.h>
- #include <LibWeb/DOM/Text.h>
- #include <LibWeb/HTML/BrowsingContext.h>
- #include <LibWeb/Layout/Viewport.h>
- #include <LibWeb/Page/EditEventHandler.h>
- namespace Web {
- void EditEventHandler::handle_delete_character_after(JS::NonnullGCPtr<DOM::Position> cursor_position)
- {
- auto& node = verify_cast<DOM::Text>(*cursor_position->node());
- auto& text = node.data();
- auto next_grapheme_offset = Unicode::next_grapheme_segmentation_boundary(Utf8View { text }, cursor_position->offset());
- if (!next_grapheme_offset.has_value()) {
- // FIXME: Move to the next node and delete the first character there.
- return;
- }
- StringBuilder builder;
- builder.append(text.bytes_as_string_view().substring_view(0, cursor_position->offset()));
- builder.append(text.bytes_as_string_view().substring_view(*next_grapheme_offset));
- node.set_data(MUST(builder.to_string()));
- m_browsing_context->did_edit({});
- }
- // This method is quite convoluted but this is necessary to make editing feel intuitive.
- void EditEventHandler::handle_delete(DOM::Range& range)
- {
- auto* start = verify_cast<DOM::Text>(range.start_container());
- auto* end = verify_cast<DOM::Text>(range.end_container());
- if (start == end) {
- StringBuilder builder;
- builder.append(start->data().bytes_as_string_view().substring_view(0, range.start_offset()));
- builder.append(end->data().bytes_as_string_view().substring_view(range.end_offset()));
- start->set_data(MUST(builder.to_string()));
- } else {
- // Remove all the nodes that are fully enclosed in the range.
- HashTable<DOM::Node*> queued_for_deletion;
- for (auto* node = start->next_in_pre_order(); node; node = node->next_in_pre_order()) {
- if (node == end)
- break;
- queued_for_deletion.set(node);
- }
- for (auto* parent = start->parent(); parent; parent = parent->parent())
- queued_for_deletion.remove(parent);
- for (auto* parent = end->parent(); parent; parent = parent->parent())
- queued_for_deletion.remove(parent);
- for (auto* node : queued_for_deletion)
- node->remove();
- // Join the parent nodes of start and end.
- DOM::Node *insert_after = start, *remove_from = end, *parent_of_end = end->parent();
- while (remove_from) {
- auto* next_sibling = remove_from->next_sibling();
- remove_from->remove();
- insert_after->parent()->insert_before(*remove_from, *insert_after);
- insert_after = remove_from;
- remove_from = next_sibling;
- }
- if (!parent_of_end->has_children()) {
- if (parent_of_end->parent())
- parent_of_end->remove();
- }
- // Join the start and end nodes.
- StringBuilder builder;
- builder.append(start->data().bytes_as_string_view().substring_view(0, range.start_offset()));
- builder.append(end->data().bytes_as_string_view().substring_view(range.end_offset()));
- start->set_data(MUST(builder.to_string()));
- end->remove();
- }
- m_browsing_context->did_edit({});
- }
- void EditEventHandler::handle_insert(JS::NonnullGCPtr<DOM::Position> position, u32 code_point)
- {
- StringBuilder builder;
- builder.append_code_point(code_point);
- handle_insert(position, MUST(builder.to_string()));
- }
- void EditEventHandler::handle_insert(JS::NonnullGCPtr<DOM::Position> position, String data)
- {
- if (is<DOM::Text>(*position->node())) {
- auto& node = verify_cast<DOM::Text>(*position->node());
- StringBuilder builder;
- builder.append(node.data().bytes_as_string_view().substring_view(0, position->offset()));
- builder.append(data);
- builder.append(node.data().bytes_as_string_view().substring_view(position->offset()));
- // Cut string by max length
- // FIXME: Cut by UTF-16 code units instead of raw bytes
- if (auto max_length = node.max_length(); max_length.has_value() && builder.string_view().length() > *max_length) {
- node.set_data(MUST(String::from_utf8(builder.string_view().substring_view(0, *max_length))));
- } else {
- node.set_data(MUST(builder.to_string()));
- }
- node.invalidate_style();
- } else {
- auto& node = *position->node();
- auto& realm = node.realm();
- auto text = realm.heap().allocate<DOM::Text>(realm, node.document(), data);
- MUST(node.append_child(*text));
- position->set_node(text);
- position->set_offset(1);
- }
- m_browsing_context->did_edit({});
- }
- }
|