{"action":"created","comment":{"url":"https://api.github.com/repos/apache/incubator-guacamole-client/pulls/comments/68857396","id":68857396,"diff_hunk":"@@ -0,0 +1,409 @@\n+/*\n+ * Licensed to the Apache Software Foundation (ASF) under one\n+ * or more contributor license agreements.  See the NOTICE file\n+ * distributed with this work for additional information\n+ * regarding copyright ownership.  The ASF licenses this file\n+ * to you under the Apache License, Version 2.0 (the\n+ * \"License\"); you may not use this file except in compliance\n+ * with the License.  You may obtain a copy of the License at\n+ *\n+ *   http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing,\n+ * software distributed under the License is distributed on an\n+ * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n+ * KIND, either express or implied.  See the License for the\n+ * specific language governing permissions and limitations\n+ * under the License.\n+ */\n+\n+/**\n+ * A service for accessing local clipboard data.\n+ */\n+angular.module('clipboard').factory('clipboardService', ['$injector',\n+        function clipboardService($injector) {\n+\n+    // Get required services\n+    var $q      = $injector.get('$q');\n+    var $window = $injector.get('$window');\n+\n+    // Required types\n+    var ClipboardData = $injector.get('ClipboardData');\n+\n+    var service = {};\n+\n+    /**\n+     * Reference to the window.document object.\n+     *\n+     * @private\n+     * @type HTMLDocument\n+     */\n+    var document = $window.document;\n+\n+    /**\n+     * The textarea that will be used to hold the local clipboard contents.\n+     *\n+     * @type Element\n+     */\n+    var clipboardContent = document.createElement('div');\n+\n+    // Ensure clipboard target is selectable but not visible\n+    clipboardContent.setAttribute('contenteditable', 'true');\n+    clipboardContent.className = 'clipboard-service-target';\n+\n+    // Add clipboard target to DOM\n+    document.body.appendChild(clipboardContent);\n+\n+    /**\n+     * Stops the propogation of the given event through the DOM tree. This is\n+     * identical to invoking stopPropogation() on the event directly, except\n+     * that this function is usable as an event handler itself.\n+     *\n+     * @param {Event} e\n+     *     The event whose propogation through the DOM tree should be stopped.\n+     */\n+    var stopEventPropagation = function stopEventPropagation(e) {\n+        e.stopPropagation();\n+    };\n+\n+    // Prevent events generated due to execCommand() from disturbing external things\n+    clipboardContent.addEventListener('copy',  stopEventPropagation);\n+    clipboardContent.addEventListener('paste', stopEventPropagation);\n+\n+    /**\n+     * A stack of past node selection ranges. A range convering the nodes\n+     * currently selected within the document can be pushed onto this stack\n+     * with pushSelection(), and the most recently pushed selection can be\n+     * popped off the stack (and thus re-selected) with popSelection().\n+     *\n+     * @type Range[]\n+     */\n+    var selectionStack = [];\n+\n+    /**\n+     * Pushes the current selection range to the selection stack such that it\n+     * can later be restored with popSelection().\n+     */\n+    var pushSelection = function pushSelection() {\n+\n+        // Add a range representing the current selection to the stack\n+        var selection = $window.getSelection();\n+        if (selection.getRangeAt && selection.rangeCount)\n+            selectionStack.push(selection.getRangeAt(0));\n+\n+    };\n+\n+    /**\n+     * Pops a selection range off the selection stack restoring the document's\n+     * previous selection state. The selection range will be the most recent\n+     * selection range pushed by pushSelection(). If there are no selection\n+     * ranges currently on the stack, this function has no effect.\n+     */\n+    var popSelection = function popSelection() {\n+\n+        // Pull one selection range from the stack\n+        var range = selectionStack.pop();\n+        if (!range)\n+            return;\n+\n+        // Replace any current selection with the retrieved selection\n+        var selection = $window.getSelection();\n+        selection.removeAllRanges();\n+        selection.addRange(range);\n+\n+    };\n+\n+    /**\n+     * Selects all nodes within the given element. This will replace the\n+     * current selection with a new selection range that covers the element's\n+     * contents. If the original selection should be preserved, use\n+     * pushSelection() and popSelection().\n+     *\n+     * @param {Element} element\n+     *     The element whose contents should be selected.\n+     */\n+    var selectAll = function selectAll(element) {\n+\n+        // Generate a range which selects all nodes within the given element\n+        var range = document.createRange();\n+        range.selectNodeContents(element);\n+\n+        // Replace any current selection with the generated range\n+        var selection = $window.getSelection();\n+        selection.removeAllRanges();\n+        selection.addRange(range);\n+\n+    };\n+\n+    /**\n+     * Modifies the contents of the given element such that it contains only\n+     * plain text. All non-text child elements will be stripped and replaced\n+     * with their text equivalents. As this function performs the conversion\n+     * through incremental changes only, cursor position within the given\n+     * element is preserved.\n+     *\n+     * @param {Element} element\n+     *     The elements whose contents should be converted to plain text.\n+     */\n+    var convertToText = function convertToText(element) {\n+\n+        // For each child of the given element\n+        var current = element.firstChild;\n+        while (current) {\n+\n+            // Preserve the next child in the list, in case the current\n+            // node is replaced\n+            var next = current.nextSibling;\n+\n+            // If the child is not already a text node, replace it with its\n+            // own text contents\n+            if (current.nodeType !== Node.TEXT_NODE) {\n+                var textNode = document.createTextNode(current.textContent);\n+                current.parentElement.replaceChild(textNode, current);\n+            }\n+\n+            // Advance to next child\n+            current = next;\n+\n+        }\n+\n+    };\n+\n+    /**\n+     * Sets the local clipboard, if possible, to the given text.\n+     *\n+     * @param {ClipboardData} data\n+     *     The data to assign to the local clipboard should be set.\n+     *\n+     * @return {Promise}\n+     *     A promise that will resolve if setting the clipboard was successful,\n+     *     and will reject if it failed.\n+     */\n+    service.setLocalClipboard = function setLocalClipboard(data) {\n+\n+        var deferred = $q.defer();\n+\n+        // Track the originally-focused element prior to changing focus\n+        var originalElement = document.activeElement;\n+        pushSelection();\n+\n+        // Copy the given value into the clipboard DOM element\n+        if (typeof data.data === 'string')\n+            clipboardContent.textContent = data.data;\n+        else {\n+            clipboardContent.innerHTML = '';\n+            var img = document.createElement('img');\n+            img.src = URL.createObjectURL(data.data);\n+            clipboardContent.appendChild(img);\n+        }\n+\n+        // Select all data within the clipboard target\n+        selectAll(clipboardContent);\n+\n+        // Attempt to copy data from clipboard element into local clipboard\n+        if (document.execCommand('copy'))\n+            deferred.resolve();\n+        else\n+            deferred.reject();\n+\n+        // Unfocus the clipboard DOM event to avoid mobile keyboard opening,\n+        // restoring whichever element was originally focused\n+        clipboardContent.blur();\n+        originalElement.focus();\n+        popSelection();\n+\n+        return deferred.promise;\n+    };\n+\n+    /**\n+     * Parses the given data URL, returning its decoded contents as a new Blob.\n+     * If the URL is not a valid data URL, null will be returned instead.\n+     *\n+     * @param {String} url\n+     *     The data URL to parse.\n+     *\n+     * @returns {Blob}\n+     *     A new Blob containing the decoded contents of the data URL, or null\n+     *     if the URL is not a valid data URL.\n+     */\n+    service.parseDataURL = function parseDataURL(url) {\n+\n+        // Parse given string as a data URL\n+        var result = /^data:([^;]*);base64,([a-zA-Z0-9+/]*[=]*)$/.exec(url);\n+        if (!result)\n+            return null;\n+\n+        // Pull the mimetype and base64 contents of the data URL\n+        var type = result[1];\n+        var data = $window.atob(result[2]);\n+\n+        // Convert the decoded binary string into a typed array\n+        var buffer = new Uint8Array(data.length);\n+        for (var i = 0; i < data.length; i++)\n+            buffer[i] = data.charCodeAt(i);\n+\n+        // Produce a proper blob containing the data and type provided in\n+        // the data URL\n+        return new Blob([buffer], { type : type });\n+\n+    };\n+\n+    /**\n+     * Replaces the current text content of the given element with the given\n+     * text. To avoid affecting the position of the cursor within an editable\n+     * element, or firing unnecessary DOM modification events, the underlying\n+     * <code>textContent</code> property of the element is only touched if\n+     * doing so would actually change the text.\n+     *\n+     * @param {Element} element\n+     *     The element whose text content should be changed.\n+     *\n+     * @param {String} text\n+     *     The text content to assign to the given element.\n+     */\n+    service.setTextContent = function setTextContent(element, text) {\n+\n+        // Strip out any non-text content while preserving cursor position\n+        convertToText(element);\n+\n+        // Reset text content only if doing so will actually change the content\n+        if (element.textContent !== text)\n+            element.textContent = text;\n+\n+    };\n+\n+    /**\n+     * Returns the URL of the single image within the given element, if the\n+     * element truly contains only one child and that child is an image. If the\n+     * content of the element is mixed or not an image, null is returned.\n+     *\n+     * @param {Element} element\n+     *     The element whose image content should be retrieved.\n+     *\n+     * @returns {String}\n+     *     The URL of the image contained within the given element, if that\n+     *     element contains only a single child element which happens to be an\n+     *     image, or null if the content of the element is not purely an image.\n+     */\n+    service.getImageContent = function getImageContent(element) {\n+\n+        // Return the source of the single child element, if it is an image\n+        var firstChild = element.firstChild;\n+        if (firstChild && firstChild.nodeName === 'IMG' && !firstChild.nextSibling)\n+            return firstChild.getAttribute('src');\n+\n+        // Otherwise, the content of this element is not simply an image\n+        return null;\n+\n+    };\n+\n+    /**\n+     * Replaces the current contents of the given element with a single image\n+     * having the given URL. To avoid affecting the position of the cursor\n+     * within an editable element, or firing unnecessary DOM modification\n+     * events, the content of the element is only touched if doing so would\n+     * actually change content.\n+     *\n+     * @param {Element} element\n+     *     The element whose image content should be changed.\n+     *\n+     * @param {String} url\n+     *     The URL of the image which should be assigned as the contents of the\n+     *     given element.\n+     */\n+    service.setImageContent = function setImageContent(element, url) {\n+\n+        // Retrieve the URL of the current image contents, if any\n+        var currentImage = service.getImageContent(element);\n+\n+        // If the current contents are not the given image (or not an image\n+        // at all), reassign the contents\n+        if (currentImage !== url) {\n+\n+            // Clear current contents\n+            element.innerHTML = '';\n+\n+            // Add a new image as the sole contents of the element\n+            var img = document.createElement('img');\n+            img.src = url;\n+            element.appendChild(img);\n+\n+        }\n+\n+    };\n+\n+    /**\n+     * Get the current value of the local clipboard.\n+     *\n+     * @return {Promise.<ClipboardData>}\n+     *     A promise that will resolve with the contents of the local clipboard\n+     *     if getting the clipboard was successful, and will reject if it\n+     *     failed.\n+     */\n+    service.getLocalClipboard = function getLocalClipboard() {\n+\n+        var deferred = $q.defer();\n+\n+        // Wait for the next event queue run before attempting to read\n+        // clipboard data (in case the copy/cut has not yet completed)\n+        $window.setTimeout(function deferredClipboardRead() {\n+\n+            // Track the originally-focused element prior to changing focus\n+            var originalElement = document.activeElement;\n+            pushSelection();\n+\n+            // Clear and select the clipboard DOM element\n+            clipboardContent.innerHTML = '';\n+            clipboardContent.focus();\n+            selectAll(clipboardContent);\n+\n+            // Attempt paste local clipboard into clipboard DOM element\n+            if (document.activeElement === clipboardContent && document.execCommand('paste')) {\n+\n+                // If the pasted data is a single image, resolve with a blob\n+                // containing that image\n+                var currentImage = service.getImageContent(clipboardContent);\n+                if (currentImage) {\n+\n+                    // Convert the image's data URL into a blob\n+                    var blob = service.parseDataURL(currentImage);\n+                    if (blob) {\n+                        deferred.resolve(new ClipboardData({\n+                            type : blob.type,\n+                            data : blob\n+                        }));\n+                    }\n+\n+                    // Reject if conversion fails\n+                    else\n+                        deferred.reject();\n+\n+                } // end if clipboard is an image\n+\n+                // Otherwise, assume the clipboard contains plain text\n+                else\n+                    deferred.resolve(new ClipboardData({\n+                        type : 'text/plain',\n+                        data : clipboardContent.textContent\n+                    }));\n+\n+            }\n+\n+            // Otherwise, reading from the clipboard has failed\n+            else\n+                deferred.reject();\n+\n+            // Unfocus the clipboard DOM event to avoid mobile keyboard opening,\n+            // restoring whichever element was originally focused\n+            clipboardContent.blur();\n+            originalElement.focus();\n+            popSelection();\n+\n+        }, 100);","path":"guacamole/src/main/webapp/app/clipboard/services/clipboardService.js","position":402,"original_position":402,"commit_id":"942fd5dd677f21af409ea40c086cad0a422b396e","original_commit_id":"942fd5dd677f21af409ea40c086cad0a422b396e","user":{"login":"jmuehlner","id":4633119,"avatar_url":"https://avatars.githubusercontent.com/u/4633119?v=3","gravatar_id":"","url":"https://api.github.com/users/jmuehlner","html_url":"https://github.com/jmuehlner","followers_url":"https://api.github.com/users/jmuehlner/followers","following_url":"https://api.github.com/users/jmuehlner/following{/other_user}","gists_url":"https://api.github.com/users/jmuehlner/gists{/gist_id}","starred_url":"https://api.github.com/users/jmuehlner/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jmuehlner/subscriptions","organizations_url":"https://api.github.com/users/jmuehlner/orgs","repos_url":"https://api.github.com/users/jmuehlner/repos","events_url":"https://api.github.com/users/jmuehlner/events{/privacy}","received_events_url":"https://api.github.com/users/jmuehlner/received_events","type":"User","site_admin":false},"body":"Maybe constantify this timeout value 100?","created_at":"2016-06-28T22:41:49Z","updated_at":"2016-06-28T22:41:49Z","html_url":"https://github.com/apache/incubator-guacamole-client/pull/24#discussion_r68857396","pull_request_url":"https://api.github.com/repos/apache/incubator-guacamole-client/pulls/24","_links":{"self":{"href":"https://api.github.com/repos/apache/incubator-guacamole-client/pulls/comments/68857396"},"html":{"href":"https://github.com/apache/incubator-guacamole-client/pull/24#discussion_r68857396"},"pull_request":{"href":"https://api.github.com/repos/apache/incubator-guacamole-client/pulls/24"}}},"pull_request":{"url":"https://api.github.com/repos/apache/incubator-guacamole-client/pulls/24","id":75558924,"html_url":"https://github.com/apache/incubator-guacamole-client/pull/24","diff_url":"https://github.com/apache/incubator-guacamole-client/pull/24.diff","patch_url":"https://github.com/apache/incubator-guacamole-client/pull/24.patch","issue_url":"https://api.github.com/repos/apache/incubator-guacamole-client/issues/24","number":24,"state":"open","locked":false,"title":"GUACAMOLE-55: Add interface-level support for image clipboard contents","user":{"login":"mike-jumper","id":4632905,"avatar_url":"https://avatars.githubusercontent.com/u/4632905?v=3","gravatar_id":"","url":"https://api.github.com/users/mike-jumper","html_url":"https://github.com/mike-jumper","followers_url":"https://api.github.com/users/mike-jumper/followers","following_url":"https://api.github.com/users/mike-jumper/following{/other_user}","gists_url":"https://api.github.com/users/mike-jumper/gists{/gist_id}","starred_url":"https://api.github.com/users/mike-jumper/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mike-jumper/subscriptions","organizations_url":"https://api.github.com/users/mike-jumper/orgs","repos_url":"https://api.github.com/users/mike-jumper/repos","events_url":"https://api.github.com/users/mike-jumper/events{/privacy}","received_events_url":"https://api.github.com/users/mike-jumper/received_events","type":"User","site_admin":false},"body":"This change implements a new directive, `guacClipboard`, which provides support for image clipboard contents in addition to text. All clipboard contents intended for (or coming from) this directive are communicated via the `data` property on the scope as `ClipboardData` objects. These objects are also consumed and produced by the `clipboardService` which has been refactored to support them. All of these objects, services, etc. have been collected together into their own \"clipboard\" module.","created_at":"2016-06-28T22:08:20Z","updated_at":"2016-06-28T22:41:49Z","closed_at":null,"merged_at":null,"merge_commit_sha":"2dfc1b384e200ec6057758deef87cd4a30adb6ba","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/apache/incubator-guacamole-client/pulls/24/commits","review_comments_url":"https://api.github.com/repos/apache/incubator-guacamole-client/pulls/24/comments","review_comment_url":"https://api.github.com/repos/apache/incubator-guacamole-client/pulls/comments{/number}","comments_url":"https://api.github.com/repos/apache/incubator-guacamole-client/issues/24/comments","statuses_url":"https://api.github.com/repos/apache/incubator-guacamole-client/statuses/942fd5dd677f21af409ea40c086cad0a422b396e","head":{"label":"mike-jumper:image-clipboard","ref":"image-clipboard","sha":"942fd5dd677f21af409ea40c086cad0a422b396e","user":{"login":"mike-jumper","id":4632905,"avatar_url":"https://avatars.githubusercontent.com/u/4632905?v=3","gravatar_id":"","url":"https://api.github.com/users/mike-jumper","html_url":"https://github.com/mike-jumper","followers_url":"https://api.github.com/users/mike-jumper/followers","following_url":"https://api.github.com/users/mike-jumper/following{/other_user}","gists_url":"https://api.github.com/users/mike-jumper/gists{/gist_id}","starred_url":"https://api.github.com/users/mike-jumper/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mike-jumper/subscriptions","organizations_url":"https://api.github.com/users/mike-jumper/orgs","repos_url":"https://api.github.com/users/mike-jumper/repos","events_url":"https://api.github.com/users/mike-jumper/events{/privacy}","received_events_url":"https://api.github.com/users/mike-jumper/received_events","type":"User","site_admin":false},"repo":{"id":54506694,"name":"incubator-guacamole-client","full_name":"mike-jumper/incubator-guacamole-client","owner":{"login":"mike-jumper","id":4632905,"avatar_url":"https://avatars.githubusercontent.com/u/4632905?v=3","gravatar_id":"","url":"https://api.github.com/users/mike-jumper","html_url":"https://github.com/mike-jumper","followers_url":"https://api.github.com/users/mike-jumper/followers","following_url":"https://api.github.com/users/mike-jumper/following{/other_user}","gists_url":"https://api.github.com/users/mike-jumper/gists{/gist_id}","starred_url":"https://api.github.com/users/mike-jumper/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mike-jumper/subscriptions","organizations_url":"https://api.github.com/users/mike-jumper/orgs","repos_url":"https://api.github.com/users/mike-jumper/repos","events_url":"https://api.github.com/users/mike-jumper/events{/privacy}","received_events_url":"https://api.github.com/users/mike-jumper/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/mike-jumper/incubator-guacamole-client","description":"Mirror of Apache Guacamole Client (Incubating)","fork":true,"url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client","forks_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/forks","keys_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/teams","hooks_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/hooks","issue_events_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/issues/events{/number}","events_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/events","assignees_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/assignees{/user}","branches_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/branches{/branch}","tags_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/tags","blobs_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/git/refs{/sha}","trees_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/statuses/{sha}","languages_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/languages","stargazers_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/stargazers","contributors_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/contributors","subscribers_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/subscribers","subscription_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/subscription","commits_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/commits{/sha}","git_commits_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/git/commits{/sha}","comments_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/comments{/number}","issue_comment_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/issues/comments{/number}","contents_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/contents/{+path}","compare_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/merges","archive_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/downloads","issues_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/issues{/number}","pulls_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/pulls{/number}","milestones_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/milestones{/number}","notifications_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/labels{/name}","releases_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/releases{/id}","deployments_url":"https://api.github.com/repos/mike-jumper/incubator-guacamole-client/deployments","created_at":"2016-03-22T20:30:39Z","updated_at":"2016-03-22T20:30:42Z","pushed_at":"2016-06-28T22:05:56Z","git_url":"git://github.com/mike-jumper/incubator-guacamole-client.git","ssh_url":"git@github.com:mike-jumper/incubator-guacamole-client.git","clone_url":"https://github.com/mike-jumper/incubator-guacamole-client.git","svn_url":"https://github.com/mike-jumper/incubator-guacamole-client","homepage":null,"size":10581,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"apache:master","ref":"master","sha":"3b94ac8571d8d516cc0c9ed9d78d2d52e326b243","user":{"login":"apache","id":47359,"avatar_url":"https://avatars.githubusercontent.com/u/47359?v=3","gravatar_id":"","url":"https://api.github.com/users/apache","html_url":"https://github.com/apache","followers_url":"https://api.github.com/users/apache/followers","following_url":"https://api.github.com/users/apache/following{/other_user}","gists_url":"https://api.github.com/users/apache/gists{/gist_id}","starred_url":"https://api.github.com/users/apache/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/apache/subscriptions","organizations_url":"https://api.github.com/users/apache/orgs","repos_url":"https://api.github.com/users/apache/repos","events_url":"https://api.github.com/users/apache/events{/privacy}","received_events_url":"https://api.github.com/users/apache/received_events","type":"Organization","site_admin":false},"repo":{"id":54452622,"name":"incubator-guacamole-client","full_name":"apache/incubator-guacamole-client","owner":{"login":"apache","id":47359,"avatar_url":"https://avatars.githubusercontent.com/u/47359?v=3","gravatar_id":"","url":"https://api.github.com/users/apache","html_url":"https://github.com/apache","followers_url":"https://api.github.com/users/apache/followers","following_url":"https://api.github.com/users/apache/following{/other_user}","gists_url":"https://api.github.com/users/apache/gists{/gist_id}","starred_url":"https://api.github.com/users/apache/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/apache/subscriptions","organizations_url":"https://api.github.com/users/apache/orgs","repos_url":"https://api.github.com/users/apache/repos","events_url":"https://api.github.com/users/apache/events{/privacy}","received_events_url":"https://api.github.com/users/apache/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/apache/incubator-guacamole-client","description":"Mirror of Apache Guacamole Client (Incubating)","fork":false,"url":"https://api.github.com/repos/apache/incubator-guacamole-client","forks_url":"https://api.github.com/repos/apache/incubator-guacamole-client/forks","keys_url":"https://api.github.com/repos/apache/incubator-guacamole-client/keys{/key_id}","collaborators_url":"https://api.github.com/repos/apache/incubator-guacamole-client/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/apache/incubator-guacamole-client/teams","hooks_url":"https://api.github.com/repos/apache/incubator-guacamole-client/hooks","issue_events_url":"https://api.github.com/repos/apache/incubator-guacamole-client/issues/events{/number}","events_url":"https://api.github.com/repos/apache/incubator-guacamole-client/events","assignees_url":"https://api.github.com/repos/apache/incubator-guacamole-client/assignees{/user}","branches_url":"https://api.github.com/repos/apache/incubator-guacamole-client/branches{/branch}","tags_url":"https://api.github.com/repos/apache/incubator-guacamole-client/tags","blobs_url":"https://api.github.com/repos/apache/incubator-guacamole-client/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/apache/incubator-guacamole-client/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/apache/incubator-guacamole-client/git/refs{/sha}","trees_url":"https://api.github.com/repos/apache/incubator-guacamole-client/git/trees{/sha}","statuses_url":"https://api.github.com/repos/apache/incubator-guacamole-client/statuses/{sha}","languages_url":"https://api.github.com/repos/apache/incubator-guacamole-client/languages","stargazers_url":"https://api.github.com/repos/apache/incubator-guacamole-client/stargazers","contributors_url":"https://api.github.com/repos/apache/incubator-guacamole-client/contributors","subscribers_url":"https://api.github.com/repos/apache/incubator-guacamole-client/subscribers","subscription_url":"https://api.github.com/repos/apache/incubator-guacamole-client/subscription","commits_url":"https://api.github.com/repos/apache/incubator-guacamole-client/commits{/sha}","git_commits_url":"https://api.github.com/repos/apache/incubator-guacamole-client/git/commits{/sha}","comments_url":"https://api.github.com/repos/apache/incubator-guacamole-client/comments{/number}","issue_comment_url":"https://api.github.com/repos/apache/incubator-guacamole-client/issues/comments{/number}","contents_url":"https://api.github.com/repos/apache/incubator-guacamole-client/contents/{+path}","compare_url":"https://api.github.com/repos/apache/incubator-guacamole-client/compare/{base}...{head}","merges_url":"https://api.github.com/repos/apache/incubator-guacamole-client/merges","archive_url":"https://api.github.com/repos/apache/incubator-guacamole-client/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/apache/incubator-guacamole-client/downloads","issues_url":"https://api.github.com/repos/apache/incubator-guacamole-client/issues{/number}","pulls_url":"https://api.github.com/repos/apache/incubator-guacamole-client/pulls{/number}","milestones_url":"https://api.github.com/repos/apache/incubator-guacamole-client/milestones{/number}","notifications_url":"https://api.github.com/repos/apache/incubator-guacamole-client/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/apache/incubator-guacamole-client/labels{/name}","releases_url":"https://api.github.com/repos/apache/incubator-guacamole-client/releases{/id}","deployments_url":"https://api.github.com/repos/apache/incubator-guacamole-client/deployments","created_at":"2016-03-22T07:00:06Z","updated_at":"2016-06-28T07:29:26Z","pushed_at":"2016-06-28T22:08:20Z","git_url":"git://github.com/apache/incubator-guacamole-client.git","ssh_url":"git@github.com:apache/incubator-guacamole-client.git","clone_url":"https://github.com/apache/incubator-guacamole-client.git","svn_url":"https://github.com/apache/incubator-guacamole-client","homepage":null,"size":10509,"stargazers_count":54,"watchers_count":54,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":23,"mirror_url":"git://git.apache.org/incubator-guacamole-client.git","open_issues_count":3,"forks":23,"open_issues":3,"watchers":54,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/apache/incubator-guacamole-client/pulls/24"},"html":{"href":"https://github.com/apache/incubator-guacamole-client/pull/24"},"issue":{"href":"https://api.github.com/repos/apache/incubator-guacamole-client/issues/24"},"comments":{"href":"https://api.github.com/repos/apache/incubator-guacamole-client/issues/24/comments"},"review_comments":{"href":"https://api.github.com/repos/apache/incubator-guacamole-client/pulls/24/comments"},"review_comment":{"href":"https://api.github.com/repos/apache/incubator-guacamole-client/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/apache/incubator-guacamole-client/pulls/24/commits"},"statuses":{"href":"https://api.github.com/repos/apache/incubator-guacamole-client/statuses/942fd5dd677f21af409ea40c086cad0a422b396e"}}}}
{"actor":{"display_login":"jmuehlner"}}