diff --git a/app/assets/javascripts/discourse/app/components/post-text-selection.gjs b/app/assets/javascripts/discourse/app/components/post-text-selection.gjs index 1c054715272..f92e55c7f0e 100644 --- a/app/assets/javascripts/discourse/app/components/post-text-selection.gjs +++ b/app/assets/javascripts/discourse/app/components/post-text-selection.gjs @@ -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; } } diff --git a/app/assets/javascripts/discourse/app/lib/utilities.js b/app/assets/javascripts/discourse/app/lib/utilities.js index 3e72bb344b8..c8657df0cf2 100644 --- a/app/assets/javascripts/discourse/app/lib/utilities.js +++ b/app/assets/javascripts/discourse/app/lib/utilities.js @@ -749,3 +749,7 @@ export function cleanNullQueryParams(params) { } return params; } + +export function getElement(node) { + return node.nodeType === Node.TEXT_NODE ? node.parentElement : node; +} diff --git a/spec/system/post_selection_fast_edit_spec.rb b/spec/system/post_selection_fast_edit_spec.rb index 5a89126ca6d..fe03dd4d8fe 100644 --- a/spec/system/post_selection_fast_edit_spec.rb +++ b/spec/system/post_selection_fast_edit_spec.rb @@ -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)