diff options
Diffstat (limited to 'app/assets/javascripts')
-rw-r--r-- | app/assets/javascripts/api.js.coffee | 14 | ||||
-rw-r--r-- | app/assets/javascripts/application.js.coffee | 8 | ||||
-rw-r--r-- | app/assets/javascripts/awards_handler.coffee | 77 | ||||
-rw-r--r-- | app/assets/javascripts/ci/build.coffee | 13 | ||||
-rw-r--r-- | app/assets/javascripts/dispatcher.js.coffee | 3 | ||||
-rw-r--r-- | app/assets/javascripts/gl_dropdown.js.coffee | 272 | ||||
-rw-r--r-- | app/assets/javascripts/issue_status_select.js.coffee | 11 | ||||
-rw-r--r-- | app/assets/javascripts/labels_select.js.coffee | 92 | ||||
-rw-r--r-- | app/assets/javascripts/markdown_preview.js.coffee | 46 | ||||
-rw-r--r-- | app/assets/javascripts/merge_request_tabs.js.coffee | 3 | ||||
-rw-r--r-- | app/assets/javascripts/milestone_select.js.coffee | 60 | ||||
-rw-r--r-- | app/assets/javascripts/notes.js.coffee | 104 | ||||
-rw-r--r-- | app/assets/javascripts/profile.js.coffee | 7 | ||||
-rw-r--r-- | app/assets/javascripts/projects_list.js.coffee | 6 | ||||
-rw-r--r-- | app/assets/javascripts/shortcuts.js.coffee | 15 | ||||
-rw-r--r-- | app/assets/javascripts/users_select.js.coffee | 75 |
16 files changed, 741 insertions, 65 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..1212e89975b 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -220,17 +220,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') 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/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 54b28f2dd8d..ee81fee5868 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -74,8 +74,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() diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee new file mode 100644 index 00000000000..b94de4c7b5e --- /dev/null +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -0,0 +1,272 @@ +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("") + + 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" + + 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/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/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee new file mode 100644 index 00000000000..5ade2cb66cb --- /dev/null +++ b/app/assets/javascripts/labels_select.js.coffee @@ -0,0 +1,92 @@ +class @LabelsSelect + constructor: -> + $('.js-label-select').each (i, 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') + + 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 "" && 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) -> + # We have to fetch the JS version of the labels list because there is no + # public facing JSON url for labels + $.ajax( + url: labelUrl + ).done (data) -> + html = $(data) + data = [] + html.find('.label-row a').each -> + data.push( + title: $(@).text().trim() + ) + + if showNo + data.unshift( + id: "0" + title: 'No label' + ) + + if showAny + data.unshift( + 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 "" && 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 + fieldName: $(dropdown).data('field-name') + id: (label) -> + label.title + clicked: -> + if $(dropdown).hasClass "js-filter-submit" + $(dropdown).parents('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..8322b4c46ad 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -189,7 +189,7 @@ class @MergeRequestTabs $('.container-fluid').removeClass('container-limited') shrinkView: -> - $gutterIcon = $('.gutter-toggle i') + $gutterIcon = $('.js-sidebar-toggle i') # Wait until listeners are set setTimeout( -> @@ -197,4 +197,3 @@ class @MergeRequestTabs if $gutterIcon.is('.fa-angle-double-right') $gutterIcon.closest('a').trigger('click',[true]) , 0) - diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee new file mode 100644 index 00000000000..5e884454a65 --- /dev/null +++ b/app/assets/javascripts/milestone_select.js.coffee @@ -0,0 +1,60 @@ +class @MilestoneSelect + constructor: -> + $('.js-milestone-select').each (i, 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') + + $(dropdown).glDropdown( + data: (term, callback) -> + $.ajax( + url: milestonesUrl + ).done (data) -> + html = $(data) + data = [] + html.find('.milestone strong a').each -> + link = $(@).attr("href").split("/") + data.push( + id: link[link.length - 1] + title: $(@).text().trim() + ) + + if showNo + data.unshift( + id: "0" + title: 'No Milestone' + ) + + if showAny + data.unshift( + title: 'Any Milestone' + ) + + if data.length > 2 + data.splice 2, 0, "divider" + + callback(data) + filterable: true + search: + fields: ['title'] + selectable: true + fieldName: $(dropdown).data('field-name') + text: (milestone) -> + milestone.title + id: (milestone) -> + if !useId + if milestone.title isnt "Any milestone" + milestone.title + else + "" + else + milestone.id + isSelected: (milestone) -> + milestone.title is selectedMilestone + clicked: -> + if $(dropdown).hasClass "js-filter-submit" + $(dropdown).parents('form').submit() + ) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index c95ead22e6c..75d7f52bbb6 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 @@ -347,22 +362,26 @@ class @Notes 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() + form = note.find(".note-edit-form") + isNewForm = form.is(':not(.gfm-form)') + if isNewForm + form.addClass('gfm-form') + form.addClass('current-note-edit-form') + form.show() # 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 +390,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 @@ -383,7 +403,9 @@ class @Notes note = $(this).closest(".note") note.find(".note-body > .note-text").show() note.find(".note-header").show() - note.find(".current-note-edit-form").remove() + note.find(".current-note-edit-form") + .removeClass("current-note-edit-form") + .hide() ### Called in response to deleting a note of any kind. @@ -462,6 +484,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 +588,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(':not(.btn-comment-and-reopen)') + reopenbtn.removeClass('btn-comment-and-reopen') + + if closebtn.is(':not(.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..59d44c30bee 100644 --- a/app/assets/javascripts/profile.js.coffee +++ b/app/assets/javascripts/profile.js.coffee @@ -4,12 +4,13 @@ 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() 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/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index 9467011799f..987c6f4b8d2 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -3,6 +3,81 @@ class @UsersSelect @usersPath = "/autocomplete/users.json" @userPath = "/autocomplete/users/:id.json" + $('.js-user-search').each (i, 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') + + $(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') + clicked: -> + if $(dropdown).hasClass "js-filter-submit" + $(dropdown).parents('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') |