public inbox for [email protected]  
help / color / mirror / Atom feed
From: Dave Page <[email protected]>
To: Magnus Hagander <[email protected]>
Cc: PostgreSQL WWW <[email protected]>
Subject: Re: Copy script button cleanup
Date: Wed, 3 Dec 2025 10:38:26 +0000
Message-ID: <CA+OCxoyE1J2hY7tR=mw+ikQSPbD61ftjouk4fkvkmA4qfaODDA@mail.gmail.com> (raw)
In-Reply-To: <CABUevExmHVYwddKvFUWkjY8Ev-xAE2p0-mP0wJqJDs1bazMn7A@mail.gmail.com>
References: <CA+OCxoxH75C4Q79bOfxpwXJtgZxjgTtR1xa+cWL0NVBNnT_OyA@mail.gmail.com>
	<CABUevExmHVYwddKvFUWkjY8Ev-xAE2p0-mP0wJqJDs1bazMn7A@mail.gmail.com>

On Tue, 2 Dec 2025 at 14:41, Magnus Hagander <[email protected]> wrote:

>
>
> On Tue, 2 Dec 2025 at 12:30, Dave Page <[email protected]> wrote:
>
>> The Copy Script buttons on the Linux package installation pages look
>> horrific. See before.png.
>>
>> The attached patch makes them look much nicer, with proper sizing and an
>> icon based button. On click, it briefly changes to a green checkmark to
>> show confirmation. See the after screenshots.
>>
>> I'll apply this in a couple of days if there are no objections.
>>
>>
> LGTM in general and certainly is a lot prettier.
>
> Can you explain what that "fallback for text based buttons" is?  Aren't
> they all replaced with icons?
>

Oh, yeah. That's no longer needed now the patch is complete. I'll remove it.


>
> Oh, and I'm told you're not supposed to use "var" for variables in js due
> to the broken scoping :)
>

Old habits die hard :-(


>
> And while you're poking at it, one thing I've really wanted is a "copy
> without the sudo parts". But I'm guessing that would be a lot more
> complicated than what you're fixing here...
>

Ask and thou shalt receive.

2 patches attached. Both fix the original issue. One also adds the no sudo
button.

-- 
Dave Page
pgAdmin: https://www.pgadmin.org
PostgreSQL: https://www.postgresql.org
pgEdge: https://www.pgedge.com


Attachments:

  [application/octet-stream] copy_script_buttons_no_sudo_v1.diff (11.6K, ../CA+OCxoyE1J2hY7tR=mw+ikQSPbD61ftjouk4fkvkmA4qfaODDA@mail.gmail.com/3-copy_script_buttons_no_sudo_v1.diff)
  download | inline diff:
diff --git a/media/css/main.css b/media/css/main.css
index dede2b90..4f4e007f 100644
--- a/media/css/main.css
+++ b/media/css/main.css
@@ -2001,10 +2001,53 @@ button.imagebutton {
   position: relative;
 }
 
+.pg-script-container pre.code {
+  padding-right: 4rem;
+  min-height: 3rem;
+  display: flex;
+  align-items: center;
+}
+
 .pg-script-copy-btn {
   position: absolute;
-  top: 8px;
-  right: 8px;
+  top: 0.5rem;
+  right: 0.5rem;
+  background: transparent;
+  border: none;
+  font-size: 1rem;
+  padding: 4px 6px;
+  cursor: pointer;
+  transition: color 0.2s;
+}
+
+.pg-script-copy-btn-root {
+  right: 2rem;
+}
+
+.pg-script-copy-btn i {
+  color: #555 !important;
+  margin: 0 !important;
+  transition: color 0.2s;
+}
+
+.pg-script-copy-btn:hover i {
+  color: #336791 !important;
+}
+
+.pg-script-copy-btn.copied i {
+  color: #28a745 !important;
+}
+
+[data-theme="dark"] .pg-script-copy-btn i {
+  color: #ccc !important;
+}
+
+[data-theme="dark"] .pg-script-copy-btn:hover i {
+  color: #fff !important;
+}
+
+[data-theme="dark"] .pg-script-copy-btn.copied i {
+  color: #7dff7d !important;
 }
 
 .nobr {
diff --git a/media/js/download.js b/media/js/download.js
index 60bc9f6e..03bb99b3 100644
--- a/media/js/download.js
+++ b/media/js/download.js
@@ -22,16 +22,31 @@ function setupHandlers() {
             copyScript(this, 'script-box');
         });
     }
