From 489c4ea02accf628f6d1b1933df45905c4e046e5 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 23 Jul 2014 13:05:53 +0200 Subject: [PATCH] Split directives.coffee into few separated files. --- .../{directives.coffee => components.coffee} | 229 +++++++++++++----- app/coffee/modules/common/popovers.coffee | 97 ++++++++ app/coffee/modules/common/wisiwyg.coffee | 189 +++++++++++++++ app/coffee/modules/issues/detail.coffee | 162 ------------- 4 files changed, 452 insertions(+), 225 deletions(-) rename app/coffee/modules/common/{directives.coffee => components.coffee} (51%) create mode 100644 app/coffee/modules/common/popovers.coffee create mode 100644 app/coffee/modules/common/wisiwyg.coffee diff --git a/app/coffee/modules/common/directives.coffee b/app/coffee/modules/common/components.coffee similarity index 51% rename from app/coffee/modules/common/directives.coffee rename to app/coffee/modules/common/components.coffee index 77c4738f..3f8b9457 100644 --- a/app/coffee/modules/common/directives.coffee +++ b/app/coffee/modules/common/components.coffee @@ -16,12 +16,17 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # -# File: modules/common/directives.coffee +# File: modules/common/components.coffee ### taiga = @.taiga bindOnce = @.taiga.bindOnce +module = angular.module("taigaCommon") + +############################################################################# +## Date Range Directive (used mainly for sprint date range) +############################################################################# DateRangeDirective = -> renderRange = ($el, first, second) -> @@ -38,6 +43,12 @@ DateRangeDirective = -> return {link:link} +module.directive("tgDateRange", DateRangeDirective) + + +############################################################################# +## Sprint Progress Bar Directive +############################################################################# SprintProgressBarDirective = -> renderProgress = ($el, percentage, visual_percentage) -> @@ -60,6 +71,12 @@ SprintProgressBarDirective = -> return {link: link} +module.directive("tgSprintProgressbar", SprintProgressBarDirective) + + +############################################################################# +## Date Selector Directive (using pikaday) +############################################################################# DateSelectorDirective =-> link = ($scope, $el, $attrs, $model) -> @@ -70,77 +87,169 @@ DateSelectorDirective =-> require: "ngModel" } +module.directive("tgDateSelector", DateSelectorDirective) + ############################################################################# -## User story status directive +## Watchers directive ############################################################################# +WatchersDirective = ($rootscope, $confirm) -> + # TODO: i18n + template = _.template(""" +
+ watchers + <% if (editable) { %> + + <% } %> +
-# TODO: change to less generic name. - -UsStatusDirective = ($repo) -> - ### - Print the status of a US and a popover to change it. - - tg-us-status: The user story - - on-update: Method call after US is updated - - Example: - - div.status(tg-us-status="us" on-update="ctrl.loadSprintState()") - a.us-status(href="", title="Status Name") - - NOTE: This directive need 'usStatusById' and 'project'. - ### - selectionTemplate = _.template(""" - """) - updateUsStatus = ($el, us, usStatusById) -> - usStatusDomParent = $el.find(".us-status") - usStatusDom = $el.find(".us-status .us-status-bind") - usStatusDom.text(usStatusById[us.status].name) - usStatusDomParent.css('color', usStatusById[us.status].color) + <% if (editable) { %> + + + <% } %> + + + <% }); %> + """) - link = ($scope, $el, $attrs) -> - $ctrl = $el.controller() - us = $scope.$eval($attrs.tgUsStatus) + link = ($scope, $el, $attrs, $model) -> + editable = $attrs.editable? - taiga.bindOnce $scope, "project", (project) -> - $el.append(selectionTemplate({ 'statuses': project.us_statuses })) - updateUsStatus($el, us, $scope.usStatusById) + renderWatchers = (watchers) -> + html = template({watchers: watchers, editable:editable}) + $el.html(html) - $el.on "click", ".us-status", (event) -> + if watchers.length == 0 + if editable + $el.find(".title").text("Add watchers") + $el.find(".watchers-header").addClass("no-watchers") + else + $el.find(".watchers-header").hide() + + $scope.$watch $attrs.ngModel, (item) -> + return if not item? + watchers = _.map(item.watchers, (watcherId) -> $scope.usersById[watcherId]) + renderWatchers(watchers) + + if not editable + $el.find(".add-watcher").remove() + + $el.on "click", ".icon-delete", (event) -> event.preventDefault() - event.stopPropagation() - $el.find(".pop-status").show() - - body = angular.element("body") - body.one "click", (event) -> - $el.find(".popover").hide() - - $el.on "click", ".status", (event) -> - event.preventDefault() - event.stopPropagation() target = angular.element(event.currentTarget) - us.status = target.data("status-id") - $el.find(".pop-status").hide() - updateUsStatus($el, us, $scope.usStatusById) + watcherId = target.data("watcher-id") - $scope.$apply () -> - $repo.save(us).then -> - $scope.$eval($attrs.onUpdate) + title = "Remove watcher" + subtitle = $scope.usersById[watcherId].full_name_display - $scope.$on "$destroy", -> - $el.off() + $confirm.ask(title, subtitle).then => + watcherIds = _.clone($model.$modelValue, false) + watcherIds = _.pull(watcherIds, watcherId) + $model.$setViewValue(watcherIds) - return {link: link} + $el.on "click", ".add-watcher", (event) -> + event.preventDefault() + $scope.$apply -> + $rootscope.$broadcast("watcher:add", $model.$modelValue) + + $scope.$on "watcher:added", (ctx, watcherId) -> + watchers = _.clone($model.$modelValue.watchers, false) + watchers.push(watcherId) + watchers = _.uniq(watchers) + + item = $model.$modelValue.clone() + item.watchers = watchers + + $model.$setViewValue(item) + + return {link:link, require:"ngModel"} + +module.directive("tgWatchers", ["$rootScope", "$tgConfirm", WatchersDirective]) + + +############################################################################# +## Assigned to directive +############################################################################# + +AssignedToDirective = ($rootscope, $confirm) -> + # TODO: i18n + template = _.template(""" + <% if (assignedTo) { %> +
+ + <%- assignedTo.full_name_display %> + +
+ <% } %> + + + """) + + link = ($scope, $el, $attrs, $model) -> + editable = $attrs.editable? + + renderAssignedTo = (issue) -> + assignedToId = issue?.assigned_to + assignedTo = null + assignedTo = $scope.usersById[assignedToId] if assignedToId? + html = template({assignedTo: assignedTo, editable:editable}) + $el.html(html) + + $scope.$watch $attrs.ngModel, (instance) -> + renderAssignedTo(instance) + + $el.on "click", ".user-assigned", (event) -> + event.preventDefault() + $scope.$apply -> + $rootscope.$broadcast("assigned-to:add", $model.$modelValue) + + $el.on "click", ".icon-delete", (event) -> + event.preventDefault() + title = "Remove assigned to" + subtitle = "" + $confirm.ask(title, subtitle).then => + $model.$modelValue.assigned_to = null + renderAssignedTo($model.$modelValue) + + $scope.$on "assigned-to:added", (ctx, issue) -> + renderAssignedTo(issue) + + return { + link:link, + require:"ngModel" + } + + +module.directive("tgAssignedTo", ["$rootScope", "$tgConfirm", AssignedToDirective]) ############################################################################# @@ -240,12 +349,6 @@ ListItemSeverityDirective = -> } -module = angular.module("taigaCommon") -module.directive("tgDateRange", DateRangeDirective) -module.directive("tgSprintProgressbar", SprintProgressBarDirective) -module.directive("tgDateSelector", DateSelectorDirective) -module.directive("tgUsStatus", ["$tgRepo", UsStatusDirective]) - module.directive("tgListitemIssueStatus", ListItemIssueStatusDirective) module.directive("tgListitemAssignedto", ListItemAssignedtoDirective) module.directive("tgListitemPriority", ListItemPriorityDirective) diff --git a/app/coffee/modules/common/popovers.coffee b/app/coffee/modules/common/popovers.coffee new file mode 100644 index 00000000..2d1631b2 --- /dev/null +++ b/app/coffee/modules/common/popovers.coffee @@ -0,0 +1,97 @@ +### +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino Garcia +# Copyright (C) 2014 David Barragán Merino +# +# 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 . +# +# File: modules/common/popovers.coffee +### + +taiga = @.taiga +bindOnce = @.taiga.bindOnce + +module = angular.module("taigaCommon") + +############################################################################# +## UserStory status Directive (popover for change status) +############################################################################# + +# FIXME: change to less generic name. + +UsStatusDirective = ($repo) -> + ### + Print the status of a US and a popover to change it. + - tg-us-status: The user story + - on-update: Method call after US is updated + + Example: + + div.status(tg-us-status="us" on-update="ctrl.loadSprintState()") + a.us-status(href="", title="Status Name") + + NOTE: This directive need 'usStatusById' and 'project'. + ### + selectionTemplate = _.template(""" + """) + + updateUsStatus = ($el, us, usStatusById) -> + usStatusDomParent = $el.find(".us-status") + usStatusDom = $el.find(".us-status .us-status-bind") + usStatusDom.text(usStatusById[us.status].name) + usStatusDomParent.css('color', usStatusById[us.status].color) + + link = ($scope, $el, $attrs) -> + $ctrl = $el.controller() + us = $scope.$eval($attrs.tgUsStatus) + + taiga.bindOnce $scope, "project", (project) -> + $el.append(selectionTemplate({ 'statuses': project.us_statuses })) + updateUsStatus($el, us, $scope.usStatusById) + + $el.on "click", ".us-status", (event) -> + event.preventDefault() + event.stopPropagation() + $el.find(".pop-status").show() + + body = angular.element("body") + body.one "click", (event) -> + $el.find(".popover").hide() + + $el.on "click", ".status", (event) -> + event.preventDefault() + event.stopPropagation() + target = angular.element(event.currentTarget) + us.status = target.data("status-id") + $el.find(".pop-status").hide() + updateUsStatus($el, us, $scope.usStatusById) + + $scope.$apply () -> + $repo.save(us).then -> + $scope.$eval($attrs.onUpdate) + + $scope.$on "$destroy", -> + $el.off() + + return {link: link} + +module.directive("tgUsStatus", ["$tgRepo", UsStatusDirective]) diff --git a/app/coffee/modules/common/wisiwyg.coffee b/app/coffee/modules/common/wisiwyg.coffee new file mode 100644 index 00000000..62cd8788 --- /dev/null +++ b/app/coffee/modules/common/wisiwyg.coffee @@ -0,0 +1,189 @@ +### +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino Garcia +# Copyright (C) 2014 David Barragán Merino +# +# 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 . +# +# File: modules/common/wisiwyg.coffee +### + +taiga = @.taiga +bindOnce = @.taiga.bindOnce + +module = angular.module("taigaCommon") + + +############################################################################# +## WYSIWYG markitup editor directive +############################################################################# + +# TODO: fix when i18n is implemented +$i18next = {t: (key) -> key} + +tgMarkitupDirective = ($rootscope, $rs) -> + previewTemplate = _.template(""" +
+
+ Edit +
+
+ <%= data %> +
+
+ """) + + link = ($scope, $el, $attrs, $model) -> + element = angular.element($el) + previewDomNode = $("
", {class: "preview"}) + + openHelp = -> + window.open($rootscope.urls.wikiHelpUrl(), '_blank') + + preview = -> + markdownDomNode = element.parents(".markdown") + markItUpDomNode = element.parents(".markItUp") + $rs.mdrender.render($scope.projectId, $model.$modelValue).then (data) -> + markdownDomNode.append(previewTemplate({data: data.data})) + markItUpDomNode.hide() + + # FIXME: Really `.parents()` is need? seems `.closest` + # function is better aproach for it + element.parents(".markdown").one "click", ".preview", (event) -> + event.preventDefault() + markdownDomNode.find(".preview").remove() + markItUpDomNode.show() + + markdownSettings = + nameSpace: 'markdown' + onShiftEnter: {keepDefault:false, openWith:'\n\n'} + markupSet: [ + { + name: $i18next.t('wiki-editor.heading-1') + key: "1" + placeHolder: $i18next.t('wiki-editor.placeholder') + closeWith: (markItUp) -> markdownTitle(markItUp, '=') + }, + { + name: $i18next.t('wiki-editor.heading-2') + key: "2" + placeHolder: $i18next.t('wiki-editor.placeholder') + closeWith: (markItUp) -> markdownTitle(markItUp, '-') + }, + { + name: $i18next.t('wiki-editor.heading-3') + key: "3" + openWith: '### ' + placeHolder: $i18next.t('wiki-editor.placeholder') + }, + { + separator: '---------------' + }, + { + name: $i18next.t('wiki-editor.bold') + key: "B" + openWith: '**' + closeWith: '**' + }, + { + name: $i18next.t('wiki-editor.italic') + key: "I" + openWith: '_' + closeWith: '_' + }, + { + name: $i18next.t('wiki-editor.strike') + key: "S" + openWith: '~~' + closeWith: '~~' + }, + { + separator: '---------------' + }, + { + name: $i18next.t('wiki-editor.bulleted-list') + openWith: '- ' + }, + { + name: $i18next.t('wiki-editor.numeric-list') + openWith: (markItUp) -> markItUp.line+'. ' + }, + { + separator: '---------------' + }, + { + name: $i18next.t('wiki-editor.picture') + key: "P" + replaceWith: '![[![Alternative text]!]]([![Url:!:http://]!] "[![Title]!]")' + }, + { + name: $i18next.t('wiki-editor.link') + key: "L" + openWith: '[' + closeWith: ']([![Url:!:http://]!] "[![Title]!]")' + placeHolder: $i18next.t('wiki-editor.link-placeholder') + }, + { + separator: '---------------' + }, + { + name: $i18next.t('wiki-editor.quotes') + openWith: '> ' + }, + { + name: $i18next.t('wiki-editor.code-block') + openWith: '```\n' + closeWith: '\n```' + }, + { + separator: '---------------' + }, + { + name: $i18next.t('wiki-editor.preview') + call: preview + className: "preview-icon" + }, + # { + # separator: '---------------' + # }, + # { + # name: $i18next.t('wiki-editor.help') + # call: openHelp + # className: "help" + # } + ] + afterInsert: (event) -> + target = angular.element(event.textarea) + $model.$setViewValue(target.val()) + + markdownTitle = (markItUp, char) -> + heading = '' + n = $.trim(markItUp.selection or markItUp.placeHolder).length + + for i in [0..n-1] + heading += char + + return '\n'+heading+'\n' + + element.markItUp(markdownSettings) + + element.on "keypress", (event) -> + $scope.$apply() + + $scope.$on "$destroy", -> + $el.off() + + return {link:link, require:"ngModel"} + +module.directive("tgMarkitup", ["$rootScope", "$tgResources", tgMarkitupDirective]) diff --git a/app/coffee/modules/issues/detail.coffee b/app/coffee/modules/issues/detail.coffee index 1f52bf45..2bcf8f39 100644 --- a/app/coffee/modules/issues/detail.coffee +++ b/app/coffee/modules/issues/detail.coffee @@ -548,165 +548,3 @@ CommentDirective = () -> return {link:link} module.directive("tgComment", CommentDirective) - -############################################################################# -## WYSIWYG markitup editor directive -############################################################################# - -#TODO: fix when i18n is implemented -$i18next = {t: (key) -> key} - -tgMarkitupDirective = ($rootscope, $rs) -> - previewTemplate = _.template(""" -
-
- Edit -
-
- <%= data %> -
-
- """) - - link = ($scope, $el, $attrs, $model) -> - element = angular.element($el) - previewDomNode = $("
", {class: "preview"}) - - openHelp = () -> - window.open($rootscope.urls.wikiHelpUrl(), '_blank') - - preview = () -> - markdownDomNode = element.parents(".markdown") - markItUpDomNode = element.parents(".markItUp") - $rs.mdrender.render($scope.projectId, $model.$modelValue).then (data) -> - markdownDomNode.append(previewTemplate({data: data.data})) - markItUpDomNode.hide() - - element.parents(".markdown").one "click", ".preview", (event) -> - event.preventDefault() - markdownDomNode.find(".preview").remove() - markItUpDomNode.show() - - - markdownSettings = - nameSpace: 'markdown' - onShiftEnter: {keepDefault:false, openWith:'\n\n'} - markupSet: [ - { - name: $i18next.t('wiki-editor.heading-1') - key: "1" - placeHolder: $i18next.t('wiki-editor.placeholder') - closeWith: (markItUp) -> markdownTitle(markItUp, '=') - }, - { - name: $i18next.t('wiki-editor.heading-2') - key: "2" - placeHolder: $i18next.t('wiki-editor.placeholder') - closeWith: (markItUp) -> markdownTitle(markItUp, '-') - }, - { - name: $i18next.t('wiki-editor.heading-3') - key: "3" - openWith: '### ' - placeHolder: $i18next.t('wiki-editor.placeholder') - }, - { - separator: '---------------' - }, - { - name: $i18next.t('wiki-editor.bold') - key: "B" - openWith: '**' - closeWith: '**' - }, - { - name: $i18next.t('wiki-editor.italic') - key: "I" - openWith: '_' - closeWith: '_' - }, - { - name: $i18next.t('wiki-editor.strike') - key: "S" - openWith: '~~' - closeWith: '~~' - }, - { - separator: '---------------' - }, - { - name: $i18next.t('wiki-editor.bulleted-list') - openWith: '- ' - }, - { - name: $i18next.t('wiki-editor.numeric-list') - openWith: (markItUp) -> markItUp.line+'. ' - }, - { - separator: '---------------' - }, - { - name: $i18next.t('wiki-editor.picture') - key: "P" - replaceWith: '![[![Alternative text]!]]([![Url:!:http://]!] "[![Title]!]")' - }, - { - name: $i18next.t('wiki-editor.link') - key: "L" - openWith: '[' - closeWith: ']([![Url:!:http://]!] "[![Title]!]")' - placeHolder: $i18next.t('wiki-editor.link-placeholder') - }, - { - separator: '---------------' - }, - { - name: $i18next.t('wiki-editor.quotes') - openWith: '> ' - }, - { - name: $i18next.t('wiki-editor.code-block') - openWith: '```\n' - closeWith: '\n```' - }, - { - separator: '---------------' - }, - { - name: $i18next.t('wiki-editor.preview') - call: preview - className: "preview-icon" - }, - # { - # separator: '---------------' - # }, - # { - # name: $i18next.t('wiki-editor.help') - # call: openHelp - # className: "help" - # } - ] - afterInsert: (event) -> - target = angular.element(event.textarea) - $model.$setViewValue(target.val()) - - markdownTitle = (markItUp, char) -> - heading = '' - n = $.trim(markItUp.selection or markItUp.placeHolder).length - - for i in [0..n-1] - heading += char - - return '\n'+heading+'\n' - - element.markItUp(markdownSettings) - - element.on "keypress", (event) -> - $scope.$apply() - - $scope.$on "$destroy", -> - $el.off() - - return {link:link, require:"ngModel"} - -module.directive("tgMarkitup", ["$rootScope", "$tgResources", tgMarkitupDirective])