diff --git a/src/ActivationInstructionsTrait.php b/src/ActivationInstructionsTrait.php index ff2683bb17a88eb527eaec59e806984515d8d45a..ac5a57582b98380a92a68be91713af7724f2e16e 100644 --- a/src/ActivationInstructionsTrait.php +++ b/src/ActivationInstructionsTrait.php @@ -37,13 +37,20 @@ trait ActivationInstructionsTrait { * The given command, in a format that can be copied and pasted. */ protected function commandBox(string $command, string $action, ?TranslatableMarkup $alt = NULL): string { - $alt ??= $this->t('Copy the @action command', ['@action' => $action]); + $rows = substr_count($command, "\n") + 1; + + $alt ??= $this->formatPlural( + $rows, + 'Copy the @action command', + 'Copy the @action commands', + ['@action' => $action], + ); $icon_url = $this->moduleList->getPath('project_browser') . '/images/copy-icon.svg'; $icon_url = $this->fileUrlGenerator->generateString($icon_url); $command_box = '<div class="command-box">'; - $command_box .= '<input value="' . $command . '" readonly />'; + $command_box .= '<textarea rows="' . $rows . '" readonly>' . $command . '</textarea>'; $command_box .= '<button data-copy-command id="' . $action . '-btn">'; $command_box .= '<img src="' . $icon_url . '" alt="' . $alt . '" />'; $command_box .= '</button>'; diff --git a/src/ModuleActivator.php b/src/ModuleActivator.php index e001e9350d2fb4b4ab9f6360b5f05af9985bd727..411f4e4cb58f99f2f913110211ce349d29f3fe98 100644 --- a/src/ModuleActivator.php +++ b/src/ModuleActivator.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Drupal\project_browser; +use Composer\InstalledVersions; use Drupal\Core\Extension\ModuleExtensionList; use Drupal\Core\Extension\ModuleInstallerInterface; use Drupal\Core\File\FileUrlGeneratorInterface; @@ -93,14 +94,19 @@ final class ModuleActivator implements ActivatorInterface { ]); $commands .= '</p>'; $commands .= '<p>'; - $commands .= $this->t('Alternatively, you can use <a href="@url" target="_blank" rel="noreferrer noopener">Drush</a> to install it via the command line:', [ + $commands .= $this->t('Alternatively, you can use <a href="@url" target="_blank" rel="noreferrer noopener">Drush</a> to install it via the command line.', [ '@url' => 'https://www.drush.org/latest', ]); $commands .= '</p>'; - $commands .= $this->commandBox('drush install ' . $project->machineName, 'install'); - $commands .= '<p>' . $this->t('If Drush is not installed, this will add the tool to your codebase:') . '</p>'; - $commands .= $this->commandBox('composer require drush/drush', 'install-drush', $this->t('Copy the install Drush command')); + $command = ''; + // Only show the command to install Drush if necessary. + if (!in_array('drush/drush', InstalledVersions::getInstalledPackages(), TRUE)) { + $command .= "composer require drush/drush\n"; + } + $command .= 'drush install ' . $project->machineName; + + $commands .= $this->commandBox($command, 'install'); return $commands; } diff --git a/src/ProjectBrowser/Project.php b/src/ProjectBrowser/Project.php index 57d83c74d5267a8668d4df40249215879a24b4fc..301dd60ac444e5d9e059caad6c12344baab90c48 100644 --- a/src/ProjectBrowser/Project.php +++ b/src/ProjectBrowser/Project.php @@ -192,7 +192,7 @@ class Project implements \JsonSerializable { $commands = $commands->setAbsolute()->toString(); } elseif (is_string($commands)) { - $commands = Xss::filter($commands, [...Xss::getAdminTagList(), 'input', 'button']); + $commands = Xss::filter($commands, [...Xss::getAdminTagList(), 'textarea', 'button']); } return [ diff --git a/src/RecipeActivator.php b/src/RecipeActivator.php index 85d940f32316d4f1e5acb743143e6db0bd3ae5ca..deb459a009ceda6c430634bc1cf11c87f93dce9f 100644 --- a/src/RecipeActivator.php +++ b/src/RecipeActivator.php @@ -106,7 +106,7 @@ class RecipeActivator implements ActivatorInterface, EventSubscriberInterface { $instructions = '<p>' . $this->t('To apply this recipe, run the following command at the command line:') . '</p>'; $command = sprintf( - 'cd %s && %s/php %s/core/scripts/drupal recipe %s', + "cd %s\n%s/php %s/core/scripts/drupal recipe %s", $this->appRoot, // cspell:ignore BINDIR PHP_BINDIR, diff --git a/sveltejs/css/claro.css b/sveltejs/css/claro.css index bfbb95b1ce931fb7a5a24acde25269ca3c5c20d8..57f7614defec6b06c5a30473b990c538931fa5b1 100644 --- a/sveltejs/css/claro.css +++ b/sveltejs/css/claro.css @@ -1,11 +1,12 @@ .messages__item a { word-break: break-word; } -.project-browser-popup input { +.project-browser-popup textarea { width: 90%; color: #fff; border: none; background-color: #292b32; + resize: none; } .project-browser-popup img { display: block; diff --git a/sveltejs/public/build/bundle.js b/sveltejs/public/build/bundle.js index a02e401a38171b2ce7c54a58b1c78de9f10f0d15..716ae1595f609b2bce6ade934e8a4e93a1e58c92 100644 Binary files a/sveltejs/public/build/bundle.js and b/sveltejs/public/build/bundle.js differ diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map index 824e39092505c97f2c56c6852c77d3739ad2722c..46a61313dd45984c8ad3945b14051e91c978c23a 100644 Binary files a/sveltejs/public/build/bundle.js.map and b/sveltejs/public/build/bundle.js.map differ diff --git a/sveltejs/src/popup.js b/sveltejs/src/popup.js index 616ff3163958f52abfe2e27894cc51a4a36e1ccf..2d4ddb1e81e2a4321f0f8cf8c17d6406f4c3fcb9 100644 --- a/sveltejs/src/popup.js +++ b/sveltejs/src/popup.js @@ -16,9 +16,9 @@ const enableCopyButtons = () => { copyButton.addEventListener('click', (e) => { // The copy button must be contained in a div const container = e.target.closest('div'); - // The only <input> within the parent dive should have its value set + // The only <textarea> within the parent div should have its value set // to the command that should be copied. - const input = container.querySelector('input'); + const input = container.querySelector('textarea'); // Make the input value the selected text input.select() diff --git a/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php b/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php index fd0ac9f1867f89dfb713531038f5611911b07395..c6dc1d71704aece76763cbe09a802005b4b5cc23 100644 --- a/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php +++ b/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php @@ -221,19 +221,20 @@ class ProjectBrowserUiTest extends WebDriverTestBase { $this->getSession()->executeScript('navigator.clipboard = true'); $this->assertTrue($assert_session->waitForText('By Hel Vetica')); $this->clickWithWait('#project-browser .project__action_button'); - $require_command = $assert_session->waitForElement('css', 'input[value="composer require drupal/helvetica"]'); - $this->assertNotEmpty($require_command); - $this->assertTrue($require_command->hasAttribute('readonly')); - $install_command = $assert_session->waitForElement('css', 'input[value="drush install helvetica"]'); - $this->assertNotEmpty($install_command); - $this->assertTrue($install_command->hasAttribute('readonly')); + + $command_boxes = $page->waitFor(10, fn ($page) => $page->findAll('css', '.command-box textarea[readonly]')); + $this->assertCount(2, $command_boxes); + + // The first textarea should have the command to require the module. + $this->assertSame('composer require drupal/helvetica', $command_boxes[0]->getValue()); + // And the second textarea should have the command to install it. + $this->assertStringEndsWith('drush install helvetica', $command_boxes[1]->getValue()); // Tests alt text for copy command image. $download_commands = $page->findAll('css', '.command-box img'); - $this->assertCount(3, $download_commands); + $this->assertCount(2, $download_commands); $this->assertEquals('Copy the download command', $download_commands[0]->getAttribute('alt')); - $this->assertEquals('Copy the install command', $download_commands[1]->getAttribute('alt')); - $this->assertEquals('Copy the install Drush command', $download_commands[2]->getAttribute('alt')); + $this->assertStringStartsWith('Copy the install command', $download_commands[1]->getAttribute('alt')); } /** @@ -1067,13 +1068,13 @@ class ProjectBrowserUiTest extends WebDriverTestBase { $card = $assert_session->waitForElementVisible('css', '.pb-project:contains("Image media type")'); $this->assertNotEmpty($card); $assert_session->buttonExists('View Commands', $card)->press(); - $input = $assert_session->waitForElementVisible('css', '.command-box input'); + $input = $assert_session->waitForElementVisible('css', '.command-box textarea'); $this->assertNotEmpty($input); $command = $input->getValue(); // A full path to the PHP executable should be in the command. $this->assertMatchesRegularExpression('/[^\s]+\/php /', $command); $drupal_root = $this->getDrupalRoot(); - $this->assertStringStartsWith("cd $drupal_root && ", $command); + $this->assertStringStartsWith("cd $drupal_root\n", $command); $this->assertStringEndsWith("php $drupal_root/core/scripts/drupal recipe $drupal_root/core/recipes/image_media_type", $command); }