diff --git a/app/assets/javascripts/initializers/initializeCommentsPage.js.erb b/app/assets/javascripts/initializers/initializeCommentsPage.js.erb index a09cbb2a0..b67b60719 100644 --- a/app/assets/javascripts/initializers/initializeCommentsPage.js.erb +++ b/app/assets/javascripts/initializers/initializeCommentsPage.js.erb @@ -306,11 +306,8 @@ function handleFocus(event) { area.blur(); } else { var form = event.target.closest(".comment-form"); - if (form) { - form.classList.add("comment-form--initiated"); - } + form.classList.add("comment-form--initiated"); handleSizeChange(event); - window.Forem.initializeMentionAutocompleteTextArea(area); } } @@ -437,13 +434,14 @@ function handleSizeChange(event) { function handleButtonsActivation(event) { var textarea = event.target; - var commentForm = textarea.closest('.comment-form'); - if (commentForm) { - var buttons = textarea.closest('.comment-form').getElementsByClassName('js-btn-enable'); - Array.from(buttons).forEach(function(button) { - button.disabled = textarea.value.length === 0; - }); - }; + var buttons = textarea.closest('.comment-form').getElementsByClassName('js-btn-enable'); + Array.from(buttons).forEach(function(button) { + if (textarea.value.length > 0) { + button.disabled = false; + } else { + button.disabled = true; + } + }); } function validateField(event) { diff --git a/app/javascript/crayons/MentionAutocompleteTextArea/MentionAutocompleteTextArea.jsx b/app/javascript/crayons/MentionAutocompleteTextArea/MentionAutocompleteTextArea.jsx index 7327e76d5..e6fdb524b 100644 --- a/app/javascript/crayons/MentionAutocompleteTextArea/MentionAutocompleteTextArea.jsx +++ b/app/javascript/crayons/MentionAutocompleteTextArea/MentionAutocompleteTextArea.jsx @@ -35,8 +35,6 @@ const replaceTextArea = (originalNodeToReplace, newNode) => { originalNodeToReplace, '', ).cssText; - // Make sure no transition replays when the new textarea is mounted - newNode.style.transition = 'none'; // We need to manually remove the element, as Preact's diffing algorithm won't replace it in render originalNodeToReplace.remove(); @@ -195,7 +193,7 @@ export const MentionAutocompleteTextArea = ({ const textWithSelection = `${textContent.substring( 0, selectionInsertIndex, - )}${username} ${textContent.substring(inputRef.current.selectionStart)}`; + )}${username}${textContent.substring(inputRef.current.selectionStart)}`; // Clear the current search setSearchTerm(''); @@ -205,8 +203,8 @@ export const MentionAutocompleteTextArea = ({ // Update the text area value setTextContent(textWithSelection); - // Update the cursor to directly after the selection (+2 accounts for the @ sign, and adding a space after the username) - const newCursorPosition = selectionInsertIndex + username.length + 2; + // Update the cursor to directly after the selection + const newCursorPosition = selectionInsertIndex + username.length + 1; setCursorPosition(newCursorPosition); }; diff --git a/app/javascript/crayons/MentionAutocompleteTextArea/__tests__/__snapshots__/MentionAutocompleteTextArea.test.jsx.snap b/app/javascript/crayons/MentionAutocompleteTextArea/__tests__/__snapshots__/MentionAutocompleteTextArea.test.jsx.snap index 8387988e6..9859b017f 100644 --- a/app/javascript/crayons/MentionAutocompleteTextArea/__tests__/__snapshots__/MentionAutocompleteTextArea.test.jsx.snap +++ b/app/javascript/crayons/MentionAutocompleteTextArea/__tests__/__snapshots__/MentionAutocompleteTextArea.test.jsx.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` should render 1`] = `"
"`; +exports[` should render 1`] = `"
"`; diff --git a/app/javascript/packs/base.jsx b/app/javascript/packs/base.jsx index 1b6b80583..b142edcb7 100644 --- a/app/javascript/packs/base.jsx +++ b/app/javascript/packs/base.jsx @@ -5,39 +5,6 @@ import { initializeTouchDevice, } from '../topNavigation/utilities'; -// Namespace for functions which need to be accessed in plain JS initializers -window.Forem = {}; - -window.Forem.initializeMentionAutocompleteTextArea = async ( - originalTextArea, -) => { - const parentContainer = originalTextArea.parentElement; - - const alreadyInitialized = parentContainer.id === 'combobox-container'; - if (alreadyInitialized) { - return; - } - - const [ - { MentionAutocompleteTextArea }, - { fetchSearch }, - { render, h }, - ] = await Promise.all([ - import('@crayons/MentionAutocompleteTextArea'), - import('@utilities/search'), - import('preact'), - ]); - - render( - fetchSearch('usernames', { username })} - />, - parentContainer, - originalTextArea, - ); -}; - window.showModal = async ({ title, contentSelector, diff --git a/app/javascript/utilities/textAreaUtils.js b/app/javascript/utilities/textAreaUtils.js index a5786e859..4768c44ed 100644 --- a/app/javascript/utilities/textAreaUtils.js +++ b/app/javascript/utilities/textAreaUtils.js @@ -101,7 +101,11 @@ const getIndexOfCurrentWordAutocompleteSymbol = (content, selectionIndex) => { const currentCharacter = content.charAt(selectionIndex); const previousCharacter = content.charAt(selectionIndex - 1); - if (selectionIndex !== 0 && ![' ', '', '\n'].includes(previousCharacter)) { + if ( + selectionIndex !== 0 && + previousCharacter !== ' ' && + previousCharacter !== '' + ) { return getIndexOfCurrentWordAutocompleteSymbol(content, selectionIndex - 1); } diff --git a/app/views/notifications/shared/_comment_box.html.erb b/app/views/notifications/shared/_comment_box.html.erb index fd2ffb306..7796e5ae4 100644 --- a/app/views/notifications/shared/_comment_box.html.erb +++ b/app/views/notifications/shared/_comment_box.html.erb @@ -76,7 +76,7 @@ <%= f.hidden_field :commentable_type, value: json_data["comment"]["commentable"]["class"]["name"] %> <%= f.hidden_field :parent_id, value: json_data["comment"]["id"] %> - <%= f.text_area :body_markdown, id: "comment-textarea-for-#{json_data['comment']['id']}", class: "crayons-textfield mb-2 resize-y", placeholder: "Reply...", 'aria-label': "Reply to a comment...", onfocus: "handleFocus(event)" %> + <%= f.text_area :body_markdown, id: "comment-textarea-for-#{json_data['comment']['id']}", class: "crayons-textfield mb-2 resize-y", placeholder: "Reply...", 'aria-label': "Reply to a comment..." %>
diff --git a/cypress/fixtures/search/emptyUsernamesSearch.json b/cypress/fixtures/search/emptyUsernamesSearch.json deleted file mode 100644 index 6b2dcd458..000000000 --- a/cypress/fixtures/search/emptyUsernamesSearch.json +++ /dev/null @@ -1 +0,0 @@ -{ "result": [] } diff --git a/cypress/fixtures/search/usernames.json b/cypress/fixtures/search/usernames.json deleted file mode 100644 index 0f4c980f9..000000000 --- a/cypress/fixtures/search/usernames.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "result": [ - { - "username": "search_user_1", - "name": "Search user 1", - "profile_image_90": "/images/apple-icon.png" - }, - { - "username": "search_user_2", - "name": "Search user 2", - "profile_image_90": "/images/apple-icon.png" - }, - { - "username": "search_user_3", - "name": "Search user 3", - "profile_image_90": "/images/apple-icon.png" - }, - { - "username": "search_user_4", - "name": "Search user 4", - "profile_image_90": "/images/apple-icon.png" - }, - { - "username": "search_user_5", - "name": "Search user 5", - "profile_image_90": "/images/apple-icon.png" - }, - { - "username": "search_user_6", - "name": "Search user 6", - "profile_image_90": "/images/apple-icon.png" - }, - { - "username": "search_user_7", - "name": "Search user 7", - "profile_image_90": "/images/apple-icon.png" - } - ] -} diff --git a/cypress/integration/articleFlows/commentOnArticle.spec.js b/cypress/integration/articleFlows/commentOnArticle.spec.js deleted file mode 100644 index 06d93d316..000000000 --- a/cypress/integration/articleFlows/commentOnArticle.spec.js +++ /dev/null @@ -1,169 +0,0 @@ -describe('Comment on articles', () => { - beforeEach(() => { - cy.testSetup(); - cy.fixture('users/articleEditorV1User.json').as('user'); - - cy.get('@user').then((user) => { - cy.loginUser(user).then(() => { - cy.visit('/'); - cy.findAllByText('Test article').last().click(); - }); - }); - }); - - it('should comment on an article with user mention autocomplete suggesting max 6 users', () => { - cy.intercept( - { method: 'GET', url: '/search/usernames' }, - { fixture: 'search/usernames.json' }, - ); - - cy.findByLabelText('Add a comment to the discussion').click(); - cy.findByRole('combobox'); - - cy.findByLabelText('Add a comment to the discussion').as( - 'autocompleteCommentBox', - ); - - cy.get('@autocompleteCommentBox').type('Some text @s'); - cy.findByText('Type to search for a user').should('exist'); - - cy.get('@autocompleteCommentBox').type('earch'); - - const expectedUsernames = [ - '@search_user_1', - '@search_user_2', - '@search_user_2', - '@search_user_3', - '@search_user_4', - '@search_user_5', - '@search_user_6', - ]; - - expectedUsernames.forEach((name) => cy.findByText(name).should('exist')); - cy.findByText('@search_user_7').should('not.exist'); - - cy.findByText('@search_user_3').click(); - cy.findByDisplayValue('Some text @search_user_3').should('exist'); - }); - - it('should select a mention autocomplete suggestion by keyboard', () => { - cy.intercept( - { method: 'GET', url: '/search/usernames' }, - { fixture: 'search/usernames.json' }, - ); - - cy.findByLabelText('Add a comment to the discussion').click(); - cy.findByRole('combobox'); - - cy.findByLabelText('Add a comment to the discussion').type( - 'Some text @search_user{downarrow}{enter}', - ); - - cy.findByDisplayValue('Some text @search_user_1').should('exist'); - }); - - it('should accept entered comment text without user mention if no autocomplete suggestions', () => { - cy.intercept( - { method: 'GET', url: '/search/usernames' }, - { fixture: 'search/emptyUsernamesSearch.json' }, - ); - - cy.findByLabelText('Add a comment to the discussion').click(); - cy.findByRole('combobox'); - - cy.findByLabelText('Add a comment to the discussion').as( - 'autocompleteCommentBox', - ); - - cy.get('@autocompleteCommentBox').type('Some text @user'); - - cy.findByText('No results found').should('exist'); - cy.get('@autocompleteCommentBox').type(' '); - cy.findByText('No results found').should('not.exist'); - cy.findByDisplayValue('Some text @user').should('exist'); - }); - - it('should stop showing mention autocomplete suggestions on text delete', () => { - cy.intercept( - { method: 'GET', url: '/search/usernames' }, - { fixture: 'search/usernames.json' }, - ); - - cy.findByLabelText('Add a comment to the discussion').click(); - cy.findByRole('combobox'); - - cy.findByLabelText('Add a comment to the discussion').as( - 'autocompleteCommentBox', - ); - - cy.get('@autocompleteCommentBox').type('Some text @se'); - cy.findByText('@search_user_1').should('exist'); - - cy.get('@autocompleteCommentBox').type('{backspace}{backspace}{backspace}'); - cy.findByText('@search_user_1').should('not.exist'); - }); - - it('should resume search suggestions when user types after deleting', () => { - cy.intercept( - { method: 'GET', url: '/search/usernames' }, - { fixture: 'search/usernames.json' }, - ); - - cy.findByLabelText('Add a comment to the discussion').click(); - cy.findByRole('combobox'); - - cy.findByLabelText('Add a comment to the discussion').as( - 'autocompleteCommentBox', - ); - - cy.get('@autocompleteCommentBox').type('Some text @se'); - cy.get('@autocompleteCommentBox').type('{backspace}{backspace}'); - cy.findByText('@search_user_1').should('not.exist'); - cy.get('@autocompleteCommentBox').type('se'); - cy.findByText('@search_user_1').should('exist'); - }); - - it('should close the autocomplete suggestions on Escape press', () => { - cy.intercept( - { method: 'GET', url: '/search/usernames' }, - { fixture: 'search/usernames.json' }, - ); - - cy.findByLabelText('Add a comment to the discussion').click(); - cy.findByRole('combobox'); - - cy.findByLabelText('Add a comment to the discussion').as( - 'autocompleteCommentBox', - ); - - cy.get('@autocompleteCommentBox').type('Some text @search'); - cy.findByText('@search_user_1').should('be.visible'); - - cy.get('@autocompleteCommentBox').type('{Esc}'); - cy.findByText('@search_user_1').should('not.be.visible'); - }); - - it('should reply to a comment with user mention autocomplete', () => { - cy.intercept( - { method: 'GET', url: '/search/usernames' }, - { fixture: 'search/usernames.json' }, - ); - - cy.findByLabelText('Add a comment to the discussion').click(); - cy.findByRole('combobox'); - - cy.findByLabelText('Add a comment to the discussion').type('first comment'); - cy.findByRole('button', { name: /Submit/ }).click(); - - cy.findByRole('link', { name: /Reply/ }).click(); - - cy.findByRole('combobox', { name: /Reply to a comment/ }).as( - 'replyCombobox', - ); - cy.get('@replyCombobox').click(); - cy.get('@replyCombobox').type('Some text @search_user'); - - cy.findByText('@search_user_1').click(); - cy.findByDisplayValue('Some text @search_user_1'); - }); -});