diff --git a/package.json b/package.json --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "d3": "^5.8.2", "datatables.net-bs4": "^1.10.19", "ejs": "^2.6.1", + "elementsfrompoint-polyfill": "^1.0.0", "eslint": "^5.15.1", "eslint-loader": "^2.1.2", "eslint-plugin-import": "^2.16.0", diff --git a/swh/web/assets/src/bundles/vendors/index.js b/swh/web/assets/src/bundles/vendors/index.js --- a/swh/web/assets/src/bundles/vendors/index.js +++ b/swh/web/assets/src/bundles/vendors/index.js @@ -8,10 +8,11 @@ // vendors bundles centralizing assets used in all swh-web applications // polyfills in order to use advanced js features (like Promise or fetch) -// in older browsers +// in browsers that do not support them import '@babel/polyfill'; import 'whatwg-fetch/dist/fetch.umd'; import 'url-search-params-polyfill'; +import 'elementsfrompoint-polyfill'; // jquery and bootstrap import 'jquery'; diff --git a/swh/web/assets/src/bundles/webapp/webapp-utils.js b/swh/web/assets/src/bundles/webapp/webapp-utils.js --- a/swh/web/assets/src/bundles/webapp/webapp-utils.js +++ b/swh/web/assets/src/bundles/webapp/webapp-utils.js @@ -1,5 +1,6 @@ import objectFitImages from 'object-fit-images'; import {Layout} from 'admin-lte'; +import {selectText} from 'utils/functions'; let collapseSidebar = false; let previousSidebarState = localStorage.getItem('swh-sidebar-collapsed'); @@ -80,6 +81,35 @@ // reparent the modals to the top navigation div in order to be able // to display them $('.swh-browse-top-navigation').append($('.modal')); + + let selectedCode = null; + + // click handler to set focus on code block for copy + $(document).click(e => { + let elts = document.elementsFromPoint(e.clientX, e.clientY); + selectedCode = null; + for (let elt of elts) { + if (elt.nodeName === 'CODE' || elt.nodeName === 'PRE') { + selectedCode = elt; + break; + } + } + }); + + // select the whole text of focused code block when user hits Ctrl+A + $(document).keydown(e => { + if ((e.ctrlKey || e.metaKey) && e.key === 'a') { + if (selectedCode) { + let hljsLnCodeElts = $(selectedCode).find('.hljs-ln-code'); + if (hljsLnCodeElts.length) { + selectText(hljsLnCodeElts[0], hljsLnCodeElts[hljsLnCodeElts.length - 1]); + } else { + selectText(selectedCode.firstChild, selectedCode.lastChild); + } + e.preventDefault(); + } + } + }); }); export function initPage(page) { diff --git a/swh/web/assets/src/utils/functions.js b/swh/web/assets/src/utils/functions.js --- a/swh/web/assets/src/utils/functions.js +++ b/swh/web/assets/src/utils/functions.js @@ -47,3 +47,16 @@ export function removeUrlFragment() { history.replaceState('', document.title, window.location.pathname + window.location.search); } + +export function selectText(startNode, endNode) { + let selection = window.getSelection(); + selection.removeAllRanges(); + let range = document.createRange(); + range.setStart(startNode, 0); + if (endNode.nodeName !== '#text') { + range.setEnd(endNode, endNode.childNodes.length); + } else { + range.setEnd(endNode, endNode.textContent.length); + } + selection.addRange(range); +} diff --git a/yarn.lock b/yarn.lock --- a/yarn.lock +++ b/yarn.lock @@ -2941,6 +2941,11 @@ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.113.tgz#b1ccf619df7295aea17bc6951dc689632629e4a9" integrity sha512-De+lPAxEcpxvqPTyZAXELNpRZXABRxf+uL/rSykstQhzj/B0l1150G/ExIIxKc16lI89Hgz81J0BHAcbTqK49g== +elementsfrompoint-polyfill@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/elementsfrompoint-polyfill/-/elementsfrompoint-polyfill-1.0.0.tgz#045291ca64fa4236e8edc01fdfe395ffc5613659" + integrity sha512-hw+c1PUOkZ5B8ulcIuwNH0rHLGL9tOKOjazS+jFWpoRC3we68fYEuQ+ap70CMd778iHfe+3lSEbElz0t6Y0yvw== + elliptic@^6.0.0: version "6.4.1" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.1.tgz#c2d0b7776911b86722c632c3c06c60f2f819939a"