Skip to content

Instantly share code, notes, and snippets.

@spiralx
Created May 20, 2016 11:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save spiralx/1eeeaa3b4da220f8e6a99147c2af50a8 to your computer and use it in GitHub Desktop.
Save spiralx/1eeeaa3b4da220f8e6a99147c2af50a8 to your computer and use it in GitHub Desktop.
An ES6 class for auto-copying text content to the clipboard when particular elements are clicked
/* jshint asi: true, esnext: true */
; (function() {
'use strict'
/*
Iterator for a selection's range objects e.g.
const r = [...ranges()]
*/
function* ranges(sel=window.getSelection()) {
for (let i = 0; i < sel.rangeCount; i++) {
yield sel.getRangeAt(i)
}
}
// ----------------------------------------------------------------------------
/*
Replace current selection with a single node.
*/
function selectNode(sel, node) {
const r = document.createRange()
r.selectNode(node)
sel.removeAllRanges()
sel.addRange(r)
return sel
}
// ----------------------------------------------------------------------------
/*
Wrap a function so that any changes it makes to the current
selection are reversed after it finishes e.g.
restoreSelection(sel => {
selectNode(sel, document.body)
document.execCommand('copy')
})
would copy the contents of the document to the clipboard and
then restore the original selection.
*/
function restoreSelection(fn) {
const selection = window.getSelection()
const savedRanges = [...ranges(selection)]
try {
return fn(selection)
}
finally {
selection.removeAllRanges()
for (const r of savedRanges) {
selection.addRange(r)
}
}
}
// ----------------------------------------------------------------------------
class CopyOnClick {
constructor(...selectors) {
this.selectors = new Set()
this._listener = null
this._css = document.createElement('style')
this._css.type = 'text/css'
this.attach(...selectors)
}
get listening() {
return !!this._listener
}
attach(...selectors) {
const num_selectors = this.selectors.size
for (const sel of selectors) {
this.selectors.add(sel)
}
this._css.innerHTML = this.styles()
if (num_selectors === 0 && this.selectors.size > 0) {
this.on()
}
}
detach(...selectors) {
if (this.selectors.size > 0) {
for (const sel of selectors) {
this.selectors.remove(sel)
}
this._css.innerHTML = this.styles()
if (this.selectors.size === 0) {
this.off()
}
}
}
on() {
if (!this._listener && this.selectors.size > 0) {
this._listener = this.listener.bind(this)
document.body.addEventListener('click', this._listener)
document.body.appendChild(this._css)
console.info(`Listening for click events matching: ${this.selector()}`)
}
}
off() {
if (this._listener) {
document.body.removeChild(this._css)
document.body.removeEventListener('click', this._listener)
this._listener = null
console.info(`Stopped listening for click events`)
}
}
selector(suffix='') {
return [...this.selectors].map(s => s + suffix).sort().join(', ')
}
styles() {
return `${this.selector()} { cursor: pointer } ${this.selector(':hover')} { outline: solid 2px rgba(255, 255, 128, 0.4) }`
}
listener(event) {
const target = event.target
if (!target.matches(this.selector())) return
restoreSelection(sel => {
selectNode(sel, target)
try {
// Now that we've selected the anchor text, execute the copy command
const successful = document.execCommand('copy')
console.log(`Copy command was ${successful ? 'successful' : 'unsuccessful'}`)
return successful
} catch(err) {
console.log('Unable to copy: ${err}')
return false
}
})
}
toString() {
return `CopyOnClick(${this.listening ? 'ON' : 'OFF'}, targets: ${this.selector()})`
}
}
// ----------------------------------------------------------------------------
// Example for e.g. GitHub pages.
// window.copier = new CopyOnClick('code')
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment