diff --git a/core/modules/ckeditor/js/plugins/drupallink/plugin.es6.js b/core/modules/ckeditor/js/plugins/drupallink/plugin.es6.js index a90fa8a5c1..2c7be2d431 100644 --- a/core/modules/ckeditor/js/plugins/drupallink/plugin.es6.js +++ b/core/modules/ckeditor/js/plugins/drupallink/plugin.es6.js @@ -165,6 +165,9 @@ focusedLinkableWidget.data.link, ); } + // Get the currently selected text to allow the user to edit it in the + // Dialog window. + existingValues.text = editor.getSelection().getSelectedText(); // Prepare a save callback to be used upon saving the dialog. const saveCallback = function(returnValues) { @@ -228,6 +231,17 @@ }); } + // If the linkElement has been selected or created and a text has + // been provided by the Dialog window. + if ( + linkElement && + returnValues.hasOwnProperty('text') && + returnValues.text.length + ) { + // Update the link content. + linkElement.setText(returnValues.text); + } + // Save snapshot for undo support. editor.fire('saveSnapshot'); }; diff --git a/core/modules/ckeditor/js/plugins/drupallink/plugin.js b/core/modules/ckeditor/js/plugins/drupallink/plugin.js index ca71ef15dd..f4038f2c67 100644 --- a/core/modules/ckeditor/js/plugins/drupallink/plugin.js +++ b/core/modules/ckeditor/js/plugins/drupallink/plugin.js @@ -114,6 +114,8 @@ existingValues = CKEDITOR.tools.clone(focusedLinkableWidget.data.link); } + existingValues.text = editor.getSelection().getSelectedText(); + var saveCallback = function saveCallback(returnValues) { if (focusedLinkableWidget) { focusedLinkableWidget.setData('link', CKEDITOR.tools.extend(returnValues.attributes, focusedLinkableWidget.data.link)); @@ -153,6 +155,10 @@ }); } + if (linkElement && returnValues.hasOwnProperty('text') && returnValues.text.length) { + linkElement.setText(returnValues.text); + } + editor.fire('saveSnapshot'); }; diff --git a/core/modules/ckeditor/tests/modules/ckeditor_test.module b/core/modules/ckeditor/tests/modules/ckeditor_test.module index a75ad86354..46142ea8fb 100644 --- a/core/modules/ckeditor/tests/modules/ckeditor_test.module +++ b/core/modules/ckeditor/tests/modules/ckeditor_test.module @@ -5,6 +5,7 @@ * Helper module for the CKEditor tests. */ +use Drupal\Core\Form\FormStateInterface; use Drupal\editor\Entity\Editor; /** @@ -13,3 +14,13 @@ function ckeditor_test_ckeditor_css_alter(array &$css, Editor $editor) { $css[] = drupal_get_path('module', 'ckeditor_test') . '/ckeditor_test.css'; } + +/** + * Implements hook_form_FORM_ID_alter(). + * + * Make the text field on EditorLinkDialog editable. + */ +function ckeditor_test_form_editor_link_dialog_alter(&$form, FormStateInterface $form_state, $form_id) { + $form['text']['#type'] = 'textfield'; + $form['text']['#title'] = t('Link text'); +} diff --git a/core/modules/ckeditor/tests/modules/src/FunctionalJavascript/DrupalLinkTest.php b/core/modules/ckeditor/tests/modules/src/FunctionalJavascript/DrupalLinkTest.php new file mode 100644 index 0000000000..6f9d237c5b --- /dev/null +++ b/core/modules/ckeditor/tests/modules/src/FunctionalJavascript/DrupalLinkTest.php @@ -0,0 +1,162 @@ + 'llama', + 'name' => 'Llama', + 'weight' => 0, + 'filters' => [], + ]); + $llama_format->save(); + $editor = Editor::create([ + 'format' => 'llama', + 'editor' => 'ckeditor', + ]); + $editor->save(); + + // Create a node type for testing. + NodeType::create(['type' => 'page', 'name' => 'page'])->save(); + + $field_storage = FieldStorageConfig::loadByName('node', 'body'); + + // Create a body field instance for the 'page' node type. + FieldConfig::create([ + 'field_storage' => $field_storage, + 'bundle' => 'page', + 'label' => 'Body', + 'settings' => ['display_summary' => TRUE], + 'required' => TRUE, + ])->save(); + + // Assign widget settings for the 'default' form mode. + EntityFormDisplay::create([ + 'targetEntityType' => 'node', + 'bundle' => 'page', + 'mode' => 'default', + 'status' => TRUE, + ]) + ->setComponent('body', ['type' => 'text_textarea_with_summary']) + ->save(); + + $account = $this->drupalCreateUser([ + 'administer nodes', + 'create page content', + 'use text format llama', + ]); + $this->drupalLogin($account); + } + + /** + * Test the default link behavior. + */ + public function testLinkText() { + $session = $this->getSession(); + $web_assert = $this->assertSession(); + + // Go to node creation page. + $this->drupalGet('node/add/page'); + $page = $session->getPage(); + + // Wait until the editor has been loaded. + $ckeditor_loaded = $this->getSession()->wait(5000, "jQuery('.cke_contents').length > 0"); + $this->assertTrue($ckeditor_loaded, 'The editor has been loaded.'); + + $page->find('css', 'a.cke_button__drupallink')->click(); + $web_assert->assertWaitOnAjaxRequest(); + + // Fill in link. + $page->fillField('attributes[href]', '/node/add/page'); + + // Save the dialog input. + $page->find('css', '.editor-link-dialog')->find('css', '.button.form-submit span')->click(); + $web_assert->assertWaitOnAjaxRequest(); + + $this->assertFirstLinkText('/node/add/page'); + } + + /** + * Test overriding the link text. + */ + public function testOverrideLinkText() { + $session = $this->getSession(); + $web_assert = $this->assertSession(); + + // Go to node creation page. + $this->drupalGet('node/add/page'); + $page = $session->getPage(); + + // Wait until the editor has been loaded. + $ckeditor_loaded = $this->getSession()->wait(5000, "jQuery('.cke_contents').length > 0"); + $this->assertTrue($ckeditor_loaded, 'The editor has been loaded.'); + + $page->find('css', 'a.cke_button__drupallink')->click(); + $web_assert->assertWaitOnAjaxRequest(); + + // Fill in link. + $page->fillField('attributes[href]', '/node/add/page'); + + // Fill in link text. + $page->fillField('text', 'Add page'); + + // Save the dialog input. + $page->find('css', '.editor-link-dialog')->find('css', '.button.form-submit span')->click(); + $web_assert->assertWaitOnAjaxRequest(); + + $this->assertFirstLinkText('Add page'); + } + + /** + * Asserts that the first link in the CKEditor instance has the given text. + * + * @param string $expected_text + * The expected link text. + */ + protected function assertFirstLinkText($expected_text) { + // We can't use $session->switchToIFrame() here, because the iframe does + // not have a name. + $javascript = <<getSession()->evaluateScript($javascript); + $this->assertSame($expected_text, $link_text); + } + +} diff --git a/core/modules/editor/src/Form/EditorLinkDialog.php b/core/modules/editor/src/Form/EditorLinkDialog.php index 5f34e10786..40e0ae704b 100644 --- a/core/modules/editor/src/Form/EditorLinkDialog.php +++ b/core/modules/editor/src/Form/EditorLinkDialog.php @@ -41,6 +41,13 @@ public function buildForm(array $form, FormStateInterface $form_state, Editor $e $form['#prefix'] = ''; + // Text of the link element. This may be converted into a different #type + // by contributed modules to allow users to modify the link text. + $form['text'] = [ + '#type' => 'value', + '#default_value' => isset($input['text']) ? $input['text'] : '', + ]; + // Everything under the "attributes" key is merged directly into the // generated link tag's attributes. $form['attributes']['href'] = [ diff --git a/core/modules/editor/tests/src/Kernel/EditorLinkDialogTest.php b/core/modules/editor/tests/src/Kernel/EditorLinkDialogTest.php new file mode 100644 index 0000000000..4fbba0bb8b --- /dev/null +++ b/core/modules/editor/tests/src/Kernel/EditorLinkDialogTest.php @@ -0,0 +1,75 @@ + 'foobar', + 'name' => $this->randomMachineName(), + ])->save(); + Editor::create([ + 'format' => 'foobar', + 'editor' => 'unicorn', + ])->save(); + } + + /** + * Tests that editor link dialog accepts data from the client as expected. + */ + public function testEditorLinkDialog() { + $input = [ + 'editor_object' => [ + 'href' => '/node/1', + 'text' => 'some text', + ], + 'dialogOptions' => [ + 'title' => 'Edit Link', + 'dialogClass' => 'editor-link-dialog', + 'autoResize' => 'true', + ], + '_drupal_ajax' => '1', + 'ajax_page_state' => [ + 'theme' => 'bartik', + 'theme_token' => 'some-token', + 'libraries' => '', + ], + ]; + $form_state = (new FormState()) + ->setRequestMethod('POST') + ->setUserInput($input) + ->addBuildInfo('args', [Editor::load('foobar')]); + + $form_builder = $this->container->get('form_builder'); + $form_object = new EditorLinkDialog(); + $form_id = $form_builder->getFormId($form_object, $form_state); + $form = $form_builder->retrieveForm($form_id, $form_state); + $form_builder->prepareForm($form_id, $form, $form_state); + $form_builder->processForm($form_id, $form, $form_state); + + $this->assertSame('some text', $form_state->getValue(['text'])); + } + +}