ui: Use native clipboard functionality, but keep fallback

This commit is contained in:
John Cowen 2021-07-20 14:02:00 +01:00
parent 499250cbf1
commit 22650621f2
8 changed files with 103 additions and 11 deletions

View File

@ -0,0 +1,16 @@
import NavigatorClipboard from 'consul-ui/services/clipboard/native';
import ExecClipboard from 'consul-ui/services/clipboard/polyfill';
export function initialize(application) {
// which clipboard impl. depends on whether we have native support
if (!application.hasRegistration('service:clipboard')) {
application.register(
`service:clipboard`,
window.navigator.clipboard ? NavigatorClipboard : ExecClipboard
);
}
}
export default {
initialize,
};

View File

@ -6,7 +6,7 @@ const typeAssertion = (type, value, withDefault) => {
return typeof value === type ? value : withDefault;
};
export default class WithCopyableModifier extends Modifier {
@service('clipboard/os') clipboard;
@service('clipboard') clipboard;
hash = null;
source = null;
@ -19,7 +19,9 @@ export default class WithCopyableModifier extends Modifier {
return typeAssertion('function', _hash.success, () => {})(e);
},
error: e => {
runInDebug(_ => console.info(`with-copyable: Error copying \`${value}\``));
runInDebug(_ =>
console.info(`with-copyable: Error copying \`${value}\` - ${e.toString()}`)
);
return typeAssertion('function', _hash.error, () => {})(e);
},
};

View File

@ -0,0 +1,65 @@
import Service from '@ember/service';
const map = new WeakMap();
// we should only have one listener per event per element as we have a thin
// modifier layer over this.
// Event arrays are guaranteed to exist as the arrays are created at the same
// time as the functions themselves, if there are no arrays there are also no
// functions to be called.
const EVENTS = ['success', 'error'];
const addEventListener = function(eventName, cb) {
if (EVENTS.includes(eventName)) {
map.get(this)[eventName].push(cb);
}
return this;
};
const removeEventListener = function(eventName, cb) {
if (EVENTS.includes(eventName)) {
let listeners = map.get(this)[eventName];
const pos = listeners.findIndex(item => item === cb);
if (pos !== -1) {
listeners.splice(pos, 1);
}
}
return this;
};
//
export default class OsService extends Service {
constructor(owner, clipboard = window.navigator.clipboard) {
super(...arguments);
this.clipboard = clipboard;
}
execute($el, options) {
// make a pseudo-target that follows the ClipboardJS emitter until we want
// to reverse the interface
const target = {
on: addEventListener,
off: removeEventListener,
};
// we only want to support clicking/pressing enter
const click = e => {
const text = options.text();
// ClipboardJS events also have action and trigger props
// but we don't use them
this.clipboard.writeText(text).then(
() => map.get(target).success.forEach(cb => cb({ text: text })),
e => map.get(target).error.forEach(cb => cb(e))
);
};
// add all the events as empty arrays
map.set(
target,
EVENTS.reduce((prev, item) => {
prev[item] = [];
return prev;
}, {})
);
// listen plus remove using ClipboardJS interface
$el.addEventListener('click', click);
target.destroy = function() {
$el.removeEventListener('click', click);
map.delete(target);
};
return target;
}
}

View File

@ -1,9 +0,0 @@
import Service from '@ember/service';
import Clipboard from 'clipboard';
export default class OsService extends Service {
execute() {
return new Clipboard(...arguments);
}
}

View File

@ -0,0 +1,9 @@
/* global ClipboardJS*/
import Service from '@ember/service';
export default class PolyfillService extends Service {
execute() {
// Access the ClipboardJS lib see vendor/init.js for polyfill loading
return new ClipboardJS(...arguments);
}
}

View File

@ -136,6 +136,11 @@ module.exports = function(defaults, $ = process.env) {
// CSS.escape polyfill
app.import('node_modules/css.escape/css.escape.js', { outputFile: 'assets/css.escape.js' });
// Clipboard ponyfill
app.import('node_modules/clipboard/dist/clipboard.js', {
outputFile: 'assets/clipboard/clipboard.js',
});
// JSON linting support. Possibly dynamically loaded via CodeMirror linting. See components/code-editor.js
app.import('node_modules/jsonlint/lib/jsonlint.js', {
outputFile: 'assets/codemirror/mode/javascript/javascript.js',

View File

@ -36,6 +36,7 @@ ${environment === 'production' ? `{{jsonEncode .}}` : JSON.stringify(config.oper
"text-encoding/encoding-indexes.js": "${rootURL}assets/encoding-indexes.js",
"text-encoding/encoding.js": "${rootURL}assets/encoding.js",
"css.escape/css.escape.js": "${rootURL}assets/css.escape.js",
"clipboard/clipboard.js": "${rootURL}assets/clipboard/clipboard.js",
"codemirror/mode/javascript/javascript.js": "${rootURL}assets/codemirror/mode/javascript/javascript.js",
"codemirror/mode/ruby/ruby.js": "${rootURL}assets/codemirror/mode/ruby/ruby.js",
"codemirror/mode/yaml/yaml.js": "${rootURL}assets/codemirror/mode/yaml/yaml.js"

View File

@ -16,6 +16,9 @@
if (!(window.CSS && window.CSS.escape)) {
appendScript(fs.get(`${['css.escape', 'css.escape'].join('/')}.js`));
}
if (!window.navigator.clipboard) {
appendScript(fs.get(`${['clipboard', 'clipboard'].join('/')}.js`));
}
try {
const $appMeta = doc.querySelector(`[name="${appName}/config/environment"]`);