72bf4547d4
* Clean up workbench and integrate initialization data * Uncomment Electron fill * Run server & client together * Clean up Electron fill & patch * Bind fs methods This makes them usable with the promise form: `promisify(access)(...)`. * Add space between tag and title to browser logger * Add typescript dep to server and default __dirname for path * Serve web files from server * Adjust some dev options * Rework workbench a bit to use a class and catch unexpected errors * No mkdirs for now, fix util fill, use bash with exec * More fills, make general client abstract * More fills * Fix cp.exec * Fix require calls in fs fill being aliased * Create data and storage dir * Implement fs.watch Using exec for now. * Implement storage database fill * Fix os export and homedir * Add comment to use navigator.sendBeacon * Fix fs callbacks (some args are optional) * Make sure data directory exists when passing it back * Update patch * Target es5 * More fills * Add APIs required for bootstrap-fork to function (#15) * Add bootstrap-fork execution * Add createConnection * Bundle bootstrap-fork into cli * Remove .node directory created from spdlog * Fix npm start * Remove unnecessary comment * Add webpack-hot-middleware if CLI env is not set * Add restarting to shared process * Fix starting with yarn
133 lines
3.4 KiB
TypeScript
133 lines
3.4 KiB
TypeScript
import { IDisposable } from "@coder/disposable";
|
|
import { Emitter } from "@coder/events";
|
|
|
|
/**
|
|
* Native clipboard.
|
|
*/
|
|
export class Clipboard {
|
|
|
|
private readonly enableEmitter: Emitter<boolean> = new Emitter();
|
|
private _isEnabled: boolean = false;
|
|
|
|
/**
|
|
* Ask for permission to use the clipboard.
|
|
*/
|
|
public initialize(): void {
|
|
// tslint:disable no-any
|
|
const navigatorClip = (navigator as any).clipboard;
|
|
const navigatorPerms = (navigator as any).permissions;
|
|
// tslint:enable no-any
|
|
if (navigatorClip && navigatorPerms) {
|
|
navigatorPerms.query({
|
|
name: "clipboard-read",
|
|
}).then((permissionStatus: {
|
|
onchange: () => void,
|
|
state: "denied" | "granted" | "prompt",
|
|
}) => {
|
|
const updateStatus = (): void => {
|
|
this._isEnabled = permissionStatus.state !== "denied";
|
|
this.enableEmitter.emit(this.isEnabled);
|
|
};
|
|
updateStatus();
|
|
permissionStatus.onchange = (): void => {
|
|
updateStatus();
|
|
};
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return true if the native clipboard is supported.
|
|
*/
|
|
public get isSupported(): boolean {
|
|
// tslint:disable no-any
|
|
return typeof navigator !== "undefined"
|
|
&& typeof (navigator as any).clipboard !== "undefined"
|
|
&& typeof (navigator as any).clipboard.readText !== "undefined";
|
|
// tslint:enable no-any
|
|
}
|
|
|
|
/**
|
|
* Register a function to be called when the native clipboard is
|
|
* enabled/disabled.
|
|
*/
|
|
public onPermissionChange(cb: (enabled: boolean) => void): IDisposable {
|
|
return this.enableEmitter.event(cb);
|
|
}
|
|
|
|
/**
|
|
* Read text from the clipboard.
|
|
*/
|
|
public readText(): Promise<string> {
|
|
return this.instance ? this.instance.readText() : Promise.resolve("");
|
|
}
|
|
|
|
/**
|
|
* Write text to the clipboard.
|
|
*/
|
|
public writeText(value: string): Promise<void> {
|
|
return this.instance
|
|
? this.instance.writeText(value)
|
|
: this.writeTextFallback(value);
|
|
}
|
|
|
|
/**
|
|
* Return true if the clipboard is currently enabled.
|
|
*/
|
|
public get isEnabled(): boolean {
|
|
return !!this._isEnabled;
|
|
}
|
|
|
|
/**
|
|
* Return clipboard instance if there is one.
|
|
*/
|
|
private get instance(): ({
|
|
readText(): Promise<string>;
|
|
writeText(value: string): Promise<void>;
|
|
}) | undefined {
|
|
// tslint:disable-next-line no-any
|
|
return this.isSupported ? (navigator as any).clipboard : undefined;
|
|
}
|
|
|
|
/**
|
|
* Fallback for writing text to the clipboard.
|
|
* Taken from https://hackernoon.com/copying-text-to-clipboard-with-javascript-df4d4988697f
|
|
*/
|
|
private writeTextFallback(value: string): Promise<void> {
|
|
// Note the current focus and selection.
|
|
const active = document.activeElement as HTMLElement;
|
|
const selection = document.getSelection();
|
|
const selected = selection && selection.rangeCount > 0
|
|
? selection.getRangeAt(0)
|
|
: false;
|
|
|
|
// Insert a hidden textarea to put the text to copy in.
|
|
const el = document.createElement("textarea");
|
|
el.value = value;
|
|
el.setAttribute("readonly", "");
|
|
el.style.position = "absolute";
|
|
el.style.left = "-9999px";
|
|
document.body.appendChild(el);
|
|
|
|
// Select the textarea and execute a copy (this will only work as part of a
|
|
// user interaction).
|
|
el.select();
|
|
document.execCommand("copy");
|
|
|
|
// Remove the textarea and put focus and selection back to where it was
|
|
// previously.
|
|
document.body.removeChild(el);
|
|
active.focus();
|
|
if (selected && selection) {
|
|
selection.removeAllRanges();
|
|
selection.addRange(selected);
|
|
}
|
|
|
|
return Promise.resolve();
|
|
}
|
|
|
|
}
|
|
|
|
// Global clipboard instance since it's used in the Electron fill.
|
|
export const clipboard = new Clipboard();
|