From b5517c6fce740c9923523825334865e7726399f4 Mon Sep 17 00:00:00 2001 From: Chirag Chhatrala <60499540+chiragchhatrala@users.noreply.github.com> Date: Fri, 16 May 2025 20:39:07 +0530 Subject: [PATCH] Fix quill and mentions (#758) * Enhance MentionParser and Related Components for Improved Mention Handling - Updated `MentionParser.php` to support the `mention="true"` syntax, allowing for more flexible mention parsing. - Added tests in `MentionParserTest.php` to verify the handling of mentions with the `mention="true"` attribute, including support for URL-encoded field IDs. - Refactored `MentionInput.vue`, `MentionDropdown.vue`, and `RichTextAreaInput.client.vue` to ensure consistent use of `mention-state` and improve mention dropdown functionality. - Enhanced `quillMentionExtension.js` to better manage mention data and improve integration with Quill editor. These changes aim to improve the functionality and reliability of the mention feature across the application, ensuring a better user experience. * Refactor FormInformation Component for Improved Logic and Structure - Updated `FormInformation.vue` to utilize the composition API with script setup syntax, enhancing readability and maintainability. - Replaced `v-if` condition for form visibility with a computed property `isFormClosingOrClosed` for better clarity. - Streamlined data handling by converting data and computed properties to reactive state and computed properties, respectively. - Improved the copy settings logic to utilize refs, ensuring proper state management. These changes aim to enhance the overall structure and functionality of the `FormInformation` component, providing a better user experience and code clarity. --- api/app/Open/MentionParser.php | 28 +- .../Unit/Service/Forms/MentionParserTest.php | 82 +++++ client/components/forms/MentionInput.vue | 297 +++++++++------- .../forms/RichTextAreaInput.client.vue | 20 +- .../forms/components/MentionDropdown.vue | 105 +++--- .../forms/components/QuillyEditor.vue | 31 +- .../global/Settings/SettingsSection.vue | 2 +- .../open/forms/OpenCompleteForm.vue | 2 +- .../form-components/FormInformation.vue | 200 +++++------ client/lib/quill/quillMentionExtension.js | 335 ++++++++++-------- 10 files changed, 633 insertions(+), 469 deletions(-) diff --git a/api/app/Open/MentionParser.php b/api/app/Open/MentionParser.php index b679e8e3..5e254bda 100644 --- a/api/app/Open/MentionParser.php +++ b/api/app/Open/MentionParser.php @@ -4,6 +4,7 @@ namespace App\Open; use DOMDocument; use DOMXPath; +use DOMElement; class MentionParser { @@ -39,21 +40,24 @@ class MentionParser libxml_use_internal_errors($internalErrors); $xpath = new DOMXPath($doc); - $mentionElements = $xpath->query("//span[@mention]"); + + $mentionElements = $xpath->query("//span[@mention or @mention='true']"); foreach ($mentionElements as $element) { - $fieldId = $element->getAttribute('mention-field-id'); - $fallback = $element->getAttribute('mention-fallback'); - $value = $this->getData($fieldId); + if ($element instanceof DOMElement) { + $fieldId = $element->getAttribute('mention-field-id'); + $fallback = $element->getAttribute('mention-fallback'); + $value = $this->getData($fieldId); - if ($value !== null) { - $textNode = $doc->createTextNode(is_array($value) ? implode($this->urlFriendly ? ',+' : ', ', $value) : $value); - $element->parentNode->replaceChild($textNode, $element); - } elseif ($fallback) { - $textNode = $doc->createTextNode($fallback); - $element->parentNode->replaceChild($textNode, $element); - } else { - $element->parentNode->removeChild($element); + if ($value !== null) { + $textNode = $doc->createTextNode(is_array($value) ? implode($this->urlFriendly ? ',+' : ', ', $value) : $value); + $element->parentNode->replaceChild($textNode, $element); + } elseif ($fallback) { + $textNode = $doc->createTextNode($fallback); + $element->parentNode->replaceChild($textNode, $element); + } else { + $element->parentNode->removeChild($element); + } } } diff --git a/api/tests/Unit/Service/Forms/MentionParserTest.php b/api/tests/Unit/Service/Forms/MentionParserTest.php index 0607e17b..61bdbb58 100644 --- a/api/tests/Unit/Service/Forms/MentionParserTest.php +++ b/api/tests/Unit/Service/Forms/MentionParserTest.php @@ -35,6 +35,18 @@ describe('MentionParser', function () { expect($result)->toBe('
Hello Full Name
'; + $data = [ + ['id' => '123', 'value' => 'John Doe'] + ]; + + $parser = new MentionParser($content, $data); + $result = $parser->parse(); + + expect($result)->toBe('Hello John Doe
'); + }); + describe('parseAsText', function () { it('converts HTML to plain text with proper line breaks', function () { $content = 'Tags: PHP, Laravel, Testing
'); }); + test('it supports mention="true" attributes', function () { + $content = 'Hello Full Name
'; + $data = [['id' => '123', 'value' => 'John Doe']]; + + $parser = new MentionParser($content, $data); + $result = $parser->parse(); + + expect($result)->toBe('Hello John Doe
'); + }); + + test('it handles multiple mentions with mention="true" syntax', function () { + $content = 'Name and Title
'; + $data = [ + ['id' => '123', 'value' => 'John Doe'], + ['id' => '456', 'value' => 'Developer'], + ]; + + $parser = new MentionParser($content, $data); + $result = $parser->parse(); + + expect($result)->toBe('John Doe and Developer
'); + }); + test('it preserves HTML structure', function () { $content = 'Hello Placeholder
How are you?
Hello Full Name
'; + $data = [['id' => '%3ARGE', 'value' => 'John Doe']]; + + $parser = new MentionParser($content, $data); + $result = $parser->parse(); + + expect($result)->toBe('Hello John Doe
'); + }); + + test('it handles URL-encoded field IDs with mention="true" syntax', function () { + $content = 'Hello Full Name
'; + $data = [['id' => '%3ARGE', 'value' => 'John Doe']]; + + $parser = new MentionParser($content, $data); + $result = $parser->parse(); + + expect($result)->toBe('Hello John Doe
'); + }); + + test('it handles multiple mentions with URL-encoded IDs and mention="true" syntax', function () { + $content = 'Full Name and Phone
'; + $data = [ + ['id' => '%3ARGE', 'value' => 'John Doe'], + ['id' => 'V%7D%40S', 'value' => '123-456-7890'], + ]; + + $parser = new MentionParser($content, $data); + $result = $parser->parse(); + + expect($result)->toBe('John Doe and 123-456-7890
'); + }); + + test('it recreates real-world example with URL-encoded IDs', function () { + $content = 'Hello there 👋
This is a confirmation that your submission was successfully saved.
Full NameContact FormPhone Number
'; + $data = [ + ['id' => '%3ARGE', 'value' => 'jujujujuju'], + ['id' => 'title', 'value' => 'jujuuj'], + ['id' => 'V%7D%40S', 'value' => '555-1234'], + ]; + + $parser = new MentionParser($content, $data); + $result = $parser->parse(); + + expect($result)->toBe('Hello there 👋
This is a confirmation that your submission was successfully saved.
jujujujujujujuuj555-1234
'); + }); + describe('urlFriendlyOutput', function () { test('it encodes special characters in values', function () { $content = 'Test: Placeholder
'; diff --git a/client/components/forms/MentionInput.vue b/client/components/forms/MentionInput.vue index 287b4708..f14ad612 100644 --- a/client/components/forms/MentionInput.vue +++ b/client/components/forms/MentionInput.vue @@ -3,12 +3,12 @@