+    if (document.getElementById("copy-btn-root") && document.getElementById("script-box")) {
+        document.getElementById('copy-btn-root').addEventListener('click', function () {
+            copyScript(this, 'script-box', true);
+        });
+    }
     if (document.getElementById("copy-btn2") && document.getElementById("script-box2")) {
         document.getElementById('copy-btn2').addEventListener('click', function () {
             copyScript(this, 'script-box2');
         });
     }
+    if (document.getElementById("copy-btn2-root") && document.getElementById("script-box2")) {
+        document.getElementById('copy-btn2-root').addEventListener('click', function () {
+            copyScript(this, 'script-box2', true);
+        });
+    }
     if (document.getElementById("copy-btn3") && document.getElementById("script-box3")) {
         document.getElementById('copy-btn3').addEventListener('click', function () {
             copyScript(this, 'script-box3');
         });
     }
+    if (document.getElementById("copy-btn3-root") && document.getElementById("script-box3")) {
+        document.getElementById('copy-btn3-root').addEventListener('click', function () {
+            copyScript(this, 'script-box3', true);
+        });
+    }
 }
 
 document.addEventListener("DOMContentLoaded", setupHandlers);
diff --git a/media/js/main.js b/media/js/main.js
index 335efb2a..faa458d3 100644
--- a/media/js/main.js
+++ b/media/js/main.js
@@ -34,31 +34,37 @@ window.addEventListener("hashchange", shiftWindow);
 /* Copy a script from an HTML element to the clipboard,
  * removing comments and blank lines.
  * Arguments:
- *   trigger: The button calling the function, whose label will be updated
+ *   trigger: The button calling the function, whose icon will be updated
  *   elem: The element containing the script to copy
+ *   stripSudo: If true, remove 'sudo ' from the start of lines
  */
 
