diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,8 +9,8 @@ 'names' => array( 'conpherence.pkg.css' => '3144a5e2', 'conpherence.pkg.js' => '020aebcf', - 'core.pkg.css' => '10815c8e', - 'core.pkg.js' => 'f58c3c6e', + 'core.pkg.css' => '3531d5b9', + 'core.pkg.js' => 'eb58391a', 'dark-console.pkg.js' => '187792c2', 'differential.pkg.css' => '91ac6214', 'differential.pkg.js' => '46fcb3af', @@ -109,7 +109,7 @@ 'rsrc/css/application/tokens/tokens.css' => 'ce5a50bd', 'rsrc/css/application/uiexample/example.css' => 'b4795059', 'rsrc/css/core/core.css' => '531ad849', - 'rsrc/css/core/remarkup.css' => '03b6c819', + 'rsrc/css/core/remarkup.css' => 'd93e6fb6', 'rsrc/css/core/syntax.css' => '548567f6', 'rsrc/css/core/z-index.css' => 'ac3bfcd4', 'rsrc/css/diviner/diviner-shared.css' => '4bd263b0', @@ -460,6 +460,7 @@ 'rsrc/js/core/MultirowRowManager.js' => '5b54c823', 'rsrc/js/core/Notification.js' => 'a9b91e3f', 'rsrc/js/core/Prefab.js' => '5793d835', + 'rsrc/js/core/RemarkupCodeblockCopy.js' => '978cdbd3', 'rsrc/js/core/RemarkupMetadata.js' => 'e40c4991', 'rsrc/js/core/ShapedRequest.js' => '995f5102', 'rsrc/js/core/TextAreaUtils.js' => 'f340a484', @@ -512,7 +513,7 @@ 'rsrc/js/core/darkconsole/DarkLog.js' => '3b869402', 'rsrc/js/core/darkconsole/DarkMessage.js' => '26cd4b73', 'rsrc/js/core/darkconsole/behavior-dark-console.js' => '457f4d16', - 'rsrc/js/core/phtize.js' => '2f1db1ed', + 'rsrc/js/core/phtize.js' => 'fef089d9', 'rsrc/js/phui/behavior-phui-dropdown-menu.js' => '5cf0501a', 'rsrc/js/phui/behavior-phui-file-upload.js' => 'e150bd50', 'rsrc/js/phui/behavior-phui-selectable-list.js' => 'b26a41e4', @@ -679,6 +680,7 @@ 'javelin-behavior-read-only-warning' => 'b9109f8f', 'javelin-behavior-redirect' => '407ee861', 'javelin-behavior-refresh-csrf' => '46116c01', + 'javelin-behavior-remarkup-codeblock-copy' => '978cdbd3', 'javelin-behavior-remarkup-load-image' => '202bfa3f', 'javelin-behavior-remarkup-preview' => 'd8a86cfb', 'javelin-behavior-reorder-applications' => 'aa371860', @@ -795,9 +797,9 @@ 'phabricator-notification-css' => '30240bd2', 'phabricator-notification-menu-css' => '4df1ee30', 'phabricator-object-selector-css' => 'ee77366f', - 'phabricator-phtize' => '2f1db1ed', + 'phabricator-phtize' => 'fef089d9', 'phabricator-prefab' => '5793d835', - 'phabricator-remarkup-css' => '03b6c819', + 'phabricator-remarkup-css' => 'd93e6fb6', 'phabricator-remarkup-metadata' => 'e40c4991', 'phabricator-search-results-css' => '9ea70ace', 'phabricator-shaped-request' => '995f5102', @@ -1187,9 +1189,6 @@ 'javelin-util', 'javelin-stratcom', ), - '2f1db1ed' => array( - 'javelin-util', - ), '2fbe234d' => array( 'javelin-install', 'javelin-dom', @@ -2207,6 +2206,9 @@ 'fdc13e4e' => array( 'javelin-install', ), + 'fef089d9' => array( + 'javelin-util', + ), 'ff688a7a' => array( 'owners-path-editor', 'javelin-behavior', diff --git a/src/applications/phame/controller/post/PhamePostViewController.php b/src/applications/phame/controller/post/PhamePostViewController.php --- a/src/applications/phame/controller/post/PhamePostViewController.php +++ b/src/applications/phame/controller/post/PhamePostViewController.php @@ -87,6 +87,17 @@ ), $engine->getOutput($post, PhamePost::MARKUP_FIELD_BODY))); + Javelin::initBehavior('phabricator-clipboard-copy'); + Javelin::initBehavior('phabricator-tooltips'); + Javelin::initBehavior('remarkup-codeblock-copy', + array( + 'pht' => array( + 'Copy to clipboard' => pht('Copy to clipboard'), + 'Code block copied.' => pht('Code block copied.'), + 'Copy of code block failed.' => pht('Copy of code block failed.'), + ), + )); + $blogger = id(new PhabricatorPeopleQuery()) ->setViewer($viewer) ->withPHIDs(array($post->getBloggerPHID())) diff --git a/src/infrastructure/markup/view/PHUIRemarkupView.php b/src/infrastructure/markup/view/PHUIRemarkupView.php --- a/src/infrastructure/markup/view/PHUIRemarkupView.php +++ b/src/infrastructure/markup/view/PHUIRemarkupView.php @@ -89,6 +89,17 @@ $viewer, $context); + Javelin::initBehavior('phabricator-clipboard-copy'); + Javelin::initBehavior('phabricator-tooltips'); + Javelin::initBehavior('remarkup-codeblock-copy', + array( + 'pht' => array( + 'Copy to clipboard' => pht('Copy to clipboard'), + 'Code block copied.' => pht('Code block copied.'), + 'Copy of code block failed.' => pht('Copy of code block failed.'), + ), + )); + return $content; } diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -913,3 +913,26 @@ left: 0; right: 0; } + +.remarkup-code-block { + position: relative; +} + +.remarkup-code-block .phui-font-fa.btn-clipboard { + position: absolute; + top: -16px; + right: 0px; + text-decoration: none; +} + +.remarkup-code-block .phui-font-fa.btn-clipboard.fa-clipboard { + color: {$darkgreybackground}; +} + +.remarkup-code-block.has-header .phui-font-fa.btn-clipboard { + top: 16px; +} + +.remarkup-code-block .phui-font-fa.btn-clipboard:hover { + color: {$darkbluetext}; +} diff --git a/webroot/rsrc/js/core/RemarkupCodeblockCopy.js b/webroot/rsrc/js/core/RemarkupCodeblockCopy.js new file mode 100644 --- /dev/null +++ b/webroot/rsrc/js/core/RemarkupCodeblockCopy.js @@ -0,0 +1,47 @@ +/** + * @provides javelin-behavior-remarkup-codeblock-copy + */ + +JX.behavior('remarkup-codeblock-copy', function(config, statics) { + var pht = JX.phtize(config.pht); + + var codeBlocks = Array.from( + document.querySelectorAll('.remarkup-code-block') + ); + + for (var codeBlockIndex in codeBlocks) { + var codeBlock = codeBlocks[codeBlockIndex]; + + var button = document.createElement('a'); + button.id = 'codeBlock-' + codeBlockIndex; + button.setAttribute('data-sigil','clipboard-copy has-tooltip'); + button.href = '#'; + button.classList.add('phui-font-fa'); + button.classList.add('btn-clipboard'); + button.classList.add('fa-clipboard'); + codeBlock.appendChild(button); + + if (codeBlock.querySelector('.remarkup-code-header')) { + codeBlock.classList.add('has-header'); + } + + JX.Stratcom.addData(button, { + tip: pht('Copy to clipboard'), + align: 'W' + }); + } + + JX.Stratcom.listen('mousedown', 'clipboard-copy', function(e) { + var button = e.getTarget(); + var codeBlock = button.closest('.remarkup-code-block'); + var code = codeBlock.querySelector('pre'); + + JX.Stratcom.addData(button, { + text: code.innerText, + tip: pht('Copy to clipboard'), + align: 'E', + successMessage: pht('Code block copied.'), + errorMessage: pht('Copy of code block failed.') + }); + }); +});