diff --git a/javascript/commons/CopyToClipboard.js b/javascript/commons/CopyToClipboard.js index 28d225869e1..5451079a2e3 100644 --- a/javascript/commons/CopyToClipboard.js +++ b/javascript/commons/CopyToClipboard.js @@ -11,39 +11,24 @@ liquipedia.copytoclipboard = { } } ); }, - buttonEventListener: function( e ) { + buttonEventListener: async function( e ) { const parent = e.target.closest( '.copy-to-clipboard' ); const text = parent.querySelector( '.copy-this' ); if ( text !== null ) { + if ( !navigator.clipboard || !navigator.clipboard.writeText ) { + mw.notify( 'This browser does not support copying text to the clipboard.', { type: 'error' } ); + return; + } const rawText = text.textContent; - if ( navigator.clipboard && navigator.clipboard.writeText ) { - navigator.clipboard.writeText( rawText ); - } else { - liquipedia.copytoclipboard.createInputBox( parent, rawText ); - liquipedia.copytoclipboard.selectAndCopy(); - liquipedia.copytoclipboard.removeInputBox(); + try { + await navigator.clipboard.writeText( rawText ); + } catch { + mw.notify( 'Failed to copy text to the clipboard.', { type: 'error' } ); + return; } liquipedia.copytoclipboard.showNotification( parent ); } }, - inputBox: null, - createInputBox: function( parent, text ) { - const input = document.createElement( 'input' ); - input.value = text; - liquipedia.copytoclipboard.inputBox = input; - parent.appendChild( input ); - }, - removeInputBox: function() { - const input = liquipedia.copytoclipboard.inputBox; - liquipedia.copytoclipboard.inputBox = null; - input.parentNode.removeChild( input ); - }, - selectAndCopy: function() { - const input = liquipedia.copytoclipboard.inputBox; - input.focus(); - input.select(); - document.execCommand( 'copy' ); - }, showNotification: function( copy ) { const timeout = 2000; let text = 'Copied...'; diff --git a/javascript/commons/Selectall.js b/javascript/commons/Selectall.js index 99998f181b7..dcd4f849dd3 100644 --- a/javascript/commons/Selectall.js +++ b/javascript/commons/Selectall.js @@ -1,59 +1,72 @@ /******************************************************************************* * Template(s): Select all for pre elements - * Author(s): FO-nTTaX ******************************************************************************/ + +class SelectAllContainer { + /** + * @param {HTMLElement} selectAllElement + */ + constructor( selectAllElement ) { + this.element = selectAllElement; + } + + createWrapper() { + const wrapper = document.createElement( 'div' ); + wrapper.classList.add( 'selectall-wrapper' ); + const buttonWrapper = document.createElement( 'div' ); + buttonWrapper.classList.add( 'selectall-buttons' ); + wrapper.appendChild( buttonWrapper ); + this.element.parentNode.replaceChild( wrapper, this.element ); + wrapper.appendChild( this.element ); + buttonWrapper.append( this.createSelectButton(), this.createSelectAllButton() ); + } + + createSelectButton() { + const selectButton = document.createElement( 'button' ); + selectButton.classList.add( 'btn' ); + selectButton.classList.add( 'btn-secondary' ); + selectButton.innerHTML = 'Select'; + selectButton.addEventListener( 'click', () => this.selectElementText() ); + return selectButton; + } + + createSelectAllButton() { + const selectCopyButton = document.createElement( 'button' ); + selectCopyButton.classList.add( 'btn' ); + selectCopyButton.classList.add( 'btn-primary' ); + selectCopyButton.innerHTML = 'Select and copy'; + + selectCopyButton.addEventListener( 'click', async () => { + if ( !navigator.clipboard || !navigator.clipboard.writeText ) { + mw.notify( 'This browser does not support copying text to the clipboard.', { type: 'error' } ); + return; + } + + this.selectElementText(); + try { + await navigator.clipboard.writeText( this.element.textContent ); + } catch { + mw.notify( 'Failed to copy text to the clipboard.', { type: 'error' } ); + } + } ); + return selectCopyButton; + } + + selectElementText() { + const range = document.createRange(); + range.selectNodeContents( this.element ); + const selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange( range ); + } +} + liquipedia.selectall = { init: function() { - document.querySelectorAll( '.selectall' ).forEach( ( selectall ) => { - const wrapper = document.createElement( 'div' ); - wrapper.classList.add( 'selectall-wrapper' ); - const buttonwrapper = document.createElement( 'div' ); - buttonwrapper.classList.add( 'selectall-buttons' ); - wrapper.appendChild( buttonwrapper ); - const relative = document.createElement( 'div' ); - relative.classList.add( 'selectall-relative' ); - wrapper.appendChild( relative ); - selectall.parentNode.replaceChild( wrapper, selectall ); - relative.appendChild( selectall ); - const selectbutton = document.createElement( 'button' ); - selectbutton.classList.add( 'btn' ); - selectbutton.classList.add( 'btn-secondary' ); - selectbutton.innerHTML = 'Select'; - selectbutton.onclick = function() { - liquipedia.selectall.selectText( this ); - }; - buttonwrapper.appendChild( selectbutton ); - buttonwrapper.appendChild( document.createTextNode( ' ' ) ); - const selectcopybutton = document.createElement( 'button' ); - selectcopybutton.classList.add( 'btn' ); - selectcopybutton.classList.add( 'btn-primary' ); - selectcopybutton.innerHTML = 'Select and copy'; - selectcopybutton.onclick = function() { - liquipedia.selectall.selectText( this ); - document.execCommand( 'copy' ); - }; - buttonwrapper.appendChild( selectcopybutton ); + document.querySelectorAll( 'pre.selectall' ).forEach( ( selectall ) => { + new SelectAllContainer( selectall ).createWrapper(); } ); - }, - removeTextarea: function() { - this.parentNode.removeChild( this ); - }, - selectText: function( button ) { - const wrapper = button.closest( '.selectall-wrapper' ); - const selectall = wrapper.querySelector( '.selectall' ); - const textarea = document.createElement( 'textarea' ); - textarea.readOnly = true; - textarea.classList.add( 'selectall-duplicate' ); - textarea.innerHTML = selectall.innerHTML; - textarea.style.padding = window.getComputedStyle( selectall ).padding; - textarea.style.lineHeight = window.getComputedStyle( selectall ).lineHeight; - textarea.style.fontFamily = window.getComputedStyle( selectall ).fontFamily; - textarea.style.fontSize = window.getComputedStyle( selectall ).fontSize; - textarea.style.height = window.getComputedStyle( selectall ).height; - textarea.onblur = liquipedia.selectall.removeTextarea; - wrapper.querySelector( '.selectall-relative' ).appendChild( textarea ); - textarea.focus(); - textarea.select(); } }; + liquipedia.core.modules.push( 'selectall' ); diff --git a/package.json b/package.json index 654d398f4d8..47c480ed10f 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "sass": "^1.97.3", "stylelint": "^17.4.0", "stylelint-config-wikimedia": "^0.18.0", - "stylelint-scss": "^7.x.x" + "stylelint-scss": "^7.x.x", + "types-mediawiki": "^2.0.0" }, "jest": { "reporters": [ diff --git a/stylesheets/commons/Selectall.scss b/stylesheets/commons/Selectall.scss index ace2d5c265a..5650809b203 100644 --- a/stylesheets/commons/Selectall.scss +++ b/stylesheets/commons/Selectall.scss @@ -1,23 +1,13 @@ /******************************************************************************* Template(s): Select all for pre elements -Author(s): Chapatiyaq *******************************************************************************/ -div.selectall-relative { - position: relative; +.selectall-wrapper { + display: flex; + flex-direction: column; + gap: 0.25rem; } -textarea.selectall-duplicate { - -moz-box-sizing: border-box; - box-sizing: border-box; - background: #ffffff; - border: 1px solid transparent; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - overflow: hidden; - white-space: pre; - -moz-tab-size: 13; - tab-size: 13; +.selectall-buttons { + display: flex; + gap: 0.25rem; }