From f67e67f92d7127e8b0ec7dc055702a9939b6efaf Mon Sep 17 00:00:00 2001
From: Adam G-H <32250-phenaproxima@users.noreply.drupalcode.org>
Date: Wed, 21 Aug 2024 16:40:32 +0000
Subject: [PATCH] Issue #3458287 by phenaproxima, chrisfromredfin, rajdip_755:
 Don't show the command to install Drush if Drush is already installed

---
 src/ActivationInstructionsTrait.php           |  11 +++++++--
 src/ModuleActivator.php                       |  14 ++++++++---
 src/ProjectBrowser/Project.php                |   2 +-
 src/RecipeActivator.php                       |   2 +-
 sveltejs/css/claro.css                        |   3 ++-
 sveltejs/public/build/bundle.js               | Bin 302727 -> 302732 bytes
 sveltejs/public/build/bundle.js.map           | Bin 280298 -> 280303 bytes
 sveltejs/src/popup.js                         |   4 +--
 .../ProjectBrowserUiTest.php                  |  23 +++++++++---------
 9 files changed, 37 insertions(+), 22 deletions(-)

diff --git a/src/ActivationInstructionsTrait.php b/src/ActivationInstructionsTrait.php
index ff2683bb1..ac5a57582 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 e001e9350..411f4e4cb 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 57d83c74d..301dd60ac 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 85d940f32..deb459a00 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 bfbb95b1c..57f7614de 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
GIT binary patch
delta 73
zcmZqQE7Y@BsG)^%3lpn=d`W6WNn%lIqMbr{W=Te7o<d1RszO0xQEFa^LP}=YbR_|%
XW)7I5_EiE*?W+Wsx33alx!Vf>%tIP@

delta 51
zcmeC#E7ZPMsG)^%3lpmVYi3?SX~}e60VZ+A)afb$OwACH_Vofx?dt`Yx33pqx!Vf>
Dt|JiS

diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map
index 824e39092505c97f2c56c6852c77d3739ad2722c..46a61313dd45984c8ad3945b14051e91c978c23a 100644
GIT binary patch
delta 83
zcmaFWEBL-wuwe`1l>PE0sTC!OMX8B)3gww48JT$sB^jv-1&Kwec_j)dnPt;A?q{s!
ifGKJ>KESx$_yE(ZlhcpSVB%+SbaXb{ZqUWNYYzakNFpi#

delta 79
zcmaFgEBLBcuwe`1l>M@qc?G2<b_(U0B^jA{3MCn-3I&Npsd*&|DVb%d(>L#DtYrl&
hX}37QxZUCa)2oxyr*C26W3_g4bS~R&+QqzU4*)HhAgcfX

diff --git a/sveltejs/src/popup.js b/sveltejs/src/popup.js
index 616ff3163..2d4ddb1e8 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 fd0ac9f18..c6dc1d717 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);
   }
 
-- 
GitLab