diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 3da70886c5..90823f0bb8 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'c56695d0', + 'core.pkg.css' => '0c6e11ed', 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => 'a2755617', @@ -114,7 +114,7 @@ return array( 'rsrc/css/application/slowvote/slowvote.css' => 'a94b7230', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', - 'rsrc/css/core/core.css' => '9f4cb463', + 'rsrc/css/core/core.css' => '23beb330', 'rsrc/css/core/remarkup.css' => 'd1a5e11e', 'rsrc/css/core/syntax.css' => 'cae95e89', 'rsrc/css/core/z-index.css' => '9d8f7c4b', @@ -474,6 +474,7 @@ return array( 'rsrc/js/core/behavior-autofocus.js' => '7319e029', 'rsrc/js/core/behavior-badge-view.js' => '8ff5e24c', 'rsrc/js/core/behavior-choose-control.js' => '327a00d1', + 'rsrc/js/core/behavior-copy.js' => 'b0b8f86d', 'rsrc/js/core/behavior-detect-timezone.js' => '4c193c96', 'rsrc/js/core/behavior-device.js' => 'bb1dd507', 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '484a6e22', @@ -644,6 +645,7 @@ return array( 'javelin-behavior-passphrase-credential-control' => '3cb0b2fc', 'javelin-behavior-phabricator-active-nav' => 'e379b58e', 'javelin-behavior-phabricator-autofocus' => '7319e029', + 'javelin-behavior-phabricator-clipboard-copy' => 'b0b8f86d', 'javelin-behavior-phabricator-file-tree' => '88236f00', 'javelin-behavior-phabricator-gesture' => '3ab51e2c', 'javelin-behavior-phabricator-gesture-example' => '558829c2', @@ -763,7 +765,7 @@ return array( 'phabricator-busy' => '59a7976a', 'phabricator-chatlog-css' => 'd295b020', 'phabricator-content-source-view-css' => '4b8b05d4', - 'phabricator-core-css' => '9f4cb463', + 'phabricator-core-css' => '23beb330', 'phabricator-countdown-css' => '16c52f5c', 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', @@ -1738,6 +1740,11 @@ return array( 'javelin-dom', 'phuix-dropdown-menu', ), + 'b0b8f86d' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + ), 'b23b49e6' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/applications/uiexample/examples/PHUIButtonExample.php b/src/applications/uiexample/examples/PHUIButtonExample.php index 8f2e1e57a7..fa4d317a5c 100644 --- a/src/applications/uiexample/examples/PHUIButtonExample.php +++ b/src/applications/uiexample/examples/PHUIButtonExample.php @@ -106,18 +106,49 @@ final class PHUIButtonExample extends PhabricatorUIExample { $column = array(); $icons = array( - 'Comment' => 'fa-comment', - 'Give Token' => 'fa-trophy', - 'Reverse Time' => 'fa-clock-o', - 'Implode Earth' => 'fa-exclamation-triangle red', + array( + 'text' => pht('Comment'), + 'icon' => 'fa-comment', + ), + array( + 'text' => pht('Give Token'), + 'icon' => 'fa-trophy', + ), + array( + 'text' => pht('Reverse Time'), + 'icon' => 'fa-clock-o', + ), + array( + 'text' => pht('Implode Earth'), + 'icon' => 'fa-exclamation-triangle', + ), + array( + 'text' => pht('Copy "Quack" to Clipboard'), + 'icon' => 'fa-clipboard', + 'copy' => pht('Quack'), + ), ); - foreach ($icons as $text => $icon) { - $column[] = id(new PHUIButtonView()) + foreach ($icons as $text => $spec) { + $button = id(new PHUIButtonView()) ->setTag('a') ->setColor(PHUIButtonView::GREY) - ->setIcon($icon) - ->setText($text) + ->setIcon(idx($spec, 'icon')) + ->setText(idx($spec, 'text')) ->addClass(PHUI::MARGIN_SMALL_RIGHT); + + $copy = idx($spec, 'copy'); + if ($copy !== null) { + Javelin::initBehavior('phabricator-clipboard-copy'); + + $button->addClass('clipboard-copy'); + $button->addSigil('clipboard-copy'); + $button->setMetadata( + array( + 'text' => $copy, + )); + } + + $column[] = $button; } $layout3 = id(new AphrontMultiColumnView()) diff --git a/webroot/rsrc/css/core/core.css b/webroot/rsrc/css/core/core.css index 01c4414454..d7145bb485 100644 --- a/webroot/rsrc/css/core/core.css +++ b/webroot/rsrc/css/core/core.css @@ -173,3 +173,16 @@ hr { height: 2px; background: {$sky}; } + +.clipboard-copy { + visibility: hidden; +} + +.supports-clipboard .clipboard-copy { + visibility: visible; +} + +.clipboard-buffer { + position: absolute; + left: -9999px; +} diff --git a/webroot/rsrc/js/core/behavior-copy.js b/webroot/rsrc/js/core/behavior-copy.js new file mode 100644 index 0000000000..4457f6e022 --- /dev/null +++ b/webroot/rsrc/js/core/behavior-copy.js @@ -0,0 +1,43 @@ +/** + * @provides javelin-behavior-phabricator-clipboard-copy + * @requires javelin-behavior + * javelin-dom + * javelin-stratcom + * @javelin + */ + +JX.behavior('phabricator-clipboard-copy', function() { + + if (!document.queryCommandSupported) { + return; + } + + if (!document.queryCommandSupported('copy')) { + 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 attr = { + value: data.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. + } + + JX.DOM.remove(node); + }); + +});