Skip to content
Snippets Groups Projects
Commit aaa9509d authored by Fatih Acet's avatar Fatih Acet
Browse files

ES6ify all the things!

parent 56b79181
No related branches found
No related tags found
No related merge requests found
Showing
with 1616 additions and 1288 deletions
(function() {
if (window.GitLab == null) {
window.GitLab = {};
}
GitLab.GfmAutoComplete = {
dataLoading: false,
dataLoaded: false,
cachedData: {},
dataSource: '',
Emoji: {
template: '<li>${name} <img alt="${name}" height="20" src="${path}" width="20" /></li>'
},
Members: {
template: '<li>${username} <small>${title}</small></li>'
},
Labels: {
template: '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>'
},
Issues: {
template: '<li><small>${id}</small> ${title}</li>'
},
Milestones: {
template: '<li>${title}</li>'
},
Loading: {
template: '<li><i class="fa fa-refresh fa-spin"></i> Loading...</li>'
},
DefaultOptions: {
sorter: function(query, items, searchKey) {
if ((items[0].name != null) && items[0].name === 'loading') {
return items;
}
return $.fn.atwho["default"].callbacks.sorter(query, items, searchKey);
},
filter: function(query, data, searchKey) {
if (data[0] === 'loading') {
return data;
}
return $.fn.atwho["default"].callbacks.filter(query, data, searchKey);
},
beforeInsert: function(value) {
if (!GitLab.GfmAutoComplete.dataLoaded) {
return this.at;
} else {
return value;
}
}
},
setup: function(wrap) {
this.input = $('.js-gfm-input');
this.destroyAtWho();
this.setupAtWho();
if (this.dataSource) {
if (!this.dataLoading && !this.cachedData) {
this.dataLoading = true;
setTimeout((function(_this) {
return function() {
var fetch;
fetch = _this.fetchData(_this.dataSource);
return fetch.done(function(data) {
_this.dataLoading = false;
return _this.loadData(data);
});
};
})(this), 1000);
}
if (this.cachedData != null) {
return this.loadData(this.cachedData);
}
}
},
setupAtWho: function() {
this.input.atwho({
at: ':',
displayTpl: (function(_this) {
return function(value) {
if (value.path != null) {
return _this.Emoji.template;
} else {
return _this.Loading.template;
}
};
})(this),
insertTpl: ':${name}:',
data: ['loading'],
callbacks: {
sorter: this.DefaultOptions.sorter,
filter: this.DefaultOptions.filter,
beforeInsert: this.DefaultOptions.beforeInsert
}
});
this.input.atwho({
at: '@',
displayTpl: (function(_this) {
return function(value) {
if (value.username != null) {
return _this.Members.template;
} else {
return _this.Loading.template;
}
};
})(this),
insertTpl: '${atwho-at}${username}',
searchKey: 'search',
data: ['loading'],
callbacks: {
sorter: this.DefaultOptions.sorter,
filter: this.DefaultOptions.filter,
beforeInsert: this.DefaultOptions.beforeInsert,
beforeSave: function(members) {
return $.map(members, function(m) {
var title;
if (m.username == null) {
return m;
}
title = m.name;
if (m.count) {
title += " (" + m.count + ")";
}
return {
username: m.username,
title: sanitize(title),
search: sanitize(m.username + " " + m.name)
};
});
}
}
});
this.input.atwho({
at: '#',
alias: 'issues',
searchKey: 'search',
displayTpl: (function(_this) {
return function(value) {
if (value.title != null) {
return _this.Issues.template;
} else {
return _this.Loading.template;
}
};
})(this),
data: ['loading'],
insertTpl: '${atwho-at}${id}',
callbacks: {
sorter: this.DefaultOptions.sorter,
filter: this.DefaultOptions.filter,
beforeInsert: this.DefaultOptions.beforeInsert,
beforeSave: function(issues) {
return $.map(issues, function(i) {
if (i.title == null) {
return i;
}
return {
id: i.iid,
title: sanitize(i.title),
search: i.iid + " " + i.title
};
});
}
}
});
this.input.atwho({
at: '%',
alias: 'milestones',
searchKey: 'search',
displayTpl: (function(_this) {
return function(value) {
if (value.title != null) {
return _this.Milestones.template;
} else {
return _this.Loading.template;
}
};
})(this),
insertTpl: '${atwho-at}"${title}"',
data: ['loading'],
callbacks: {
beforeSave: function(milestones) {
return $.map(milestones, function(m) {
if (m.title == null) {
return m;
}
return {
id: m.iid,
title: sanitize(m.title),
search: "" + m.title
};
});
}
}
});
this.input.atwho({
at: '!',
alias: 'mergerequests',
searchKey: 'search',
displayTpl: (function(_this) {
return function(value) {
if (value.title != null) {
return _this.Issues.template;
} else {
return _this.Loading.template;
}
};
})(this),
data: ['loading'],
insertTpl: '${atwho-at}${id}',
callbacks: {
sorter: this.DefaultOptions.sorter,
filter: this.DefaultOptions.filter,
beforeInsert: this.DefaultOptions.beforeInsert,
beforeSave: function(merges) {
return $.map(merges, function(m) {
if (m.title == null) {
return m;
}
return {
id: m.iid,
title: sanitize(m.title),
search: m.iid + " " + m.title
};
});
}
}
});
return this.input.atwho({
at: '~',
alias: 'labels',
searchKey: 'search',
displayTpl: this.Labels.template,
insertTpl: '${atwho-at}${title}',
callbacks: {
beforeSave: function(merges) {
var sanitizeLabelTitle;
sanitizeLabelTitle = function(title) {
if (/[\w\?&]+\s+[\w\?&]+/g.test(title)) {
return "\"" + (sanitize(title)) + "\"";
} else {
return sanitize(title);
}
};
return $.map(merges, function(m) {
return {
title: sanitizeLabelTitle(m.title),
color: m.color,
search: "" + m.title
};
});
}
}
});
},
destroyAtWho: function() {
return this.input.atwho('destroy');
},
fetchData: function(dataSource) {
return $.getJSON(dataSource);
},
loadData: function(data) {
this.cachedData = data;
this.dataLoaded = true;
this.input.atwho('load', '@', data.members);
this.input.atwho('load', 'issues', data.issues);
this.input.atwho('load', 'milestones', data.milestones);
this.input.atwho('load', 'mergerequests', data.mergerequests);
this.input.atwho('load', ':', data.emojis);
this.input.atwho('load', '~', data.labels);
return $(':focus').trigger('keyup');
}
};
}).call(this);
# Creates the variables for setting up GFM auto-completion
window.GitLab ?= {}
GitLab.GfmAutoComplete =
dataLoading: false
dataLoaded: false
cachedData: {}
dataSource: ''
# Emoji
Emoji:
template: '<li>${name} <img alt="${name}" height="20" src="${path}" width="20" /></li>'
# Team Members
Members:
template: '<li>${username} <small>${title}</small></li>'
Labels:
template: '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>'
# Issues and MergeRequests
Issues:
template: '<li><small>${id}</small> ${title}</li>'
# Milestones
Milestones:
template: '<li>${title}</li>'
Loading:
template: '<li><i class="fa fa-refresh fa-spin"></i> Loading...</li>'
DefaultOptions:
sorter: (query, items, searchKey) ->
return items if items[0].name? and items[0].name is 'loading'
$.fn.atwho.default.callbacks.sorter(query, items, searchKey)
filter: (query, data, searchKey) ->
return data if data[0] is 'loading'
$.fn.atwho.default.callbacks.filter(query, data, searchKey)
beforeInsert: (value) ->
if not GitLab.GfmAutoComplete.dataLoaded
@at
else
value
# Add GFM auto-completion to all input fields, that accept GFM input.
setup: (wrap) ->
@input = $('.js-gfm-input')
# destroy previous instances
@destroyAtWho()
# set up instances
@setupAtWho()
if @dataSource
if not @dataLoading and not @cachedData
@dataLoading = true
# We should wait until initializations are done
# and only trigger the last .setup since
# The previous .dataSource belongs to the previous issuable
# and the last one will have the **proper** .dataSource property
# TODO: Make this a singleton and turn off events when moving to another page
setTimeout( =>
fetch = @fetchData(@dataSource)
fetch.done (data) =>
@dataLoading = false
@loadData(data)
, 1000)
if @cachedData?
@loadData(@cachedData)
setupAtWho: ->
# Emoji
@input.atwho
at: ':'
displayTpl: (value) =>
if value.path?
@Emoji.template
else
@Loading.template
insertTpl: ':${name}:'
data: ['loading']
callbacks:
sorter: @DefaultOptions.sorter
filter: @DefaultOptions.filter
beforeInsert: @DefaultOptions.beforeInsert
# Team Members
@input.atwho
at: '@'
displayTpl: (value) =>
if value.username?
@Members.template
else
@Loading.template
insertTpl: '${atwho-at}${username}'
searchKey: 'search'
data: ['loading']
callbacks:
sorter: @DefaultOptions.sorter
filter: @DefaultOptions.filter
beforeInsert: @DefaultOptions.beforeInsert
beforeSave: (members) ->
$.map members, (m) ->
return m if not m.username?
title = m.name
title += " (#{m.count})" if m.count
username: m.username
title: sanitize(title)
search: sanitize("#{m.username} #{m.name}")
@input.atwho
at: '#'
alias: 'issues'
searchKey: 'search'
displayTpl: (value) =>
if value.title?
@Issues.template
else
@Loading.template
data: ['loading']
insertTpl: '${atwho-at}${id}'
callbacks:
sorter: @DefaultOptions.sorter
filter: @DefaultOptions.filter
beforeInsert: @DefaultOptions.beforeInsert
beforeSave: (issues) ->
$.map issues, (i) ->
return i if not i.title?
id: i.iid
title: sanitize(i.title)
search: "#{i.iid} #{i.title}"
@input.atwho
at: '%'
alias: 'milestones'
searchKey: 'search'
displayTpl: (value) =>
if value.title?
@Milestones.template
else
@Loading.template
insertTpl: '${atwho-at}"${title}"'
data: ['loading']
callbacks:
beforeSave: (milestones) ->
$.map milestones, (m) ->
return m if not m.title?
id: m.iid
title: sanitize(m.title)
search: "#{m.title}"
@input.atwho
at: '!'
alias: 'mergerequests'
searchKey: 'search'
displayTpl: (value) =>
if value.title?
@Issues.template
else
@Loading.template
data: ['loading']
insertTpl: '${atwho-at}${id}'
callbacks:
sorter: @DefaultOptions.sorter
filter: @DefaultOptions.filter
beforeInsert: @DefaultOptions.beforeInsert
beforeSave: (merges) ->
$.map merges, (m) ->
return m if not m.title?
id: m.iid
title: sanitize(m.title)
search: "#{m.iid} #{m.title}"
@input.atwho
at: '~'
alias: 'labels'
searchKey: 'search'
displayTpl: @Labels.template
insertTpl: '${atwho-at}${title}'
callbacks:
beforeSave: (merges) ->
sanitizeLabelTitle = (title)->
if /[\w\?&]+\s+[\w\?&]+/g.test(title)
"\"#{sanitize(title)}\""
else
sanitize(title)
$.map merges, (m) ->
title: sanitizeLabelTitle(m.title)
color: m.color
search: "#{m.title}"
destroyAtWho: ->
@input.atwho('destroy')
fetchData: (dataSource) ->
$.getJSON(dataSource)
loadData: (data) ->
@cachedData = data
@dataLoaded = true
# load members
@input.atwho 'load', '@', data.members
# load issues
@input.atwho 'load', 'issues', data.issues
# load milestones
@input.atwho 'load', 'milestones', data.milestones
# load merge requests
@input.atwho 'load', 'mergerequests', data.mergerequests
# load emojis
@input.atwho 'load', ':', data.emojis
# load labels
@input.atwho 'load', '~', data.labels
# This trigger at.js again
# otherwise we would be stuck with loading until the user types
$(':focus').trigger('keyup')
This diff is collapsed.
class GitLabDropdownFilter
BLUR_KEYCODES = [27, 40]
ARROW_KEY_CODES = [38, 40]
HAS_VALUE_CLASS = "has-value"
constructor: (@input, @options) ->
{
@filterInputBlur = true
} = @options
$inputContainer = @input.parent()
$clearButton = $inputContainer.find('.js-dropdown-input-clear')
@indeterminateIds = []
# Clear click
$clearButton.on 'click', (e) =>
e.preventDefault()
e.stopPropagation()
@input
.val('')
.trigger('keyup')
.focus()
# Key events
timeout = ""
@input.on "keyup", (e) =>
keyCode = e.which
return if ARROW_KEY_CODES.indexOf(keyCode) >= 0
if @input.val() isnt "" and !$inputContainer.hasClass HAS_VALUE_CLASS
$inputContainer.addClass HAS_VALUE_CLASS
else if @input.val() is "" and $inputContainer.hasClass HAS_VALUE_CLASS
$inputContainer.removeClass HAS_VALUE_CLASS
if keyCode is 13
return false
# Only filter asynchronously only if option remote is set
if @options.remote
clearTimeout timeout
timeout = setTimeout =>
blur_field = @shouldBlur keyCode
if blur_field and @filterInputBlur
@input.blur()
@options.query @input.val(), (data) =>
@options.callback(data)
, 250
else
@filter @input.val()
shouldBlur: (keyCode) ->
return BLUR_KEYCODES.indexOf(keyCode) >= 0
filter: (search_text) ->
@options.onFilter(search_text) if @options.onFilter
data = @options.data()
if data? and not @options.filterByText
results = data
if search_text isnt ''
# When data is an array of objects therefore [object Array] e.g.
# [
# { prop: 'foo' },
# { prop: 'baz' }
# ]
if _.isArray(data)
results = fuzzaldrinPlus.filter(data, search_text,
key: @options.keys
)
else
# If data is grouped therefore an [object Object]. e.g.
# {
# groupName1: [
# { prop: 'foo' },
# { prop: 'baz' }
# ],
# groupName2: [
# { prop: 'abc' },
# { prop: 'def' }
# ]
# }
if gl.utils.isObject data
results = {}
for key, group of data
tmp = fuzzaldrinPlus.filter(group, search_text,
key: @options.keys
)
if tmp.length
results[key] = tmp.map (item) -> item
@options.callback results
else
elements = @options.elements()
if search_text
elements.each ->
$el = $(@)
matches = fuzzaldrinPlus.match($el.text().trim(), search_text)
unless $el.is('.dropdown-header')
if matches.length
$el.show()
else
$el.hide()
else
elements.show()
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"
INDETERMINATE_CLASS = "is-indeterminate"
currentIndex = -1
FILTER_INPUT = '.dropdown-input .dropdown-input-field'
constructor: (@el, @options) ->
self = @
selector = $(@el).data "target"
@dropdown = if selector? then $(selector) else $(@el).parent()
# Set Defaults
{
# If no input is passed create a default one
@filterInput = @getElement(FILTER_INPUT)
@highlight = false
@filterInputBlur = true
} = @options
self = @
# If selector was passed
if _.isString(@filterInput)
@filterInput = @getElement(@filterInput)
searchFields = if @options.search then @options.search.fields else [];
if @options.data
# If we provided data
# data could be an array of objects or a group of arrays
if _.isObject(@options.data) and not _.isFunction(@options.data)
@fullData = @options.data
@parseData @options.data
else
# Remote data
@remote = new GitLabDropdownRemote @options.data, {
dataType: @options.dataType,
beforeSend: @toggleLoading.bind(@)
success: (data) =>
@fullData = data
@parseData @fullData
@filter.input.trigger('keyup') if @options.filterable and @filter and @filter.input
}
# Init filterable
if @options.filterable
@filter = new GitLabDropdownFilter @filterInput,
filterInputBlur: @filterInputBlur
filterByText: @options.filterByText
onFilter: @options.onFilter
remote: @options.filterRemote
query: @options.data
keys: searchFields
elements: =>
selector = '.dropdown-content li:not(.divider)'
if @dropdown.find('.dropdown-toggle-page').length
selector = ".dropdown-page-one #{selector}"
return $(selector)
data: =>
return @fullData
callback: (data) =>
@parseData data
unless @filterInput.val() is ''
selector = '.dropdown-content li:not(.divider):visible'
if @dropdown.find('.dropdown-toggle-page').length
selector = ".dropdown-page-one #{selector}"
$(selector, @dropdown)
.first()
.find('a')
.addClass('is-focused')
currentIndex = 0
# Event listeners
@dropdown.on "shown.bs.dropdown", @opened
@dropdown.on "hidden.bs.dropdown", @hidden
$(@el).on "update.label", @updateLabel
@dropdown.on "click", ".dropdown-menu, .dropdown-menu-close", @shouldPropagate
@dropdown.on 'keyup', (e) =>
if e.which is 27 # Escape key
$('.dropdown-menu-close', @dropdown).trigger 'click'
@dropdown.on 'blur', 'a', (e) =>
if e.relatedTarget?
$relatedTarget = $(e.relatedTarget)
$dropdownMenu = $relatedTarget.closest('.dropdown-menu')
if $dropdownMenu.length is 0
@dropdown.removeClass('open')
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) ->
$el = $(@)
selected = self.rowClicked $el
if self.options.clicked
self.options.clicked(selected, $el, e)
$el.trigger('blur')
# Finds an element inside wrapper element
getElement: (selector) ->
@dropdown.find selector
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
# Focus first visible input on active page
@dropdown.find('[class^="dropdown-page-"]:visible :text:visible:first').focus()
parseData: (data) ->
@renderedData = data
if @options.filterable and data.length is 0
# render no matching results
html = [@noResults()]
else
# Handle array groups
if gl.utils.isObject data
html = []
for name, groupData of data
# Add header for each group
html.push(@renderItem(header: name, name))
@renderData(groupData, name)
.map (item) ->
html.push item
else
# Render each row
html = @renderData(data)
# Render the full menu
full_html = @renderMenu(html)
@appendMenu(full_html)
renderData: (data, group = false) ->
data.map (obj, index) =>
return @renderItem(obj, group, index)
shouldPropagate: (e) =>
if @options.multiSelect
$target = $(e.target)
if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon') and not $target.data('is-link')
e.stopPropagation()
return false
else
return true
opened: =>
@addArrowKeyEvent()
if @options.setIndeterminateIds
@options.setIndeterminateIds.call(@)
if @options.setActiveIds
@options.setActiveIds.call(@)
# Makes indeterminate items effective
if @fullData and @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')
@parseData @fullData
contentHtml = $('.dropdown-content', @dropdown).html()
if @remote && contentHtml is ""
@remote.execute()
if @options.filterable
@filterInput.focus()
@dropdown.trigger('shown.gl.dropdown')
hidden: (e) =>
@removeArrayKeyEvent()
$input = @dropdown.find(".dropdown-input-field")
if @options.filterable
$input
.blur()
.val("")
# Triggering 'keyup' will re-render the dropdown which is not always required
# specially if we want to keep the state of the dropdown needed for bulk-assignment
if not @options.persistWhenHide
$input.trigger("keyup")
if @dropdown.find(".dropdown-toggle-page").length
$('.dropdown-menu', @dropdown).removeClass PAGE_TWO_CLASS
if @options.hidden
@options.hidden.call(@,e)
@dropdown.trigger('hidden.gl.dropdown')
# Render the full menu
renderMenu: (html) ->
menu_html = ""
if @options.renderMenu
menu_html = @options.renderMenu(html)
else
menu_html = $('<ul />')
.append(html)
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)
.empty()
.append(html)
# Render the row
renderItem: (data, group = false, index = false) ->
html = ""
# Divider
return "<li class='divider'></li>" if data is "divider"
# Separator is a full-width divider
return "<li class='separator'></li>" if data is "separator"
# Header
return "<li class='dropdown-header'>#{data.header}</li>" if data.header?
if @options.renderRow
# Call the render function
html = @options.renderRow.call(@options, data, @)
else
if not selected
value = if @options.id then @options.id(data) else data.id
fieldName = @options.fieldName
field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
if field.length
selected = true
# Set URL
if @options.url?
url = @options.url(data)
else
url = if data.url? then data.url else '#'
# Set Text
if @options.text?
text = @options.text(data)
else
text = if data.text? then data.text else ''
cssClass = "";
if selected
cssClass = "is-active"
if @highlight
text = @highlightTextMatches(text, @filterInput.val())
if group
groupAttrs = "data-group='#{group}' data-index='#{index}'"
else
groupAttrs = ''
html = "<li>
<a href='#{url}' #{groupAttrs} class='#{cssClass}'>
#{text}
</a>
</li>"
return html
highlightTextMatches: (text, term) ->
occurrences = fuzzaldrinPlus.match(text, term)
text.split('').map((character, i) ->
if i in occurrences then "<b>#{character}</b>" else character
).join('')
noResults: ->
html = "<li class='dropdown-menu-empty-link'>
<a href='#' class='is-focused'>
No matching results.
</a>
</li>"
highlightRow: (index) ->
if @filterInput.val() isnt ""
selector = '.dropdown-content li:first-child a'
if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one .dropdown-content li:first-child a"
@getElement(selector).addClass 'is-focused'
rowClicked: (el) ->
fieldName = @options.fieldName
isInput = $(@el).is('input')
if @renderedData
groupName = el.data('group')
if groupName
selectedIndex = el.data('index')
selectedObject = @renderedData[groupName][selectedIndex]
else
selectedIndex = el.closest('li').index()
selectedObject = @renderedData[selectedIndex]
value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
if isInput
field = $(@el)
else
field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
if el.hasClass(ACTIVE_CLASS)
el.removeClass(ACTIVE_CLASS)
if isInput
field.val('')
else
field.remove()
# Toggle the dropdown label
if @options.toggleLabel
@updateLabel(selectedObject, el, @)
else
selectedObject
else if el.hasClass(INDETERMINATE_CLASS)
el.addClass ACTIVE_CLASS
el.removeClass INDETERMINATE_CLASS
if not value?
field.remove()
if not field.length and fieldName
@addInput(fieldName, value)
return selectedObject
else
if not @options.multiSelect or el.hasClass('dropdown-clear-active')
@dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS
unless isInput
@dropdown.parent().find("input[name='#{fieldName}']").remove()
if !value?
field.remove()
# Toggle active class for the tick mark
el.addClass ACTIVE_CLASS
# Toggle the dropdown label
if @options.toggleLabel
@updateLabel(selectedObject, el, @)
if value?
if !field.length and fieldName
@addInput(fieldName, value)
else
field
.val value
.trigger 'change'
return selectedObject
addInput: (fieldName, value)->
# Create hidden input for form
$input = $('<input>').attr('type', 'hidden')
.attr('name', fieldName)
.val(value)
if @options.inputId?
$input.attr('id', @options.inputId)
@dropdown.before $input
selectRowAtIndex: (e, index) ->
selector = ".dropdown-content li:not(.divider,.dropdown-header,.separator):eq(#{index}) a"
if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one #{selector}"
# simulate a click on the first link
$el = $(selector, @dropdown)
if $el.length
e.preventDefault()
e.stopImmediatePropagation()
$el.first().trigger('click')
addArrowKeyEvent: ->
ARROW_KEY_CODES = [38, 40]
$input = @dropdown.find(".dropdown-input-field")
selector = '.dropdown-content li:not(.divider,.dropdown-header,.separator)'
if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one #{selector}"
$('body').on 'keydown', (e) =>
currentKeyCode = e.which
if ARROW_KEY_CODES.indexOf(currentKeyCode) >= 0
e.preventDefault()
e.stopImmediatePropagation()
PREV_INDEX = currentIndex
$listItems = $(selector, @dropdown)
# if @options.filterable
# $input.blur()
if currentKeyCode is 40
# Move down
currentIndex += 1 if currentIndex < ($listItems.length - 1)
else if currentKeyCode is 38
# Move up
currentIndex -= 1 if currentIndex > 0
@highlightRowAtIndex($listItems, currentIndex) if currentIndex isnt PREV_INDEX
return false
if currentKeyCode is 13 and currentIndex isnt -1
@selectRowAtIndex e, currentIndex
removeArrayKeyEvent: ->
$('body').off 'keydown'
highlightRowAtIndex: ($listItems, index) ->
# Remove the class for the previously focused row
$('.is-focused', @dropdown).removeClass 'is-focused'
# Update the class for the row at the specific index
$listItem = $listItems.eq(index)
$listItem.find('a:first-child').addClass "is-focused"
# Dropdown content scroll area
$dropdownContent = $listItem.closest('.dropdown-content')
dropdownScrollTop = $dropdownContent.scrollTop()
dropdownContentHeight = $dropdownContent.outerHeight()
dropdownContentTop = $dropdownContent.prop('offsetTop')
dropdownContentBottom = dropdownContentTop + dropdownContentHeight
# Get the offset bottom of the list item
listItemHeight = $listItem.outerHeight()
listItemTop = $listItem.prop('offsetTop')
listItemBottom = listItemTop + listItemHeight
if listItemBottom > dropdownContentBottom + dropdownScrollTop
# Scroll the dropdown content down
$dropdownContent.scrollTop(listItemBottom - dropdownContentBottom)
else if listItemTop < dropdownContentTop + dropdownScrollTop
# Scroll the dropdown content up
$dropdownContent.scrollTop(listItemTop - dropdownContentTop)
updateLabel: (selected = null, el = null, instance = null) =>
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selected, el, instance)
$.fn.glDropdown = (opts) ->
return @.each ->
if (!$.data @, 'glDropdown')
$.data(@, 'glDropdown', new GitLabDropdown @, opts)
(function() {
this.GLForm = (function() {
function GLForm(form) {
this.form = form;
this.textarea = this.form.find('textarea.js-gfm-input');
this.destroy();
this.setupForm();
this.form.data('gl-form', this);
}
GLForm.prototype.destroy = function() {
this.clearEventListeners();
return this.form.data('gl-form', null);
};
GLForm.prototype.setupForm = function() {
var isNewForm;
isNewForm = this.form.is(':not(.gfm-form)');
this.form.removeClass('js-new-note-form');
if (isNewForm) {
this.form.find('.div-dropzone').remove();
this.form.addClass('gfm-form');
disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
GitLab.GfmAutoComplete.setup();
new DropzoneInput(this.form);
autosize(this.textarea);
this.addEventListeners();
gl.text.init(this.form);
}
this.form.find('.js-note-discard').hide();
return this.form.show();
};
GLForm.prototype.clearEventListeners = function() {
this.textarea.off('focus');
this.textarea.off('blur');
return gl.text.removeListeners(this.form);
};
GLForm.prototype.addEventListeners = function() {
this.textarea.on('focus', function() {
return $(this).closest('.md-area').addClass('is-focused');
});
return this.textarea.on('blur', function() {
return $(this).closest('.md-area').removeClass('is-focused');
});
};
return GLForm;
})();
}).call(this);
class @GLForm
constructor: (@form) ->
@textarea = @form.find('textarea.js-gfm-input')
# Before we start, we should clean up any previous data for this form
@destroy()
# Setup the form
@setupForm()
@form.data 'gl-form', @
destroy: ->
# Clean form listeners
@clearEventListeners()
@form.data 'gl-form', null
setupForm: ->
isNewForm = @form.is(':not(.gfm-form)')
@form.removeClass 'js-new-note-form'
if isNewForm
@form.find('.div-dropzone').remove()
@form.addClass('gfm-form')
disableButtonIfEmptyField @form.find('.js-note-text'), @form.find('.js-comment-button')
# remove notify commit author checkbox for non-commit notes
GitLab.GfmAutoComplete.setup()
new DropzoneInput(@form)
autosize(@textarea)
# form and textarea event listeners
@addEventListeners()
gl.text.init(@form)
# hide discard button
@form.find('.js-note-discard').hide()
@form.show()
clearEventListeners: ->
@textarea.off 'focus'
@textarea.off 'blur'
gl.text.removeListeners(@form)
addEventListeners: ->
@textarea.on 'focus', ->
$(@).closest('.md-area').addClass 'is-focused'
@textarea.on 'blur', ->
$(@).closest('.md-area').removeClass 'is-focused'
/*= require_tree . */
(function() {
}).call(this);
# This is a manifest file that'll be compiled into including all the files listed below.
# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
# be included in the compiled file accessible from http://example.com/assets/application.js
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
#= require_tree .
(function() {
this.StatGraph = (function() {
function StatGraph() {}
StatGraph.log = {};
StatGraph.get_log = function() {
return this.log;
};
StatGraph.set_log = function(data) {
return this.log = data;
};
return StatGraph;
})();
}).call(this);
class @StatGraph
@log: {}
@get_log: ->
@log
@set_log: (data) ->
@log = data
/*= require d3 */
(function() {
this.ContributorsStatGraph = (function() {
function ContributorsStatGraph() {}
ContributorsStatGraph.prototype.init = function(log) {
var author_commits, total_commits;
this.parsed_log = ContributorsStatGraphUtil.parse_log(log);
this.set_current_field("commits");
total_commits = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field);
author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field);
this.add_master_graph(total_commits);
this.add_authors_graph(author_commits);
return this.change_date_header();
};
ContributorsStatGraph.prototype.add_master_graph = function(total_data) {
this.master_graph = new ContributorsMasterGraph(total_data);
return this.master_graph.draw();
};
ContributorsStatGraph.prototype.add_authors_graph = function(author_data) {
var limited_author_data;
this.authors = [];
limited_author_data = author_data.slice(0, 100);
return _.each(limited_author_data, (function(_this) {
return function(d) {
var author_graph, author_header;
author_header = _this.create_author_header(d);
$(".contributors-list").append(author_header);
_this.authors[d.author_name] = author_graph = new ContributorsAuthorGraph(d.dates);
return author_graph.draw();
};
})(this));
};
ContributorsStatGraph.prototype.format_author_commit_info = function(author) {
var commits;
commits = $('<span/>', {
"class": 'graph-author-commits-count'
});
commits.text(author.commits + " commits");
return $('<span/>').append(commits);
};
ContributorsStatGraph.prototype.create_author_header = function(author) {
var author_commit_info, author_commit_info_span, author_email, author_name, list_item;
list_item = $('<li/>', {
"class": 'person',
style: 'display: block;'
});
author_name = $('<h4>' + author.author_name + '</h4>');
author_email = $('<p class="graph-author-email">' + author.author_email + '</p>');
author_commit_info_span = $('<span/>', {
"class": 'commits'
});
author_commit_info = this.format_author_commit_info(author);
author_commit_info_span.html(author_commit_info);
list_item.append(author_name);
list_item.append(author_email);
list_item.append(author_commit_info_span);
return list_item;
};
ContributorsStatGraph.prototype.redraw_master = function() {
var total_data;
total_data = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field);
this.master_graph.set_data(total_data);
return this.master_graph.redraw();
};
ContributorsStatGraph.prototype.redraw_authors = function() {
var author_commits, x_domain;
$("ol").html("");
x_domain = ContributorsGraph.prototype.x_domain;
author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field, x_domain);
return _.each(author_commits, (function(_this) {
return function(d) {
_this.redraw_author_commit_info(d);
$(_this.authors[d.author_name].list_item).appendTo("ol");
_this.authors[d.author_name].set_data(d.dates);
return _this.authors[d.author_name].redraw();
};
})(this));
};
ContributorsStatGraph.prototype.set_current_field = function(field) {
return this.field = field;
};
ContributorsStatGraph.prototype.change_date_header = function() {
var print, print_date_format, x_domain;
x_domain = ContributorsGraph.prototype.x_domain;
print_date_format = d3.time.format("%B %e %Y");
print = print_date_format(x_domain[0]) + " - " + print_date_format(x_domain[1]);
return $("#date_header").text(print);
};
ContributorsStatGraph.prototype.redraw_author_commit_info = function(author) {
var author_commit_info, author_list_item;
author_list_item = $(this.authors[author.author_name].list_item);
author_commit_info = this.format_author_commit_info(author);
return author_list_item.find("span").html(author_commit_info);
};
return ContributorsStatGraph;
})();
}).call(this);
#= require d3
class @ContributorsStatGraph
init: (log) ->
@parsed_log = ContributorsStatGraphUtil.parse_log(log)
@set_current_field("commits")
total_commits = ContributorsStatGraphUtil.get_total_data(@parsed_log, @field)
author_commits = ContributorsStatGraphUtil.get_author_data(@parsed_log, @field)
@add_master_graph(total_commits)
@add_authors_graph(author_commits)
@change_date_header()
add_master_graph: (total_data) ->
@master_graph = new ContributorsMasterGraph(total_data)
@master_graph.draw()
add_authors_graph: (author_data) ->
@authors = []
limited_author_data = author_data.slice(0, 100)
_.each(limited_author_data, (d) =>
author_header = @create_author_header(d)
$(".contributors-list").append(author_header)
@authors[d.author_name] = author_graph = new ContributorsAuthorGraph(d.dates)
author_graph.draw()
)
format_author_commit_info: (author) ->
commits = $('<span/>', {
class: 'graph-author-commits-count'
})
commits.text(author.commits + " commits")
$('<span/>').append(commits)
create_author_header: (author) ->
list_item = $('<li/>', {
class: 'person'
style: 'display: block;'
})
author_name = $('<h4>' + author.author_name + '</h4>')
author_email = $('<p class="graph-author-email">' + author.author_email + '</p>')
author_commit_info_span = $('<span/>', {
class: 'commits'
})
author_commit_info = @format_author_commit_info(author)
author_commit_info_span.html(author_commit_info)
list_item.append(author_name)
list_item.append(author_email)
list_item.append(author_commit_info_span)
list_item
redraw_master: ->
total_data = ContributorsStatGraphUtil.get_total_data(@parsed_log, @field)
@master_graph.set_data(total_data)
@master_graph.redraw()
redraw_authors: ->
$("ol").html("")
x_domain = ContributorsGraph.prototype.x_domain
author_commits = ContributorsStatGraphUtil.get_author_data(@parsed_log, @field, x_domain)
_.each(author_commits, (d) =>
@redraw_author_commit_info(d)
$(@authors[d.author_name].list_item).appendTo("ol")
@authors[d.author_name].set_data(d.dates)
@authors[d.author_name].redraw()
)
set_current_field: (field) ->
@field = field
change_date_header: ->
x_domain = ContributorsGraph.prototype.x_domain
print_date_format = d3.time.format("%B %e %Y")
print = print_date_format(x_domain[0]) + " - " + print_date_format(x_domain[1])
$("#date_header").text(print)
redraw_author_commit_info: (author) ->
author_list_item = $(@authors[author.author_name].list_item)
author_commit_info = @format_author_commit_info(author)
author_list_item.find("span").html(author_commit_info)
/*= require d3 */
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
this.ContributorsGraph = (function() {
function ContributorsGraph() {}
ContributorsGraph.prototype.MARGIN = {
top: 20,
right: 20,
bottom: 30,
left: 50
};
ContributorsGraph.prototype.x_domain = null;
ContributorsGraph.prototype.y_domain = null;
ContributorsGraph.prototype.dates = [];
ContributorsGraph.set_x_domain = function(data) {
return ContributorsGraph.prototype.x_domain = data;
};
ContributorsGraph.set_y_domain = function(data) {
return ContributorsGraph.prototype.y_domain = [
0, d3.max(data, function(d) {
var ref, ref1;
return d.commits = (ref = (ref1 = d.commits) != null ? ref1 : d.additions) != null ? ref : d.deletions;
})
];
};
ContributorsGraph.init_x_domain = function(data) {
return ContributorsGraph.prototype.x_domain = d3.extent(data, function(d) {
return d.date;
});
};
ContributorsGraph.init_y_domain = function(data) {
return ContributorsGraph.prototype.y_domain = [
0, d3.max(data, function(d) {
var ref, ref1;
return d.commits = (ref = (ref1 = d.commits) != null ? ref1 : d.additions) != null ? ref : d.deletions;
})
];
};
ContributorsGraph.init_domain = function(data) {
ContributorsGraph.init_x_domain(data);
return ContributorsGraph.init_y_domain(data);
};
ContributorsGraph.set_dates = function(data) {
return ContributorsGraph.prototype.dates = data;
};
ContributorsGraph.prototype.set_x_domain = function() {
return this.x.domain(this.x_domain);
};
ContributorsGraph.prototype.set_y_domain = function() {
return this.y.domain(this.y_domain);
};
ContributorsGraph.prototype.set_domain = function() {
this.set_x_domain();
return this.set_y_domain();
};
ContributorsGraph.prototype.create_scale = function(width, height) {
this.x = d3.time.scale().range([0, width]).clamp(true);
return this.y = d3.scale.linear().range([height, 0]).nice();
};
ContributorsGraph.prototype.draw_x_axis = function() {
return this.svg.append("g").attr("class", "x axis").attr("transform", "translate(0, " + this.height + ")").call(this.x_axis);
};
ContributorsGraph.prototype.draw_y_axis = function() {
return this.svg.append("g").attr("class", "y axis").call(this.y_axis);
};
ContributorsGraph.prototype.set_data = function(data) {
return this.data = data;
};
return ContributorsGraph;
})();
this.ContributorsMasterGraph = (function(superClass) {
extend(ContributorsMasterGraph, superClass);
function ContributorsMasterGraph(data1) {
this.data = data1;
this.update_content = bind(this.update_content, this);
this.width = $('.content').width() - 70;
this.height = 200;
this.x = null;
this.y = null;
this.x_axis = null;
this.y_axis = null;
this.area = null;
this.svg = null;
this.brush = null;
this.x_max_domain = null;
}
ContributorsMasterGraph.prototype.process_dates = function(data) {
var dates;
dates = this.get_dates(data);
this.parse_dates(data);
return ContributorsGraph.set_dates(dates);
};
ContributorsMasterGraph.prototype.get_dates = function(data) {
return _.pluck(data, 'date');
};
ContributorsMasterGraph.prototype.parse_dates = function(data) {
var parseDate;
parseDate = d3.time.format("%Y-%m-%d").parse;
return data.forEach(function(d) {
return d.date = parseDate(d.date);
});
};
ContributorsMasterGraph.prototype.create_scale = function() {
return ContributorsMasterGraph.__super__.create_scale.call(this, this.width, this.height);
};
ContributorsMasterGraph.prototype.create_axes = function() {
this.x_axis = d3.svg.axis().scale(this.x).orient("bottom");
return this.y_axis = d3.svg.axis().scale(this.y).orient("left").ticks(5);
};
ContributorsMasterGraph.prototype.create_svg = function() {
return this.svg = d3.select("#contributors-master").append("svg").attr("width", this.width + this.MARGIN.left + this.MARGIN.right).attr("height", this.height + this.MARGIN.top + this.MARGIN.bottom).attr("class", "tint-box").append("g").attr("transform", "translate(" + this.MARGIN.left + "," + this.MARGIN.top + ")");
};
ContributorsMasterGraph.prototype.create_area = function(x, y) {
return this.area = d3.svg.area().x(function(d) {
return x(d.date);
}).y0(this.height).y1(function(d) {
var ref, ref1, xa;
xa = d.commits = (ref = (ref1 = d.commits) != null ? ref1 : d.additions) != null ? ref : d.deletions;
return y(xa);
}).interpolate("basis");
};
ContributorsMasterGraph.prototype.create_brush = function() {
return this.brush = d3.svg.brush().x(this.x).on("brushend", this.update_content);
};
ContributorsMasterGraph.prototype.draw_path = function(data) {
return this.svg.append("path").datum(data).attr("class", "area").attr("d", this.area);
};
ContributorsMasterGraph.prototype.add_brush = function() {
return this.svg.append("g").attr("class", "selection").call(this.brush).selectAll("rect").attr("height", this.height);
};
ContributorsMasterGraph.prototype.update_content = function() {
ContributorsGraph.set_x_domain(this.brush.empty() ? this.x_max_domain : this.brush.extent());
return $("#brush_change").trigger('change');
};
ContributorsMasterGraph.prototype.draw = function() {
this.process_dates(this.data);
this.create_scale();
this.create_axes();
ContributorsGraph.init_domain(this.data);
this.x_max_domain = this.x_domain;
this.set_domain();
this.create_area(this.x, this.y);
this.create_svg();
this.create_brush();
this.draw_path(this.data);
this.draw_x_axis();
this.draw_y_axis();
return this.add_brush();
};
ContributorsMasterGraph.prototype.redraw = function() {
this.process_dates(this.data);
ContributorsGraph.set_y_domain(this.data);
this.set_y_domain();
this.svg.select("path").datum(this.data);
this.svg.select("path").attr("d", this.area);
return this.svg.select(".y.axis").call(this.y_axis);
};
return ContributorsMasterGraph;
})(ContributorsGraph);
this.ContributorsAuthorGraph = (function(superClass) {
extend(ContributorsAuthorGraph, superClass);
function ContributorsAuthorGraph(data1) {
this.data = data1;
if ($(window).width() < 768) {
this.width = $('.content').width() - 80;
} else {
this.width = ($('.content').width() / 2) - 100;
}
this.height = 200;
this.x = null;
this.y = null;
this.x_axis = null;
this.y_axis = null;
this.area = null;
this.svg = null;
this.list_item = null;
}
ContributorsAuthorGraph.prototype.create_scale = function() {
return ContributorsAuthorGraph.__super__.create_scale.call(this, this.width, this.height);
};
ContributorsAuthorGraph.prototype.create_axes = function() {
this.x_axis = d3.svg.axis().scale(this.x).orient("bottom").ticks(8);
return this.y_axis = d3.svg.axis().scale(this.y).orient("left").ticks(5);
};
ContributorsAuthorGraph.prototype.create_area = function(x, y) {
return this.area = d3.svg.area().x(function(d) {
var parseDate;
parseDate = d3.time.format("%Y-%m-%d").parse;
return x(parseDate(d));
}).y0(this.height).y1((function(_this) {
return function(d) {
if (_this.data[d] != null) {
return y(_this.data[d]);
} else {
return y(0);
}
};
})(this)).interpolate("basis");
};
ContributorsAuthorGraph.prototype.create_svg = function() {
this.list_item = d3.selectAll(".person")[0].pop();
return this.svg = d3.select(this.list_item).append("svg").attr("width", this.width + this.MARGIN.left + this.MARGIN.right).attr("height", this.height + this.MARGIN.top + this.MARGIN.bottom).attr("class", "spark").append("g").attr("transform", "translate(" + this.MARGIN.left + "," + this.MARGIN.top + ")");
};
ContributorsAuthorGraph.prototype.draw_path = function(data) {
return this.svg.append("path").datum(data).attr("class", "area-contributor").attr("d", this.area);
};
ContributorsAuthorGraph.prototype.draw = function() {
this.create_scale();
this.create_axes();
this.set_domain();
this.create_area(this.x, this.y);
this.create_svg();
this.draw_path(this.dates);
this.draw_x_axis();
return this.draw_y_axis();
};
ContributorsAuthorGraph.prototype.redraw = function() {
this.set_domain();
this.svg.select("path").datum(this.dates);
this.svg.select("path").attr("d", this.area);
this.svg.select(".x.axis").call(this.x_axis);
return this.svg.select(".y.axis").call(this.y_axis);
};
return ContributorsAuthorGraph;
})(ContributorsGraph);
}).call(this);
#= require d3
class @ContributorsGraph
MARGIN:
top: 20
right: 20
bottom: 30
left: 50
x_domain: null
y_domain: null
dates: []
@set_x_domain: (data) =>
@prototype.x_domain = data
@set_y_domain: (data) =>
@prototype.y_domain = [0, d3.max(data, (d) ->
d.commits = d.commits ? d.additions ? d.deletions
)]
@init_x_domain: (data) =>
@prototype.x_domain = d3.extent(data, (d) ->
d.date
)
@init_y_domain: (data) =>
@prototype.y_domain = [0, d3.max(data, (d) ->
d.commits = d.commits ? d.additions ? d.deletions
)]
@init_domain: (data) =>
@init_x_domain(data)
@init_y_domain(data)
@set_dates: (data) =>
@prototype.dates = data
set_x_domain: ->
@x.domain(@x_domain)
set_y_domain: ->
@y.domain(@y_domain)
set_domain: ->
@set_x_domain()
@set_y_domain()
create_scale: (width, height) ->
@x = d3.time.scale().range([0, width]).clamp(true)
@y = d3.scale.linear().range([height, 0]).nice()
draw_x_axis: ->
@svg.append("g").attr("class", "x axis").attr("transform", "translate(0, #{@height})")
.call(@x_axis)
draw_y_axis: ->
@svg.append("g").attr("class", "y axis").call(@y_axis)
set_data: (data) ->
@data = data
class @ContributorsMasterGraph extends ContributorsGraph
constructor: (@data) ->
@width = $('.content').width() - 70
@height = 200
@x = null
@y = null
@x_axis = null
@y_axis = null
@area = null
@svg = null
@brush = null
@x_max_domain = null
process_dates: (data) ->
dates = @get_dates(data)
@parse_dates(data)
ContributorsGraph.set_dates(dates)
get_dates: (data) ->
_.pluck(data, 'date')
parse_dates: (data) ->
parseDate = d3.time.format("%Y-%m-%d").parse
data.forEach((d) ->
d.date = parseDate(d.date)
)
create_scale: ->
super @width, @height
create_axes: ->
@x_axis = d3.svg.axis().scale(@x).orient("bottom")
@y_axis = d3.svg.axis().scale(@y).orient("left").ticks(5)
create_svg: ->
@svg = d3.select("#contributors-master").append("svg")
.attr("width", @width + @MARGIN.left + @MARGIN.right)
.attr("height", @height + @MARGIN.top + @MARGIN.bottom)
.attr("class", "tint-box")
.append("g")
.attr("transform", "translate(" + @MARGIN.left + "," + @MARGIN.top + ")")
create_area: (x, y) ->
@area = d3.svg.area().x((d) ->
x(d.date)
).y0(@height).y1((d) ->
xa = d.commits = d.commits ? d.additions ? d.deletions
y(xa)
).interpolate("basis")
create_brush: ->
@brush = d3.svg.brush().x(@x).on("brushend", @update_content)
draw_path: (data) ->
@svg.append("path").datum(data).attr("class", "area").attr("d", @area)
add_brush: ->
@svg.append("g").attr("class", "selection").call(@brush).selectAll("rect").attr("height", @height)
update_content: =>
ContributorsGraph.set_x_domain(if @brush.empty() then @x_max_domain else @brush.extent())
$("#brush_change").trigger('change')
draw: ->
@process_dates(@data)
@create_scale()
@create_axes()
ContributorsGraph.init_domain(@data)
@x_max_domain = @x_domain
@set_domain()
@create_area(@x, @y)
@create_svg()
@create_brush()
@draw_path(@data)
@draw_x_axis()
@draw_y_axis()
@add_brush()
redraw: ->
@process_dates(@data)
ContributorsGraph.set_y_domain(@data)
@set_y_domain()
@svg.select("path").datum(@data)
@svg.select("path").attr("d", @area)
@svg.select(".y.axis").call(@y_axis)
class @ContributorsAuthorGraph extends ContributorsGraph
constructor: (@data) ->
# Don't split graph size in half for mobile devices.
if $(window).width() < 768
@width = $('.content').width() - 80
else
@width = ($('.content').width() / 2) - 100
@height = 200
@x = null
@y = null
@x_axis = null
@y_axis = null
@area = null
@svg = null
@list_item = null
create_scale: ->
super @width, @height
create_axes: ->
@x_axis = d3.svg.axis().scale(@x).orient("bottom").ticks(8)
@y_axis = d3.svg.axis().scale(@y).orient("left").ticks(5)
create_area: (x, y) ->
@area = d3.svg.area().x((d) ->
parseDate = d3.time.format("%Y-%m-%d").parse
x(parseDate(d))
).y0(@height).y1((d) =>
if @data[d]? then y(@data[d]) else y(0)
).interpolate("basis")
create_svg: ->
@list_item = d3.selectAll(".person")[0].pop()
@svg = d3.select(@list_item).append("svg")
.attr("width", @width + @MARGIN.left + @MARGIN.right)
.attr("height", @height + @MARGIN.top + @MARGIN.bottom)
.attr("class", "spark")
.append("g")
.attr("transform", "translate(" + @MARGIN.left + "," + @MARGIN.top + ")")
draw_path: (data) ->
@svg.append("path").datum(data).attr("class", "area-contributor").attr("d", @area)
draw: ->
@create_scale()
@create_axes()
@set_domain()
@create_area(@x, @y)
@create_svg()
@draw_path(@dates)
@draw_x_axis()
@draw_y_axis()
redraw: ->
@set_domain()
@svg.select("path").datum(@dates)
@svg.select("path").attr("d", @area)
@svg.select(".x.axis").call(@x_axis)
@svg.select(".y.axis").call(@y_axis)
(function() {
window.ContributorsStatGraphUtil = {
parse_log: function(log) {
var by_author, by_email, data, entry, i, len, total;
total = {};
by_author = {};
by_email = {};
for (i = 0, len = log.length; i < len; i++) {
entry = log[i];
if (total[entry.date] == null) {
this.add_date(entry.date, total);
}
data = by_author[entry.author_name] || by_email[entry.author_email];
if (data == null) {
data = this.add_author(entry, by_author, by_email);
}
if (!data[entry.date]) {
this.add_date(entry.date, data);
}
this.store_data(entry, total[entry.date], data[entry.date]);
}
total = _.toArray(total);
by_author = _.toArray(by_author);
return {
total: total,
by_author: by_author
};
},
add_date: function(date, collection) {
collection[date] = {};
return collection[date].date = date;
},
add_author: function(author, by_author, by_email) {
var data;
data = {};
data.author_name = author.author_name;
data.author_email = author.author_email;
by_author[author.author_name] = data;
return by_email[author.author_email] = data;
},
store_data: function(entry, total, by_author) {
this.store_commits(total, by_author);
this.store_additions(entry, total, by_author);
return this.store_deletions(entry, total, by_author);
},
store_commits: function(total, by_author) {
this.add(total, "commits", 1);
return this.add(by_author, "commits", 1);
},
add: function(collection, field, value) {
if (collection[field] == null) {
collection[field] = 0;
}
return collection[field] += value;
},
store_additions: function(entry, total, by_author) {
if (entry.additions == null) {
entry.additions = 0;
}
this.add(total, "additions", entry.additions);
return this.add(by_author, "additions", entry.additions);
},
store_deletions: function(entry, total, by_author) {
if (entry.deletions == null) {
entry.deletions = 0;
}
this.add(total, "deletions", entry.deletions);
return this.add(by_author, "deletions", entry.deletions);
},
get_total_data: function(parsed_log, field) {
var log, total_data;
log = parsed_log.total;
total_data = this.pick_field(log, field);
return _.sortBy(total_data, function(d) {
return d.date;
});
},
pick_field: function(log, field) {
var total_data;
total_data = [];
_.each(log, function(d) {
return total_data.push(_.pick(d, [field, 'date']));
});
return total_data;
},
get_author_data: function(parsed_log, field, date_range) {
var author_data, log;
if (date_range == null) {
date_range = null;
}
log = parsed_log.by_author;
author_data = [];
_.each(log, (function(_this) {
return function(log_entry) {
var parsed_log_entry;
parsed_log_entry = _this.parse_log_entry(log_entry, field, date_range);
if (!_.isEmpty(parsed_log_entry.dates)) {
return author_data.push(parsed_log_entry);
}
};
})(this));
return _.sortBy(author_data, function(d) {
return d[field];
}).reverse();
},
parse_log_entry: function(log_entry, field, date_range) {
var parsed_entry;
parsed_entry = {};
parsed_entry.author_name = log_entry.author_name;
parsed_entry.author_email = log_entry.author_email;
parsed_entry.dates = {};
parsed_entry.commits = parsed_entry.additions = parsed_entry.deletions = 0;
_.each(_.omit(log_entry, 'author_name', 'author_email'), (function(_this) {
return function(value, key) {
if (_this.in_range(value.date, date_range)) {
parsed_entry.dates[value.date] = value[field];
parsed_entry.commits += value.commits;
parsed_entry.additions += value.additions;
return parsed_entry.deletions += value.deletions;
}
};
})(this));
return parsed_entry;
},
in_range: function(date, date_range) {
var ref;
if (date_range === null || (date_range[0] <= (ref = new Date(date)) && ref <= date_range[1])) {
return true;
} else {
return false;
}
}
};
}).call(this);
window.ContributorsStatGraphUtil =
parse_log: (log) ->
total = {}
by_author = {}
by_email = {}
for entry in log
@add_date(entry.date, total) unless total[entry.date]?
data = by_author[entry.author_name] || by_email[entry.author_email]
data ?= @add_author(entry, by_author, by_email)
@add_date(entry.date, data) unless data[entry.date]
@store_data(entry, total[entry.date], data[entry.date])
total = _.toArray(total)
by_author = _.toArray(by_author)
total: total, by_author: by_author
add_date: (date, collection) ->
collection[date] = {}
collection[date].date = date
add_author: (author, by_author, by_email) ->
data = {}
data.author_name = author.author_name
data.author_email = author.author_email
by_author[author.author_name] = data
by_email[author.author_email] = data
store_data: (entry, total, by_author) ->
@store_commits(total, by_author)
@store_additions(entry, total, by_author)
@store_deletions(entry, total, by_author)
store_commits: (total, by_author) ->
@add(total, "commits", 1)
@add(by_author, "commits", 1)
add: (collection, field, value) ->
collection[field] ?= 0
collection[field] += value
store_additions: (entry, total, by_author) ->
entry.additions ?= 0
@add(total, "additions", entry.additions)
@add(by_author, "additions", entry.additions)
store_deletions: (entry, total, by_author) ->
entry.deletions ?= 0
@add(total, "deletions", entry.deletions)
@add(by_author, "deletions", entry.deletions)
get_total_data: (parsed_log, field) ->
log = parsed_log.total
total_data = @pick_field(log, field)
_.sortBy(total_data, (d) ->
d.date
)
pick_field: (log, field) ->
total_data = []
_.each(log, (d) ->
total_data.push(_.pick(d, [field, 'date']))
)
total_data
get_author_data: (parsed_log, field, date_range = null) ->
log = parsed_log.by_author
author_data = []
_.each(log, (log_entry) =>
parsed_log_entry = @parse_log_entry(log_entry, field, date_range)
if not _.isEmpty(parsed_log_entry.dates)
author_data.push(parsed_log_entry)
)
_.sortBy(author_data, (d) ->
d[field]
).reverse()
parse_log_entry: (log_entry, field, date_range) ->
parsed_entry = {}
parsed_entry.author_name = log_entry.author_name
parsed_entry.author_email = log_entry.author_email
parsed_entry.dates = {}
parsed_entry.commits = parsed_entry.additions = parsed_entry.deletions = 0
_.each(_.omit(log_entry, 'author_name', 'author_email'), (value, key) =>
if @in_range(value.date, date_range)
parsed_entry.dates[value.date] = value[field]
parsed_entry.commits += value.commits
parsed_entry.additions += value.additions
parsed_entry.deletions += value.deletions
)
return parsed_entry
in_range: (date, date_range) ->
if date_range is null || date_range[0] <= new Date(date) <= date_range[1]
true
else
false
(function() {
this.GroupAvatar = (function() {
function GroupAvatar() {
$('.js-choose-group-avatar-button').bind("click", function() {
var form;
form = $(this).closest("form");
return form.find(".js-group-avatar-input").click();
});
$('.js-group-avatar-input').bind("change", function() {
var filename, form;
form = $(this).closest("form");
filename = $(this).val().replace(/^.*[\\\/]/, '');
return form.find(".js-avatar-filename").text(filename);
});
}
return GroupAvatar;
})();
}).call(this);
class @GroupAvatar
constructor: ->
$('.js-choose-group-avatar-button').bind "click", ->
form = $(this).closest("form")
form.find(".js-group-avatar-input").click()
$('.js-group-avatar-input').bind "change", ->
form = $(this).closest("form")
filename = $(this).val().replace(/^.*[\\\/]/, '')
form.find(".js-avatar-filename").text(filename)
(function() {
this.GroupMembers = (function() {
function GroupMembers() {
$('li.group_member').bind('ajax:success', function() {
return $(this).fadeOut();
});
}
return GroupMembers;
})();
}).call(this);
class @GroupMembers
constructor: ->
$('li.group_member').bind 'ajax:success', ->
$(this).fadeOut()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment