FIX: bypass fast edit when selected text isn't editable

When selected some text inside a post, we offer the ability to "fast edit" the selected text without opening the composer.

However, there are certain cases where this isn't working quite a expected, due to the fact that we have some text in the "cooked" version of the post that isn't literally in the "raw" version of the post.

This ensures that whenever someone selects the within

- a quote
- a onebox
- an encrypted message
- a "cooked" date

we directly show the composer instead of showing the fast edit modal and then leaving the user with an invisible error.

Internal ref. t/128400
This commit is contained in:
Régis Hanol 2024-05-24 18:06:29 +02:00
parent a658465b7d
commit bc089dc52b
3 changed files with 42 additions and 16 deletions

View File

@ -8,6 +8,7 @@ import PostTextSelectionToolbar from "discourse/components/post-text-selection-t
import isElementInViewport from "discourse/lib/is-element-in-viewport";
import toMarkdown from "discourse/lib/to-markdown";
import {
getElement,
selectedNode,
selectedRange,
selectedText,
@ -32,6 +33,13 @@ function getQuoteTitle(element) {
return titleEl.textContent.trim().replace(/:$/, "");
}
const CSS_TO_DISABLE_FAST_EDIT = [
"aside.quote",
"aside.onebox",
".cooked-date",
"body.encrypted-topic-page",
].join(",");
export default class PostTextSelection extends Component {
@service appEvents;
@service capabilities;
@ -122,14 +130,8 @@ export default class PostTextSelection extends Component {
let postId;
for (let r = 0; r < selection.rangeCount; r++) {
const range = selection.getRangeAt(r);
const selectionStart =
range.startContainer.nodeType === Node.ELEMENT_NODE
? range.startContainer
: range.startContainer.parentElement;
const ancestor =
range.commonAncestorContainer.nodeType === Node.ELEMENT_NODE
? range.commonAncestorContainer
: range.commonAncestorContainer.parentElement;
const selectionStart = getElement(range.startContainer);
const ancestor = getElement(range.commonAncestorContainer);
if (!selectionStart.closest(".cooked")) {
return await this.hideToolbar();
@ -142,10 +144,7 @@ export default class PostTextSelection extends Component {
}
}
const _selectedElement =
selectedNode().nodeType === Node.ELEMENT_NODE
? selectedNode()
: selectedNode().parentElement;
const _selectedElement = getElement(selectedNode());
const cooked =
_selectedElement.querySelector(".cooked") ||
_selectedElement.closest(".cooked");
@ -176,7 +175,14 @@ export default class PostTextSelection extends Component {
quoteState.selected(postId, _selectedText, opts);
let supportsFastEdit = this.canEditPost;
if (this.canEditPost) {
const start = getElement(selection.getRangeAt(0).startContainer);
if (!start || start.closest(CSS_TO_DISABLE_FAST_EDIT)) {
supportsFastEdit = false;
}
if (supportsFastEdit) {
const regexp = new RegExp(escapeRegExp(quoteState.buffer), "gi");
const matches = cooked.innerHTML.match(regexp);
@ -184,11 +190,9 @@ export default class PostTextSelection extends Component {
quoteState.buffer.length === 0 ||
quoteState.buffer.includes("|") || // tables are too complex
quoteState.buffer.match(/\n/g) || // linebreaks are too complex
matches?.length > 1 // duplicates are too complex
matches?.length !== 1 // duplicates are too complex
) {
supportsFastEdit = false;
} else if (matches?.length === 1) {
supportsFastEdit = true;
}
}

View File

@ -749,3 +749,7 @@ export function cleanNullQueryParams(params) {
}
return params;
}
export function getElement(node) {
return node.nodeType === Node.TEXT_NODE ? node.parentElement : node;
}

View File

@ -9,6 +9,13 @@ describe "Post selection | Fast edit", type: :system do
fab!(:spanish_post) { Fabricate(:post, topic: topic, raw: "Hola Juan, ¿cómo estás?") }
fab!(:chinese_post) { Fabricate(:post, topic: topic, raw: "这是一个测试") }
fab!(:post_with_emoji) { Fabricate(:post, topic: topic, raw: "Good morning :wave:!") }
fab!(:post_with_quote) do
Fabricate(
:post,
topic: topic,
raw: "[quote]\n#{post_2.raw}\n[/quote]\n\nBelle journée, n'est-ce pas ?",
)
end
fab!(:current_user) { Fabricate(:admin) }
before { sign_in(current_user) }
@ -40,6 +47,17 @@ describe "Post selection | Fast edit", type: :system do
end
end
context "when text selected is inside a quote" do
it "opens the composer directly" do
topic_page.visit_topic(topic)
select_text_range("#{topic_page.post_by_number_selector(6)} .cooked p", 5, 10)
topic_page.click_fast_edit_button
expect(topic_page).to have_expanded_composer
end
end
context "when editing text that has strange characters" do
it "saves when paragraph contains apostrophe" do
topic_page.visit_topic(topic)