-function copyScript(trigger, elem) {
-    var raw = document.getElementById(elem).innerHTML;
+function copyScript(trigger, elem, stripSudo = false) {
+    const raw = document.getElementById(elem).innerHTML;
 
     // Create a scratch div to copy from
-    var scratch = document.createElement("div");
+    const scratch = document.createElement("div");
     document.body.appendChild(scratch);
 
     // Copy the contents of the script box into the scratch div, removing
-    // comments and blank lines
-    var lines = raw.split("\n");
-    var output = '';
-    for (var l = 0; l < lines.length; l++) {
-        if (lines[l][0] != '#' && lines[l].trim() != '')
-            output += lines[l] + '<br />';
+    // comments and blank lines, and optionally stripping sudo
+    const lines = raw.split("\n");
+    let output = '';
+    for (let l = 0; l < lines.length; l++) {
+        if (lines[l][0] != '#' && lines[l].trim() != '') {
+            let line = lines[l];
+            if (stripSudo) {
+                line = line.replace(/^(\s*)sudo /, '$1');
+            }
+            output += line + '<br />';
+        }
     }
     scratch.innerHTML = output.trim();
 
     // Perform the copy
     if(document.body.createTextRange) {
         // IE 11
-        var range = document.body.createTextRange();
+        const range = document.body.createTextRange();
         range.moveToElementText(scratch);
         range.select();
         document.execCommand("Copy");
@@ -66,8 +72,8 @@ function copyScript(trigger, elem) {
     }
     else if(window.getSelection) {
         // Sane browsers
-        var selection = window.getSelection();
-        var range = document.createRange();
+        const selection = window.getSelection();
+        const range = document.createRange();
         range.selectNodeContents(scratch);
         selection.removeAllRanges();
         selection.addRange(range);
@@ -79,11 +85,16 @@ function copyScript(trigger, elem) {
     scratch.parentNode.removeChild(scratch);
 
     // Indicate to the user that the script was copied
-    var label = trigger.innerHTML;
-    trigger.innerHTML = 'Copied!';
+    const icon = trigger.querySelector('i');
+    const originalClass = stripSudo ? 'fa-terminal' : 'fa-copy';
+    icon.classList.remove(originalClass);
+    icon.classList.add('fa-check');
+    trigger.classList.add('copied');
 
     setTimeout(function() {
-        trigger.innerHTML = label;
+        icon.classList.remove('fa-check');
+        icon.classList.add(originalClass);
+        trigger.classList.remove('copied');
     }, 3000);
 }
 
diff --git a/templates/downloads/js/yum.js b/templates/downloads/js/yum.js
index 30e7b51a..1ff54aed 100644
--- a/templates/downloads/js/yum.js
+++ b/templates/downloads/js/yum.js
@@ -151,6 +151,7 @@ function verChanged() {
 
   if (!ver || ver === "-1") {
      document.getElementById('copy-btn').style.display = 'none';
+     document.getElementById('copy-btn-root').style.display = 'none';
      scriptBox.innerHTML = 'Select platform, architecture, and version above';
      return;
   }
@@ -184,6 +185,7 @@ function verChanged() {
   }
 
   document.getElementById('copy-btn').style.display = 'block';
+  document.getElementById('copy-btn-root').style.display = 'block';
 }
 
 /* Event handlers */
@@ -191,6 +193,9 @@ function setupHandlers() {
     document.getElementById('copy-btn').addEventListener('click', function () {
         copyScript(this, 'script-box');
     });
+    document.getElementById('copy-btn-root').addEventListener('click', function () {
+        copyScript(this, 'script-box', true);
+    });
     document.getElementById('version').addEventListener('change', verChanged);
     document.getElementById('platform').addEventListener('change', platChanged);
     document.getElementById('arch').addEventListener('change', archChanged);
diff --git a/templates/pages/download/linux/debian.html b/templates/pages/download/linux/debian.html
index a11e7a0f..7cf91d2a 100644
--- a/templates/pages/download/linux/debian.html
+++ b/templates/pages/download/linux/debian.html
@@ -58,7 +58,8 @@ Automated repository configuration:
 <div class="pg-script-container">
     <pre id="script-box" class="code">sudo apt install -y postgresql-common
 sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh</pre>
-    <button id="copy-btn" class="pg-script-copy-btn">Copy Script</button>
+    <button id="copy-btn-root" class="pg-script-copy-btn pg-script-copy-btn-root" title="Copy to clipboard (without sudo, for root)"><i class="fas fa-terminal"></i></button>
+    <button id="copy-btn" class="pg-script-copy-btn" title="Copy to clipboard"><i class="far fa-copy"></i></button>
 </div>
 
 <p>
@@ -77,14 +78,16 @@ sudo sh -c "echo 'deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresq
 
 # Update the package lists:
 sudo apt update</pre>
-    <button id="copy-btn2" class="pg-script-copy-btn">Copy Script</button>
+    <button id="copy-btn2-root" class="pg-script-copy-btn pg-script-copy-btn-root" title="Copy to clipboard (without sudo, for root)"><i class="fas fa-terminal"></i></button>
+    <button id="copy-btn2" class="pg-script-copy-btn" title="Copy to clipboard"><i class="far fa-copy"></i></button>
 </div>
 
 Install PostgreSQL: (replace "18" by the version you want)
 
 <div class="pg-script-container">
     <pre id="script-box3" class="code">sudo apt install postgresql-18</pre>
-    <button id="copy-btn3" class="pg-script-copy-btn">Copy Script</button>
+    <button id="copy-btn3-root" class="pg-script-copy-btn pg-script-copy-btn-root" title="Copy to clipboard (without sudo, for root)"><i class="fas fa-terminal"></i></button>
+    <button id="copy-btn3" class="pg-script-copy-btn" title="Copy to clipboard"><i class="far fa-copy"></i></button>
 </div>
 
 <p>
diff --git a/templates/pages/download/linux/redhat.html b/templates/pages/download/linux/redhat.html
index e352dccb..5a75d0c1 100644
--- a/templates/pages/download/linux/redhat.html
+++ b/templates/pages/download/linux/redhat.html
@@ -60,7 +60,8 @@ To use the PostgreSQL Yum Repository, follow these steps:
   <li>Copy, paste and run the relevant parts of the setup script:
     <div class="pg-script-container">
         <pre id="script-box" class="code"></pre>
-        <button id="copy-btn" class="pg-script-copy-btn">Copy Script</button>
+        <button id="copy-btn-root" class="pg-script-copy-btn pg-script-copy-btn-root" title="Copy to clipboard (without sudo, for root)"><i class="fas fa-terminal"></i></button>
+        <button id="copy-btn" class="pg-script-copy-btn" title="Copy to clipboard"><i class="far fa-copy"></i></button>
     </div>
   </li>
 </ol>
diff --git a/templates/pages/download/linux/ubuntu.html b/templates/pages/download/linux/ubuntu.html
index d2f123fa..150a2f1d 100644
--- a/templates/pages/download/linux/ubuntu.html
+++ b/templates/pages/download/linux/ubuntu.html
@@ -57,7 +57,8 @@ Automated repository configuration:
 <div class="pg-script-container">
     <pre id="script-box" class="code">sudo apt install -y postgresql-common
 sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh</pre>
-    <button id="copy-btn" class="pg-script-copy-btn">Copy Script</button>
+    <button id="copy-btn-root" class="pg-script-copy-btn pg-script-copy-btn-root" title="Copy to clipboard (without sudo, for root)"><i class="fas fa-terminal"></i></button>
+    <button id="copy-btn" class="pg-script-copy-btn" title="Copy to clipboard"><i class="far fa-copy"></i></button>
 </div>
 
 <p>
@@ -76,14 +77,16 @@ sudo sh -c "echo 'deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresq
 
 # Update the package lists:
 sudo apt update</pre>
-    <button id="copy-btn2" class="pg-script-copy-btn">Copy Script</button>
+    <button id="copy-btn2-root" class="pg-script-copy-btn pg-script-copy-btn-root" title="Copy to clipboard (without sudo, for root)"><i class="fas fa-terminal"></i></button>
+    <button id="copy-btn2" class="pg-script-copy-btn" title="Copy to clipboard"><i class="far fa-copy"></i></button>
 </div>
 
 Install PostgreSQL: (replace "18" by the version you want)
 
 <div class="pg-script-container">
     <pre id="script-box3" class="code">sudo apt install postgresql-18</pre>
-    <button id="copy-btn3" class="pg-script-copy-btn">Copy Script</button>
+    <button id="copy-btn3-root" class="pg-script-copy-btn pg-script-copy-btn-root" title="Copy to clipboard (without sudo, for root)"><i class="fas fa-terminal"></i></button>
+    <button id="copy-btn3" class="pg-script-copy-btn" title="Copy to clipboard"><i class="far fa-copy"></i></button>
 </div>
 
 <p>


  [application/octet-stream] copy_script_buttons_v2.diff (6.8K, ../CA+OCxoyE1J2hY7tR=mw+ikQSPbD61ftjouk4fkvkmA4qfaODDA@mail.gmail.com/4-copy_script_buttons_v2.diff)
  download | inline diff:
diff --git a/media/css/main.css b/media/css/main.css
index dede2b90..34e04cb5 100644
--- a/media/css/main.css
+++ b/media/css/main.css
@@ -2001,10 +2001,49 @@ button.imagebutton {
   position: relative;
 }
 
+.pg-script-container pre.code {
+  padding-right: 2.5rem;
+  min-height: 3rem;
+  display: flex;
+  align-items: center;
+}
+
 .pg-script-copy-btn {
   position: absolute;
-  top: 8px;
-  right: 8px;
+  top: 0.5rem;
+  right: 0.5rem;
+  background: transparent;
+  border: none;
+  font-size: 1rem;
+  padding: 4px 6px;
+  cursor: pointer;
+  transition: color 0.2s;
+}
+
+.pg-script-copy-btn i {
+  color: #555 !important;
+  margin: 0 !important;
+  transition: color 0.2s;
+}
+
+.pg-script-copy-btn:hover i {
+  color: #336791 !important;
+}
+
+.pg-script-copy-btn.copied i {
+  color: #28a745 !important;
+}
+
+[data-theme="dark"] .pg-script-copy-btn i {
+  color: #ccc !important;
+}
+
+[data-theme="dark"] .pg-script-copy-btn:hover i {
+  color: #fff !important;
+}
+
+[data-theme="dark"] .pg-script-copy-btn.copied i {
+  color: #7dff7d !important;
 }
 
 .nobr {
diff --git a/media/js/main.js b/media/js/main.js
index 335efb2a..4d18db87 100644
--- a/media/js/main.js
+++ b/media/js/main.js
@@ -39,17 +39,17 @@ window.addEventListener("hashchange", shiftWindow);
  */
 
 function copyScript(trigger, elem) {
-    var raw = document.getElementById(elem).innerHTML;
+    const raw = document.getElementById(elem).innerHTML;
 
     // Create a scratch div to copy from
-    var scratch = document.createElement("div");
+    const scratch = document.createElement("div");
     document.body.appendChild(scratch);
 
     // Copy the contents of the script box into the scratch div, removing
     // comments and blank lines
-    var lines = raw.split("\n");
-    var output = '';
-    for (var l = 0; l < lines.length; l++) {
+    const lines = raw.split("\n");
+    let output = '';
+    for (let l = 0; l < lines.length; l++) {
         if (lines[l][0] != '#' && lines[l].trim() != '')
             output += lines[l] + '<br />';
     }
@@ -58,7 +58,7 @@ function copyScript(trigger, elem) {
     // Perform the copy
     if(document.body.createTextRange) {
         // IE 11
-        var range = document.body.createTextRange();
+        const range = document.body.createTextRange();
         range.moveToElementText(scratch);
         range.select();
         document.execCommand("Copy");
@@ -66,8 +66,8 @@ function copyScript(trigger, elem) {
     }
     else if(window.getSelection) {
         // Sane browsers
-        var selection = window.getSelection();
-        var range = document.createRange();
+        const selection = window.getSelection();
+        const range = document.createRange();
         range.selectNodeContents(scratch);
         selection.removeAllRanges();
         selection.addRange(range);
@@ -79,11 +79,15 @@ function copyScript(trigger, elem) {
     scratch.parentNode.removeChild(scratch);
 
     // Indicate to the user that the script was copied
-    var label = trigger.innerHTML;
-    trigger.innerHTML = 'Copied!';
+    const icon = trigger.querySelector('i');
+    icon.classList.remove('fa-copy');
+    icon.classList.add('fa-check');
+    trigger.classList.add('copied');
 
     setTimeout(function() {
-        trigger.innerHTML = label;
+        icon.classList.remove('fa-check');
+        icon.classList.add('fa-copy');
+        trigger.classList.remove('copied');
     }, 3000);
 }
 
diff --git a/templates/pages/download/linux/debian.html b/templates/pages/download/linux/debian.html
index a11e7a0f..6901eb38 100644
--- a/templates/pages/download/linux/debian.html
+++ b/templates/pages/download/linux/debian.html
@@ -58,7 +58,7 @@ Automated repository configuration:
 <div class="pg-script-container">
     <pre id="script-box" class="code">sudo apt install -y postgresql-common
 sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh</pre>
-    <button id="copy-btn" class="pg-script-copy-btn">Copy Script</button>
+    <button id="copy-btn" class="pg-script-copy-btn" title="Copy to clipboard"><i class="far fa-copy"></i></button>
 </div>
 
 <p>
@@ -77,14 +77,14 @@ sudo sh -c "echo 'deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresq
 
 # Update the package lists:
 sudo apt update</pre>
-    <button id="copy-btn2" class="pg-script-copy-btn">Copy Script</button>
+    <button id="copy-btn2" class="pg-script-copy-btn" title="Copy to clipboard"><i class="far fa-copy"></i></button>
 </div>
 
 Install PostgreSQL: (replace "18" by the version you want)
 
 <div class="pg-script-container">
     <pre id="script-box3" class="code">sudo apt install postgresql-18</pre>
-    <button id="copy-btn3" class="pg-script-copy-btn">Copy Script</button>
+    <button id="copy-btn3" class="pg-script-copy-btn" title="Copy to clipboard"><i class="far fa-copy"></i></button>
 </div>
 
 <p>
diff --git a/templates/pages/download/linux/redhat.html b/templates/pages/download/linux/redhat.html
index e352dccb..7215a083 100644
--- a/templates/pages/download/linux/redhat.html
+++ b/templates/pages/download/linux/redhat.html
@@ -60,7 +60,7 @@ To use the PostgreSQL Yum Repository, follow these steps:
   <li>Copy, paste and run the relevant parts of the setup script:
     <div class="pg-script-container">
         <pre id="script-box" class="code"></pre>
-        <button id="copy-btn" class="pg-script-copy-btn">Copy Script</button>
+        <button id="copy-btn" class="pg-script-copy-btn" title="Copy to clipboard"><i class="far fa-copy"></i></button>
     </div>
   </li>
 </ol>
diff --git a/templates/pages/download/linux/ubuntu.html b/templates/pages/download/linux/ubuntu.html
index d2f123fa..7226616a 100644
--- a/templates/pages/download/linux/ubuntu.html
+++ b/templates/pages/download/linux/ubuntu.html
@@ -57,7 +57,7 @@ Automated repository configuration:
 <div class="pg-script-container">
     <pre id="script-box" class="code">sudo apt install -y postgresql-common
 sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh</pre>
-    <button id="copy-btn" class="pg-script-copy-btn">Copy Script</button>
+    <button id="copy-btn" class="pg-script-copy-btn" title="Copy to clipboard"><i class="far fa-copy"></i></button>
 </div>
 
 <p>
@@ -76,14 +76,14 @@ sudo sh -c "echo 'deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresq
 
 # Update the package lists:
 sudo apt update</pre>
-    <button id="copy-btn2" class="pg-script-copy-btn">Copy Script</button>
+    <button id="copy-btn2" class="pg-script-copy-btn" title="Copy to clipboard"><i class="far fa-copy"></i></button>
 </div>
 
 Install PostgreSQL: (replace "18" by the version you want)
 
 <div class="pg-script-container">
     <pre id="script-box3" class="code">sudo apt install postgresql-18</pre>
-    <button id="copy-btn3" class="pg-script-copy-btn">Copy Script</button>
+    <button id="copy-btn3" class="pg-script-copy-btn" title="Copy to clipboard"><i class="far fa-copy"></i></button>
 </div>
 
 <p>


view thread (8+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected], [email protected]
  Subject: Re: Copy script button cleanup
  In-Reply-To: <CA+OCxoyE1J2hY7tR=mw+ikQSPbD61ftjouk4fkvkmA4qfaODDA@mail.gmail.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox