{"action":"created","comment":{"url":"https://api.github.com/repos/indico/indico/pulls/comments/51582343","id":51582343,"diff_hunk":"@@ -0,0 +1,534 @@\n+{% extends 'forms/base_widget.html' %}\n+\n+{% block html %}\n+    <div class=\"i-location-field\">\n+        <input type=\"hidden\" id=\"{{ field.id }}\" name=\"{{ field.name }}\"\n+               value=\"{{ field._value() | tojson | forceescape }}\">\n+        <div class=\"wrapper\">\n+            <div class=\"typeahead-container venue-container\">\n+                <div class=\"typeahead-field\">\n+                    <input id=\"location-venue-{{ field.id }}\"\n+                           class=\"i-location-input-field\"\n+                           name=\"location-venue-{{ field.name }}\"\n+                           type=\"text\"\n+                           placeholder=\"{% trans %}Venue{% endtrans %}\"\n+                           autocomplete=\"off\">\n+                    <span class=\"highlighter\"></span>\n+                    <span class=\"toggle-results-list icon-arrow-down\"></span>\n+                    <div class=\"typeahead-results\">\n+                        <div class=\"results-list-container\" id=\"location-venue-{{ field.id }}-results\"></div>\n+                    </div>\n+                </div>\n+            </div>\n+            <div class=\"typeahead-container room-container\">\n+                <div class=\"typeahead-field\">\n+                    <input id=\"location-room-{{ field.id }}\"\n+                           class=\"i-location-input-field\"\n+                           name=\"location-room-{{ field.name }}\"\n+                           type=\"text\"\n+                           placeholder=\"{% trans %}Room{% endtrans %}\"\n+                           autocomplete=\"off\">\n+                    <span class=\"highlighter\"></span>\n+                    <span class=\"toggle-results-list icon-arrow-down\"></span>\n+                    <div class=\"typeahead-results\">\n+                        <div class=\"results-list-container\" id=\"location-room-{{ field.id }}-results\"></div>\n+                    </div>\n+                </div>\n+            </div>\n+            <textarea id=\"location-address-{{ field.id }}\" name=\"location-address\"\n+                      placeholder=\"{% trans %}Address{% endtrans %}\"></textarea>\n+            <div class=\"location-inheritance-field\">\n+                <div class=\"checkbox-container\">\n+                    <input id=\"location-inheritance-{{ field.id }}\"\n+                           name=\"location-inheritance-{{ field.name }}\"\n+                           type=\"checkbox\">\n+                    <label for=\"location-inheritance-{{ field.id }}\">{% trans %}Use default{% endtrans %}</label>\n+                </div>\n+            </div>\n+        </div>\n+    </div>\n+    <div class=\"clearfix\"></div>\n+{% endblock %}\n+\n+{% block javascript %}\n+    <script>\n+        (function() {\n+            'use strict';\n+\n+            var field = $('#{{ field.id }}');\n+            var description = field.closest('.form-field').find('.form-field-description');\n+            var venueInput = $('#location-venue-{{ field.id }}');\n+            var roomInput = $('#location-room-{{ field.id }}');\n+            var address = $('#location-address-{{ field.id }}');\n+            var usingDefault = $('#location-inheritance-{{ field.id }}');\n+            var venues = {{ venues | tojson }};\n+            var venueNames = $.map(venues.data, function(venue) {return venue.name;});\n+            var rooms = {{ rooms | tojson }};\n+            var hiddenData = field.val() ? JSON.parse(field.val()) : {};\n+            var latestUsedField = '';\n+            var latestSelectedOption = {field: '', value: ''};\n+            var latestGroup = '';\n+            var currentScrollPos = 0;\n+            var delta = 0;\n+            var validRoom = false;\n+            var validVenue = false;\n+            var preventGuessing = false;\n+            var roomJustCleared = false;\n+            var venueJustChanged = false;\n+            var justUseOptionSelected = false;\n+            var resultsWereOpen = false;\n+\n+            function initializeInputs() {\n+                address.val(hiddenData.address);\n+                roomInput.val(hiddenData.room_name);\n+                venueInput.val(hiddenData.venue_name);\n+                if (hiddenData.venue_id) {\n+                    highlightOption(venueInput, venueInput.val());\n+                }\n+                if (hiddenData.room_id) {\n+                    highlightOption(roomInput, roomInput.val());\n+                }\n+                usingDefault.prop('checked', hiddenData.inheriting);\n+            }\n+\n+            function toggleDefaultLocation(isChecked, isInitStep) {\n+                var message = ($T.gettext('Using default location from: ') + {{ parent[1] | tojson }} +\n+                               ' (' + {{ parent[0] | tojson }} + ')');\n+                var text = isChecked ? message : $T.gettext('Using custom location');\n+                description.html(text);\n+                venueInput.prop('disabled', isChecked).toggleClass('disabled', isChecked);\n+                roomInput.prop('disabled', isChecked).toggleClass('disabled', isChecked);\n+                address.prop('disabled', isChecked).toggleClass('disabled', isChecked);\n+                field.closest('.i-location-field').toggleClass('disabled', isChecked);\n+\n+                hiddenData.inheriting = isChecked;\n+                field.val(JSON.stringify(hiddenData)).trigger('change');\n+\n+                if (!isInitStep) {\n+                    if (isChecked) {\n+                        hiddenData = {{ field.data.source.location_data_for_ui | tojson }}\n+                        hiddenData.inheriting = true;\n+                        field.val(JSON.stringify(hiddenData)).trigger('change');\n+                        initializeInputs();\n+                    } else {\n+                        clearVenue();\n+                    }\n+                }\n+            };\n+\n+            function reorderCategories(venueName) {\n+                var groupOrder = [venueName];\n+                for (var i = venueNames.length - 1; i >= 0; i--) {\n+                    if (!~groupOrder.indexOf(venueNames[i])) {\n+                        groupOrder.push(venueNames[i]);\n+                    }\n+                }\n+                Typeahead[roomInput.selector].options.groupOrder = groupOrder;\n+            }\n+\n+            function updateVenueOnRoomChange(item) {\n+                if (item.venue_id != hiddenData.venue_id) {\n+                    venueJustChanged = true;\n+                    var data = {item_name: item.group, venue_id: item.venue_id, room_id: item.id};\n+                    venueInput.val(item.group).trigger('typeahead:click-on-option', data);\n+                }\n+            }\n+\n+            function clearRoom() {\n+                delete hiddenData.room_id;\n+                delete hiddenData.room_name;\n+                roomInput.val('');\n+                roomInput.next('.highlighter').removeClass('visible');\n+                roomJustCleared = true;\n+                postSelectionActions(roomInput, '');\n+            }\n+\n+            function clearVenue() {\n+                delete hiddenData.venue_id;\n+                delete hiddenData.venue_name;\n+                venueInput.val('');\n+                venueInput.next('.highlighter').removeClass('visible');\n+                venueJustChanged = true;\n+                postSelectionActions(venueInput, '');\n+                clearRoom();\n+            }\n+\n+            function clearField(field) {\n+                if (field.is(venueInput)) {\n+                    clearVenue();\n+                } else if (field.is(roomInput)) {\n+                    clearRoom();\n+                }\n+            }\n+\n+            function updateResultsList(node, resultHtmlList) {\n+                var resultsWrapper = node.siblings('.typeahead-results');\n+                resultsWrapper.css('top', node.outerHeight() + 'px');\n+                var justUse = node.parent().find('.just-use-option');\n+                if (node.val().length > 0) {\n+                    if (justUse.length === 0) {\n+                        justUse = $('<div>').attr({\n+                            'class': 'just-use-option'\n+                        })\n+                        .on('click', function() {\n+                            node.trigger('typeahead:click-on-custom-option', {item_name: node.val()});\n+                        });\n+                        resultsWrapper.append(justUse);\n+                    }\n+                    justUse.text('Just use \"' + node.val() + '\"');\n+                } else {\n+                    justUse.remove();\n+                }\n+                var firstOption = resultHtmlList.find('li.typeahead-item').first();\n+                if (firstOption.length === 0) {\n+                    node.parent().find('.just-use-option').addClass('active');\n+                    justUseOptionSelected = true;\n+                } else {\n+                    resultHtmlList.find('li.typeahead-item').first().addClass('active');\n+                    justUseOptionSelected = false;\n+                }\n+                return resultHtmlList;\n+            }\n+\n+            function scrollResultsList(inputField) {\n+                var parent = inputField.parent();\n+                var activeOption = parent.find('.typeahead-results').find('.typeahead-item.active');\n+                var dropdownList = parent.find('.typeahead-list');\n+                var resultsListHeight = parent.find('.typeahead-results').outerHeight();\n+                if (activeOption.length != 0) {\n+                    if (activeOption.position().top + activeOption.outerHeight() > dropdownList.outerHeight()) {\n+                        delta = (activeOption.position().top + activeOption.outerHeight()\n+                                 - dropdownList.outerHeight());\n+                        currentScrollPos += delta;\n+                        dropdownList.animate({\n+                            scrollTop: currentScrollPos\n+                        }, 50);\n+                    } else if (activeOption.position().top < dropdownList.position().top){\n+                        currentScrollPos += activeOption.position().top;\n+                        dropdownList.animate({\n+                            scrollTop: currentScrollPos\n+                        }, 50);\n+                    }\n+                }\n+                parent.find('.just-use-option').toggleClass('active', activeOption.length === 0);\n+                justUseOptionSelected = activeOption.length === 0;\n+            }\n+\n+            function postSelectionActions(inputField, value) {\n+                field.val(JSON.stringify(hiddenData)).trigger('change');\n+                if (inputField.is(venueInput)) {\n+                    latestVenueValue = value;\n+                } else if (inputField.is(roomInput)) {\n+                    latestRoomValue = value;\n+                }\n+            }\n+\n+            function highlightOption(inputField, value) {\n+                var highlighter = inputField.next('.highlighter');\n+                highlighter.html(value).attr('title', value).addClass('visible');\n+            }\n+\n+            function cleanQuery(query) {\n+                return query ? query.trim() : '';\n+            }\n+\n+            function findRoomInVenue(query, venue) {\n+                if (rooms[venue]) {\n+                    return $.grep(rooms[venue].data, function(room) {\n+                        return room.name.toUpperCase() == query.toUpperCase();\n+                    });\n+                }\n+                return [];\n+            }\n+\n+            function toggleOpenResultsList (inputField) {\n+                if (inputField.is(venueInput) && roomInput.closest('.typeahead-container').hasClass('result')) {\n+                    roomInput.trigger('typeahead:close-results-list');\n+                    venueInput.trigger('typeahead:close-results-list');\n+                    venueInput.trigger('typeahead:open-results-list');\n+                } else if (inputField.is(roomInput) && venueInput.closest('.typeahead-container').hasClass('result')) {\n+                    venueInput.trigger('typeahead:close-results-list');\n+                    roomInput.trigger('typeahead:close-results-list');\n+                    roomInput.trigger('typeahead:open-results-list');\n+                }\n+            }\n+\n+            function resetSelectedOption () {\n+                if (latestUsedField.is(venueInput)) {\n+                    venueInput.val(latestVenueValue);\n+                    if (hiddenData.venue_id) {\n+                        highlightOption(venueInput, latestVenueValue)\n+                    }\n+                } else if (latestUsedField.is(roomInput)) {\n+                    roomInput.val(latestRoomValue);\n+                    if (hiddenData.room_id) {\n+                        highlightOption(roomInput, latestRoomValue)\n+                    }\n+                }\n+            }\n+\n+            initializeInputs();\n+\n+            var latestVenueValue = venueInput.val();\n+            var latestRoomValue = roomInput.val();\n+\n+            usingDefault.on('click', function() {\n+                toggleDefaultLocation($(this).prop('checked'));\n+            });\n+\n+            toggleDefaultLocation(usingDefault.prop('checked'), true);\n+\n+            venueInput.typeahead({\n+                source: venues,\n+                minLength: 0,\n+                searchOnFocus: true,\n+                resultContainer: '#' + venueInput.siblings('.typeahead-results').find('.results-list-container').attr('id'),\n+                template: {% raw %}'<span id=\"location-venue-{{id}}\">{{name}}</span>'{% endraw %},\n+                emptyTemplate: function(query) { return \"\"; },\n+                display: 'name',\n+                callback: {\n+                    onInit: function(node) {\n+                        this.query = node.val();\n+                    },\n+                    onClickBefore: function(node, a, item, event) {\n+                        validVenue = true;\n+                    },\n+                    onClickAfter: function(node, a, item, event) {\n+                        venueInput.trigger('typeahead:click-on-option', {\n+                            item_name: item.name,\n+                            venue_id: item.id\n+                        });\n+                    },\n+                    onLayoutBuiltBefore: function(node, query, result, resultHtmlList) {\n+                        return updateResultsList(node, resultHtmlList);\n+                    },\n+                    onSearch: function(node, query) {\n+                        this.query = cleanQuery(query);\n+                    },\n+                    onShowLayout: function(node, query) {\n+                        venueInput.next('.highlighter').removeClass('visible');\n+                        currentScrollPos = 0;\n+                    },\n+                    onHideLayout: function(node, query) {\n+                        if (justUseOptionSelected) {\n+                            venueInput.trigger('typeahead:click-on-custom-option', {item_name: query});\n+                        } else if (!validVenue && node.val() && !preventGuessing) {\n+                            var result = [];\n+                            result = $.grep(venues.data, function(venue) {\n+                                return venue.name.toUpperCase() == query.toUpperCase();\n+                            });\n+                            if (result.length > 0) {\n+                                $('#location-venue-' + result[0].id).parent().trigger('click');\n+                            } else {\n+                                venueInput.trigger('typeahead:click-on-custom-option', {item_name: query});\n+                            }\n+                        }\n+                        preventGuessing = false\n+                        validVenue = false;\n+                    },\n+                    onNavigateBefore: function(node, query, event) {\n+                        if (event.keyCode == 9) {\n+                            preventGuessing = true\n+                        }\n+                        resultsWereOpen = node.closest('.typeahead-container').hasClass('result');\n+                    },\n+                    onResult: function(node, query, result, resultCount, resultCountPerGroup) {\n+                        node.parent().find('.just-use-option').toggleClass('active', resultCount === 0);\n+                        justUseOptionSelected = resultCount === 0;\n+                    }\n+\n+                }\n+            });\n+\n+            roomInput.typeahead({\n+                source: rooms,\n+                minLength: 0,\n+                searchOnFocus: true,\n+                group: true,\n+                maxItem: 0,\n+                resultContainer: '#' + roomInput.siblings('.typeahead-results').find('.results-list-container').attr('id'),\n+                template: {% raw %}'<span id=\"location-room-{{id}}\">{{name}}</span>'{% endraw %},\n+                emptyTemplate: function(query) { return \"\"; },\n+                display: 'name',\n+                callback: {\n+                    onInit: function(node) {\n+                        this.query = node.val();\n+                    },\n+                    onClickBefore: function(node, a, item, event) {\n+                        validRoom = true;\n+                    },\n+                    onClickAfter: function(node, a, item, event) {\n+                        updateVenueOnRoomChange(item);\n+\n+                        hiddenData.room_id = item.id;\n+                        delete hiddenData.room_name;\n+\n+                        postSelectionActions(roomInput, item.name);\n+                        latestSelectedOption = {field: node.attr('id'), value: item.name}\n+                        highlightOption(roomInput, item.name);\n+                    },\n+                    onLayoutBuiltBefore: function(node, query, result, resultHtmlList) {\n+                        return updateResultsList(node, resultHtmlList);\n+                    },\n+                    onSearch: function(node, query) {\n+                        this.query = cleanQuery(query);\n+                    },\n+                    onShowLayout: function(node, query) {\n+                        roomInput.next('.highlighter').removeClass('visible');\n+                        currentScrollPos = 0;\n+                    },\n+                    onHideLayout: function(node, query) {\n+                        if (justUseOptionSelected) {\n+                            roomInput.trigger('typeahead:click-on-custom-option', {item_name: query});\n+                        } else if (!validRoom && node.val() && !preventGuessing) {\n+                            var result = [];\n+                            var venueValue = venueInput.val();\n+                            if (venueValue) {\n+                                result = findRoomInVenue(query, venueValue);\n+                            } else {\n+                                var count = 0;\n+                                while (count < venueNames.length && result.length == 0) {\n+                                    result = findRoomInVenue(query, venueNames[count]);\n+                                    count++;\n+                                }\n+                            }\n+                            if (result.length > 0) {\n+                                $('#location-room-' + result[0].id).parent().trigger('click');\n+                            } else {\n+                                roomInput.trigger('typeahead:click-on-custom-option', {item_name: query});\n+                            }\n+                        }\n+                        preventGuessing = false\n+                        validRoom = false;\n+                    },\n+                    onNavigateBefore: function(node, query, event) {\n+                        if (event.keyCode == 9) {\n+                            preventGuessing = true\n+                        }\n+                        resultsWereOpen = node.closest('.typeahead-container').hasClass('result');\n+                    },\n+                    onResult: function(node, query, result, resultCount, resultCountPerGroup) {\n+                        node.parent().find('.just-use-option').toggleClass('active', resultCount === 0);\n+                        justUseOptionSelected = resultCount === 0;\n+                    }\n+                }\n+            });\n+\n+            venueInput.on('typeahead:click-on-option', function(e, data) {\n+                reorderCategories(data.item_name);\n+                if (hiddenData.venue_id && data.venue_id != hiddenData.venue_id && !data.room_id) {\n+                    clearRoom();\n+                }\n+                hiddenData.venue_name = data.item_name;\n+                hiddenData.venue_id = data.venue_id;\n+                postSelectionActions(venueInput, data.item_name);\n+                highlightOption(venueInput, data.item_name);\n+                latestSelectedOption = {field: venueInput.attr('id'), value: data.item_name};\n+            })\n+            .on('typeahead:click-on-custom-option', function(e, data) {\n+                if (hiddenData.room_id) {\n+                    clearRoom();\n+                }\n+                delete hiddenData.venue_id;\n+                hiddenData.venue_name = data.item_name;\n+                postSelectionActions(venueInput, data.item_name);\n+                venueInput.val(data.item_name);\n+                validVenue = true;\n+                justUseOptionSelected = false;\n+                venueInput.trigger('typeahead:close-results-list');\n+            });\n+\n+            roomInput.on('typeahead:click-on-custom-option', function(e, data) {\n+                delete hiddenData.room_id;\n+                hiddenData.room_name = data.item_name;\n+                postSelectionActions(roomInput, data.item_name);\n+                roomInput.val(data.item_name);\n+                validRoom = true;\n+                justUseOptionSelected = false;\n+                roomInput.trigger('typeahead:close-results-list');\n+            });\n+\n+\n+            $('.i-location-input-field').on('typeahead:close-results-list', function() {\n+                Typeahead[\"#\" + this.id].hideLayout();\n+            }).on('typeahead:open-results-list', function() {\n+                Typeahead[\"#\" + this.id].showLayout();\n+            })\n+            .on('focus.typeahead', function() {\n+                // In case the input is cleared, this ensures that the results list will contain all available options.\n+                var $this = $(this);\n+                if (roomJustCleared && $this.is(roomInput)) {\n+                    $this.trigger('input.typeahead');\n+                    roomJustCleared = false;\n+                }\n+                if (venueJustChanged && $this.is(venueInput)) {\n+                    $this.trigger('input.typeahead');\n+                    venueJustChanged = false;\n+                }\n+                $('.ui-dialog-content').css('overflow', 'inherit');\n+\n+                toggleOpenResultsList($this);\n+\n+                latestUsedField = $(this);\n+            })\n+            .on('keydown', function(e) {\n+                var $this = $(this);\n+                if (resultsWereOpen) {\n+                    if (e.keyCode == 9) {  // on tab\n+                        if ($this.val()) {\n+                            // treat tab as the return key\n+                            var returnKey = jQuery.Event(\"keydown\");\n+                            returnKey.which = 13;\n+                            returnKey.keyCode = 13;\n+                            $this.trigger(returnKey);\n+                        } else {\n+                            clearField($this);\n+                        }\n+                    } else if (e.keyCode == 13) { // on enter\n+                        e.preventDefault();\n+                        if ($this.val()) {\n+                            if (latestSelectedOption.value == '' || latestSelectedOption.field == $this.attr('id') && latestSelectedOption.value != $this.val()) {\n+                                $this.trigger('typeahead:click-on-custom-option', {item_name: $this.val()});\n+                            }\n+                        } else {\n+                            clearField($this);\n+                        }\n+                    } else if (e.keyCode == 27) {  // on esc\n+                        e.preventDefault();\n+                        resetSelectedOption();\n+                    } else if (~[38,40].indexOf(e.keyCode)) {  // on key down/up\n+                        scrollResultsList($this);\n+                        $this.trigger('focus.typeahead');\n+                    }\n+                }\n+            })\n+            .on('click', function() {\n+                // Since focus remains on the field after selection, we need special handling for the click event\n+                // in order to reopen the results list\n+                Typeahead[\"#\" + $(this).attr('id')].showLayout();\n+                $(this).trigger('input');\n+            });\n+\n+            $('.toggle-results-list').on('click', function() {\n+                var container = $(this).parent().parent();\n+                var input = $(container.find('.i-location-input-field')[0]);\n+                if (container.hasClass('result')) {\n+                    if (!input.val()) {\n+                        input.trigger('typeahead:close-results-list');\n+                    }\n+                } else {\n+                    input.trigger('focus.typeahead');\n+                }\n+            });\n+\n+            address.on('keyup', function() {\n+                hiddenData.address = address.val();\n+                field.val(JSON.stringify(hiddenData)).trigger('change');\n+            });\n+\n+            $('.typeahead-field .highlighter').on('click', function() {\n+                $($(this).parent().find('.i-location-input-field')[0]).trigger('focus.typeahead');","path":"indico/web/templates/forms/location_widget.html","position":530,"original_position":530,"commit_id":"30ff207bd296ecfcb2117625df23ffd65e66d3ea","original_commit_id":"30ff207bd296ecfcb2117625df23ffd65e66d3ea","user":{"login":"nop33","id":1579899,"avatar_url":"https://avatars.githubusercontent.com/u/1579899?v=3","gravatar_id":"","url":"https://api.github.com/users/nop33","html_url":"https://github.com/nop33","followers_url":"https://api.github.com/users/nop33/followers","following_url":"https://api.github.com/users/nop33/following{/other_user}","gists_url":"https://api.github.com/users/nop33/gists{/gist_id}","starred_url":"https://api.github.com/users/nop33/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nop33/subscriptions","organizations_url":"https://api.github.com/users/nop33/orgs","repos_url":"https://api.github.com/users/nop33/repos","events_url":"https://api.github.com/users/nop33/events{/privacy}","received_events_url":"https://api.github.com/users/nop33/received_events","type":"User","site_admin":false},"body":"thx ;)","created_at":"2016-02-02T15:23:38Z","updated_at":"2016-02-02T15:23:38Z","html_url":"https://github.com/indico/indico/pull/2194#discussion_r51582343","pull_request_url":"https://api.github.com/repos/indico/indico/pulls/2194","_links":{"self":{"href":"https://api.github.com/repos/indico/indico/pulls/comments/51582343"},"html":{"href":"https://github.com/indico/indico/pull/2194#discussion_r51582343"},"pull_request":{"href":"https://api.github.com/repos/indico/indico/pulls/2194"}}},"pull_request":{"url":"https://api.github.com/repos/indico/indico/pulls/2194","id":55463570,"html_url":"https://github.com/indico/indico/pull/2194","diff_url":"https://github.com/indico/indico/pull/2194.diff","patch_url":"https://github.com/indico/indico/pull/2194.patch","issue_url":"https://api.github.com/repos/indico/indico/issues/2194","number":2194,"state":"open","locked":false,"title":"Implement WTForm location field/widget","user":{"login":"nop33","id":1579899,"avatar_url":"https://avatars.githubusercontent.com/u/1579899?v=3","gravatar_id":"","url":"https://api.github.com/users/nop33","html_url":"https://github.com/nop33","followers_url":"https://api.github.com/users/nop33/followers","following_url":"https://api.github.com/users/nop33/following{/other_user}","gists_url":"https://api.github.com/users/nop33/gists{/gist_id}","starred_url":"https://api.github.com/users/nop33/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nop33/subscriptions","organizations_url":"https://api.github.com/users/nop33/orgs","repos_url":"https://api.github.com/users/nop33/repos","events_url":"https://api.github.com/users/nop33/events{/privacy}","received_events_url":"https://api.github.com/users/nop33/received_events","type":"User","site_admin":false},"body":"","created_at":"2016-01-08T13:54:36Z","updated_at":"2016-02-02T15:23:38Z","closed_at":null,"merged_at":null,"merge_commit_sha":"bc8d7497ef3f48ea50309139e9520ba55faad5e5","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/indico/indico/pulls/2194/commits","review_comments_url":"https://api.github.com/repos/indico/indico/pulls/2194/comments","review_comment_url":"https://api.github.com/repos/indico/indico/pulls/comments{/number}","comments_url":"https://api.github.com/repos/indico/indico/issues/2194/comments","statuses_url":"https://api.github.com/repos/indico/indico/statuses/30ff207bd296ecfcb2117625df23ffd65e66d3ea","head":{"label":"nop33:devel","ref":"devel","sha":"30ff207bd296ecfcb2117625df23ffd65e66d3ea","user":{"login":"nop33","id":1579899,"avatar_url":"https://avatars.githubusercontent.com/u/1579899?v=3","gravatar_id":"","url":"https://api.github.com/users/nop33","html_url":"https://github.com/nop33","followers_url":"https://api.github.com/users/nop33/followers","following_url":"https://api.github.com/users/nop33/following{/other_user}","gists_url":"https://api.github.com/users/nop33/gists{/gist_id}","starred_url":"https://api.github.com/users/nop33/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nop33/subscriptions","organizations_url":"https://api.github.com/users/nop33/orgs","repos_url":"https://api.github.com/users/nop33/repos","events_url":"https://api.github.com/users/nop33/events{/privacy}","received_events_url":"https://api.github.com/users/nop33/received_events","type":"User","site_admin":false},"repo":{"id":22684703,"name":"indico","full_name":"nop33/indico","owner":{"login":"nop33","id":1579899,"avatar_url":"https://avatars.githubusercontent.com/u/1579899?v=3","gravatar_id":"","url":"https://api.github.com/users/nop33","html_url":"https://github.com/nop33","followers_url":"https://api.github.com/users/nop33/followers","following_url":"https://api.github.com/users/nop33/following{/other_user}","gists_url":"https://api.github.com/users/nop33/gists{/gist_id}","starred_url":"https://api.github.com/users/nop33/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nop33/subscriptions","organizations_url":"https://api.github.com/users/nop33/orgs","repos_url":"https://api.github.com/users/nop33/repos","events_url":"https://api.github.com/users/nop33/events{/privacy}","received_events_url":"https://api.github.com/users/nop33/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/nop33/indico","description":"The Indico Project (mirror)","fork":true,"url":"https://api.github.com/repos/nop33/indico","forks_url":"https://api.github.com/repos/nop33/indico/forks","keys_url":"https://api.github.com/repos/nop33/indico/keys{/key_id}","collaborators_url":"https://api.github.com/repos/nop33/indico/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/nop33/indico/teams","hooks_url":"https://api.github.com/repos/nop33/indico/hooks","issue_events_url":"https://api.github.com/repos/nop33/indico/issues/events{/number}","events_url":"https://api.github.com/repos/nop33/indico/events","assignees_url":"https://api.github.com/repos/nop33/indico/assignees{/user}","branches_url":"https://api.github.com/repos/nop33/indico/branches{/branch}","tags_url":"https://api.github.com/repos/nop33/indico/tags","blobs_url":"https://api.github.com/repos/nop33/indico/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/nop33/indico/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/nop33/indico/git/refs{/sha}","trees_url":"https://api.github.com/repos/nop33/indico/git/trees{/sha}","statuses_url":"https://api.github.com/repos/nop33/indico/statuses/{sha}","languages_url":"https://api.github.com/repos/nop33/indico/languages","stargazers_url":"https://api.github.com/repos/nop33/indico/stargazers","contributors_url":"https://api.github.com/repos/nop33/indico/contributors","subscribers_url":"https://api.github.com/repos/nop33/indico/subscribers","subscription_url":"https://api.github.com/repos/nop33/indico/subscription","commits_url":"https://api.github.com/repos/nop33/indico/commits{/sha}","git_commits_url":"https://api.github.com/repos/nop33/indico/git/commits{/sha}","comments_url":"https://api.github.com/repos/nop33/indico/comments{/number}","issue_comment_url":"https://api.github.com/repos/nop33/indico/issues/comments{/number}","contents_url":"https://api.github.com/repos/nop33/indico/contents/{+path}","compare_url":"https://api.github.com/repos/nop33/indico/compare/{base}...{head}","merges_url":"https://api.github.com/repos/nop33/indico/merges","archive_url":"https://api.github.com/repos/nop33/indico/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/nop33/indico/downloads","issues_url":"https://api.github.com/repos/nop33/indico/issues{/number}","pulls_url":"https://api.github.com/repos/nop33/indico/pulls{/number}","milestones_url":"https://api.github.com/repos/nop33/indico/milestones{/number}","notifications_url":"https://api.github.com/repos/nop33/indico/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/nop33/indico/labels{/name}","releases_url":"https://api.github.com/repos/nop33/indico/releases{/id}","deployments_url":"https://api.github.com/repos/nop33/indico/deployments","created_at":"2014-08-06T14:02:24Z","updated_at":"2015-09-07T15:17:59Z","pushed_at":"2016-02-01T14:22:01Z","git_url":"git://github.com/nop33/indico.git","ssh_url":"git@github.com:nop33/indico.git","clone_url":"https://github.com/nop33/indico.git","svn_url":"https://github.com/nop33/indico","homepage":"http://indico-software.org","size":182112,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"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":"indico:devel","ref":"devel","sha":"aa9b2037558aa22e8245980d8118634ab8070eb7","user":{"login":"indico","id":715236,"avatar_url":"https://avatars.githubusercontent.com/u/715236?v=3","gravatar_id":"","url":"https://api.github.com/users/indico","html_url":"https://github.com/indico","followers_url":"https://api.github.com/users/indico/followers","following_url":"https://api.github.com/users/indico/following{/other_user}","gists_url":"https://api.github.com/users/indico/gists{/gist_id}","starred_url":"https://api.github.com/users/indico/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/indico/subscriptions","organizations_url":"https://api.github.com/users/indico/orgs","repos_url":"https://api.github.com/users/indico/repos","events_url":"https://api.github.com/users/indico/events{/privacy}","received_events_url":"https://api.github.com/users/indico/received_events","type":"Organization","site_admin":false},"repo":{"id":2113067,"name":"indico","full_name":"indico/indico","owner":{"login":"indico","id":715236,"avatar_url":"https://avatars.githubusercontent.com/u/715236?v=3","gravatar_id":"","url":"https://api.github.com/users/indico","html_url":"https://github.com/indico","followers_url":"https://api.github.com/users/indico/followers","following_url":"https://api.github.com/users/indico/following{/other_user}","gists_url":"https://api.github.com/users/indico/gists{/gist_id}","starred_url":"https://api.github.com/users/indico/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/indico/subscriptions","organizations_url":"https://api.github.com/users/indico/orgs","repos_url":"https://api.github.com/users/indico/repos","events_url":"https://api.github.com/users/indico/events{/privacy}","received_events_url":"https://api.github.com/users/indico/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/indico/indico","description":"The Indico Project","fork":false,"url":"https://api.github.com/repos/indico/indico","forks_url":"https://api.github.com/repos/indico/indico/forks","keys_url":"https://api.github.com/repos/indico/indico/keys{/key_id}","collaborators_url":"https://api.github.com/repos/indico/indico/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/indico/indico/teams","hooks_url":"https://api.github.com/repos/indico/indico/hooks","issue_events_url":"https://api.github.com/repos/indico/indico/issues/events{/number}","events_url":"https://api.github.com/repos/indico/indico/events","assignees_url":"https://api.github.com/repos/indico/indico/assignees{/user}","branches_url":"https://api.github.com/repos/indico/indico/branches{/branch}","tags_url":"https://api.github.com/repos/indico/indico/tags","blobs_url":"https://api.github.com/repos/indico/indico/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/indico/indico/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/indico/indico/git/refs{/sha}","trees_url":"https://api.github.com/repos/indico/indico/git/trees{/sha}","statuses_url":"https://api.github.com/repos/indico/indico/statuses/{sha}","languages_url":"https://api.github.com/repos/indico/indico/languages","stargazers_url":"https://api.github.com/repos/indico/indico/stargazers","contributors_url":"https://api.github.com/repos/indico/indico/contributors","subscribers_url":"https://api.github.com/repos/indico/indico/subscribers","subscription_url":"https://api.github.com/repos/indico/indico/subscription","commits_url":"https://api.github.com/repos/indico/indico/commits{/sha}","git_commits_url":"https://api.github.com/repos/indico/indico/git/commits{/sha}","comments_url":"https://api.github.com/repos/indico/indico/comments{/number}","issue_comment_url":"https://api.github.com/repos/indico/indico/issues/comments{/number}","contents_url":"https://api.github.com/repos/indico/indico/contents/{+path}","compare_url":"https://api.github.com/repos/indico/indico/compare/{base}...{head}","merges_url":"https://api.github.com/repos/indico/indico/merges","archive_url":"https://api.github.com/repos/indico/indico/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/indico/indico/downloads","issues_url":"https://api.github.com/repos/indico/indico/issues{/number}","pulls_url":"https://api.github.com/repos/indico/indico/pulls{/number}","milestones_url":"https://api.github.com/repos/indico/indico/milestones{/number}","notifications_url":"https://api.github.com/repos/indico/indico/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/indico/indico/labels{/name}","releases_url":"https://api.github.com/repos/indico/indico/releases{/id}","deployments_url":"https://api.github.com/repos/indico/indico/deployments","created_at":"2011-07-27T13:56:30Z","updated_at":"2016-01-30T09:19:23Z","pushed_at":"2016-02-02T15:19:24Z","git_url":"git://github.com/indico/indico.git","ssh_url":"git@github.com:indico/indico.git","clone_url":"https://github.com/indico/indico.git","svn_url":"https://github.com/indico/indico","homepage":"http://indico-software.org","size":192992,"stargazers_count":127,"watchers_count":127,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":68,"mirror_url":null,"open_issues_count":580,"forks":68,"open_issues":580,"watchers":127,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/indico/indico/pulls/2194"},"html":{"href":"https://github.com/indico/indico/pull/2194"},"issue":{"href":"https://api.github.com/repos/indico/indico/issues/2194"},"comments":{"href":"https://api.github.com/repos/indico/indico/issues/2194/comments"},"review_comments":{"href":"https://api.github.com/repos/indico/indico/pulls/2194/comments"},"review_comment":{"href":"https://api.github.com/repos/indico/indico/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/indico/indico/pulls/2194/commits"},"statuses":{"href":"https://api.github.com/repos/indico/indico/statuses/30ff207bd296ecfcb2117625df23ffd65e66d3ea"}}}}