1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-22 13:30:55 +01:00

Implements copy button in clone repo modal

Summary:
This diff adds a copy button to every repo uri in the clone repo modal. I have made the button to select the text to a merely structural span before the input - it just shows the type of the repository uri. When you click inside the input, the entire uri will be selected. Also I have uncluttered the HTML structure. A table is not needed here, nothing a flex block can't handle.

| Before    | After     |
|-----------|-----------|
| {F1360344} | {F1368592} |

While at it, I have extended the used javascript copy behavior. First of all: `document.execCommand('copy')` [[ https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand | could stop working every moment in every browser ]]. The [[ https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Interact_with_the_clipboard | new clipboard API ]] is the way to go, so I have implemented it as the preferred method. The old method is kept as a fallback. And I have added a very nice feature: If defined, the behavior will now issue success or error notifications. See the changed UIExamples for that.

To support the shrinking of JS code with async functions I have patched the JsShrink source.

Test Plan: Go to a repository, hit the clone button and use the new copy button. You will see a shiny notification as a reward.

Reviewers: O1 Blessed Committers, avivey, valerio.bozzolan

Reviewed By: O1 Blessed Committers, avivey, valerio.bozzolan

Subscribers: avivey, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno

Differential Revision: https://we.phorge.it/D25536
This commit is contained in:
Benjamin Kausch 2024-02-15 19:30:57 +01:00
parent e2bec4c1f5
commit 89a5d3132c
7 changed files with 138 additions and 60 deletions

View file

