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:
parent
e2bec4c1f5
commit
89a5d3132c
7 changed files with 138 additions and 60 deletions
2
externals/JsShrink/jsShrink.php
vendored
2
externals/JsShrink/jsShrink.php
vendored
|
@ -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];
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.'),
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -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%;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue