mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
FIX: fast edit with a typographic character
When a post containing an apostrophe (') is being cooked, the apostrophe is being converted to the "typographic" version (’) (because we enable markdown-it's **typographer** mode by default in Discourse) When you select text that contains such apostrophe and then try to save your fast edit, it fails miserably without any error. That's because when you select text from the DOM, it uses the cooked version which has the typographic apostrophe. When you save your fast edit, we fetch the raw version of the post, which has the "regular" apostrophe. Thus doing `raw.replace(selectedText, editedText)` doesn't work because `raw` has the regular apostrophe but `selectedText` has the typographic apostrophe. Since it's somewhat complicated to handle all typographic characters, we would basically have to reverse the process done in `custom-typographer-replacements.js`, we instead bail out and show the composer when we detect such character in the selection. Internal ref - t/143836
This commit is contained in:
parent
f35128c6ed
commit
aaec80413d
@ -53,6 +53,13 @@ export default class FastEdit extends Component {
|
|||||||
const result = await ajax(`/posts/${this.args.post.id}`);
|
const result = await ajax(`/posts/${this.args.post.id}`);
|
||||||
const newRaw = result.raw.replace(this.args.initialValue, this.value);
|
const newRaw = result.raw.replace(this.args.initialValue, this.value);
|
||||||
|
|
||||||
|
// Warn the user if we failed to update the post
|
||||||
|
if (newRaw === result.raw) {
|
||||||
|
throw new Error(
|
||||||
|
"Failed to update the post. Did your fast edit include a special character?"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await this.args.post.save({ raw: newRaw });
|
await this.args.post.save({ raw: newRaw });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
popupAjaxError(error);
|
popupAjaxError(error);
|
||||||
|
@ -190,6 +190,7 @@ export default class PostTextSelection extends Component {
|
|||||||
quoteState.buffer.length === 0 ||
|
quoteState.buffer.length === 0 ||
|
||||||
quoteState.buffer.includes("|") || // tables are too complex
|
quoteState.buffer.includes("|") || // tables are too complex
|
||||||
quoteState.buffer.match(/\n/g) || // linebreaks are too complex
|
quoteState.buffer.match(/\n/g) || // linebreaks are too complex
|
||||||
|
quoteState.buffer.match(/[‚‘’„“”«»‹›™±…→←↔¶]/g) || // typopgraphic characters are too complex
|
||||||
matches?.length !== 1 // duplicates are too complex
|
matches?.length !== 1 // duplicates are too complex
|
||||||
) {
|
) {
|
||||||
supportsFastEdit = false;
|
supportsFastEdit = false;
|
||||||
|
@ -11,9 +11,7 @@ import { cloneJSON } from "discourse-common/lib/object";
|
|||||||
|
|
||||||
acceptance("Fast Edit", function (needs) {
|
acceptance("Fast Edit", function (needs) {
|
||||||
needs.user();
|
needs.user();
|
||||||
needs.settings({
|
needs.settings({ enable_fast_edit: true });
|
||||||
enable_fast_edit: true,
|
|
||||||
});
|
|
||||||
needs.pretender((server, helper) => {
|
needs.pretender((server, helper) => {
|
||||||
server.get("/posts/419", () => {
|
server.get("/posts/419", () => {
|
||||||
return helper.response(cloneJSON(postFixtures["/posts/398"]));
|
return helper.response(cloneJSON(postFixtures["/posts/398"]));
|
||||||
@ -86,6 +84,19 @@ acceptance("Fast Edit", function (needs) {
|
|||||||
assert.dom(".d-editor-input").exists();
|
assert.dom(".d-editor-input").exists();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Opens full composer when selection has typographic characters", async function (assert) {
|
||||||
|
await visit("/t/internationalization-localization/280");
|
||||||
|
|
||||||
|
query("#post_2 .cooked").append(`That’s what she said!`);
|
||||||
|
const textNode = query("#post_2 .cooked").childNodes[2];
|
||||||
|
|
||||||
|
await selectText(textNode);
|
||||||
|
await click(".quote-button .quote-edit-label");
|
||||||
|
|
||||||
|
assert.dom("#fast-edit-input").doesNotExist();
|
||||||
|
assert.dom(".d-editor-input").exists();
|
||||||
|
});
|
||||||
|
|
||||||
test("Works with diacritics", async function (assert) {
|
test("Works with diacritics", async function (assert) {
|
||||||
await visit("/t/internationalization-localization/280");
|
await visit("/t/internationalization-localization/280");
|
||||||
|
|
||||||
|
@ -3,46 +3,54 @@
|
|||||||
describe "Post selection | Fast edit", type: :system do
|
describe "Post selection | Fast edit", type: :system do
|
||||||
let(:topic_page) { PageObjects::Pages::Topic.new }
|
let(:topic_page) { PageObjects::Pages::Topic.new }
|
||||||
let(:fast_editor) { PageObjects::Components::FastEditor.new }
|
let(:fast_editor) { PageObjects::Components::FastEditor.new }
|
||||||
|
|
||||||
fab!(:topic)
|
fab!(:topic)
|
||||||
fab!(:post) { Fabricate(:post, topic: topic) }
|
|
||||||
fab!(:post_2) { Fabricate(:post, topic: topic, raw: "It ‘twas a great’ “time”!") }
|
fab!(:post) { Fabricate(:post, topic:) }
|
||||||
fab!(:spanish_post) { Fabricate(:post, topic: topic, raw: "Hola Juan, ¿cómo estás?") }
|
fab!(:post_2) { Fabricate(:post, topic:, raw: "It ‘twas a great’ “time”!") }
|
||||||
fab!(:chinese_post) { Fabricate(:post, topic: topic, raw: "这是一个测试") }
|
fab!(:spanish_post) { Fabricate(:post, topic:, raw: "Hola Juan, ¿cómo estás?") }
|
||||||
fab!(:post_with_emoji) { Fabricate(:post, topic: topic, raw: "Good morning :wave:!") }
|
fab!(:chinese_post) { Fabricate(:post, topic:, raw: "这是一个测试") }
|
||||||
|
fab!(:post_with_emoji) { Fabricate(:post, topic:, raw: "Good morning :wave:!") }
|
||||||
fab!(:post_with_quote) do
|
fab!(:post_with_quote) do
|
||||||
Fabricate(
|
Fabricate(
|
||||||
:post,
|
:post,
|
||||||
topic: topic,
|
topic:,
|
||||||
raw: "[quote]\n#{post_2.raw}\n[/quote]\n\nBelle journée, n'est-ce pas ?",
|
raw: "[quote]\n#{post_2.raw}\n[/quote]\n\nBelle journée, n'est-ce pas ?",
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
fab!(:current_user) { Fabricate(:admin) }
|
fab!(:current_user) { Fabricate(:admin) }
|
||||||
|
|
||||||
before { sign_in(current_user) }
|
before { sign_in(current_user) }
|
||||||
|
|
||||||
context "when text selected it opens contact menu and fast editor" do
|
def css(post) = "#{topic_page.post_by_number_selector(post.post_number)} .cooked p"
|
||||||
it "opens context menu and fast edit dialog" do
|
|
||||||
|
context "when text is selected" do
|
||||||
|
before do
|
||||||
topic_page.visit_topic(topic)
|
topic_page.visit_topic(topic)
|
||||||
|
select_text_range(css(post), 0, 5)
|
||||||
select_text_range("#{topic_page.post_by_number_selector(1)} .cooked p", 0, 10)
|
|
||||||
expect(topic_page.fast_edit_button).to be_visible
|
|
||||||
|
|
||||||
topic_page.click_fast_edit_button
|
|
||||||
expect(topic_page.fast_edit_input).to be_visible
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "edits first paragraph and saves changes" do
|
it "opens context menu" do
|
||||||
topic_page.visit_topic(topic)
|
expect(topic_page.fast_edit_button).to be_visible
|
||||||
|
end
|
||||||
|
|
||||||
select_text_range("#{topic_page.post_by_number_selector(1)} .cooked p", 0, 5)
|
context "when clicking the fast edit button" do
|
||||||
topic_page.click_fast_edit_button
|
before { topic_page.click_fast_edit_button }
|
||||||
|
|
||||||
fast_editor.fill_content("Howdy")
|
it "opens the fast editor" do
|
||||||
fast_editor.save
|
expect(topic_page.fast_edit_input).to be_visible
|
||||||
|
end
|
||||||
|
|
||||||
within("#post_1 .cooked > p") do |el|
|
context "when entering some text and clicking the save button" do
|
||||||
expect(el).not_to eq("Hello world")
|
before do
|
||||||
expect(el).to have_content("Howdy")
|
fast_editor.fill_content("Howdy")
|
||||||
|
fast_editor.save
|
||||||
|
end
|
||||||
|
|
||||||
|
it "saves changes" do
|
||||||
|
expect(page).to have_selector(css(post), text: "Howdy world")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -51,7 +59,7 @@ describe "Post selection | Fast edit", type: :system do
|
|||||||
it "opens the composer directly" do
|
it "opens the composer directly" do
|
||||||
topic_page.visit_topic(topic)
|
topic_page.visit_topic(topic)
|
||||||
|
|
||||||
select_text_range("#{topic_page.post_by_number_selector(6)} .cooked p", 5, 10)
|
select_text_range(css(post_with_quote), 5, 10)
|
||||||
topic_page.click_fast_edit_button
|
topic_page.click_fast_edit_button
|
||||||
|
|
||||||
expect(topic_page).to have_expanded_composer
|
expect(topic_page).to have_expanded_composer
|
||||||
@ -59,63 +67,56 @@ describe "Post selection | Fast edit", type: :system do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context "when editing text that has strange characters" do
|
context "when editing text that has strange characters" do
|
||||||
it "saves when paragraph contains apostrophe" do
|
it "saves when paragraph contains apostrophes" do
|
||||||
topic_page.visit_topic(topic)
|
topic_page.visit_topic(topic)
|
||||||
|
|
||||||
select_text_range("#{topic_page.post_by_number_selector(2)} .cooked p", 19, 4)
|
select_text_range(css(post_2), 19, 4)
|
||||||
topic_page.click_fast_edit_button
|
topic_page.click_fast_edit_button
|
||||||
|
|
||||||
fast_editor.fill_content("day")
|
fast_editor.fill_content("day")
|
||||||
fast_editor.save
|
fast_editor.save
|
||||||
|
|
||||||
expect(page).to have_selector(
|
expect(page).to have_selector(css(post_2), text: "It ‘twas a great’ “day”!")
|
||||||
"#{topic_page.post_by_number_selector(2)} .cooked p",
|
|
||||||
text: "It ‘twas a great’ “day”!",
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "saves when text contains diacratics" do
|
it "saves when text contains diacritics" do
|
||||||
topic_page.visit_topic(topic)
|
topic_page.visit_topic(topic)
|
||||||
|
|
||||||
select_text_range("#{topic_page.post_by_number_selector(3)} .cooked p", 11, 12)
|
select_text_range(css(spanish_post), 11, 12)
|
||||||
|
|
||||||
topic_page.click_fast_edit_button
|
topic_page.click_fast_edit_button
|
||||||
|
|
||||||
fast_editor.fill_content("¿está todo bien?")
|
fast_editor.fill_content("¿está todo bien?")
|
||||||
fast_editor.save
|
fast_editor.save
|
||||||
|
|
||||||
expect(page).to have_selector(
|
expect(page).to have_selector(css(spanish_post), text: "Hola Juan, ¿está todo bien?")
|
||||||
"#{topic_page.post_by_number_selector(3)} .cooked p",
|
|
||||||
text: "Hola Juan, ¿está todo bien?",
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "saves when text contains CJK ranges" do
|
it "saves when text contains CJK ranges" do
|
||||||
topic_page.visit_topic(topic)
|
topic_page.visit_topic(topic)
|
||||||
|
|
||||||
select_text_range("#{topic_page.post_by_number_selector(4)} .cooked p", 0, 2)
|
select_text_range(css(chinese_post), 0, 2)
|
||||||
topic_page.click_fast_edit_button
|
topic_page.click_fast_edit_button
|
||||||
|
|
||||||
fast_editor.fill_content("今天")
|
fast_editor.fill_content("今天")
|
||||||
fast_editor.save
|
fast_editor.save
|
||||||
|
|
||||||
expect(page).to have_selector(
|
expect(page).to have_selector(css(chinese_post), text: "今天一个测试")
|
||||||
"#{topic_page.post_by_number_selector(4)} .cooked p",
|
|
||||||
text: "今天一个测试",
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "saves when text contains emoji" do
|
it "saves when text contains emoji" do
|
||||||
topic_page.visit_topic(topic)
|
topic_page.visit_topic(topic)
|
||||||
|
|
||||||
select_text_range("#{topic_page.post_by_number_selector(5)} .cooked p", 5, 7)
|
select_text_range(css(post_with_emoji), 5, 7)
|
||||||
topic_page.click_fast_edit_button
|
topic_page.click_fast_edit_button
|
||||||
|
|
||||||
fast_editor.fill_content("day")
|
fast_editor.fill_content("day")
|
||||||
fast_editor.save
|
fast_editor.save
|
||||||
|
|
||||||
expect(page).to have_no_css("#fast-edit-input")
|
# NOTE: the emoji isn't picked up by the "text:" selector
|
||||||
expect(post_with_emoji.raw).to eq("Good day :wave:!")
|
expect(page).to have_selector(css(post_with_emoji), text: "Good day !")
|
||||||
|
# So we also check the raw content to ensure it's been saved correctly
|
||||||
|
expect(post_with_emoji.reload.raw).to eq "Good day :wave:!"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user