Merge branch 'master' into stable

stable
Miguel Gonzalez 2018-03-07 07:00:32 +01:00
commit f8ba446334
1560 changed files with 645 additions and 301 deletions

View File

@ -1,5 +1,23 @@
# Changelog #
## 3.2.0 Betula nana (2018-03-07)
### Features
- Emojis support on subjects and tags.
- Add "confirm dialog" before closing edit lightboxes.
- Wiki activity hidden by default.
- Allow ascending votes ordering in issues list.
- Add multiple drag in Kanban.
- Show US counter and wip limit in Kanban columns title.
- Add role filtering in US.
## 3.1.3 (2018-02-28)
### Features
- Minor bug fixes.
## 3.1.0 Perovskia Atriplicifolia (2017-03-10)
### Features

View File

@ -12,6 +12,7 @@ window.taigaConfig = {
"defaultTheme": "taiga",
"publicRegisterEnabled": true,
"feedbackEnabled": true,
"supportUrl": null,
"privacyPolicyUrl": null,
"termsOfServiceUrl": null,
"maxUploadFileSize": null,
@ -70,10 +71,14 @@ promise.fail () ->
console.error "Your conf.json file is not a valid json file, please review it."
promise.always ->
emojisPromise = $.getJSON("/#{window._version}/emojis/emojis-data.json").then (emojis) ->
window.emojis = emojis
if window.taigaConfig.contribPlugins.length > 0
loadPlugins(window.taigaConfig.contribPlugins).then () ->
ljs.load "/#{window._version}/js/app.js", ->
angular.bootstrap(document, ['taiga'])
emojisPromise.then ->
angular.bootstrap(document, ['taiga'])
else
ljs.load "/#{window._version}/js/app.js", ->
angular.bootstrap(document, ['taiga'])
emojisPromise.then ->
angular.bootstrap(document, ['taiga'])

View File

@ -23,6 +23,7 @@
###
@taiga = taiga = {}
taiga.emojis = window.emojis
@.taigaContribPlugins = @.taigaContribPlugins or window.taigaContribPlugins or []
# Generic function for generate hash from a arbitrary length

View File

@ -521,6 +521,8 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
@.generateFilters().then () =>
@rootscope.$broadcast("filters:update")
@.loadProjectStats()
if @.isFilterDataTypeSelected('status')
@.filtersReloadContent()
editUserStory: (projectId, ref, $event) ->
target = $($event.target)

View File

@ -68,7 +68,7 @@ BacklogSortableDirective = () ->
window.dragMultiple.start(item, container)
drake.on 'cloned', (item) ->
$(item).addClass('backlog-us-mirror')
$(item).addClass('multiple-drag-mirror')
drake.on 'dragend', (item) ->
parent = $(item).parent()

View File

@ -251,8 +251,6 @@ Qqueue = ($q) ->
bindAdd: (fn) =>
return (args...) =>
lastPromise = lastPromise.then () => fn.apply(@, args)
return qqueue
add: (fn) =>
if !lastPromise
lastPromise = fn()

View File

@ -495,92 +495,6 @@ DeleteButtonDirective = ($log, $repo, $confirm, $location, $template) ->
module.directive("tgDeleteButton", ["$log", "$tgRepo", "$tgConfirm", "$tgLocation", "$tgTemplate", DeleteButtonDirective])
#############################################################################
## Editable subject directive
#############################################################################
EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading, $modelTransform, $template) ->
template = $template.get("common/components/editable-subject.html")
link = ($scope, $el, $attrs, $model) ->
$scope.$on "object:updated", () ->
$el.find('.edit-subject').hide()
$el.find('.view-subject').show()
isEditable = ->
return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1
save = (subject) ->
currentLoading = $loading()
.target($el.find('.save-container'))
.start()
transform = $modelTransform.save (item) ->
item.subject = subject
return item
transform.then =>
$confirm.notify("success")
$rootscope.$broadcast("object:updated")
$el.find('.edit-subject').hide()
$el.find('.view-subject').show()
transform.then null, ->
$confirm.notify("error")
transform.finally ->
currentLoading.finish()
return transform
$el.click ->
return if not isEditable()
$el.find('.edit-subject').show()
$el.find('.view-subject').hide()
$el.find('input').focus()
$el.on "click", ".save", (e) ->
e.preventDefault()
subject = $scope.item.subject
save(subject)
$el.on "keyup", "input", (event) ->
if event.keyCode == 13
subject = $scope.item.subject
save(subject)
else if event.keyCode == 27
$scope.$apply () => $model.$modelValue.revert()
$el.find('.edit-subject').hide()
$el.find('.view-subject').show()
$el.find('.edit-subject').hide()
$scope.$watch $attrs.ngModel, (value) ->
return if not value
$scope.item = value
if not isEditable()
$el.find('.view-subject .edit').remove()
$scope.$on "$destroy", ->
$el.off()
return {
link: link
restrict: "EA"
require: "ngModel"
template: template
}
module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQueueModelTransformation",
"$tgTemplate", EditableSubjectDirective])
#############################################################################
## Common list directives
#############################################################################

View File

@ -0,0 +1,84 @@
###
# Copyright (C) 2014-2017 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2017 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2017 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2017 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# Copyright (C) 2014-2017 Juan Francisco Alcántara <juanfran.alcantara@kaleidos.net>
# Copyright (C) 2014-2017 Xavi Julian <xavier.julian@kaleidos.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# File: modules/common/analytics.coffee
###
taiga = @.taiga
module = angular.module("taigaCommon")
class EmojisService extends taiga.Service
@.$inject = []
constructor: () ->
@.emojis = _.map taiga.emojis, (it) ->
it.image = "/#{window._version}/emojis/" + it.image
return it
@.emojisById = _.keyBy(@.emojis, 'id')
@.emojisByName = _.keyBy(@.emojis, 'name')
searchByName: (name) =>
return _.filter @.emojis, (it) -> it.name.indexOf(name) != -1
getEmojiById: (id) =>
return @.emojisById[id]
getEmojiByName: (name) =>
return @.emojisByName[name]
replaceImgsByEmojiName: (html) =>
emojiIds = taiga.getMatches(html, /emojis\/([^"]+).png"/gi)
for emojiId in emojiIds
regexImgs = new RegExp('<img(.*)' + emojiId + '[^>]+\>', 'g')
emoji = @.getEmojiById(emojiId)
html = html.replace(regexImgs, ':' + emoji.name + ':')
return html
replaceEmojiNameByImgs: (text) =>
emojiIds = taiga.getMatches(text, /:([\w +-]*):/g)
for emojiId in emojiIds
regexImgs = new RegExp(':' + emojiId + ':', 'g')
emoji = @.getEmojiByName(emojiId)
if emoji
text = text.replace(regexImgs, '![alt](' + emoji.image + ')')
return text
replaceEmojiNameByHtmlImgs: (text) =>
emojiIds = taiga.getMatches(text, /:([\w +-]*):/g)
for emojiId in emojiIds
regexImgs = new RegExp(':' + _.escapeRegExp(emojiId) + ':', 'g')
emoji = @.getEmojiByName(emojiId)
if emoji
text = text.replace(regexImgs, '<img src="' + emoji.image + '" />')
return text
module.service("$tgEmojis", EmojisService)

View File

@ -143,3 +143,12 @@ inArray = ($filter) ->
return filter list, (listItem) ->
return arrayFilter.indexOf(listItem[element]) != -1
module.filter("inArray", ["$filter", inArray])
emojify = ($emojis) ->
return (input) ->
if input
return $emojis.replaceEmojiNameByHtmlImgs(_.escape(input))
return ""
module.filter("emojify", ["$tgEmojis", emojify])

View File

@ -371,6 +371,8 @@ CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService,
status: status
is_archived: false
tags: []
subject: ""
description: ""
})
# Update texts for creation
@ -478,24 +480,32 @@ CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService,
submitButton = $el.find(".submit-button")
$el.on "submit", "form", submit
$el.on "click", ".close", (event) ->
event.preventDefault()
$scope.$apply ->
$scope.us.revert()
lightboxService.close($el)
$el.keydown (event) ->
code = if event.keyCode then event.keyCode else event.which
if code == 27
close = () =>
if !$scope.us.isModified()
lightboxService.close($el)
$scope.$apply ->
$scope.us.revert()
else
$confirm.ask($translate.instant("LIGHTBOX.CREATE_EDIT_US.CONFIRM_CLOSE")).then (result) ->
lightboxService.close($el)
$scope.us.revert()
result.finish()
$el.on "submit", "form", submit
$el.find('.close').on "click", (event) ->
event.preventDefault()
event.stopPropagation()
close()
$el.keydown (event) ->
event.stopPropagation()
code = if event.keyCode then event.keyCode else event.which
if code == 27
close()
$scope.$on "$destroy", ->
$el.find('.close').off()
$el.off()
return {link: link}

View File

