diff --git a/app/assets/stylesheets/components/_terminal.scss b/app/assets/stylesheets/components/_terminal.scss index 74e926d..1bca699 100644 --- a/app/assets/stylesheets/components/_terminal.scss +++ b/app/assets/stylesheets/components/_terminal.scss @@ -38,9 +38,25 @@ pre { margin: 0; - overflow-x: auto; padding: .875rem 1rem; + // Wrap, do not scroll. + // + // This block sits in a one-third column on /get-started and the ipctool + // URL is longer than the column is wide, so overflow-x: auto ended the + // visible line at "https://github.com/OpenI" and put the rest behind a + // horizontal scrollbar -- which most systems do not draw until you touch + // it, so there was nothing to suggest the command continued at all. + // + // The command being clipped is one a reader is about to paste into a root + // shell on their camera. Being able to read it to the end before running + // it is the whole point of showing it rather than just linking a script. + white-space: pre-wrap; + // Break inside the URL when there is nowhere else to break. `anywhere` + // rather than `break-all`, so ordinary prose still breaks at spaces and + // only an unbreakable token is split. + overflow-wrap: anywhere; + code { color: inherit; font-size: .875rem; diff --git a/app/javascript/src/copy.js b/app/javascript/src/copy.js index 7035b4b..21fa882 100644 --- a/app/javascript/src/copy.js +++ b/app/javascript/src/copy.js @@ -7,7 +7,12 @@ export default function initCopy() { const source = document.querySelector(btn.dataset.copyTarget) if (!source) return - navigator.clipboard.writeText(source.innerText.trim()).then(() => { + // textContent, not innerText. innerText is the *rendered* text, so with the + // terminal block now soft-wrapping a long URL there is no guarantee an + // engine will not fold those visual breaks into the string. textContent is + // the markup's own text: exactly the command, with only the newlines that + // were actually written. + navigator.clipboard.writeText(source.textContent.trim()).then(() => { const icon = btn.querySelector('i') || btn const original = icon.className icon.className = 'bi bi-check-lg' diff --git a/app/views/pages/get_started.html.erb b/app/views/pages/get_started.html.erb index 8490a2c..9e9a5b9 100644 --- a/app/views/pages/get_started.html.erb +++ b/app/views/pages/get_started.html.erb @@ -9,8 +9,6 @@

1<%= t('.step1_title') %>

<%= t('.step1_text_html') %>

- <%= render 'shared/terminal', id: 'ipctool-cmd', title: 'camera shell', - code: "curl -L -o /tmp/ipctool https://github.com/OpenIPC/ipctool/releases/download/latest/ipctool\nchmod +x /tmp/ipctool && /tmp/ipctool" %>

2<%= t('.step2_title') %>

@@ -23,6 +21,20 @@
+ <%# The command for step 1, given the full width of the article rather than + the third of it the step sits in. + + It was inside the step-1 column, where the ipctool URL is longer than + the column is wide: the visible line ended at "https://github.com/OpenI" + and the rest sat behind a horizontal scrollbar most systems do not draw. + The block wraps rather than scrolls now, so nothing is ever hidden -- but + at this width the command does not have to wrap at all on a desktop, and + two lines that are two commands read as two commands. %> +
+ <%= render 'shared/terminal', id: 'ipctool-cmd', title: 'camera shell', + code: "curl -L -o /tmp/ipctool https://github.com/OpenIPC/ipctool/releases/download/latest/ipctool\nchmod +x /tmp/ipctool && /tmp/ipctool" %> +
+ <%# Honesty box %>
diff --git a/test/controllers/relaunch_pages_test.rb b/test/controllers/relaunch_pages_test.rb index 27fae83..5eeda64 100644 --- a/test/controllers/relaunch_pages_test.rb +++ b/test/controllers/relaunch_pages_test.rb @@ -115,6 +115,23 @@ class RelaunchPagesTest < ActionDispatch::IntegrationTest end end + # /get-started shows a command to paste into a root shell on the camera. It + # has to reach the page whole -- the URL was being clipped by CSS, and the + # tempting fix is to shorten the command rather than let it wrap. + test 'the ipctool command reaches the page complete' do + get '/get-started' + + block = css_select('#ipctool-cmd').first + + assert_not_nil block, 'the terminal block is gone' + command = block.text + assert_includes command, 'https://github.com/OpenIPC/ipctool/releases/download/latest/ipctool' + assert_includes command, 'chmod +x /tmp/ipctool' + # The copy button copies this element, so it must name it correctly. + assert_not_empty css_select('[data-copy-target="#ipctool-cmd"]'), + 'nothing on the page copies the command' + end + # The integrator wall is territory-specific: these companies serve Russia and # were explicitly not to be shown to everyone. test 'Russian integrators appear for ru and for nobody else' do