diff options
Diffstat (limited to 'app/assets/javascripts')
27 files changed, 1045 insertions, 176 deletions
diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee index 3e0fdb3f795..2ddf8612db3 100644 --- a/app/assets/javascripts/api.js.coffee +++ b/app/assets/javascripts/api.js.coffee @@ -4,6 +4,7 @@ namespaces_path: "/api/:version/namespaces.json" group_projects_path: "/api/:version/groups/:id/projects.json" projects_path: "/api/:version/projects.json" + labels_path: "/api/:version/projects/:id/labels" group: (group_id, callback) -> url = Api.buildUrl(Api.group_path) @@ -61,6 +62,19 @@ ).done (projects) -> callback(projects) + newLabel: (project_id, data, callback) -> + url = Api.buildUrl(Api.labels_path) + url = url.replace(':id', project_id) + + data.private_token = gon.api_token + $.ajax( + url: url + type: "POST" + data: data + dataType: "json" + ).done (label) -> + callback(label) + # Return group projects list. Filtered by query groupProjects: (group_id, query, callback) -> url = Api.buildUrl(Api.group_projects_path) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 321da10a009..7f983325049 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -7,6 +7,7 @@ #= require jquery #= require jquery-ui/autocomplete #= require jquery-ui/datepicker +#= require jquery-ui/draggable #= require jquery-ui/effect-highlight #= require jquery-ui/sortable #= require jquery_ujs @@ -42,7 +43,6 @@ #= require jquery.nicescroll #= require_tree . #= require fuzzaldrin-plus -#= require cropper.js window.slugify = (text) -> text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase() @@ -108,6 +108,8 @@ window.onload = -> setTimeout shiftWindow, 100 $ -> + bootstrapBreakpoint = bp.getBreakpointSize() + $(".nicescroll").niceScroll(cursoropacitymax: '0.4', cursorcolor: '#FFF', cursorborder: "1px solid #FFF") # Click a .js-select-on-focus field, select the contents @@ -220,17 +222,17 @@ $ -> .off 'breakpoint:change' .on 'breakpoint:change', (e, breakpoint) -> if breakpoint is 'sm' or breakpoint is 'xs' - $gutterIcon = $('aside .gutter-toggle').find('i') + $gutterIcon = $('.js-sidebar-toggle').find('i') if $gutterIcon.hasClass('fa-angle-double-right') $gutterIcon.closest('a').trigger('click') $(document) - .off 'click', 'aside .gutter-toggle' - .on 'click', 'aside .gutter-toggle', (e, triggered) -> + .off 'click', '.js-sidebar-toggle' + .on 'click', '.js-sidebar-toggle', (e, triggered) -> e.preventDefault() $this = $(this) $thisIcon = $this.find 'i' - $allGutterToggleIcons = $('.gutter-toggle i') + $allGutterToggleIcons = $('.js-sidebar-toggle i') if $thisIcon.hasClass('fa-angle-double-right') $allGutterToggleIcons .removeClass('fa-angle-double-right') @@ -256,35 +258,14 @@ $ -> $('.right-sidebar') .hasClass('right-sidebar-collapsed'), { path: '/' }) - bootstrapBreakpoint = undefined; - checkBootstrapBreakpoints = -> - if $('.device-xs').is(':visible') - bootstrapBreakpoint = "xs" - else if $('.device-sm').is(':visible') - bootstrapBreakpoint = "sm" - else if $('.device-md').is(':visible') - bootstrapBreakpoint = "md" - else if $('.device-lg').is(':visible') - bootstrapBreakpoint = "lg" - - setBootstrapBreakpoints = -> - if $('.device-xs').length - return - - $("body") - .append('<div class="device-xs visible-xs"></div>'+ - '<div class="device-sm visible-sm"></div>'+ - '<div class="device-md visible-md"></div>'+ - '<div class="device-lg visible-lg"></div>') - checkBootstrapBreakpoints() - fitSidebarForSize = -> oldBootstrapBreakpoint = bootstrapBreakpoint - checkBootstrapBreakpoints() + bootstrapBreakpoint = bp.getBreakpointSize() if bootstrapBreakpoint != oldBootstrapBreakpoint $(document).trigger('breakpoint:change', [bootstrapBreakpoint]) checkInitialSidebarSize = -> + bootstrapBreakpoint = bp.getBreakpointSize() if bootstrapBreakpoint is "xs" or "sm" $(document).trigger('breakpoint:change', [bootstrapBreakpoint]) @@ -293,6 +274,5 @@ $ -> .on "resize", (e) -> fitSidebarForSize() - setBootstrapBreakpoints() checkInitialSidebarSize() new Aside() diff --git a/app/assets/javascripts/aside.js.coffee b/app/assets/javascripts/aside.js.coffee index 85473101944..66ab5054326 100644 --- a/app/assets/javascripts/aside.js.coffee +++ b/app/assets/javascripts/aside.js.coffee @@ -5,7 +5,6 @@ class @Aside e.preventDefault() btn = $(e.currentTarget) icon = btn.find('i') - console.log('1') if icon.hasClass('fa-angle-left') btn.parent().find('section').hide() diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index 8f89d3e61a2..03a44874161 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -1,6 +1,6 @@ class @AwardsHandler constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) -> - $(".add-award").click (event) => + $(".js-add-award").on "click", (event) => event.stopPropagation() event.preventDefault() @@ -9,27 +9,46 @@ class @AwardsHandler $("html").on 'click', (event) -> if !$(event.target).closest(".emoji-menu").length if $(".emoji-menu").is(":visible") - $(".emoji-menu").hide() + $(".emoji-menu").removeClass "is-visible" + + $(".awards") + .off "click" + .on "click", ".js-emoji-btn", @handleClick @renderFrequentlyUsedBlock() - @setupSearch() + + handleClick: (e) -> + e.preventDefault() + emoji = $(this) + .find(".icon") + .data "emoji" + awards_handler.addAward emoji showEmojiMenu: -> if $(".emoji-menu").length - $(".emoji-menu").show() - $("#emoji_search").focus() - else - $.get "/emojis", (response) -> - $(".add-award").after response - $(".emoji-menu").show() + if $(".emoji-menu").is ".is-visible" + $(".emoji-menu").removeClass "is-visible" + $("#emoji_search").blur() + else + $(".emoji-menu").addClass "is-visible" $("#emoji_search").focus() + else + $('.js-add-award').addClass "is-loading" + $.get "/emojis", (response) => + $('.js-add-award').removeClass "is-loading" + $(".js-award-holder").append response + setTimeout => + $(".emoji-menu").addClass "is-visible" + $("#emoji_search").focus() + @setupSearch() + , 200 addAward: (emoji) -> emoji = @normilizeEmojiName(emoji) @postEmoji emoji, => @addAwardToEmojiBar(emoji) - $(".emoji-menu").hide() + $(".emoji-menu").removeClass "is-visible" addAwardToEmojiBar: (emoji) -> @addEmojiToFrequentlyUsedList(emoji) @@ -39,7 +58,7 @@ class @AwardsHandler if @isActive(emoji) @decrementCounter(emoji) else - counter = @findEmojiIcon(emoji).siblings(".counter") + counter = @findEmojiIcon(emoji).siblings(".js-counter") counter.text(parseInt(counter.text()) + 1) counter.parent().addClass("active") @addMeToAuthorList(emoji) @@ -53,7 +72,7 @@ class @AwardsHandler @findEmojiIcon(emoji).parent().hasClass("active") decrementCounter: (emoji) -> - counter = @findEmojiIcon(emoji).siblings(".counter") + counter = @findEmojiIcon(emoji).siblings(".js-counter") emojiIcon = counter.parent() if parseInt(counter.text()) > 1 counter.text(parseInt(counter.text()) - 1) @@ -70,9 +89,13 @@ class @AwardsHandler removeMeFromAuthorList: (emoji) -> award_block = @findEmojiIcon(emoji).parent() - authors = award_block.attr("data-original-title").split(", ") + authors = award_block + .attr("data-original-title") + .split(", ") authors.splice(authors.indexOf("me"),1) - award_block.closest(".award").attr("data-original-title", authors.join(", ")) + award_block + .closest(".js-emoji-btn") + .attr("data-original-title", authors.join(", ")) @resetTooltip(award_block) addMeToAuthorList: (emoji) -> @@ -98,14 +121,18 @@ class @AwardsHandler emojiCssClass = @resolveNameToCssClass(emoji) nodes = [] - nodes.push("<div class='award active' title='me'>") - nodes.push("<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>") - nodes.push("<div class='counter'>1</div>") - nodes.push("</div>") - - emoji_node = $(nodes.join("\n")).insertBefore(".awards-controls").find(".emoji-icon").data("emoji", emoji) - - $(".award").tooltip() + nodes.push( + "<button class='btn award-control js-emoji-btn has_tooltip active' title='me'>", + "<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>", + "<span class='award-control-text js-counter'>1</span>", + "</button>" + ) + + emoji_node = $(nodes.join("\n")) + .insertBefore(".js-award-holder") + .find(".emoji-icon") + .data("emoji", emoji) + $('.award-control').tooltip() resolveNameToCssClass: (emoji) -> emoji_icon = $(".emoji-menu-content [data-emoji='#{emoji}']") @@ -128,7 +155,7 @@ class @AwardsHandler callback.call() findEmojiIcon: (emoji) -> - $(".award [data-emoji='#{emoji}']") + $(".awards > .js-emoji-btn [data-emoji='#{emoji}']") scrollToAwards: -> $('body, html').animate({ @@ -164,13 +191,13 @@ class @AwardsHandler term = $(ev.target).val() # Clean previous search results - $("ul.emoji-search,h5.emoji-search").remove() + $("ul.emoji-menu-search, h5.emoji-search").remove() if term # Generate a search result block h5 = $("<h5>").text("Search results").addClass("emoji-search") found_emojis = @searchEmojis(term).show() - ul = $("<ul>").addClass("emoji-search").append(found_emojis) + ul = $("<ul>").addClass("emoji-menu-list emoji-menu-search").append(found_emojis) $(".emoji-menu-content ul, .emoji-menu-content h5").hide() $(".emoji-menu-content").append(h5).append(ul) else diff --git a/app/assets/javascripts/breakpoints.coffee b/app/assets/javascripts/breakpoints.coffee new file mode 100644 index 00000000000..5457430f921 --- /dev/null +++ b/app/assets/javascripts/breakpoints.coffee @@ -0,0 +1,37 @@ +class @Breakpoints + instance = null; + + class BreakpointInstance + BREAKPOINTS = ["xs", "sm", "md", "lg"] + + constructor: -> + @setup() + + setup: -> + allDeviceSelector = BREAKPOINTS.map (breakpoint) -> + ".device-#{breakpoint}" + return if $(allDeviceSelector.join(",")).length + + # Create all the elements + els = $.map BREAKPOINTS, (breakpoint) -> + "<div class='device-#{breakpoint} visible-#{breakpoint}'></div>" + $("body").append els.join('') + + visibleDevice: -> + allDeviceSelector = BREAKPOINTS.map (breakpoint) -> + ".device-#{breakpoint}" + $(allDeviceSelector.join(",")).filter(":visible") + + getBreakpointSize: -> + $visibleDevice = @visibleDevice + # the page refreshed via turbolinks + if not $visibleDevice().length + @setup() + $visibleDevice = @visibleDevice() + return $visibleDevice.attr("class").split("visible-")[1] + + @get: -> + return instance ?= new BreakpointInstance + +$ => + @bp = Breakpoints.get() diff --git a/app/assets/javascripts/ci/build.coffee b/app/assets/javascripts/ci/build.coffee index 44d5ddb7d95..7afe8bf79e2 100644 --- a/app/assets/javascripts/ci/build.coffee +++ b/app/assets/javascripts/ci/build.coffee @@ -4,6 +4,8 @@ class CiBuild constructor: (build_url, build_status) -> clearInterval(CiBuild.interval) + @initScrollButtonAffix() + if build_status == "running" || build_status == "pending" # # Bind autoscroll button to follow build output @@ -38,4 +40,15 @@ class CiBuild checkAutoscroll: -> $("html,body").scrollTop $("#build-trace").height() if "enabled" is $("#autoscroll-button").data("state") + initScrollButtonAffix: -> + $buildScroll = $('#js-build-scroll') + $body = $('body') + $buildTrace = $('#build-trace') + + $buildScroll.affix( + offset: + bottom: -> + $body.outerHeight() - ($buildTrace.outerHeight() + $buildTrace.offset().top) + ) + @CiBuild = CiBuild diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index d7feb5d5c87..f5e1ca9860d 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -14,7 +14,6 @@ class Dispatcher path = page.split(':') shortcut_handler = null - switch page when 'projects:issues:index' Issues.init() @@ -23,8 +22,10 @@ class Dispatcher new Issue() shortcut_handler = new ShortcutsIssuable() new ZenMode() - when 'projects:milestones:show' + when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show' new Milestone() + when 'dashboard:todos:index' + new Todos() when 'projects:milestones:new', 'projects:milestones:edit' new ZenMode() new DropzoneInput($('.milestone-form')) @@ -74,8 +75,9 @@ class Dispatcher shortcut_handler = new ShortcutsNavigation() new TreeView() if $('#tree-slider').length - when 'groups:show' + when 'groups:activity' new Activities() + when 'groups:show' shortcut_handler = new ShortcutsNavigation() when 'groups:group_members:index' new GroupMembers() @@ -103,6 +105,8 @@ class Dispatcher new ProjectFork() when 'projects:artifacts:browse' new BuildArtifacts() + when 'projects:group_links:index' + new GroupsSelect() switch path.first() when 'admin' diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee new file mode 100644 index 00000000000..960585245d7 --- /dev/null +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -0,0 +1,280 @@ +class GitLabDropdownFilter + BLUR_KEYCODES = [27, 40] + + constructor: (@dropdown, @options) -> + @input = @dropdown.find(".dropdown-input .dropdown-input-field") + + # Key events + timeout = "" + @input.on "keyup", (e) => + if e.keyCode is 13 && @input.val() isnt "" + if @options.enterCallback + @options.enterCallback() + return + + clearTimeout timeout + timeout = setTimeout => + blur_field = @shouldBlur e.keyCode + search_text = @input.val() + + if blur_field + @input.blur() + + if @options.remote + @options.query search_text, (data) => + @options.callback(data) + else + @filter search_text + , 250 + + shouldBlur: (keyCode) -> + return BLUR_KEYCODES.indexOf(keyCode) >= 0 + + filter: (search_text) -> + data = @options.data() + results = data + + if search_text isnt "" + results = fuzzaldrinPlus.filter(data, search_text, + key: @options.keys + ) + + @options.callback results + +class GitLabDropdownRemote + constructor: (@dataEndpoint, @options) -> + + execute: -> + if typeof @dataEndpoint is "string" + @fetchData() + else if typeof @dataEndpoint is "function" + if @options.beforeSend + @options.beforeSend() + + # Fetch the data by calling the data funcfion + @dataEndpoint "", (data) => + if @options.success + @options.success(data) + + if @options.beforeSend + @options.beforeSend() + + # Fetch the data through ajax if the data is a string + fetchData: -> + $.ajax( + url: @dataEndpoint, + dataType: @options.dataType, + beforeSend: => + if @options.beforeSend + @options.beforeSend() + success: (data) => + if @options.success + @options.success(data) + ) + +class GitLabDropdown + LOADING_CLASS = "is-loading" + PAGE_TWO_CLASS = "is-page-two" + ACTIVE_CLASS = "is-active" + + constructor: (@el, @options) -> + self = @ + @dropdown = $(@el).parent() + search_fields = if @options.search then @options.search.fields else []; + + if @options.data + # Remote data + @remote = new GitLabDropdownRemote @options.data, { + dataType: @options.dataType, + beforeSend: @toggleLoading.bind(@) + success: (data) => + @fullData = data + + @parseData @fullData + } + + # Init filiterable + if @options.filterable + @filter = new GitLabDropdownFilter @dropdown, + remote: @options.filterRemote + query: @options.data + keys: @options.search.fields + data: => + return @fullData + callback: (data) => + @parseData data + enterCallback: => + @selectFirstRow() + + # Event listeners + @dropdown.on "shown.bs.dropdown", @opened + @dropdown.on "hidden.bs.dropdown", @hidden + + if @dropdown.find(".dropdown-toggle-page").length + @dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on "click", (e) => + e.preventDefault() + e.stopPropagation() + + @togglePage() + + if @options.selectable + selector = ".dropdown-content a" + + if @dropdown.find(".dropdown-toggle-page").length + selector = ".dropdown-page-one .dropdown-content a" + + @dropdown.on "click", selector, (e) -> + self.rowClicked $(@) + + if self.options.clicked + self.options.clicked() + + toggleLoading: -> + $('.dropdown-menu', @dropdown).toggleClass LOADING_CLASS + + togglePage: -> + menu = $('.dropdown-menu', @dropdown) + + if menu.hasClass(PAGE_TWO_CLASS) + if @remote + @remote.execute() + + menu.toggleClass PAGE_TWO_CLASS + + parseData: (data) -> + @renderedData = data + + # Render each row + html = $.map data, (obj) => + return @renderItem(obj) + + if @options.filterable and data.length is 0 + # render no matching results + html = [@noResults()] + + # Render the full menu + full_html = @renderMenu(html.join("")) + + @appendMenu(full_html) + + opened: => + contentHtml = $('.dropdown-content', @dropdown).html() + if @remote && contentHtml is "" + @remote.execute() + + if @options.filterable + @dropdown.find(".dropdown-input-field").focus() + + hidden: => + if @options.filterable + @dropdown + .find(".dropdown-input-field") + .blur() + .val("") + .trigger("keyup") + + if @dropdown.find(".dropdown-toggle-page").length + $('.dropdown-menu', @dropdown).removeClass PAGE_TWO_CLASS + + + # Render the full menu + renderMenu: (html) -> + menu_html = "" + + if @options.renderMenu + menu_html = @options.renderMenu(html) + else + menu_html = "<ul>#{html}</ul>" + + return menu_html + + # Append the menu into the dropdown + appendMenu: (html) -> + selector = '.dropdown-content' + if @dropdown.find(".dropdown-toggle-page").length + selector = ".dropdown-page-one .dropdown-content" + + $(selector, @dropdown).html html + + # Render the row + renderItem: (data) -> + html = "" + + return "<li class='divider'></li>" if data is "divider" + + if @options.renderRow + # Call the render function + html = @options.renderRow(data) + else + selected = if @options.isSelected then @options.isSelected(data) else false + url = if @options.url then @options.url(data) else "#" + text = if @options.text then @options.text(data) else "" + cssClass = ""; + + if selected + cssClass = "is-active" + + html = "<li>" + html += "<a href='#{url}' class='#{cssClass}'>" + html += text + html += "</a>" + html += "</li>" + + return html + + noResults: -> + html = "<li>" + html += "<a href='#' class='is-focused'>" + html += "No matching results." + html += "</a>" + html += "</li>" + + rowClicked: (el) -> + fieldName = @options.fieldName + field = @dropdown.parent().find("input[name='#{fieldName}']") + + if el.hasClass(ACTIVE_CLASS) + field.remove() + else + fieldName = @options.fieldName + selectedIndex = el.parent().index() + if @renderedData + selectedObject = @renderedData[selectedIndex] + value = if @options.id then @options.id(selectedObject, el) else selectedObject.id + + if !value? + field.remove() + + if @options.multiSelect + oldValue = field.val() + if oldValue + value = "#{oldValue},#{value}" + else + @dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS + + # Toggle active class for the tick mark + el.toggleClass "is-active" + + # Toggle the dropdown label + if @options.toggleLabel + $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject) + + if value? + if !field.length + # Create hidden input for form + input = "<input type='hidden' name='#{fieldName}' />" + @dropdown.before input + + @dropdown.parent().find("input[name='#{fieldName}']").val value + + selectFirstRow: -> + selector = '.dropdown-content li:first-child a' + if @dropdown.find(".dropdown-toggle-page").length + selector = ".dropdown-page-one .dropdown-content li:first-child a" + + # similute a click on the first link + $(selector).trigger "click" + +$.fn.glDropdown = (opts) -> + return @.each -> + new GitLabDropdown @, opts diff --git a/app/assets/javascripts/issuable_form.js.coffee b/app/assets/javascripts/issuable_form.js.coffee index 48c249943f2..7a788f761b7 100644 --- a/app/assets/javascripts/issuable_form.js.coffee +++ b/app/assets/javascripts/issuable_form.js.coffee @@ -1,4 +1,7 @@ class @IssuableForm + issueMoveConfirmMsg: 'Are you sure you want to move this issue to another project?' + wipRegex: /^\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i + constructor: (@form) -> GitLab.GfmAutoComplete.setup() new UsersSelect() @@ -6,14 +9,17 @@ class @IssuableForm @titleField = @form.find("input[name*='[title]']") @descriptionField = @form.find("textarea[name*='[description]']") + @issueMoveField = @form.find("#move_to_project_id") return unless @titleField.length && @descriptionField.length @initAutosave() - @form.on "submit", @resetAutosave + @form.on "submit", @handleSubmit @form.on "click", ".btn-cancel", @resetAutosave + @initWip() + initAutosave: -> new Autosave @titleField, [ document.location.pathname, @@ -27,6 +33,50 @@ class @IssuableForm "description" ] + handleSubmit: => + if (parseInt(@issueMoveField?.val()) ? 0) > 0 + return false unless confirm(@issueMoveConfirmMsg) + + @resetAutosave() + resetAutosave: => @titleField.data("autosave").reset() @descriptionField.data("autosave").reset() + + initWip: -> + @$wipExplanation = @form.find(".js-wip-explanation") + @$noWipExplanation = @form.find(".js-no-wip-explanation") + return unless @$wipExplanation.length and @$noWipExplanation.length + + @form.on "click", ".js-toggle-wip", @toggleWip + + @titleField.on "keyup blur", @renderWipExplanation + + @renderWipExplanation() + + workInProgress: -> + @wipRegex.test @titleField.val() + + renderWipExplanation: => + if @workInProgress() + @$wipExplanation.show() + @$noWipExplanation.hide() + else + @$wipExplanation.hide() + @$noWipExplanation.show() + + toggleWip: (event) => + event.preventDefault() + + if @workInProgress() + @removeWip() + else + @addWip() + + @renderWipExplanation() + + removeWip: -> + @titleField.val @titleField.val().replace(@wipRegex, "") + + addWip: -> + @titleField.val "WIP: #{@titleField.val()}" diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index d663e34871c..f50df1f5ea3 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -7,6 +7,7 @@ class @Issue # Prevent duplicate event bindings @disableTaskList() @fixAffixScroll() + @initParticipants() if $('a.btn-close').length @initTaskList() @initIssueBtnEventListeners() @@ -84,3 +85,27 @@ class @Issue type: 'PATCH' url: $('form.js-issuable-update').attr('action') data: patchData + + initParticipants: -> + _this = @ + $(document).on "click", ".js-participants-more", @toggleHiddenParticipants + + $(".js-participants-author").each (i) -> + if i >= _this.PARTICIPANTS_ROW_COUNT + $(@) + .addClass "js-participants-hidden" + .hide() + + toggleHiddenParticipants: (e) -> + e.preventDefault() + + currentText = $(this).text().trim() + lessText = $(this).data("less-text") + originalText = $(this).data("original-text") + + if currentText is originalText + $(this).text(lessText) + else + $(this).text(originalText) + + $(".js-participants-hidden").toggle() diff --git a/app/assets/javascripts/issue_status_select.js.coffee b/app/assets/javascripts/issue_status_select.js.coffee new file mode 100644 index 00000000000..c5740f27ddd --- /dev/null +++ b/app/assets/javascripts/issue_status_select.js.coffee @@ -0,0 +1,11 @@ +class @IssueStatusSelect + constructor: -> + $('.js-issue-status').each (i, el) -> + fieldName = $(el).data("field-name") + + $(el).glDropdown( + selectable: true + fieldName: fieldName + id: (obj, el) -> + $(el).data("id") + ) diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee index a0acf3028bf..1127b289264 100644 --- a/app/assets/javascripts/issues.js.coffee +++ b/app/assets/javascripts/issues.js.coffee @@ -41,24 +41,28 @@ @timer = null $("#issue_search").keyup -> clearTimeout(@timer) - @timer = setTimeout(Issues.filterResults, 500) + @timer = setTimeout( -> + Issues.filterResults $("#issue_search_form") + , 500) - filterResults: => - form = $("#issue_search_form") - search = $("#issue_search").val() - $('.issues-holder').css("opacity", '0.5') - issues_url = form.attr('action') + '?' + form.serialize() + filterResults: (form) => + $('.issues-holder, .merge-requests-holder').css("opacity", '0.5') + formAction = form.attr('action') + formData = form.serialize() + issuesUrl = formAction + issuesUrl += ("#{if formAction.indexOf("?") < 0 then '?' else '&'}") + issuesUrl += formData $.ajax type: "GET" - url: form.attr('action') - data: form.serialize() + url: formAction + data: formData complete: -> - $('.issues-holder').css("opacity", '1.0') + $('.issues-holder, .merge-requests-holder').css("opacity", '1.0') success: (data) -> - $('.issues-holder').html(data.html) + $('.issues-holder, .merge-requests-holder').html(data.html) # Change url so if user reload a page - search results are saved - history.replaceState {page: issues_url}, document.title, issues_url + history.replaceState {page: issuesUrl}, document.title, issuesUrl Issues.reload() dataType: "json" diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee new file mode 100644 index 00000000000..4a0c18a99a6 --- /dev/null +++ b/app/assets/javascripts/labels_select.js.coffee @@ -0,0 +1,100 @@ +class @LabelsSelect + constructor: -> + $('.js-label-select').each (i, dropdown) -> + $dropdown = $(dropdown) + projectId = $dropdown.data('project-id') + labelUrl = $dropdown.data('labels') + selectedLabel = $dropdown.data('selected') + if selectedLabel + selectedLabel = selectedLabel.split(',') + newLabelField = $('#new_label_name') + newColorField = $('#new_label_color') + showNo = $dropdown.data('show-no') + showAny = $dropdown.data('show-any') + defaultLabel = $dropdown.data('default-label') + + if newLabelField.length + $('.suggest-colors-dropdown a').on 'click', (e) -> + e.preventDefault() + e.stopPropagation() + newColorField.val $(this).data('color') + $('.js-dropdown-label-color-preview') + .css 'background-color', $(this).data('color') + .addClass 'is-active' + + $('.js-new-label-btn').on 'click', (e) -> + e.preventDefault() + e.stopPropagation() + + if newLabelField.val() isnt '' and newColorField.val() isnt '' + $('.js-new-label-btn').disable() + + # Create new label with API + Api.newLabel projectId, { + name: newLabelField.val() + color: newColorField.val() + }, (label) -> + $('.js-new-label-btn').enable() + $('.dropdown-menu-back', $dropdown.parent()).trigger 'click' + + $dropdown.glDropdown( + data: (term, callback) -> + $.ajax( + url: labelUrl + ).done (data) -> + if showNo + data.unshift( + id: 0 + title: 'No Label' + ) + + if showAny + data.unshift( + isAny: true + title: 'Any Label' + ) + + if data.length > 2 + data.splice 2, 0, 'divider' + + callback data + renderRow: (label) -> + if $.isArray(selectedLabel) + selected = '' + $.each selectedLabel, (i, selectedLbl) -> + selectedLbl = selectedLbl.trim() + if selected is '' and label.title is selectedLbl + selected = 'is-active' + else + selected = if label.title is selectedLabel then 'is-active' else '' + + "<li> + <a href='#' class='#{selected}'> + #{label.title} + </a> + </li>" + filterable: true + search: + fields: ['title'] + selectable: true + toggleLabel: (selected) -> + if selected and selected.title isnt 'Any Label' + selected.title + else + defaultLabel + fieldName: $dropdown.data('field-name') + id: (label) -> + if label.isAny? + '' + else + label.title + clicked: -> + page = $('body').data 'page' + isIssueIndex = page is 'projects:issues:index' + isMRIndex = page is page is 'projects:merge_requests:index' + + if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) + Issues.filterResults $dropdown.closest('form') + else if $dropdown.hasClass 'js-filter-submit' + $dropdown.closest('form').submit() + ) diff --git a/app/assets/javascripts/markdown_preview.js.coffee b/app/assets/javascripts/markdown_preview.js.coffee index 98fc8f17340..2a0b9479445 100644 --- a/app/assets/javascripts/markdown_preview.js.coffee +++ b/app/assets/javascripts/markdown_preview.js.coffee @@ -6,6 +6,7 @@ class @MarkdownPreview # Minimum number of users referenced before triggering a warning referenceThreshold: 10 + ajaxCache: {} showPreview: (form) -> preview = form.find('.js-md-preview') @@ -24,12 +25,16 @@ class @MarkdownPreview renderMarkdown: (text, success) -> return unless window.markdown_preview_path + return success(@ajaxCache.response) if text == @ajaxCache.text + $.ajax type: 'POST' url: window.markdown_preview_path data: { text: text } dataType: 'json' - success: success + success: (response) => + @ajaxCache = text: text, response: response + success(response) hideReferencedUsers: (form) -> referencedUsers = form.find('.referenced-users') @@ -49,6 +54,7 @@ markdownPreview = new MarkdownPreview() previewButtonSelector = '.js-md-preview-button' writeButtonSelector = '.js-md-write-button' +lastTextareaPreviewed = null $.fn.setupMarkdownPreview = -> $form = $(this) @@ -58,10 +64,10 @@ $.fn.setupMarkdownPreview = -> form_textarea.on 'input', -> markdownPreview.hideReferencedUsers($form) form_textarea.on 'blur', -> markdownPreview.showPreview($form) -$(document).on 'click', previewButtonSelector, (e) -> - e.preventDefault() +$(document).on 'markdown-preview:show', (e, $form) -> + return unless $form - $form = $(this).closest('form') + lastTextareaPreviewed = $form.find('textarea.markdown-area') # toggle tabs $form.find(writeButtonSelector).parent().removeClass('active') @@ -73,10 +79,10 @@ $(document).on 'click', previewButtonSelector, (e) -> markdownPreview.showPreview($form) -$(document).on 'click', writeButtonSelector, (e) -> - e.preventDefault() +$(document).on 'markdown-preview:hide', (e, $form) -> + return unless $form - $form = $(this).closest('form') + lastTextareaPreviewed = null # toggle tabs $form.find(writeButtonSelector).parent().addClass('active') @@ -84,4 +90,30 @@ $(document).on 'click', writeButtonSelector, (e) -> # toggle content $form.find('.md-write-holder').show() + $form.find('textarea.markdown-area').focus() $form.find('.md-preview-holder').hide() + +$(document).on 'markdown-preview:toggle', (e, keyboardEvent) -> + $target = $(keyboardEvent.target) + + if $target.is('textarea.markdown-area') + $(document).triggerHandler('markdown-preview:show', [$target.closest('form')]) + keyboardEvent.preventDefault() + else if lastTextareaPreviewed + $target = lastTextareaPreviewed + $(document).triggerHandler('markdown-preview:hide', [$target.closest('form')]) + keyboardEvent.preventDefault() + +$(document).on 'click', previewButtonSelector, (e) -> + e.preventDefault() + + $form = $(this).closest('form') + + $(document).triggerHandler('markdown-preview:show', [$form]) + +$(document).on 'click', writeButtonSelector, (e) -> + e.preventDefault() + + $form = $(this).closest('form') + + $(document).triggerHandler('markdown-preview:hide', [$form]) diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index 58373ba87a5..839e6ec2c08 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -3,6 +3,8 @@ # Handles persisting and restoring the current tab selection and lazily-loading # content on the MergeRequests#show page. # +#= require jquery.cookie +# # ### Example Markup # # <ul class="nav-links merge-request-tabs"> @@ -68,11 +70,15 @@ class @MergeRequestTabs if action == 'commits' @loadCommits($target.attr('href')) + @expandView() else if action == 'diffs' @loadDiff($target.attr('href')) @shrinkView() else if action == 'builds' @loadBuilds($target.attr('href')) + @expandView() + else + @expandView() @setCurrentAction(action) @@ -189,12 +195,24 @@ class @MergeRequestTabs $('.container-fluid').removeClass('container-limited') shrinkView: -> - $gutterIcon = $('.gutter-toggle i') + $gutterIcon = $('.js-sidebar-toggle i:visible') # Wait until listeners are set setTimeout( -> - # Only when sidebar is collapsed + # Only when sidebar is expanded if $gutterIcon.is('.fa-angle-double-right') - $gutterIcon.closest('a').trigger('click',[true]) + $gutterIcon.closest('a').trigger('click', [true]) , 0) + # Expand the issuable sidebar unless the user explicitly collapsed it + expandView: -> + return if $.cookie('collapsed_gutter') == 'true' + + $gutterIcon = $('.js-sidebar-toggle i:visible') + + # Wait until listeners are set + setTimeout( -> + # Only when sidebar is collapsed + if $gutterIcon.is('.fa-angle-double-left') + $gutterIcon.closest('a').trigger('click', [true]) + , 0) diff --git a/app/assets/javascripts/milestone.js.coffee b/app/assets/javascripts/milestone.js.coffee index e6d8518bec8..0037a3a21c2 100644 --- a/app/assets/javascripts/milestone.js.coffee +++ b/app/assets/javascripts/milestone.js.coffee @@ -69,7 +69,7 @@ class @Milestone @bindIssuesSorting() @bindMergeRequestSorting() - @bindTabsSwitching + @bindTabsSwitching() bindIssuesSorting: -> $("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable( @@ -104,7 +104,7 @@ class @Milestone ).disableSelection() - bindMergeRequestSorting: -> + bindTabsSwitching: -> $('a[data-toggle="tab"]').on 'show.bs.tab', (e) -> currentTabClass = $(e.target).data('show') previousTabClass = $(e.relatedTarget).data('show') @@ -112,7 +112,8 @@ class @Milestone $(previousTabClass).hide() $(currentTabClass).removeClass('hidden') $(currentTabClass).show() - + + bindMergeRequestSorting: -> $("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").sortable( connectWith: ".merge_requests-sortable-list", dropOnEmpty: true, diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee new file mode 100644 index 00000000000..e17a1adb648 --- /dev/null +++ b/app/assets/javascripts/milestone_select.js.coffee @@ -0,0 +1,65 @@ +class @MilestoneSelect + constructor: -> + $('.js-milestone-select').each (i, dropdown) -> + $dropdown = $(dropdown) + projectId = $dropdown.data('project-id') + milestonesUrl = $dropdown.data('milestones') + selectedMilestone = $dropdown.data('selected') + showNo = $dropdown.data('show-no') + showAny = $dropdown.data('show-any') + useId = $dropdown.data('use-id') + defaultLabel = $dropdown.data('default-label') + + $dropdown.glDropdown( + data: (term, callback) -> + $.ajax( + url: milestonesUrl + ).done (data) -> + if showNo + data.unshift( + id: '0' + title: 'No Milestone' + ) + + if showAny + data.unshift( + isAny: true + title: 'Any Milestone' + ) + + if data.length > 2 + data.splice 2, 0, 'divider' + + callback(data) + filterable: true + search: + fields: ['title'] + selectable: true + toggleLabel: (selected) -> + if selected && 'id' of selected + selected.title + else + defaultLabel + fieldName: $dropdown.data('field-name') + text: (milestone) -> + milestone.title + id: (milestone) -> + if !useId + if !milestone.isAny? + milestone.title + else + '' + else + milestone.id + isSelected: (milestone) -> + milestone.title is selectedMilestone + clicked: -> + page = $('body').data 'page' + isIssueIndex = page is 'projects:issues:index' + isMRIndex = page is page is 'projects:merge_requests:index' + + if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) + Issues.filterResults $dropdown.closest('form') + else if $dropdown.hasClass 'js-filter-submit' + $dropdown.closest('form').submit() + ) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index c95ead22e6c..ff06c57f2b5 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -30,8 +30,11 @@ class @Notes $(document).on "ajax:success", ".js-main-target-form", @addNote $(document).on "ajax:success", ".js-discussion-note-form", @addDiscussionNote + # catch note ajax errors + $(document).on "ajax:error", ".js-main-target-form", @addNoteError + # change note in UI after update - $(document).on "ajax:success", "form.edit_note", @updateNote + $(document).on "ajax:success", "form.edit-note", @updateNote # Edit note link $(document).on "click", ".js-note-edit", @showEditForm @@ -51,6 +54,9 @@ class @Notes $(document).on "ajax:complete", ".js-main-target-form", @reenableTargetFormSubmitButton $(document).on "ajax:success", ".js-main-target-form", @resetMainTargetForm + # reset main target form when clicking discard + $(document).on "click", ".js-note-discard", @resetMainTargetForm + # update the file name when an attachment is selected $(document).on "change", ".js-note-attachment-input", @updateFormAttachment @@ -72,7 +78,7 @@ class @Notes cleanBinding: -> $(document).off "ajax:success", ".js-main-target-form" $(document).off "ajax:success", ".js-discussion-note-form" - $(document).off "ajax:success", "form.edit_note" + $(document).off "ajax:success", "form.edit-note" $(document).off "click", ".js-note-edit" $(document).off "click", ".note-edit-cancel" $(document).off "click", ".js-note-delete" @@ -85,6 +91,7 @@ class @Notes $(document).off "keyup", ".js-note-text" $(document).off "click", ".js-note-target-reopen" $(document).off "click", ".js-note-target-close" + $(document).off "click", ".js-note-discard" $('.note .js-task-list-container').taskList('disable') $(document).off 'tasklist:changed', '.note .js-task-list-container' @@ -219,7 +226,7 @@ class @Notes Resets text and preview. Resets buttons. ### - resetMainTargetForm: -> + resetMainTargetForm: (e) => form = $(".js-main-target-form") # remove validation errors @@ -231,6 +238,8 @@ class @Notes form.find(".js-note-text").data("autosave").reset() + @updateTargetButtons(e) + reenableTargetFormSubmitButton: -> form = $(".js-main-target-form") @@ -274,8 +283,10 @@ class @Notes form.removeClass "js-new-note-form" form.find('.div-dropzone').remove() + # hide discard button + form.find('.js-note-discard').hide() + # setup preview buttons - form.find(".js-md-write-button, .js-md-preview-button").tooltip placement: "left" previewButton = form.find(".js-md-preview-button") textarea = form.find(".js-note-text") @@ -309,6 +320,10 @@ class @Notes addNote: (xhr, note, status) => @renderNote(note) + addNoteError: (xhr, note, status) => + flash = new Flash('Your comment could not be submitted! Please check your network connection and try again.', 'alert') + flash.pinTo('.md-area') + ### Called in response to the new note form being submitted @@ -328,6 +343,7 @@ class @Notes updateNote: (_xhr, note, _status) => # Convert returned HTML to a jQuery object so we can modify it further $html = $(note.html) + $('.js-timeago', $html).timeago() $html.syntaxHighlight() $html.find('.js-task-list-container').taskList('enable') @@ -345,24 +361,26 @@ class @Notes showEditForm: (e) -> e.preventDefault() note = $(this).closest(".note") - note.find(".note-body > .note-text").hide() - note.find(".note-header").hide() - base_form = note.find(".note-edit-form") - form = base_form.clone().insertAfter(base_form) - form.addClass('current-note-edit-form gfm-form') - form.find('.div-dropzone').remove() + note.addClass "is-editting" + form = note.find(".note-edit-form") + isNewForm = form.is(':not(.gfm-form)') + if isNewForm + form.addClass('gfm-form') + form.addClass('current-note-edit-form') # Show the attachment delete link note.find(".js-note-attachment-delete").show() # Setup markdown form - GitLab.GfmAutoComplete.setup() - new DropzoneInput(form) + if isNewForm + GitLab.GfmAutoComplete.setup() + new DropzoneInput(form) - form.show() textarea = form.find("textarea") textarea.focus() - autosize(textarea) + + if isNewForm + autosize(textarea) # HACK (rspeicher/DouweM): Work around a Chrome 43 bug(?). # The textarea has the correct value, Chrome just won't show it unless we @@ -371,7 +389,8 @@ class @Notes textarea.val "" textarea.val value - disableButtonIfEmptyField textarea, form.find(".js-comment-button") + if isNewForm + disableButtonIfEmptyField textarea, form.find(".js-comment-button") ### Called in response to clicking the edit note link @@ -381,9 +400,9 @@ class @Notes cancelEdit: (e) -> e.preventDefault() note = $(this).closest(".note") - note.find(".note-body > .note-text").show() - note.find(".note-header").show() - note.find(".current-note-edit-form").remove() + note.removeClass "is-editting" + note.find(".current-note-edit-form") + .removeClass("current-note-edit-form") ### Called in response to deleting a note of any kind. @@ -462,6 +481,11 @@ class @Notes form.find("#note_line_code").val dataHolder.data("lineCode") form.find("#note_noteable_type").val dataHolder.data("noteableType") form.find("#note_noteable_id").val dataHolder.data("noteableId") + form.find('.js-note-discard') + .show() + .removeClass('js-note-discard') + .addClass('js-close-discussion-note-form') + .text(form.find('.js-close-discussion-note-form').data('cancel-text')) @setupNoteForm form form.find(".js-note-text").focus() form.addClass "js-discussion-note-form" @@ -561,21 +585,52 @@ class @Notes updateCloseButton: (e) => textarea = $(e.target) form = textarea.parents('form') - form.find('.js-note-target-close').text('Close') + closebtn = form.find('.js-note-target-close') + closebtn.text(closebtn.data('original-text')) updateTargetButtons: (e) => textarea = $(e.target) form = textarea.parents('form') + reopenbtn = form.find('.js-note-target-reopen') + closebtn = form.find('.js-note-target-close') + discardbtn = form.find('.js-note-discard') + if textarea.val().trim().length > 0 - form.find('.js-note-target-reopen').text('Comment & reopen') - form.find('.js-note-target-close').text('Comment & close') - form.find('.js-note-target-reopen').addClass('btn-comment-and-reopen') - form.find('.js-note-target-close').addClass('btn-comment-and-close') + reopentext = reopenbtn.data('alternative-text') + closetext = closebtn.data('alternative-text') + + if reopenbtn.text() isnt reopentext + reopenbtn.text(reopentext) + + if closebtn.text() isnt closetext + closebtn.text(closetext) + + if reopenbtn.is(':not(.btn-comment-and-reopen)') + reopenbtn.addClass('btn-comment-and-reopen') + + if closebtn.is(':not(.btn-comment-and-close)') + closebtn.addClass('btn-comment-and-close') + + if discardbtn.is(':hidden') + discardbtn.show() else - form.find('.js-note-target-reopen').text('Reopen') - form.find('.js-note-target-close').text('Close') - form.find('.js-note-target-reopen').removeClass('btn-comment-and-reopen') - form.find('.js-note-target-close').removeClass('btn-comment-and-close') + reopentext = reopenbtn.data('original-text') + closetext = closebtn.data('original-text') + + if reopenbtn.text() isnt reopentext + reopenbtn.text(reopentext) + + if closebtn.text() isnt closetext + closebtn.text(closetext) + + if reopenbtn.is('.btn-comment-and-reopen') + reopenbtn.removeClass('btn-comment-and-reopen') + + if closebtn.is('.btn-comment-and-close') + closebtn.removeClass('btn-comment-and-close') + + if discardbtn.is(':visible') + discardbtn.hide() initTaskList: -> @enableTaskList() diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee index 9110b732adc..20f87440551 100644 --- a/app/assets/javascripts/profile.js.coffee +++ b/app/assets/javascripts/profile.js.coffee @@ -4,64 +4,27 @@ class @Profile $('.js-preferences-form').on 'change.preference', 'input[type=radio]', -> $(this).parents('form').submit() - $('.update-username form').on 'ajax:before', -> - $('.loading-gif').show() + $('.update-username').on 'ajax:before', -> + $('.loading-username').show() $(this).find('.update-success').hide() $(this).find('.update-failed').hide() - $('.update-username form').on 'ajax:complete', -> + $('.update-username').on 'ajax:complete', -> + $('.loading-username').hide() $(this).find('.btn-save').enable() $(this).find('.loading-gif').hide() $('.update-notifications').on 'ajax:complete', -> $(this).find('.btn-save').enable() - # Avatar management - - $avatarInput = $('.js-user-avatar-input') - $filename = $('.js-avatar-filename') - $modalCrop = $('.modal-profile-crop') - $modalCropImg = $('.modal-profile-crop-image') - - $('.js-choose-user-avatar-button').on "click", -> - $form = $(this).closest("form") - $form.find(".js-user-avatar-input").click() - - $modalCrop.on 'shown.bs.modal', -> - setTimeout ( -> # The cropper must be asynchronously initialized - $modalCropImg.cropper - aspectRatio: 1 - modal: false - scalable: false - rotatable: false - zoomable: false - - crop: (event) -> - ['x', 'y'].forEach (key) -> - $("#user_avatar_crop_#{key}").val(Math.floor(event[key])) - $("#user_avatar_crop_size").val(Math.floor(event.width)) - ), 0 - - $modalCrop.on 'hidden.bs.modal', -> - $modalCropImg.attr('src', '').cropper('destroy') - $avatarInput.val('') - $filename.text($filename.data('label')) - - $('.js-upload-user-avatar').on 'click', -> - $('.edit-user').submit() + $('.js-choose-user-avatar-button').bind "click", -> + form = $(this).closest("form") + form.find(".js-user-avatar-input").click() - $avatarInput.on "change", -> + $('.js-user-avatar-input').bind "change", -> form = $(this).closest("form") filename = $(this).val().replace(/^.*[\\\/]/, '') - $filename.data('label', $filename.text()).text(filename) - - reader = new FileReader - - reader.onload = (event) -> - $modalCrop.modal('show') - $modalCropImg.attr('src', event.target.result) - - fileData = reader.readAsDataURL(this.files[0]) + form.find(".js-avatar-filename").text(filename) $ -> # Extract the SSH Key title from its comment diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee index 76bc4ff42a2..87d313ed67c 100644 --- a/app/assets/javascripts/project.js.coffee +++ b/app/assets/javascripts/project.js.coffee @@ -11,7 +11,6 @@ class @Project $(@).toggleClass('active') url = $("#project_clone").val() - console.log("url",url) # Update the input field $('#project_clone').val(url) diff --git a/app/assets/javascripts/project_new.js.coffee b/app/assets/javascripts/project_new.js.coffee index fecdb9fc2e7..63dee4ed5d7 100644 --- a/app/assets/javascripts/project_new.js.coffee +++ b/app/assets/javascripts/project_new.js.coffee @@ -3,3 +3,16 @@ class @ProjectNew $('.project-edit-container').on 'ajax:before', => $('.project-edit-container').hide() $('.save-project-loader').show() + @toggleSettings() + @toggleSettingsOnclick() + + + toggleSettings: -> + checked = $("#project_builds_enabled").prop("checked") + if checked + $('.builds-feature').show() + else + $('.builds-feature').hide() + + toggleSettingsOnclick: -> + $("#project_builds_enabled").on 'click', @toggleSettings diff --git a/app/assets/javascripts/projects_list.js.coffee b/app/assets/javascripts/projects_list.js.coffee index ed5206368ce..e4c4bf3b273 100644 --- a/app/assets/javascripts/projects_list.js.coffee +++ b/app/assets/javascripts/projects_list.js.coffee @@ -2,6 +2,7 @@ init: -> $(".projects-list-filter").off('keyup') this.initSearch() + this.initPagination() initSearch: -> @timer = null @@ -29,3 +30,8 @@ # Change url so if user reload a page - search results are saved history.replaceState {page: project_filter_url}, document.title, project_filter_url dataType: "json" + + initPagination: -> + $('.projects-list-holder .pagination').on('ajax:success', (e, data) -> + $('.projects-list-holder').replaceWith(data.html) + ) diff --git a/app/assets/javascripts/shortcuts.js.coffee b/app/assets/javascripts/shortcuts.js.coffee index 9c7c2474aa4..100e3aac535 100644 --- a/app/assets/javascripts/shortcuts.js.coffee +++ b/app/assets/javascripts/shortcuts.js.coffee @@ -4,11 +4,15 @@ class @Shortcuts Mousetrap.reset() Mousetrap.bind('?', @selectiveHelp) Mousetrap.bind('s', Shortcuts.focusSearch) + Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], @toggleMarkdownPreview) Mousetrap.bind('t', -> Turbolinks.visit(findFileURL)) if findFileURL? selectiveHelp: (e) => Shortcuts.showHelp(e, @enabledHelp) + toggleMarkdownPreview: (e) => + $(document).triggerHandler('markdown-preview:toggle', [e]) + @showHelp: (e, location) -> if $('#modal-shortcuts').length > 0 $('#modal-shortcuts').modal('show') @@ -35,3 +39,14 @@ $(document).on 'click.more_help', '.js-more-help-button', (e) -> $(@).remove() $('.hidden-shortcut').show() e.preventDefault() + +Mousetrap.stopCallback = (-> + defaultStopCallback = Mousetrap.stopCallback + + return (e, element, combo) -> + # allowed shortcuts if textarea, input, contenteditable are focused + if ['ctrl+shift+p', 'command+shift+p'].indexOf(combo) != -1 + return false + else + return defaultStopCallback.apply(@, arguments) +)() diff --git a/app/assets/javascripts/sidebar.js.coffee b/app/assets/javascripts/sidebar.js.coffee index cff309c5972..eea3f5ee910 100644 --- a/app/assets/javascripts/sidebar.js.coffee +++ b/app/assets/javascripts/sidebar.js.coffee @@ -1,8 +1,7 @@ -$(document).on("click", '.toggle-nav-collapse', (e) -> - e.preventDefault() - collapsed = 'page-sidebar-collapsed' - expanded = 'page-sidebar-expanded' +collapsed = 'page-sidebar-collapsed' +expanded = 'page-sidebar-expanded' +toggleSidebar = -> $('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}") $('header').toggleClass("header-collapsed header-expanded") $('.sidebar-wrapper').toggleClass("sidebar-collapsed sidebar-expanded") @@ -14,4 +13,15 @@ $(document).on("click", '.toggle-nav-collapse', (e) -> niceScrollBars.updateScrollBar(); ), 300 +$(document).on("click", '.toggle-nav-collapse', (e) -> + e.preventDefault() + + toggleSidebar() ) + +$ -> + size = bp.getBreakpointSize() + + if size is "xs" or size is "sm" + if $('.page-with-sidebar').hasClass(expanded) + toggleSidebar() diff --git a/app/assets/javascripts/subscription.js.coffee b/app/assets/javascripts/subscription.js.coffee index 7f41616d4e7..084f0e0dc65 100644 --- a/app/assets/javascripts/subscription.js.coffee +++ b/app/assets/javascripts/subscription.js.coffee @@ -1,17 +1,21 @@ class @Subscription - constructor: (url) -> - $(".subscribe-button").unbind("click").click (event)=> - btn = $(event.currentTarget) - action = btn.find("span").text() - current_status = $(".subscription-status").attr("data-status") - btn.prop("disabled", true) - - $.post url, => - btn.prop("disabled", false) - status = if current_status == "subscribed" then "unsubscribed" else "subscribed" - $(".subscription-status").attr("data-status", status) - action = if status == "subscribed" then "Unsubscribe" else "Subscribe" - btn.find("span").text(action) - $(".subscription-status>div").toggleClass("hidden") + constructor: (container) -> + $container = $(container) + @url = $container.attr('data-url') + @subscribe_button = $container.find('.subscribe-button') + @subscription_status = $container.find('.subscription-status') + @subscribe_button.unbind('click').click(@toggleSubscription) - + toggleSubscription: (event) => + btn = $(event.currentTarget) + action = btn.find('span').text() + current_status = @subscription_status.attr('data-status') + btn.prop('disabled', true) + + $.post @url, => + btn.prop('disabled', false) + status = if current_status == 'subscribed' then 'unsubscribed' else 'subscribed' + @subscription_status.attr('data-status', status) + action = if status == 'subscribed' then 'Unsubscribe' else 'Subscribe' + btn.find('span').text(action) + @subscription_status.find('>div').toggleClass('hidden') diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee new file mode 100644 index 00000000000..b6b4bd90e6a --- /dev/null +++ b/app/assets/javascripts/todos.js.coffee @@ -0,0 +1,56 @@ +class @Todos + constructor: (@name) -> + @clearListeners() + @initBtnListeners() + + clearListeners: -> + $('.done-todo').off('click') + $('.js-todos-mark-all').off('click') + + initBtnListeners: -> + $('.done-todo').on('click', @doneClicked) + $('.js-todos-mark-all').on('click', @allDoneClicked) + + doneClicked: (e) => + e.preventDefault() + e.stopImmediatePropagation() + + $this = $(e.currentTarget) + $this.disable() + + $.ajax + type: 'POST' + url: $this.attr('href') + dataType: 'json' + data: '_method': 'delete' + success: (data) => + @clearDone $this.closest('li') + @updateBadges data + + allDoneClicked: (e) => + e.preventDefault() + e.stopImmediatePropagation() + + $this = $(e.currentTarget) + $this.disable() + + $.ajax + type: 'POST' + url: $this.attr('href') + dataType: 'json' + data: '_method': 'delete' + success: (data) => + $this.remove() + $('.js-todos-list').remove() + @updateBadges data + + clearDone: ($row) -> + $ul = $row.closest('ul') + $row.remove() + + if not $ul.find('li').length + $ul.parents('.panel').remove() + + updateBadges: (data) -> + $('.todos-pending .badge, .todos-pending-count').text data.count + $('.todos-done .badge').text data.done_count diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index 9467011799f..3d6452d2f46 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -3,6 +3,94 @@ class @UsersSelect @usersPath = "/autocomplete/users.json" @userPath = "/autocomplete/users/:id.json" + $('.js-user-search').each (i, dropdown) => + $dropdown = $(dropdown) + @projectId = $dropdown.data('project-id') + @showCurrentUser = $dropdown.data('current-user') + showNullUser = $dropdown.data('null-user') + showAnyUser = $dropdown.data('any-user') + firstUser = $dropdown.data('first-user') + selectedId = $dropdown.data('selected') + defaultLabel = $dropdown.data('default-label') + + $dropdown.glDropdown( + data: (term, callback) => + @users term, (users) => + if term.length is 0 + showDivider = 0 + + if firstUser + # Move current user to the front of the list + for obj, index in users + if obj.username == firstUser + users.splice(index, 1) + users.unshift(obj) + break + + if showNullUser + showDivider += 1 + users.unshift( + name: 'Unassigned', + id: 0 + ) + + if showAnyUser + showDivider += 1 + name = showAnyUser + name = 'Any User' if name == true + anyUser = { + name: name, + id: null + } + users.unshift(anyUser) + + if showDivider + users.splice(showDivider, 0, "divider") + + # Send the data back + callback users + filterable: true + filterRemote: true + search: + fields: ['name', 'username'] + selectable: true + fieldName: $dropdown.data('field-name') + toggleLabel: (selected) -> + if selected && 'id' of selected + selected.name + else + defaultLabel + clicked: -> + page = $('body').data 'page' + isIssueIndex = page is 'projects:issues:index' + isMRIndex = page is page is 'projects:merge_requests:index' + + if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) + Issues.filterResults $dropdown.closest('form') + else if $dropdown.hasClass 'js-filter-submit' + $dropdown.closest('form').submit() + renderRow: (user) -> + username = if user.username then "@#{user.username}" else "" + avatar = if user.avatar_url then user.avatar_url else false + selected = if user.id is selectedId then "is-active" else "" + img = "" + + if avatar + img = "<img src='#{avatar}' class='avatar avatar-inline' width='30' />" + + "<li> + <a href='#' class='dropdown-menu-user-link #{selected}'> + #{img} + <strong class='dropdown-menu-user-full-name'> + #{user.name} + </strong> + <span class='dropdown-menu-user-username'> + #{username} + </span> + </a> + </li>" + ) + $('.ajax-users-select').each (i, select) => @projectId = $(select).data('project-id') @groupId = $(select).data('group-id') |