summaryrefslogtreecommitdiffstats
path: root/debian/missing-sources/clipboard-polyfill-be05dad.ts
diff options
context:
space:
mode:
Diffstat (limited to 'debian/missing-sources/clipboard-polyfill-be05dad.ts')
-rw-r--r--debian/missing-sources/clipboard-polyfill-be05dad.ts270
1 files changed, 270 insertions, 0 deletions
diff --git a/debian/missing-sources/clipboard-polyfill-be05dad.ts b/debian/missing-sources/clipboard-polyfill-be05dad.ts
new file mode 100644
index 0000000..602cadb
--- /dev/null
+++ b/debian/missing-sources/clipboard-polyfill-be05dad.ts
@@ -0,0 +1,270 @@
+import {Promise} from "es6-promise";
+import {DT, suppressDTWarnings} from "./DT";
+
+// Debug log strings should be short, since they are copmiled into the production build.
+// TODO: Compile debug logging code out of production builds?
+var debugLog: (s: string) => void = function(s: string) {};
+var showWarnings = true;
+var warn = (console.warn || console.log).bind(console, "[clipboard-polyfill]");
+
+var TEXT_PLAIN = "text/plain";
+
+export default class ClipboardPolyfill {
+ public static readonly DT = DT;
+
+ public static setDebugLog(f: (s: string) => void): void {
+ debugLog = f;
+ }
+
+ public static suppressWarnings() {
+ showWarnings = false;
+ suppressDTWarnings();
+ }
+
+ public static write(data: DT): Promise<void> {
+ if (showWarnings && !data.getData(TEXT_PLAIN)) {
+ warn("clipboard.write() was called without a "+
+ "`text/plain` data type. On some platforms, this may result in an "+
+ "empty clipboard. Call clipboard.suppressWarnings() "+
+ "to suppress this warning.");
+ }
+
+ return new Promise<void>((resolve, reject) => {
+ // Internet Explorer
+ if (seemToBeInIE()) {
+ if (writeIE(data)) {
+ resolve()
+ } else {
+ reject(new Error("Copying failed, possibly because the user rejected it."));
+ }
+ return;
+ }
+
+ var tracker = execCopy(data);
+ if (tracker.success) {
+ debugLog("regular execCopy worked");
+ resolve();
+ return;
+ }
+
+ // Success detection on Edge is not possible, due to bugs in all 4
+ // detection mechanisms we could try to use. Assume success.
+ if (navigator.userAgent.indexOf("Edge") > -1) {
+ debugLog("UA \"Edge\" => assuming success");
+ resolve();
+ return;
+ }
+
+ // Fallback 1 for desktop Safari.
+ tracker = copyUsingTempSelection(document.body, data);
+ if (tracker.success) {
+ debugLog("copyUsingTempSelection worked");
+ resolve();
+ return;
+ }
+
+ // Fallback 2 for desktop Safari.
+ tracker = copyUsingTempElem(data);
+ if (tracker.success) {
+ debugLog("copyUsingTempElem worked");
+ resolve();
+ return;
+ }
+
+ // Fallback for iOS Safari.
+ var text = data.getData(TEXT_PLAIN);
+ if (text !== undefined && copyTextUsingDOM(text)) {
+ debugLog("copyTextUsingDOM worked");
+ resolve();
+ return;
+ }
+
+ reject(new Error("Copy command failed."));
+ });
+ }
+
+ public static writeText(s: string): Promise<void> {
+ var dt = new DT();
+ dt.setData(TEXT_PLAIN, s);
+ return this.write(dt);
+ }
+
+ public static read(): Promise<DT> {
+ return new Promise((resolve, reject) => {
+ if (seemToBeInIE()) {
+ readIE().then(
+ (s: string) => resolve(DTFromText(s)),
+ reject
+ );
+ return;
+ }
+ // TODO: Attempt to read using async clipboard API.
+ reject("Read is not supported in your browser.")
+ });
+ }
+
+ public static readText(): Promise<string> {
+ if (seemToBeInIE()) {
+ return readIE();
+ }
+ return new Promise((resolve, reject) => {
+ // TODO: Attempt to read using async clipboard API.
+ reject("Read is not supported in your browser.")
+ });
+ }
+}
+
+/******** Implementations ********/
+
+class FallbackTracker {
+ public success: boolean = false;
+}
+
+function copyListener(tracker: FallbackTracker, data: DT, e: ClipboardEvent): void {
+ debugLog("listener called");
+ tracker.success = true;
+ data.forEach((value: string, key: string) => {
+ e.clipboardData.setData(key, value);
+ if (key === TEXT_PLAIN && e.clipboardData.getData(key) != value) {
+ debugLog("setting text/plain failed");
+ tracker.success = false;
+ }
+ });
+ e.preventDefault();
+}
+
+function execCopy(data: DT): FallbackTracker {
+ var tracker = new FallbackTracker();
+ var listener = copyListener.bind(this, tracker, data);
+
+ document.addEventListener("copy", listener);
+ try {
+ // We ignore the return value, since FallbackTracker tells us whether the
+ // listener was called. It seems that checking the return value here gives
+ // us no extra information in any browser.
+ document.execCommand("copy");
+ } finally {
+ document.removeEventListener("copy", listener);
+ }
+ return tracker;
+}
+
+// Create a temporary DOM element to select, so that `execCommand()` is not
+// rejected.
+function copyUsingTempSelection(e: HTMLElement, data: DT): FallbackTracker {
+ selectionSet(e);
+ var tracker = execCopy(data);
+ selectionClear();
+ return tracker;
+}
+
+// Create a temporary DOM element to select, so that `execCommand()` is not
+// rejected.
+function copyUsingTempElem(data: DT): FallbackTracker {
+ var tempElem = document.createElement("div");
+ // Place some text in the elem so that Safari has something to select.
+ tempElem.textContent = "temporary element";
+ document.body.appendChild(tempElem);
+
+ var tracker = copyUsingTempSelection(tempElem, data);
+
+ document.body.removeChild(tempElem);
+ return tracker;
+}
+
+// Uses shadow DOM.
+function copyTextUsingDOM(str: string): boolean {
+ debugLog("copyTextUsingDOM");
+
+ var tempElem = document.createElement("div");
+ // Use shadow DOM if available.
+ var spanParent: Node = tempElem;
+ if (tempElem.attachShadow) {
+ debugLog("Using shadow DOM.");
+ spanParent = tempElem.attachShadow({mode: "open"});
+ }
+
+ var span = document.createElement("span");
+ span.innerText = str;
+ // span.style.whiteSpace = "pre-wrap"; // TODO: Use `innerText` above instead?
+
+ spanParent.appendChild(span);
+ document.body.appendChild(tempElem);
+ selectionSet(span);
+
+ var result = document.execCommand("copy");
+
+ selectionClear();
+ document.body.removeChild(tempElem);
+
+ return result;
+}
+
+/******** Selection ********/
+
+function selectionSet(elem: Element): void {
+ var sel = document.getSelection();
+ var range = document.createRange();
+ range.selectNodeContents(elem);
+ sel.removeAllRanges();
+ sel.addRange(range);
+}
+
+function selectionClear(): void {
+ var sel = document.getSelection();
+ sel.removeAllRanges();
+}
+
+/******** Convenience ********/
+
+function DTFromText(s: string): DT {
+ var dt = new DT();
+ dt.setData(TEXT_PLAIN, s);
+ return dt;
+}
+
+/******** Internet Explorer ********/
+
+interface IEWindow extends Window {
+ clipboardData: {
+ setData: (key: string, value: string) => boolean;
+ // Always results in a string: https://msdn.microsoft.com/en-us/library/ms536436(v=vs.85).aspx
+ getData: (key: string) => string;
+ }
+}
+
+function seemToBeInIE(): boolean {
+ return typeof ClipboardEvent === "undefined" &&
+ typeof (window as IEWindow).clipboardData !== "undefined" &&
+ typeof (window as IEWindow).clipboardData.setData !== "undefined";
+}
+
+function writeIE(data: DT): boolean {
+ // IE supports text or URL, but not HTML: https://msdn.microsoft.com/en-us/library/ms536744(v=vs.85).aspx
+ // TODO: Write URLs to `text/uri-list`? https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types
+ var text = data.getData(TEXT_PLAIN);
+ if (text !== undefined) {
+ return (window as IEWindow).clipboardData.setData("Text", text);
+ }
+
+ throw ("No `text/plain` value was specified.");
+}
+
+// Returns "" if the read failed, e.g. because the user rejected the permission.
+function readIE(): Promise<string> {
+ return new Promise((resolve, reject) => {
+ var text = (window as IEWindow).clipboardData.getData("Text");
+ if (text === "") {
+ reject(new Error("Empty clipboard or could not read plain text from clipboard"));
+ } else {
+ resolve(text);
+ }
+ })
+}
+
+/******** Expose `clipboard` on the global object in browser. ********/
+
+// TODO: Figure out how to expose ClipboardPolyfill as self.clipboard using
+// WebPack?
+declare var module: any;
+module.exports = ClipboardPolyfill;