@ -35,7 +35,7 @@ function jsShrinkCallback($match) {
list(, $context, $regexp, $result, $word, $operator) = $match;
if ($word != '') {
$result = ($last == 'word' ? "\n" : ($last == 'return' ? " " : "")) . $result;
$last = ($word == 'return' || $word == 'throw' || $word == 'break' ? 'return' : 'word');
$last = ($word == 'return' || $word == 'throw' || $word == 'break' || $word == 'async' ? 'return' : 'word');
} elseif ($operator) {
$result = ($last == $operator[0] ? "\n" : "") . $result;
$last = $operator[0];

View file

@ -14,7 +14,7 @@ return array(
'dark-console.pkg.js' => '187792c2',
'differential.pkg.css' => '6d3700f0',
'differential.pkg.js' => '46fcb3af',
'diffusion.pkg.css' => '42c75c37',
'diffusion.pkg.css' => '354279ea',
'diffusion.pkg.js' => '78c9885d',
'maniphest.pkg.css' => '35995d6d',
'maniphest.pkg.js' => 'c9308721',
@ -69,7 +69,7 @@ return array(
'rsrc/css/application/differential/revision-history.css' => '237a2979',
'rsrc/css/application/differential/revision-list.css' => '93d2df7d',
'rsrc/css/application/differential/table-of-contents.css' => 'bba788b9',
'rsrc/css/application/diffusion/diffusion-icons.css' => '23b31a1b',
'rsrc/css/application/diffusion/diffusion-icons.css' => 'e812add2',
'rsrc/css/application/diffusion/diffusion-readme.css' => 'b68a76e4',
'rsrc/css/application/diffusion/diffusion-repository.css' => 'b89e8c6c',
'rsrc/css/application/diffusion/diffusion.css' => 'e46232d6',
@ -470,7 +470,7 @@ return array(
'rsrc/js/core/behavior-badge-view.js' => '92cdd7b6',
'rsrc/js/core/behavior-bulk-editor.js' => 'aa6d2308',
'rsrc/js/core/behavior-choose-control.js' => '04f8a1e3',
'rsrc/js/core/behavior-copy.js' => 'cf32921f',
'rsrc/js/core/behavior-copy.js' => '96b63a02',
'rsrc/js/core/behavior-detect-timezone.js' => '78bc5d94',
'rsrc/js/core/behavior-device.js' => 'ac2b1e01',
'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '6bc7ccf7',
@ -499,7 +499,7 @@ return array(
'rsrc/js/core/behavior-reveal-content.js' => 'b105a3a6',
'rsrc/js/core/behavior-scrollbar.js' => '92388bae',
'rsrc/js/core/behavior-search-typeahead.js' => '1cb7d027',
'rsrc/js/core/behavior-select-content.js' => 'e8240b50',
'rsrc/js/core/behavior-select-content.js' => 'c538cbfc',
'rsrc/js/core/behavior-select-on-click.js' => '66365ee2',
'rsrc/js/core/behavior-setup-check-https.js' => '01384686',
'rsrc/js/core/behavior-time-typeahead.js' => '5803b9e7',
@ -567,7 +567,7 @@ return array(
'differential-revision-list-css' => '93d2df7d',
'differential-table-of-contents-css' => 'bba788b9',
'diffusion-css' => 'e46232d6',
'diffusion-icons-css' => '23b31a1b',
'diffusion-icons-css' => 'e812add2',
'diffusion-readme-css' => 'b68a76e4',
'diffusion-repository-css' => 'b89e8c6c',
'diviner-shared-css' => '4bd263b0',
@ -644,7 +644,7 @@ return array(
'javelin-behavior-owners-path-editor' => 'ff688a7a',
'javelin-behavior-passphrase-credential-control' => '48fe33d0',
'javelin-behavior-phabricator-autofocus' => '65bb0011',
'javelin-behavior-phabricator-clipboard-copy' => 'cf32921f',
'javelin-behavior-phabricator-clipboard-copy' => '96b63a02',
'javelin-behavior-phabricator-gesture' => 'b58d1a2a',
'javelin-behavior-phabricator-gesture-example' => '242dedd0',
'javelin-behavior-phabricator-keyboard-pager' => '1325b731',
@ -687,7 +687,7 @@ return array(
'javelin-behavior-repository-crossreference' => '44d48cd1',
'javelin-behavior-scrollbar' => '92388bae',
'javelin-behavior-search-reorder-queries' => 'b86f297f',
'javelin-behavior-select-content' => 'e8240b50',
'javelin-behavior-select-content' => 'c538cbfc',
'javelin-behavior-select-on-click' => '66365ee2',
'javelin-behavior-setup-check-https' => '01384686',
'javelin-behavior-stripe-payment-form' => '02cb4398',
@ -1770,6 +1770,12 @@ return array(
'javelin-dom',
'javelin-router',
),
'96b63a02' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'phabricator-notification',
),
'98ef467f' => array(
'javelin-behavior',
'javelin-dom',
@ -2022,6 +2028,11 @@ return array(
'javelin-workboard-card',
'javelin-workboard-header',
),
'c538cbfc' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'c687e867' => array(
'javelin-behavior',
'javelin-dom',
@ -2063,11 +2074,6 @@ return array(
'phuix-formation-column-view',
'phuix-formation-flank-view',
),
'cf32921f' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'd12d214f' => array(
'javelin-install',
'javelin-dom',
@ -2146,11 +2152,6 @@ return array(
'javelin-dom',
'phabricator-draggable-list',
),
'e8240b50' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'e9a2940f' => array(
'javelin-behavior',
'javelin-request',

View file

@ -40,6 +40,7 @@ final class DiffusionCloneURIView extends AphrontView {
require_celerity_resource('diffusion-icons-css');
Javelin::initBehavior('select-content');
Javelin::initBehavior('phabricator-clipboard-copy');
$uri_id = celerity_generate_unique_node_id();
@ -53,6 +54,11 @@ final class DiffusionCloneURIView extends AphrontView {
'value' => $display,
'class' => 'diffusion-clone-uri',
'readonly' => 'true',
'sigil' => 'select-content',
'meta' => array(
'selectID' => $uri_id,
'once' => true,
),
));
$uri = $this->getRepositoryURI();
@ -71,17 +77,30 @@ final class DiffusionCloneURIView extends AphrontView {
break;
}
$io = id(new PHUIButtonView())
$io = javelin_tag(
'span',
array(
'class' => 'diffusion-clone-uri-io',
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $io_tip,
),
),
id(new PHUIIconView())->setIcon($io_icon));
$copy = id(new PHUIButtonView())
->setTag('a')
->setColor(PHUIButtonView::GREY)
->setIcon($io_icon)
->setIcon('fa-clipboard')
->setHref('#')
->addSigil('select-content')
->addSigil('clipboard-copy')
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => $io_tip,
'selectID' => $uri_id,
'tip' => pht('Copy repository URI'),
'text' => $display,
'successMessage' => pht('Repository URI copied.'),
'errorMessage' => pht('Copy of Repository URI failed.'),
));
switch ($uri->getEffectiveIOType()) {
@ -121,19 +140,18 @@ final class DiffusionCloneURIView extends AphrontView {
->setHref($auth_uri)
->setDisabled($auth_disabled);
$cells = array();
$cells[] = phutil_tag('td', array(), $input);
$cells[] = phutil_tag('th', array(), $io);
$cells[] = phutil_tag('th', array(), $credentials);
$row = phutil_tag('tr', array(), $cells);
$elements = array();
$elements[] = $io;
$elements[] = $input;
$elements[] = $copy;
$elements[] = $credentials;
return phutil_tag(
'table',
'div',
array(
'class' => 'diffusion-clone-uri-table',
'class' => 'diffusion-clone-uri-wrapper',
),
$row);
$elements);
}
}

View file

@ -115,6 +115,8 @@ final class PHUIButtonExample extends PhabricatorUIExample {
$button->setMetadata(
array(
'text' => $copy,
'successMessage' => pht('Text copied into clipboard.'),
'errorMessage' => pht('Failed to copy text into clipboard.'),
));
}

View file

@ -2,11 +2,6 @@
* @provides diffusion-icons-css
*/
input.diffusion-clone-uri {
display: block;
width: 100%;
}
.diffusion-clone-extras {
font-size: 11px;
text-align: right;
@ -35,21 +30,35 @@ input.diffusion-clone-uri {
padding: 0;
}
.diffusion-clone-uri-table {
width: 100%;
.diffusion-clone-uri-wrapper {
display: flex;
}
.diffusion-clone-uri-table th {
width: 24px;
padding: 0 0 0 4px;
.diffusion-clone-uri-wrapper .diffusion-clone-uri-io {
align-items: center;
justify-content: center;
border: 1px solid {$greyborder};
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
display: flex;
width: 38px;
}
.diffusion-clone-uri-table th a.button {
.diffusion-clone-uri-wrapper a.button {
width: 12px;
height: 19px;
margin-left: 4px;
}
.diffusion-clone-uri-table th a.button .phui-icon-view {
.diffusion-clone-uri-wrapper a.button .phui-icon-view {
left: 15px;
top: 7px;
}
.diffusion-clone-uri-wrapper input.diffusion-clone-uri {
border-left: none;
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
display: block;
width: 100%;
}

View file

@ -3,41 +3,81 @@
* @requires javelin-behavior
* javelin-dom
* javelin-stratcom
* phabricator-notification
* @javelin
*/
JX.behavior('phabricator-clipboard-copy', function() {
if (!document.queryCommandSupported) {
return;
}
var fallback_working = document.queryCommandSupported &&
document.queryCommandSupported('copy');
if (!document.queryCommandSupported('copy')) {
if (!navigator.clipboard && !fallback_working) {
return;
}
JX.DOM.alterClass(document.body, 'supports-clipboard', true);
JX.Stratcom.listen('click', 'clipboard-copy', function(e) {
e.kill();
var data = e.getNodeData('clipboard-copy');
var copy_fallback = function(text) {
var attr = {
value: data.text || '',
value: text || '',
className: 'clipboard-buffer'
};
var node = JX.$N('textarea', attr);
document.body.appendChild(node);
try {
node.select();
document.execCommand('copy');
} catch (ignored) {
// Ignore any errors we hit.
}
node.select();
document.execCommand('copy');
JX.DOM.remove(node);
JX.DOM.remove(node);
};
var show_success_message = function(message) {
if (!message) {
return;
}
new JX.Notification()
.setContent(message)
.alterClassName('jx-notification-done', true)
.setDuration(8000)
.show();
};
var show_error_message = function(message) {
if (!message) {
return;
}
new JX.Notification()
.setContent(message)
.alterClassName('jx-notification-error', true)
.setDuration(8000)
.show();
};
JX.Stratcom.listen('click', 'clipboard-copy', function(e) {
var data = e.getNodeData('clipboard-copy');
var text = data.text || '';
var copy = async function( // jshint ignore:line
text,
successMessage,
errorMessage
) {
try {
if (navigator.clipboard) {
await navigator.clipboard.writeText(text);
} else {
copy_fallback(text);
}
show_success_message(successMessage);
} catch (ex) {
show_error_message(errorMessage);
}
};
e.kill();
copy(text, data.successMessage, data.errorMessage);
});
});

View file

@ -16,8 +16,16 @@ JX.behavior('select-content', function() {
var node = e.getNode('select-content');
var data = JX.Stratcom.getData(node);
if (data.once && data.selected) {
return;
}
var target = JX.$(data.selectID);
JX.DOM.focus(target);
target.select();
if (data.once) {
JX.Stratcom.addData(node, {selected: true});
}
});
});