@ -58,14 +58,14 @@ TagsDirective = ->
module.directive("tgTags", TagsDirective)
ColorizeTagsBacklogDirective = ->
ColorizeTagsBacklogDirective = ($emojis) ->
template = _.template("""
<% _.each(tags, function(tag) { %>
<% if (tag[1] !== null) { %>
<span class="tag"
style="border-left: 5px solid <%- tag[1] %>"
title="<%- tag[0] %>">
<%- tag[0] %>
<%= emojify(tag[0]) %>
</span>
<% } %>
<% }) %>
@ -73,7 +73,7 @@ ColorizeTagsBacklogDirective = ->
<% if (tag[1] === null) { %>
<span class="tag"
title="<%- tag[0] %>">
<%- tag[0] %>
<%= emojify(tag[0]) %>
</span>
<% } %>
<% }) %>
@ -81,7 +81,7 @@ ColorizeTagsBacklogDirective = ->
link = ($scope, $el, $attrs, $ctrl) ->
render = (tags) ->
html = template({tags: tags})
html = template({tags: tags, emojify: (text) -> $emojis.replaceEmojiNameByHtmlImgs(_.escape(text))})
$el.html(html)
$scope.$watch $attrs.tgColorizeBacklogTags, (tags) ->
@ -92,7 +92,7 @@ ColorizeTagsBacklogDirective = ->
return {link: link}
module.directive("tgColorizeBacklogTags", ColorizeTagsBacklogDirective)
module.directive("tgColorizeBacklogTags", ["$tgEmojis", ColorizeTagsBacklogDirective])
#############################################################################

View File

@ -194,6 +194,7 @@ class UsFiltersMixin
filters.assigned_to = urlfilters.assigned_to
filters.owner = urlfilters.owner
filters.epic = urlfilters.epic
filters.role = urlfilters.role
@filterRemoteStorageService.getFilters(@scope.projectId, @.storeCustomFiltersName).then (userFilters) =>
userFilters[name] = filters
@ -207,6 +208,12 @@ class UsFiltersMixin
@filterRemoteStorageService.storeFilters(@scope.projectId, userFilters, @.storeCustomFiltersName).then(@.generateFilters)
@.generateFilters()
isFilterDataTypeSelected: (filterDataType) ->
for filter in @.selectedFilters
if (filter['dataType'] == filterDataType)
return true
return false
generateFilters: (milestone) ->
@.storeFilters(@params.pslug, @location.search(), @.storeFiltersName)
@ -219,6 +226,7 @@ class UsFiltersMixin
loadFilters.assigned_to = urlfilters.assigned_to
loadFilters.owner = urlfilters.owner
loadFilters.epic = urlfilters.epic
loadFilters.role = urlfilters.role
loadFilters.q = urlfilters.q
if milestone
@ -249,6 +257,15 @@ class UsFiltersMixin
it.name = it.full_name || "Unassigned"
return it
role = _.map data.roles, (it) ->
if it.id
it.id = it.id.toString()
else
it.id = "null"
it.name = it.name || "Unassigned"
return it
owner = _.map data.owners, (it) ->
it.id = it.id.toString()
@ -287,6 +304,10 @@ class UsFiltersMixin
selected = @.formatSelectedFilters("epic", epic, loadFilters.epic)
@.selectedFilters = @.selectedFilters.concat(selected)
if loadFilters.role
selected = @.formatSelectedFilters("role", role, loadFilters.role)
@.selectedFilters = @.selectedFilters.concat(selected)
@.filterQ = loadFilters.q
@.filters = [
@ -307,6 +328,11 @@ class UsFiltersMixin
dataType: "assigned_to",
content: assignedTo
},
{
title: @translate.instant("COMMON.FILTERS.CATEGORIES.ROLE"),
dataType: "role",
content: role
},
{
title: @translate.instant("COMMON.FILTERS.CATEGORIES.CREATED_BY"),
dataType: "owner",

View File

@ -127,6 +127,12 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
delete userFilters[customFilter.id]
@filterRemoteStorageService.storeFilters(@scope.projectId, userFilters, @.myFiltersHashSuffix).then(@.generateFilters)
isFilterDataTypeSelected: (filterDataType) ->
for filter in @.selectedFilters
if (filter['dataType'] == filterDataType)
return true
return false
saveCustomFilter: (name) ->
filters = {}
urlfilters = @location.search()
@ -137,6 +143,7 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
filters.priority = urlfilters.priority
filters.assigned_to = urlfilters.assigned_to
filters.owner = urlfilters.owner
filters.role = urlfilters.role
@filterRemoteStorageService.getFilters(@scope.projectId, @.myFiltersHashSuffix).then (userFilters) =>
userFilters[name] = filters
@ -157,6 +164,7 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
loadFilters.priority = urlfilters.priority
loadFilters.assigned_to = urlfilters.assigned_to
loadFilters.owner = urlfilters.owner
loadFilters.role = urlfilters.role
loadFilters.q = urlfilters.q
return @q.all([
@ -203,6 +211,15 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
it.id = it.id.toString()
it.name = it.full_name
return it
role = _.map data.roles, (it) ->
if it.id
it.id = it.id.toString()
else
it.id = "null"
it.name = it.name || "Unassigned"
return it
@.selectedFilters = []
@ -235,6 +252,10 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
selected = @.formatSelectedFilters("priority", priority, loadFilters.priority)
@.selectedFilters = @.selectedFilters.concat(selected)
if loadFilters.role
selected = @.formatSelectedFilters("role", role, loadFilters.role)
@.selectedFilters = @.selectedFilters.concat(selected)
@.filterQ = loadFilters.q
@.filters = [
@ -270,6 +291,11 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
dataType: "assigned_to",
content: assignedTo
},
{
title: @translate.instant("COMMON.FILTERS.CATEGORIES.ROLE"),
dataType: "role",
content: role
},
{
title: @translate.instant("COMMON.FILTERS.CATEGORIES.CREATED_BY"),
dataType: "owner",
@ -485,10 +511,9 @@ IssuesDirective = ($log, $location, $template, $compile) ->
currentOrder = $ctrl.getOrderBy()
newOrder = target.data("fieldname")
if newOrder == 'total_voters'
finalOrder = if currentOrder == newOrder then newOrder else "-#{newOrder}"
else
finalOrder = if currentOrder == newOrder then "-#{newOrder}" else newOrder
if newOrder == 'total_voters' and currentOrder != "-total_voters"
currentOrder = "total_voters"
finalOrder = if currentOrder == newOrder then "-#{newOrder}" else newOrder
$scope.$apply ->
$ctrl.replaceFilter("order_by", finalOrder)
@ -568,8 +593,9 @@ IssueStatusInlineEditionDirective = ($repo, $template, $rootscope) ->
$scope.$apply () ->
$repo.save(issue).then ->
$ctrl.loadIssues()
$ctrl.generateFilters()
if $ctrl.isFilterDataTypeSelected('status')
$ctrl.loadIssues()
taiga.bindOnce $scope, "project", (project) ->
$el.append(selectionTemplate({ 'statuses': project.issue_statuses }))
@ -635,13 +661,17 @@ IssueAssignedToInlineEditionDirective = ($repo, $rootscope, $translate, avatarSe
$el.unbind("click")
$el.find("a").addClass("not-clickable")
$scope.$on "assigned-to:added", (ctx, userId, updatedIssue) =>
$scope.$on "assigned-to:added", (ctx, userId, updatedIssue) ->
if updatedIssue.id == issue.id
updatedIssue.assigned_to = userId
$repo.save(updatedIssue)
updateIssue(updatedIssue)
$repo.save(issue).then ->
updateIssue(updatedIssue)
$ctrl.generateFilters()
if $ctrl.isFilterDataTypeSelected('assigned_to') \
|| $ctrl.isFilterDataTypeSelected('role')
$ctrl.loadIssues()
$scope.$watch $attrs.tgIssueAssignedToInlineEdition, (val) =>
$scope.$watch $attrs.tgIssueAssignedToInlineEdition, (val) ->
updateIssue(val)
$scope.$on "$destroy", ->

View File

@ -93,15 +93,17 @@ class KanbanUserstoriesService extends taiga.Service
@.refresh()
move: (id, statusId, index) ->
us = @.getUsModel(id)
move: (usList, statusId, index) ->
initialLength = usList.length
usByStatus = _.filter @.userstoriesRaw, (it) =>
return it.status == statusId
usByStatus = _.sortBy usByStatus, (it) => @.order[it.id]
usByStatusWithoutMoved = _.filter usByStatus, (it) => it.id != id
usByStatusWithoutMoved = _.filter usByStatus, (listIt) ->
return !_.find usList, (moveIt) -> return listIt.id == moveIt.id
beforeDestination = _.slice(usByStatusWithoutMoved, 0, index)
afterDestination = _.slice(usByStatusWithoutMoved, index)
@ -112,26 +114,54 @@ class KanbanUserstoriesService extends taiga.Service
previousWithTheSameOrder = _.filter beforeDestination, (it) =>
@.order[it.id] == @.order[previous.id]
if previousWithTheSameOrder.length > 1
for it in previousWithTheSameOrder
setOrders[it.id] = @.order[it.id]
if !previous and (!afterDestination or afterDestination.length == 0)
@.order[us.id] = 0
else if !previous and afterDestination and afterDestination.length > 0
@.order[us.id] = @.order[afterDestination[0].id] - 1
modifiedUs = []
setPreviousOrders = []
setNextOrders = []
if !previous
startIndex = 0
else if previous
@.order[us.id] = @.order[previous.id] + 1
startIndex = @.order[previous.id] + 1
for it, key in afterDestination
@.order[it.id] = @.order[us.id] + key + 1
previousWithTheSameOrder = _.filter(beforeDestination, (it) =>
it.kanban_order == @.order[previous.id]
)
for it, key in afterDestination # increase position of the us after the dragged us's
@.order[it.id] = @.order[previous.id] + key + initialLength + 1
it.kanban_order = @.order[it.id]
us.status = statusId
us.kanban_order = @.order[us.id]
setNextOrders = _.map(afterDestination, (it) =>
{us_id: it.id, order: @.order[it.id]}
)
# we must send the USs previous to the dropped USs to tell the backend
# which USs are before the dropped USs, if they have the same value to
# order, the backend doens't know after which one do you want to drop
# the USs
if previousWithTheSameOrder.length > 1
setPreviousOrders = _.map(previousWithTheSameOrder, (it) =>
{us_id: it.id, order: @.order[it.id]}
)
for us, key in usList
us.status = statusId
us.kanban_order = startIndex + key
@.order[us.id] = us.kanban_order
modifiedUs.push({us_id: us.id, order: us.kanban_order})
@.refresh()
return {"us_id": us.id, "order": @.order[us.id], "set_orders": setOrders}
return {
bulkOrders: modifiedUs.concat(setPreviousOrders, setNextOrders),
usList: modifiedUs,
set_orders: setOrders
}
moveToEnd: (id, statusId) ->
us = @.getUsModel(id)

View File

@ -71,6 +71,7 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
bindMethods(@)
@kanbanUserstoriesService.reset()
@.openFilter = false
@.selectedUss = {}
return if @.applyStoredFilters(@params.pslug, "kanban-filters")
@ -80,6 +81,13 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
taiga.defineImmutableProperty @.scope, "usByStatus", () =>
return @kanbanUserstoriesService.usByStatus
cleanSelectedUss: () ->
for key of @.selectedUss
@.selectedUss[key] = false
toggleSelectedUs: (usId) ->
@.selectedUss[usId] = !@.selectedUss[usId]
firstLoad: () ->
promise = @.loadInitialData()
@ -191,9 +199,10 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
@kanbanUserstoriesService.replaceModel(usModel)
promise = @repo.save(usModel)
promise.then null, ->
console.log "FAIL" # TODO
@repo.save(usModel).then =>
@.generateFilters()
if @.isFilterDataTypeSelected('assigned_to') || @.isFilterDataTypeSelected('role')
@.filtersReloadContent()
refreshTagsColors: ->
return @rs.projects.tagsColors(@scope.projectId).then (tags_colors) =>
@ -291,36 +300,49 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
prepareBulkUpdateData: (uses, field="kanban_order") ->
return _.map(uses, (x) -> {"us_id": x.id, "order": x[field]})
moveUs: (ctx, us, oldStatusId, newStatusId, index) ->
us = @kanbanUserstoriesService.getUsModel(us.get('id'))
newStatus = @scope.usStatusById[newStatusId]
if newStatus.is_archived and !@scope.usByStatus.get(newStatusId.toString())
moveUpdateData = @kanbanUserstoriesService.moveToEnd(us.id, newStatusId)
else
moveUpdateData = @kanbanUserstoriesService.move(us.id, newStatusId, index)
moveUs: (ctx, usList, newStatusId, index) ->
@.cleanSelectedUss()
usList = _.map usList, (us) =>
return @kanbanUserstoriesService.getUsModel(us.id)
params = {
include_attachments: true,
include_tasks: true
}
data = @kanbanUserstoriesService.move(usList, newStatusId, index)
options = {
headers: {
"set-orders": JSON.stringify(moveUpdateData.set_orders)
promise = @rs.userstories.bulkUpdateKanbanOrder(@scope.projectId, data.bulkOrders)
promise.then () =>
# saving
# drag single or different status
options = {
headers: {
"set-orders": JSON.stringify(data.setOrders)
}
}
}
promise = @repo.save(us, true, params, options, true)
params = {
include_attachments: true,
include_tasks: true
}
promise = promise.then (result) =>
headers = result[1]
promises = _.map usList, (us) =>
@repo.save(us, true, params, options, true)
if headers && headers['taiga-info-order-updated']
order = JSON.parse(headers['taiga-info-order-updated'])
@kanbanUserstoriesService.assignOrders(order)
@scope.$broadcast("redraw:wip")
promise = @q.all(promises)
promise.then (result) =>
headers = result[1]
if headers && headers['taiga-info-order-updated']
order = JSON.parse(headers['taiga-info-order-updated'])
@kanbanUserstoriesService.assignOrders(order)
@scope.$broadcast("redraw:wip")
@.generateFilters()
if @.isFilterDataTypeSelected('status')
@.filtersReloadContent()
return promise
return promise
module.controller("KanbanController", KanbanController)

View File

@ -48,7 +48,6 @@ KanbanSortableDirective = ($repo, $rs, $rootscope) ->
if not ($scope.project.my_permissions.indexOf("modify_us") > -1)
return
oldParentScope = null
newParentScope = null
itemEl = null
tdom = $el
@ -70,23 +69,44 @@ KanbanSortableDirective = ($repo, $rs, $rootscope) ->
})
drake.on 'drag', (item) ->
oldParentScope = $(item).parent().scope()
window.dragMultiple.start(item, containers)
drake.on 'cloned', (item, dropTarget) ->
$(item).addClass('multiple-drag-mirror')
drake.on 'dragend', (item) ->
parentEl = $(item).parent()
itemEl = $(item)
itemUs = itemEl.scope().us
itemIndex = itemEl.index()
newParentScope = parentEl.scope()
newStatusId = newParentScope.s.id
oldStatusId = oldParentScope.s.id
dragMultipleItems = window.dragMultiple.stop()
if newStatusId != oldStatusId
deleteElement(itemEl)
# if it is not drag multiple
if !dragMultipleItems.length
dragMultipleItems = [item]
$scope.$apply ->
$rootscope.$broadcast("kanban:us:move", itemUs, itemUs.getIn(['model', 'status']), newStatusId, itemIndex)
firstElement = dragMultipleItems[0]
index = $(firstElement).index()
newStatus = newParentScope.s.id
usList = _.map dragMultipleItems, (item) -> $(item).scope().us
finalUsList = _.map usList, (item) ->
return {
id: item.get('id'),
oldStatusId: item.getIn(['model', 'status'])
}
$scope.$apply ->
_.each usList, (item, key) =>
oldStatus = item.getIn(['model', 'status'])
sameContainer = newStatus == oldStatus
if !sameContainer
itemEl = $(dragMultipleItems[key])
deleteElement(itemEl)
$rootscope.$broadcast("kanban:us:move", finalUsList, newStatus, index)
scroll = autoScroll(containers, {
margin: 100,

View File

@ -29,7 +29,7 @@ debounce = @.taiga.debounce
module = angular.module("taigaRelatedTasks", [])
RelatedTaskRowDirective = ($repo, $compile, $confirm, $rootscope, $loading, $template, $translate) ->
RelatedTaskRowDirective = ($repo, $compile, $confirm, $rootscope, $loading, $template, $translate, $emojis) ->
templateView = $template.get("task/related-task-row.html", true)
templateEdit = $template.get("task/related-task-row-edit.html", true)
@ -82,7 +82,11 @@ RelatedTaskRowDirective = ($repo, $compile, $confirm, $rootscope, $loading, $tem
delete_task: $scope.project.my_permissions.indexOf("delete_task") != -1
}
$el.html($compile(templateView({task: task, perms: perms}))($scope))
$el.html($compile(templateView({
task: task,
perms: perms,
emojify: (text) -> $emojis.replaceEmojiNameByHtmlImgs(_.escape(text))
}))($scope))
$el.on "click", ".edit-task", ->
renderEdit($model.$modelValue)
@ -119,7 +123,7 @@ RelatedTaskRowDirective = ($repo, $compile, $confirm, $rootscope, $loading, $tem
return {link:link, require:"ngModel"}
module.directive("tgRelatedTaskRow", ["$tgRepo", "$compile", "$tgConfirm", "$rootScope", "$tgLoading",
"$tgTemplate", "$translate", RelatedTaskRowDirective])
"$tgTemplate", "$translate", "$tgEmojis", RelatedTaskRowDirective])
RelatedTaskCreateFormDirective = ($repo, $compile, $confirm, $tgmodel, $loading, $analytics) ->

View File

@ -27,7 +27,7 @@ bindOnce = @.taiga.bindOnce
debounce = @.taiga.debounce
trim = @.taiga.trim
CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxService, $translate, $q, attachmentsService) ->
CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxService, $translate, $q, $confirm, attachmentsService) ->
link = ($scope, $el, attrs) ->
$scope.isNew = true
@ -100,15 +100,17 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxSer
_.pull($scope.task.tags, value)
$scope.$on "taskform:new", (ctx, sprintId, usId) ->
$scope.task = {
$scope.task = $model.make_model('tasks', {
project: $scope.projectId
milestone: sprintId
user_story: usId
is_archived: false
status: $scope.project.default_task_status
assigned_to: null
tags: []
}
tags: [],
subject: "",
description: "",
})
$scope.isNew = true
$scope.attachments = Immutable.List()
@ -187,7 +189,30 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxSer
$el.on "submit", "form", submit
close = () =>
if !$scope.task.isModified()
lightboxService.close($el)
$scope.$apply ->
$scope.task.revert()
else
$confirm.ask($translate.instant("LIGHTBOX.CREATE_EDIT_TASK.CONFIRM_CLOSE")).then (result) ->
lightboxService.close($el)
$scope.task.revert()
result.finish()
$el.find('.close').on "click", (event) ->
event.preventDefault()
event.stopPropagation()
close()
$el.keydown (event) ->
event.stopPropagation()
code = if event.keyCode then event.keyCode else event.which
if code == 27
close()
$scope.$on "$destroy", ->
$el.find('.close').off()
$el.off()
return {link: link}
@ -250,6 +275,7 @@ module.directive("tgLbCreateEditTask", [
"lightboxService",
"$translate",
"$q",
"$tgConfirm",
"tgAttachmentsService",
CreateEditTaskDirective
])

View File

@ -134,6 +134,12 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
@filterRemoteStorageService.storeFilters(@scope.projectId, userFilters, 'tasks-custom-filters').then(@.generateFilters)
isFilterDataTypeSelected: (filterDataType) ->
for filter in @.selectedFilters
if (filter['dataType'] == filterDataType)
return true
return false
saveCustomFilter: (name) ->
filters = {}
urlfilters = @location.search()
@ -141,6 +147,7 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
filters.status = urlfilters.status
filters.assigned_to = urlfilters.assigned_to
filters.owner = urlfilters.owner
filters.role = urlfilters.role
@filterRemoteStorageService.getFilters(@scope.projectId, 'tasks-custom-filters').then (userFilters) =>
userFilters[name] = filters
@ -159,6 +166,7 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
loadFilters.status = urlfilters.status
loadFilters.assigned_to = urlfilters.assigned_to
loadFilters.owner = urlfilters.owner
loadFilters.role = urlfilters.role
loadFilters.q = urlfilters.q
return @q.all([
@ -188,6 +196,15 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
it.name = it.full_name || "Unassigned"
return it
role = _.map data.roles, (it) ->
if it.id
it.id = it.id.toString()
else
it.id = "null"
it.name = it.name || "Unassigned"
return it
owner = _.map data.owners, (it) ->
it.id = it.id.toString()
@ -213,6 +230,10 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
selected = @.formatSelectedFilters("owner", owner, loadFilters.owner)
@.selectedFilters = @.selectedFilters.concat(selected)
if loadFilters.role
selected = @.formatSelectedFilters("role", role, loadFilters.role)
@.selectedFilters = @.selectedFilters.concat(selected)
@.filterQ = loadFilters.q
@.filters = [
@ -233,6 +254,11 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
dataType: "assigned_to",
content: assignedTo
},
{
title: @translate.instant("COMMON.FILTERS.CATEGORIES.ROLE"),
dataType: "role",
content: role
},
{
title: @translate.instant("COMMON.FILTERS.CATEGORIES.CREATED_BY"),
dataType: "owner",
@ -290,9 +316,10 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
@taskboardTasksService.replaceModel(taskModel)
promise = @repo.save(taskModel)
promise.then null, ->
console.log "FAIL" # TODO
@repo.save(taskModel).then =>
@.generateFilters()
if @.isFilterDataTypeSelected('assigned_to') || @.isFilterDataTypeSelected('role')
@.loadTasks()
initializeSubscription: ->
routingKey = "changes.project.#{@scope.projectId}.tasks"
@ -438,6 +465,10 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
@taskboardTasksService.assignOrders(order)
@.loadSprintStats()
@.generateFilters()
if @.isFilterDataTypeSelected('status')
@.loadTasks()
## Template actions
addNewTask: (type, us) ->

View File

@ -7,8 +7,8 @@
var reset = function(elm) {
$(elm)
.removeAttr('style')
.removeClass('tg-backlog-us-mirror')
.removeClass('backlog-us-mirror')
.removeClass('tg-multiple-drag-mirror')
.removeClass('multiple-drag-mirror')
.data('dragMultipleIndex', null)
.data('dragMultipleActive', false);
};
@ -40,6 +40,8 @@
var currentTop = shadow.position().top;
var height = shadow.outerHeight();
$('.gu-transit').addClass('gu-transit-multi');
_.forEach(dragMultiple.items.draggingItems, function(elm, index) {
var elmIndex = parseInt(elm.data('dragMultipleIndex'), 10);
var top = currentTop + (elmIndex * height);
@ -57,22 +59,21 @@
refreshOriginal();
var current = dragMultiple.items.elm;
var container = dragMultiple.items.container;
document.documentElement.removeEventListener('mousemove', removeEventFn);
// reset
dragMultiple.items = {};
$('.' + mainClass).removeClass(mainClass);
$('.tg-backlog-us-mirror').remove();
$('.backlog-us-mirror').removeClass('backlog-us-mirror');
$('.tg-multiple-drag-mirror').remove();
$('.multiple-drag-mirror').removeClass('multiple-drag-mirror');
$('.tg-backlog-us-dragging')
.removeClass('tg-backlog-us-dragging')
$('.tg-multiple-drag-dragging')
.removeClass('tg-multiple-drag-dragging')
.show();
$('.gu-transit-multi').removeClass('gu-transit-multi');
return $('.' + multipleSortableClass);
};
@ -180,8 +181,8 @@
clone = $(item).clone(true);
clone
.addClass('backlog-us-mirror')
.addClass('tg-backlog-us-mirror')
.addClass('multiple-drag-mirror')
.addClass('tg-multiple-drag-mirror')
.data('dragmultiple:originalPosition', $(item).position())
.data('dragMultipleActive', true)
.css({
@ -194,7 +195,7 @@
$(item)
.hide()
.addClass('tg-backlog-us-dragging');
.addClass('tg-multiple-drag-dragging');
return clone;
});

View File

@ -207,6 +207,7 @@
"PRIORITIES": "Prioritats",
"TAGS": "Etiquetes",
"ASSIGNED_TO": "Assignat a",
"ROLE": "Rol",
"CREATED_BY": "Creat per",
"CUSTOM_FILTERS": "Filtres personalitzats",
"EPIC": "Epic"

View File

@ -207,6 +207,7 @@
"PRIORITIES": "Prioritäten",
"TAGS": "Schlagwörter",
"ASSIGNED_TO": "Zugeordnet zu",
"ROLE": "Rolle",
"CREATED_BY": "Erstellt durch",
"CUSTOM_FILTERS": "Benutzerfilter",
"EPIC": "Epic"

View File

@ -207,6 +207,7 @@
"PRIORITIES": "Priorities",
"TAGS": "Tags",
"ASSIGNED_TO": "Assigned to",
"ROLE": "Role",
"CREATED_BY": "Created by",
"CUSTOM_FILTERS": "Custom filters",
"EPIC": "Epic"
@ -1016,7 +1017,7 @@
"CONFIRM": "Are you sure you want to delete your Taiga account?",
"CANCEL": "Back to settings",
"ACCEPT": "Delete account",
"BLOCK_PROJECT": "Note that all the projects you own projects will be <strong>blocked</strong> after you delete your account. If you do want a project blocked, transfer ownership to another member of each project prior to deleting your account."
"BLOCK_PROJECT": "Note that all the projects you own projects will be <strong>blocked</strong> after you delete your account. If you do not want a project blocked, transfer ownership to another member of each project prior to deleting your account. "
},
"DELETE_PROJECT": {
"TITLE": "Delete project",
@ -1065,13 +1066,15 @@
"PLACEHOLDER_STATUS": "Task status",
"OPTION_UNASSIGNED": "Unassigned",
"PLACEHOLDER_SHORT_DESCRIPTION": "Type a short description",
"ACTION_EDIT": "Edit task"
"ACTION_EDIT": "Edit task",
"CONFIRM_CLOSE": "You have not saved changes.\nAre you sure you want to close the form?"
},
"CREATE_EDIT_US": {
"TITLE": "New US",
"PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this US",
"NEW_US": "New user story",
"EDIT_US": "Edit user story"
"EDIT_US": "Edit user story",
"CONFIRM_CLOSE": "You have not saved changes.\nAre you sure you want to close the form?"
},
"DELETE_SPRINT": {
"TITLE": "Delete sprint"

View File

@ -207,6 +207,7 @@
"PRIORITIES": "Prioridades",
"TAGS": "Etiquetas",
"ASSIGNED_TO": "Asignado a",
"ROLE": "Rol",
"CREATED_BY": "Creada por",
"CUSTOM_FILTERS": "Filtros personalizados",
"EPIC": "Épica"

View File

@ -207,6 +207,7 @@
"PRIORITIES": "Kiireellisyydet",
"TAGS": "Avainsanat",
"ASSIGNED_TO": "Tekijä",
"ROLE": "Rooli",
"CREATED_BY": "Luoja",
"CUSTOM_FILTERS": "Omat suodattimet",
"EPIC": "Eepos"

View File

@ -207,6 +207,7 @@
"PRIORITIES": "Priorités",
"TAGS": "Mots-clés",
"ASSIGNED_TO": "Affecté à",
"ROLE": "Rôle",
"CREATED_BY": "Créé par",
"CUSTOM_FILTERS": "Filtres personnalisés",
"EPIC": "Épopée"

View File

@ -207,6 +207,7 @@
"PRIORITIES": "Priorità",
"TAGS": "Tag",
"ASSIGNED_TO": "Assegnato a",
"ROLE": "Ruolo",
"CREATED_BY": "Creato da",
"CUSTOM_FILTERS": "Filtri personalizzati",
"EPIC": "Epic"

View File

@ -207,6 +207,7 @@
"PRIORITIES": "優先度",
"TAGS": "タグ",
"ASSIGNED_TO": "担当者",
"ROLE": "役割",
"CREATED_BY": "作成者",
"CUSTOM_FILTERS": "カスタムフィルター",
"EPIC": "エピック"

View File

@ -207,6 +207,7 @@
"PRIORITIES": "우선순위",
"TAGS": "태그",
"ASSIGNED_TO": "할당됨",
"ROLE": "역할",
"CREATED_BY": "생성함",
"CUSTOM_FILTERS": "사용자 정의 필터",
"EPIC": "에픽"

View File

@ -207,6 +207,7 @@
"PRIORITIES": "Prioriteter",
"TAGS": "Etiketter",
"ASSIGNED_TO": "Tildelt til",
"ROLE": "Rolle",
"CREATED_BY": "Laget av",
"CUSTOM_FILTERS": "Egendefinert filtre",
"EPIC": "Epic"

View File

@ -207,6 +207,7 @@
"PRIORITIES": "Prioriteit",
"TAGS": "Tags",
"ASSIGNED_TO": "Toegewezen aan",
"ROLE": "Rol",
"CREATED_BY": "Aangemaakt door",
"CUSTOM_FILTERS": "Eigen filters",
"EPIC": "Epic"

View File

@ -207,6 +207,7 @@
"PRIORITIES": "Priorytety",
"TAGS": "Tagi",
"ASSIGNED_TO": "Przypisane do",
"ROLE": "Rola",
"CREATED_BY": "Stworzona przez",
"CUSTOM_FILTERS": "Filtry niestandardowe",
"EPIC": "Epic"

View File

@ -207,6 +207,7 @@
"PRIORITIES": "Prioridades",
"TAGS": "Tags",
"ASSIGNED_TO": "Atribuído a",
"ROLE": "Função",
"CREATED_BY": "Criado por",
"CUSTOM_FILTERS": "Filtros personalizados",
"EPIC": "Épico"

View File

@ -207,6 +207,7 @@
"PRIORITIES": "Приоритеты",
"TAGS": "Тэги",
"ASSIGNED_TO": "Назначено",
"ROLE": "Роль",
"CREATED_BY": "Создано",
"CUSTOM_FILTERS": "Собственные фильтры",
"EPIC": "Epic"
@ -217,7 +218,7 @@
"DB_CLICK": "двойной клик для редактирования",
"SELECT_LANGUAGE_PLACEHOLDER": "Выбор языка",
"SELECT_LANGUAGE_REMOVE_FORMATING": "Убрать форматирование",
"OUTDATED": "Another person has made changes while you were editing. Check the new version on the activity tab before you save your changes.",
"OUTDATED": "Другой пользователь внес изменения в систему. Проверьте новую версию на вкладке активности, прежде чем вы сохраните ваши изменения.",
"MARKDOWN_HELP": "Помощь по синтаксису Markdown"
},
"PERMISIONS_CATEGORIES": {
@ -611,7 +612,7 @@
"WARNING_DELETE_ROLE": "Be careful! All role estimations will be removed",
"ERROR_DELETE_ALL": "Вы не можете удалить все значения",
"EXTERNAL_USER": "Внешний пользователь",
"NOTE_EXTERNAL_USERS": "<strong>Note:</strong> by External User we mean any anonymous user not belonging to the Taiga platform, including search engines. Please use this role with care."
"NOTE_EXTERNAL_USERS": "<strong>Примечание:</strong> под 'внешним пользователем' мы имеем ввиду анонимного пользователя, не имеющего платформы Taiga, включая поисковые движки. Пожалуйста, используйте этот функционал с осторожностью."
},
"THIRD_PARTIES": {
"SECRET_KEY": "Секретный ключ",
@ -689,7 +690,7 @@
"DEFAULT_DELETE_MESSAGE": "приглашение на {{email}}"
},
"DEFAULT_VALUES": {
"LABEL_EPIC_STATUS": "Default value for epic status selector",
"LABEL_EPIC_STATUS": "Значение по умолчанию для селектора эпического статуса",
"LABEL_US_STATUS": "Выбор значения статуса по умолчанию для пользовательской истории",
"LABEL_POINTS": "Значения по умолчанию для выбора очков",
"LABEL_TASK_STATUS": "Значение по умолчанию для статуса задачи",
@ -875,7 +876,7 @@
"TEMPLATE_SCRUM_LONGDESC": "Скрам это методология управления процессом разработки программного обеспечения построенная на коротких итерациях и постепенном прогрессе. Бэклог по продукту это те задачи которые нужно сделать, отсортированные в порядке приоритета. Бэклоги разбиваются на относительно короткие, управляемые куски - спринты. На основании возможностей, навыков и ресурсов команды в каждый спринт вносится несколько выбранных пользовательских историй из бэклога. На каждый спринт выделяется оговоренный период времени. С развитием проекта бэклог уменьшается.",
"TEMPLATE_KANBAN": "Kanban",
"TEMPLATE_KANBAN_DESC": "Придерживайтесь одинаковых принципов работы над независимыми задачами",
"TEMPLATE_KANBAN_LONGDESC": "The Kanban methodology is used to divide project development (any sort of project) into stages.\nA kanban card is like an index card or post-it note that details every task (or user story) in a project that needs to be completed. The Kanban board is used to move each card from one state of completion to the next and in so doing, helps track progress.",
"TEMPLATE_KANBAN_LONGDESC": "Метод \"Канбан\" используется, чтобы разделить разработку проекта (проекта любого типа) на стадии. В этой методологоии, карточки похожи на примечания, которые описывают каждую задачу (или пользовательскую историю) проекта, который должен быть выполнен. Карточки в системе, построенной на методе \"Канбан\", обычно переводятся из одного состояния завершенности в другое, таким образом позволяя отслеживать прогресс всего проекта.",
"DUPLICATE": "Клонировать проект",
"DUPLICATE_DESC": "Начните сначала и сохраняйте вашу конфигурацию",
"IMPORT": "Импортировать проект",
@ -922,7 +923,7 @@
"CHOOSE": "Выбрать пользователя",
"LINKS": "Связь с {{platform}}",
"LINKS_DESCRIPTION": "Do you want to keep the link of each item with the original {{platform}} card?",
"WARNING_MAIL_USER": "Note that if the user does not have a Taiga account we will not be able to assign the tasks to him.",
"WARNING_MAIL_USER": "Заметьте, если у пользователя нету аккаунта Taiga, мы не сможем назначить ему задачи.",
"ASSIGN": "Назначить",
"PROJECT_SELECTOR": {
"NO_RESULTS": "It looks like nothing was found with your search criteria",
@ -1140,7 +1141,7 @@
"TITLE_DELETE_ACTION": "Удалить пользовательскую историю",
"LIGHTBOX_TITLE_BLOKING_US": "Блокирующая ПИ",
"NOT_ESTIMATED": "Не оценено",
"OWNER_US": "This User Story belongs to",
"OWNER_US": "Эта пользовательская история принадлежит",
"TRIBE": {
"PUBLISH": "Publish as Gig in Taiga Tribe",
"PUBLISH_INFO": "Больше инфо",
@ -1551,7 +1552,7 @@
"TASK_UPDATED_WITH_US": "{{username}} изменил атрибут \"{{field_name}}\" задачи {{obj_name}}, которая принадлежит ПИ {{us_name}}",
"TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} установил атрибут \"{{field_name}}\" задачи {{obj_name}}, которая принадлежит ПИ {{us_name}}, на {{new_value}}",
"WIKI_UPDATED": "{{username}} обновил вики-страницу {{obj_name}}",
"EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}",
"EPIC_UPDATED": "{{username}} обновил атрибут \"{{field_name}}\" ПИ {{obj_name}}",
"EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} обновил атрибут поля \"{{field_name}}\" эпика {{obj_name}} на {{new_value}}",
"EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} обновил поле \"{{field_name}}\" эпика {{obj_name}} на <span class=\"new-color\" style=\"background: {{new_value}}\"></span>",
"NEW_COMMENT_US": "{{username}} прокомментировал ПИ {{obj_name}}",

View File

@ -207,6 +207,7 @@
"PRIORITIES": "Prioritet",
"TAGS": "Etiketter",
"ASSIGNED_TO": "Tilldelad till",
"ROLE": "Roll",
"CREATED_BY": "Skapad av",
"CUSTOM_FILTERS": "Anpassad filter",
"EPIC": "Epost"

View File

@ -207,6 +207,7 @@
"PRIORITIES": "Öncelikler",
"TAGS": "Etiketler ",
"ASSIGNED_TO": "Atanmış",
"ROLE": "Rol",
"CREATED_BY": "Oluşturan",
"CUSTOM_FILTERS": "Özel filtreler",
"EPIC": "Destan"

View File

@ -207,6 +207,7 @@
"PRIORITIES": "优先级",
"TAGS": "标签",
"ASSIGNED_TO": "指派给",
"ROLE": "角色",
"CREATED_BY": "由创建",
"CUSTOM_FILTERS": "定制过滤器",
"EPIC": "史诗"

View File

@ -207,6 +207,7 @@
"PRIORITIES": "優先性",
"TAGS": "標籤",
"ASSIGNED_TO": "指派給 ",
"ROLE": "角色",
"CREATED_BY": "由創建",
"CUSTOM_FILTERS": "客製過濾器 ",
"EPIC": "Epic"

View File

@ -3,7 +3,8 @@ span.belong-to-epic-text-wrapper(tg-repeat="epic in immutable_epics track by epi
a.belong-to-epic-text(
href=""
tg-nav="project-epics-detail:project=epic.getIn(['project', 'slug']),ref=epic.get('ref')"
) #{hash}{{epic.get('id')}} {{epic.get('subject')}}
ng-bind-html="'#'+epic.get('ref')+' '+epic.get('subject') | emojify"
)
span.belong-to-epic-label(
ng-style="::{'background-color': epic.get('color')}"
translate="EPICS.EPIC"

View File

@ -23,7 +23,7 @@
tg-check-permission="{{vm.getPermissionsKey()}}"
)
a.e2e-assign.card-owner-assign(
ng-click="vm.onClickAssignedTo({id: vm.item.get('id')})"
ng-click="!$event.ctrlKey && vm.onClickAssignedTo({id: vm.item.get('id')})"
href=""
)
tg-svg(svg-icon="icon-add-user")
@ -31,7 +31,7 @@
a.e2e-edit.card-edit(
href=""
ng-click="vm.onClickEdit({id: vm.item.get('id')})"
ng-click="!$event.ctrlKey && vm.onClickEdit({id: vm.item.get('id')})"
tg-loading="vm.item.get('loading')"
)
tg-svg(svg-icon="icon-edit")

View File

@ -4,4 +4,5 @@ ul.card-tasks(ng-if="vm.isRelatedTasksVisible()")
href="#"
tg-nav="project-tasks-detail:project=vm.project.slug,ref=task.get('ref')",
ng-class="{'closed-task': task.get('is_closed'), 'blocked-task': task.get('is_blocked')}"
) {{"#" + task.get('ref')}} {{task.get('subject')}}
ng-bind-html="'#'+task.get('ref')+' '+task.get('subject') | emojify"
)

View File

@ -6,7 +6,7 @@ h2.card-title
title="#{{ ::vm.item.getIn(['model', 'ref']) }} {{ vm.item.getIn(['model', 'subject'])}}"
)
span(ng-if="vm.visible('ref')") {{::"#" + vm.item.getIn(['model', 'ref'])}}
span.e2e-title(ng-if="vm.visible('subject')") {{vm.item.getIn(['model', 'subject'])}}
span.e2e-title(ng-if="vm.visible('subject')", ng-bind-html="vm.item.getIn(['model', 'subject']) | emojify")
tg-belong-to-epics(
format="pill"
ng-if="vm.item.getIn(['model', 'epics'])"

View File

@ -1,5 +1,5 @@
.card-unfold.ng-animate-disabled(
ng-click="vm.toggleFold()"
ng-click="!$event.ctrlKey && vm.toggleFold()"
ng-if="vm.visible('unfold') && (vm.hasTasks() || vm.hasVisibleAttachments())"
role="button"
)

View File

@ -14,3 +14,15 @@
images="vm.item.get('images')"
)
include card-templates/card-unfold
.card-transit-multi
div.fake-us
div.fake-img
div.column
div.fake-text
div.fake-text
div.fake-us
div.fake-img
div.column
div.fake-text
div.fake-text

View File

@ -7,10 +7,12 @@
span.detail-subject.e2e-title-subject(
ng-click="vm.editSubject(true)"
ng-if="vm.permissions.canEdit"
) {{vm.item.subject}}
ng-bind-html="vm.item.subject | emojify"
)
span.detail-subject.e2e-title-subject(
ng-if="!vm.permissions.canEdit"
) {{vm.item.subject}}
ng-bind-html="vm.item.subject | emojify"
)
a(
href=""
ng-if="vm.permissions.canEdit"
@ -55,7 +57,7 @@
title="{{'TASK.TITLE_LINK_GO_OWNER' | translate}}"
)
span.item-ref {{'#' + vm.item.user_story_extra_info.ref}}
span {{::vm.item.user_story_extra_info.subject}}
span(ng-bind-html="vm.item.user_story_extra_info.subject | emojify")
tg-belong-to-epics(
ng-if="::vm.item.user_story_extra_info.epics"
epics="::vm.item.user_story_extra_info.epics"
@ -69,7 +71,8 @@
ng-repeat="userstory in vm.item.generated_user_stories track by userstory.id"
tg-check-permission="view_us"
tg-nav="project-userstories-detail:project=vm.project.slug,ref=userstory.ref"
) {{'#' + userstory.ref}} {{userstory.subject}}
ng-bind-html="'#'+userstory.ref+' '+userstory.subject | emojify"
)
//- Issue origin from github
.issue-external-reference(ng-if="vm.item.external_reference")
@ -91,7 +94,7 @@
title="{{'US.TITLE_LINK_GO_TO_ISSUE' | translate}}"
)
span.item-ref {{'#' + vm.item.origin_issue.ref}}
span {{vm.item.origin_issue.subject}}
span(ng-bind-html="vm.item.origin_issue.subject | emojify")
//- Blocked description
.block-desc-container(ng-show="vm.item.is_blocked")

View File

@ -18,7 +18,15 @@ form(name="vm.filtersForm")
.filters-step-cat
.filters-applied
.single-filter.ng-animate-disabled(ng-repeat="it in vm.selectedFilters track by it.key")
span.name(ng-attr-style="{{it.color ? 'border-left: 3px solid ' + it.color: ''}}") {{it.name}}
span.name(
ng-attr-style="{{it.color ? 'border-left: 3px solid ' + it.color: ''}}"
ng-if="it.dataType === 'tags'"
ng-bind-html="it.name | emojify"
)
span.name(
ng-if="it.dataType !== 'tags'"
ng-attr-style="{{it.color ? 'border-left: 3px solid ' + it.color: ''}}"
) {{it.name}}
a.remove-filter.e2e-remove-filter(
ng-click="vm.unselectFilter(it)"
href=""
@ -77,7 +85,15 @@ form(name="vm.filtersForm")
ng-if="!vm.isFilterSelected(filter, it) && !(it.count == 0 && filter.hideEmpty)"
ng-click="vm.selectFilter(filter, it)"
)
span.name(ng-attr-style="{{it.color ? 'border-left: 3px solid ' + it.color: ''}}") {{it.name}}
span.name(
ng-if="filter.dataType === 'tags'",
ng-attr-style="{{it.color ? 'border-left: 3px solid ' + it.color: ''}}"
ng-bind-html="it.name | emojify"
)
span.name(
ng-if="filter.dataType !== 'tags'",
ng-attr-style="{{it.color ? 'border-left: 3px solid ' + it.color: ''}}"
) {{it.name}}
span.number.e2e-filter-count(ng-if="it.count > 0") {{it.count}}
li.custom-filters.e2e-custom-filters(

View File

@ -128,6 +128,9 @@ tg-filter {
@include ellipsis(100%);
display: block;
width: 100%;
img {
vertical-align: middle;
}
}
.number {
background: darken($whitish, 20%); // Fallback

View File

@ -1,4 +1,4 @@
span {{ tag[0] }}
span(ng-bind-html="tag[0] | emojify")
tg-svg.icon-close.e2e-delete-tag(
ng-if="hasPermissions"
svg-icon="icon-close"

View File

@ -19,4 +19,7 @@
height: 1rem;
width: 1rem;
}
img {
vertical-align: middle;
}
}

View File

@ -208,8 +208,6 @@ Medium = ($translate, $confirm, $storage, wysiwygService, animationFrame, tgLoad
$scope.codeEditorVisible = false
$scope.codeLans = []
wysiwygService.loadEmojis()
wysiwygCodeHightlighterService.getLanguages().then (codeLans) ->
$scope.codeLans = codeLans

View File

@ -26,49 +26,13 @@ class WysiwygService
@.$inject = [
"tgWysiwygCodeHightlighterService",
"tgProjectService",
"$tgNavUrls"
"$tgNavUrls",
"$tgEmojis"
]
constructor: (@wysiwygCodeHightlighterService, @projectService, @navurls) ->
constructor: (@wysiwygCodeHightlighterService, @projectService, @navurls, @emojis) ->
searchEmojiByName: (name) ->
return _.filter @.emojis, (it) -> it.name.indexOf(name) != -1
setEmojiImagePath: (emojis) ->
@.emojis = _.map emojis, (it) ->
it.image = "/#{window._version}/emojis/" + it.image
return it
loadEmojis: () ->
$.getJSON("/#{window._version}/emojis/emojis-data.json").then(@.setEmojiImagePath.bind(this))
getEmojiById: (id) ->
return _.find @.emojis, (it) -> it.id == id
getEmojiByName: (name) ->
return _.find @.emojis, (it) -> it.name == name
replaceImgsByEmojiName: (html) ->
emojiIds = taiga.getMatches(html, /emojis\/([^"]+).png"/gi)
for emojiId in emojiIds
regexImgs = new RegExp('<img(.*)' + emojiId + '[^>]+\>', 'g')
emoji = @.getEmojiById(emojiId)
html = html.replace(regexImgs, ':' + emoji.name + ':')
return html
replaceEmojiNameByImgs: (text) ->
emojiIds = taiga.getMatches(text, /:([\w ]*):/g)
for emojiId in emojiIds
regexImgs = new RegExp(':' + emojiId + ':', 'g')
emoji = @.getEmojiByName(emojiId)
if emoji
text = text.replace(regexImgs, '![alt](' + emoji.image + ')')
return text
return @emojis.searchByName(name)
pipeLinks: (text) ->
return text.replace /\[\[(.*?)\]\]/g, (match, p1, offset, str) ->
@ -134,7 +98,7 @@ class WysiwygService
}
html = html.replace(/&nbsp;(<\/.*>)/g, "$1")
html = @.replaceImgsByEmojiName(html)
html = @emojis.replaceImgsByEmojiName(html)
html = @.replaceUrls(html)
html = @.removeTrailingListBr(html)
@ -211,7 +175,7 @@ class WysiwygService
breaks: true
}
text = @.replaceEmojiNameByImgs(text)
text = @emojis.replaceEmojiNameByImgs(text)
text = @.pipeLinks(text)
md = window.markdownit({

View File

@ -20,7 +20,8 @@
a(
tg-nav="project-epics-detail:project=vm.project.slug,ref=vm.epic.get('ref')"
ng-attr-title="{{::vm.epic.get('subject')}}"
) #{hash}{{::vm.epic.get('ref')}} {{::vm.epic.get('subject')}}
ng-bind-html="'#'+vm.epic.get('ref')+' '+vm.epic.get('subject') | emojify"
)
span.epic-pill(
ng-style="::{'background-color': vm.epic.get('color')}"
translate="EPICS.EPIC"

View File

@ -13,7 +13,8 @@
a(
tg-nav="project-userstories-detail:project=vm.story.getIn(['project_extra_info', 'slug']),ref=vm.story.get('ref')"
ng-attr-title="{{::vm.story.get('subject')}}"
) #{hash}{{::vm.story.get('ref')}} {{::vm.story.get('subject')}}
ng-bind-html="'#'+vm.story.get('ref')+' '+vm.story.get('subject') | emojify"
)
tg-belong-to-epics(
ng-if="vm.story.get('epics')"
format="pill"

View File

@ -8,7 +8,8 @@ tg-svg.icon-drag(
a(
tg-nav="project-userstories-detail:project=vm.userstory.getIn(['project_extra_info', 'slug']),ref=vm.userstory.get('ref')"
ng-attr-title="{{vm.userstory.get('subject')}}"
) #{hash}{{vm.userstory.get('ref')}} {{vm.userstory.get('subject')}}
ng-bind-html="'#'+vm.userstory.get('ref')+' '+vm.userstory.get('subject') | emojify"
)
tg-belong-to-epics(
format="pill"

View File

@ -39,6 +39,11 @@ describe "dutyDirective", () ->
return value
provide.value "translateFilter", mockTranslateFilter
_mockEmojifyFilter = () ->
mockEmojifyFilter = (value) ->
return value
provide.value "emojifyFilter", mockEmojifyFilter
_mockTgProjectsService = () ->
mockTgProjectsService = {
projectsById: {
@ -60,6 +65,7 @@ describe "dutyDirective", () ->
_mockTgProjectsService()
_mockTranslate()
_mockTranslateFilter()
_mockEmojifyFilter()
return null
beforeEach ->

View File

@ -39,4 +39,4 @@ a.list-itemtype-ticket(
ng-if="::vm.duty.get('is_blocked')"
title="{{::vm.duty.get('blocked_note')}}"
) {{ 'COMMON.BLOCKED' | translate }}
span {{ ::duty.get('subject') }}
span(ng-bind-html="duty.get('subject') | emojify")

View File

@ -23,6 +23,7 @@ DropdownUserDirective = (authService, configService, locationService,
link = (scope, el, attrs, ctrl) ->
scope.vm = {}
scope.vm.isFeedbackEnabled = configService.get("feedbackEnabled")
scope.vm.supportUrl = configService.get("supportUrl")
taiga.defineImmutableProperty(scope.vm, "user", () -> authService.userData)
scope.vm.logout = ->

View File

@ -51,7 +51,7 @@ div.navbar-dropdown.dropdown-user
translate="PROJECT.NAVIGATION.FEEDBACK")
li
a(
href="https://tree.taiga.io/support/",
href="{{ vm.supportUrl }}",
target="_blank",
title="{{'PROJECT.NAVIGATION.HELP_TITLE' | translate}}",
translate="PROJECT.NAVIGATION.HELP")

View File

@ -53,25 +53,29 @@
ng-if="::vm.item.get('type') === 'epic'"
tg-nav="project-epics-detail:project=vm.item.get('project_slug'),ref=vm.item.get('ref')"
title="#{{ ::vm.item.get('ref') }} {{ ::vm.item.get('subject') }}"
) {{ ::vm.item.get('subject') }}
ng-bind-html="vm.item.get('subject') | emojify"
)
a.ticket-title(
href="#"
ng-if="::vm.item.get('type') === 'userstory'"
tg-nav="project-userstories-detail:project=vm.item.get('project_slug'),ref=vm.item.get('ref')"
title="#{{ ::vm.item.get('ref') }} {{ ::vm.item.get('subject') }}"
) {{ ::vm.item.get('subject') }}
ng-bind-html="vm.item.get('subject') | emojify"
)
a.ticket-title(
href="#"
ng-if="::vm.item.get('type') === 'task'"
tg-nav="project-tasks-detail:project=vm.item.get('project_slug'),ref=vm.item.get('ref')"
title="#{{ ::vm.item.get('ref') }} {{ ::vm.item.get('subject') }}"
) {{ ::vm.item.get('subject') }}
ng-bind-html="vm.item.get('subject') | emojify"
)
a.ticket-title(
href="#"
ng-if="::vm.item.get('type') === 'issue'"
tg-nav="project-issues-detail:project=vm.item.get('project_slug'),ref=vm.item.get('ref')"
title="#{{ ::vm.item.get('ref') }} {{ ::vm.item.get('subject') }}"
) {{ ::vm.item.get('subject') }}
ng-bind-html="vm.item.get('subject') | emojify"
)
div.list-itemtype-track
span.list-itemtype-track-likers(

View File

@ -28,6 +28,7 @@ class WikiHistoryController
constructor: (@wikiHistoryService) ->
taiga.defineImmutableProperty @, 'historyEntries', () => return @wikiHistoryService.historyEntries
@.toggle = false
initializeHistoryEntries: (wikiId) ->
if wikiId

View File

@ -1,11 +1,13 @@
nav.history-tabs(ng-if="vm.historyEntries.count()>0")
a.history-tab.active(
a.history-tab(
ng-class="{active: vm.toggle}"
ng-click="vm.toggle = !vm.toggle"
href=""
title="{{ACTIVITY.TITLE}}"
translate="ACTIVITY.TITLE"
)
section.wiki-history(ng-if="vm.historyEntries.count()>0")
section.wiki-history(ng-if="vm.historyEntries.count()>0 && vm.toggle")
tg-wiki-history-entry.wiki-history-entry(
tg-repeat="historyEntry in vm.historyEntries"
history-entry="historyEntry"

View File

@ -2,7 +2,7 @@ doctype html
div.error-main
div.error-container
img(src="/#{v}/svg/logo.svg", alt="TAIGA")
img(src="/#{v}/svg/logo.svg", alt="TAIGA", width="143px")
h1.logo Taiga
p.error-text(translate="ERROR.TEXT1")
a(href="/", title="", translate="COMMON.GO_HOME")

View File

@ -2,7 +2,7 @@ doctype html
div.error-main
div.error-container
img(src="/#{v}/svg/logo.svg", alt="TAIGA")
img(src="/#{v}/svg/logo.svg", alt="TAIGA", width="143px")
h1.logo(translate="ERROR.NOT_FOUND")
p.error-text(translate="ERROR.NOT_FOUND_TEXT")
a(href="/", title="", translate="COMMON.GO_HOME")

View File

@ -26,7 +26,7 @@
title="#{{ us.ref }} {{ us.subject }}"
)
span(tg-bo-ref="us.ref")
span(ng-bind="us.subject")
span(ng-bind-html="us.subject | emojify")
tg-belong-to-epics(
format="pill"
ng-if="us.epics"

View File

@ -53,7 +53,7 @@ section.issues-table.basic-table(ng-class="{empty: !issues.length}")
ng-if="issue.is_blocked"
title="{{issue.blocked_note}}"
) {{'ISSUES.TABLE.BLOCKED' | translate}}
span(ng-bind="issue.subject")
span(ng-bind-html="issue.subject | emojify")
div.issue-field(tg-issue-status-inline-edition="issue")

View File

@ -10,7 +10,15 @@ div.kanban-table(
tg-bo-title="s.name",
ng-class='{vfold:folds[s.id]}',
tg-class-permission="{'readonly': '!modify_task'}")
span(tg-bo-bind="s.name")
div.title
div.name(tg-bo-bind="s.name")
div.counter
span(ng-if="usByStatus.get(s.id.toString()).size") {{ usByStatus.get(s.id.toString()).size }}
span(
ng-if="!usByStatus.get(s.id.toString()).size"
ng-class='{hidden:!s.wip_limit}'
) 0
span(tg-bo-bind="' / ' + s.wip_limit", ng-if="s.wip_limit")
div.options
a.option(
href=""
@ -67,7 +75,7 @@ div.kanban-table(
tg-card.card.ng-animate-disabled(
tg-repeat="us in usByStatus.get(s.id.toString()) track by us.getIn(['model', 'id'])",
ng-class="{'kanban-task-maximized': ctrl.isMaximized(s.id), 'kanban-task-minimized': ctrl.isMinimized(s.id)}"
ng-class="{'kanban-task-maximized': ctrl.isMaximized(s.id), 'kanban-task-minimized': ctrl.isMinimized(s.id), 'kanban-task-selected': ctrl.selectedUss[us.get('id')], 'ui-multisortable-multiple': ctrl.selectedUss[us.get('id')]}"
tg-class-permission="{'readonly': '!modify_task'}"
tg-bind-scope,
on-toggle-fold="ctrl.toggleFold(id)"
@ -78,6 +86,7 @@ div.kanban-table(
zoom="ctrl.zoom"
zoom-level="ctrl.zoomLevel"
archived="ctrl.isUsInArchivedHiddenStatus(us.get('id'))"
ng-click="$event.ctrlKey && ctrl.toggleSelectedUs(us.get('id'))"
)
div.kanban-column-intro(ng-if="s.is_archived", tg-kanban-archived-status-intro="s")

View File

@ -14,7 +14,7 @@ script(type="text/ng-template", id="search-issues")
div.user-stories
div.user-story-name
a(href="", tg-nav="project-issues-detail:project=project.slug,ref=issue.ref",
tg-bo-bind="issue.subject")
tg-bind-html="issue.subject | emojify")
div.status(tg-listitem-issue-status="issue")
div.assigned-to(tg-listitem-assignedto="issue")
@ -34,7 +34,7 @@ script(type="text/ng-template", id="search-epics")
div.user-stories
div.user-story-name
a(href="", tg-nav="project-epics-detail:project=project.slug,ref=epic.ref",
tg-bo-bind="epic.subject")
tg-bind-html="epic.subject | emojify")
div.status(tg-listitem-epic-status="epic")
div.empty-search-results(ng-class="{'hidden': epics.length}")
@ -56,7 +56,7 @@ script(type="text/ng-template", id="search-userstories")
div.user-stories
div.user-story-name
a(href="", tg-nav="project-userstories-detail:project=project.slug,ref=us.ref",
tg-bo-bind="us.subject")
tg-bind-html="us.subject | emojify")
div.sprint
div.sprint-link
a(href="", tg-nav="project-taskboard:project=project.slug,sprint=us.milestone_slug",
@ -81,7 +81,7 @@ script(type="text/ng-template", id="search-tasks")
div.user-stories
div.user-story-name
a(href="", tg-nav="project-tasks-detail:project=project.slug,ref=task.ref",
tg-bo-bind="task.subject")
tg-bind-html="task.subject | emojify")
div.status(tg-listitem-task-status="task")
div.assigned-to(tg-listitem-assignedto="task")

View File

@ -18,7 +18,7 @@ div.sprint-table(tg-bind-scope, ng-class="{'sprint-empty-wrapper': !sprint.user_
tg-bo-title="'#' + us.ref + ' ' + us.subject",
ng-class="{closed: us.is_closed, blocked: us.is_blocked}")
span(tg-bo-ref="us.ref")
span(tg-bo-bind="us.subject")
span(tg-bind-html="us.subject | emojify")
tg-belong-to-epics(
format="pill"
ng-if="us.epics"

View File

@ -53,7 +53,7 @@ div.taskboard-table(
tg-nav-get-params="{\"milestone\": {{us.milestone}}}",
tg-bo-title="'#' + us.ref + ' ' + us.subject")
span.us-ref(tg-bo-ref="us.ref")
span(ng-bind="us.subject")
span(ng-bind-html="us.subject | emojify")
tg-belong-to-epics(
format="pill"
ng-if="us.epics"

View File

@ -2,7 +2,7 @@
a.clickable(
tg-nav="project-tasks-detail:project=project.slug,ref=task.ref")
span #<%- task.ref %>
span(ng-non-bindable) <%- task.subject %>
span(ng-non-bindable) <%= emojify(task.subject) %>
.task-settings
<% if(perms.modify_task) { %>

View File

@ -41,7 +41,7 @@
}
}
.backlog-us-mirror {
.multiple-drag-mirror.us-item-row {
background: $white;
border-radius: 4px;
box-shadow: 2px 2px 5px $gray;

View File

@ -91,8 +91,18 @@ $column-padding: .5rem 1rem;
&:last-child {
margin-right: 0;
}
span {
@include ellipsis(65%);
.title {
align-items: flex-end;
display: flex;
width: 100%;
.counter {
@include font-size(xsmall);
line-height: 1.6;
margin: 0 .5rem;
}
.name {
@include ellipsis(65%);
}
}
.option {
margin-right: .3rem;
@ -148,9 +158,63 @@ $column-padding: .5rem 1rem;
.kanban-uses-box {
background: $mass-white;
}
.kanban-task-selected {
&.card:not(.gu-transit-multi) {
// border: 1px solid $primary-light;
box-shadow: 0 0 0 1px $primary-light, 2px 2px 4px darken($whitish, 10%);
}
}
}
.kanban-table-inner {
display: flex;
flex-wrap: nowrap;
}
.card-transit-multi {
background: darken($whitish, 2%);
border: 1px dashed darken($whitish, 8%);
display: none;
opacity: 1;
padding: 1rem;
.fake-img,
.fake-text {
background: darken($whitish, 8%);
}
.fake-us {
display: flex;
margin-bottom: 1rem;
&:last-child {
margin-bottom: 0;
}
}
.column {
padding-left: .5rem;
width: 100%;
}
.fake-img {
flex-basis: 48px;
flex-shrink: 0;
height: 48px;
width: 48px;
}
.fake-text {
height: 1rem;
margin-bottom: 1rem;
width: 80%;
&:last-child {
margin-bottom: 0;
width: 40%;
}
}
}
.card.gu-transit-multi {
.card-transit-multi {
display: block;
}
.card-inner {
display: none;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 491 B

After

Width:  |  Height:  |  Size: 484 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 460 B

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 435 B

After

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 277 B

After

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 455 B

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 468 B

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 424 B

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 B

After

Width:  |  Height:  |  Size: 458 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 443 B

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 392 B

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 458 B

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 446 B

After

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 467 B

After

Width:  |  Height:  |  Size: 457 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 607 B

After

Width:  |  Height:  |  Size: 603 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 B

After

Width:  |  Height:  |  Size: 460 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 436 B

After

Width:  |  Height:  |  Size: 427 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 512 B

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 404 B

After

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 596 B

After

Width:  |  Height:  |  Size: 595 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 532 B

After

Width:  |  Height:  |  Size: 530 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 482 B

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 531 B

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 472 B

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 486 B

After

Width:  |  Height:  |  Size: 486 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 591 B

After

Width:  |  Height:  |  Size: 589 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 596 B

After

Width:  |  Height:  |  Size: 592 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 587 B

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 513 B

After

Width:  |  Height:  |  Size: 510 B

Some files were not shown because too many files have changed in this diff Show More