diff --git a/CHANGELOG.md b/CHANGELOG.md index 684fee24..c9327d06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,12 @@ ## 1.3.0 Dryas hookeriana (Unreleased) ### Features +- GitHub integration (Phase I): + + Add button to login/singin with a GitHub account. + + Create Admin Panel with the GitHub webhooks settings. - Differentiate blocked user stories on a milestone. -### Misc +### Misc - Lots of small and not so small bugfixes. @@ -27,12 +30,12 @@ ## 1.1.0 Alnus maximowiczii (2014-10-13) -### Features ### +### Features - Promote an issue to a user story. - Changed configuration format from coffeescript file to json. - Add builtin analytics support. -### Misc ### +### Misc - Fix bug related to stange behavior of browser autofill and angularjs on login page. - Fix bug on userstories ordering on sprints. - Fix bug of projects list visualization on project nav on first page loading. @@ -40,10 +43,10 @@ ## 1.0.0 (2014-10-07) -### Features ### +### Features - Redesign for taskboard and backlog summaries - Allow feedback for users from the platform - Real time changes for backlog, taskboard, kanban and issues -### Misc ### +### Misc - Lots of small and not so small bugfixes diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index f5c58e35..0b014ad3 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -94,18 +94,20 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven {templateUrl: "/partials/admin-memberships.html"}) $routeProvider.when("/project/:pslug/admin/roles", {templateUrl: "/partials/admin-roles.html"}) + $routeProvider.when("/project/:pslug/admin/third-parties/github", + {templateUrl: "/partials/admin-third-parties-github.html"}) # User settings $routeProvider.when("/project/:pslug/user-settings/user-profile", - {templateUrl: "/partials/user-profile.html"}) + {templateUrl: "/partials/user-profile.html"}) $routeProvider.when("/project/:pslug/user-settings/user-change-password", - {templateUrl: "/partials/user-change-password.html"}) + {templateUrl: "/partials/user-change-password.html"}) $routeProvider.when("/project/:pslug/user-settings/user-avatar", - {templateUrl: "/partials/user-avatar.html"}) + {templateUrl: "/partials/user-avatar.html"}) $routeProvider.when("/project/:pslug/user-settings/mail-notifications", - {templateUrl: "/partials/mail-notifications.html"}) + {templateUrl: "/partials/mail-notifications.html"}) $routeProvider.when("/change-email/:email_token", - {templateUrl: "/partials/change-email.html"}) + {templateUrl: "/partials/change-email.html"}) $routeProvider.when("/cancel-account/:cancel_token", {templateUrl: "/partials/cancel-account.html"}) @@ -216,6 +218,7 @@ modules = [ "taigaUserSettings", "taigaFeedback", "taigaPlugins", + "taigaIntegrations", # Vendor modules "ngRoute", diff --git a/app/coffee/modules/admin/third-parties.coffee b/app/coffee/modules/admin/third-parties.coffee new file mode 100644 index 00000000..44c4782c --- /dev/null +++ b/app/coffee/modules/admin/third-parties.coffee @@ -0,0 +1,123 @@ +### +# 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/admin/third-parties.coffee +### + +taiga = @.taiga + +mixOf = @.taiga.mixOf + +module = angular.module("taigaAdmin") + + +############################################################################# +## Github Controller +############################################################################# + +class GithubController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.FiltersMixin) + @.$inject = [ + "$scope", + "$tgRepo", + "$tgResources", + "$routeParams", + "$appTitle" + ] + + constructor: (@scope, @repo, @rs, @params, @appTitle) -> + _.bindAll(@) + + @scope.sectionName = "Github" #i18n + @scope.project = {} + @scope.anyComputableRole = true + + promise = @.loadInitialData() + + promise.then () => + @appTitle.set("Github - " + @scope.project.name) + + promise.then null, @.onInitialDataError.bind(@) + + loadModules: -> + return @rs.modules.list(@scope.projectId, "github").then (github) => + @scope.github = github + + loadProject: -> + return @rs.projects.get(@scope.projectId).then (project) => + @scope.project = project + @scope.$emit('project:loaded', project) + @scope.anyComputableRole = _.some(_.map(project.roles, (point) -> point.computable)) + + return project + + loadInitialData: -> + promise = @repo.resolve({pslug: @params.pslug}).then (data) => + @scope.projectId = data.project + return data + + return promise.then(=> @.loadProject()) + .then(=> @.loadModules()) + + +module.controller("GithubController", GithubController) + +SelectInputText = -> + link = ($scope, $el, $attrs) -> + $el.on "click", ".select-input-content", () -> + $el.find("input").select() + $el.find(".help-copy").addClass("visible") + + return {link:link} + +module.directive("tgSelectInputText", SelectInputText) + +############################################################################# +## GithubWebhooks Directive +############################################################################# + +GithubWebhooksDirective = ($repo, $confirm, $loading) -> + link = ($scope, $el, $attrs) -> + form = $el.find("form").checksley({"onlyOneErrorElement": true}) + submit = (target) => + return if not form.validate() + + $loading.start(target) + + promise = $repo.saveAttribute($scope.github, "github") + promise.then -> + $loading.finish(target) + $confirm.notify("success") + + promise.then null, (data) -> + $loading.finish(target) + form.setErrors(data) + if data._error_message + $confirm.notify("error", data._error_message) + + $el.on "click", "a.button-green", (event) -> + event.preventDefault() + target = angular.element(event.currentTarget) + submit(target) + + $el.on "submit", "form", (event) -> + event.preventDefault() + submit() + + return {link:link} + +module.directive("tgGithubWebhooks", ["$tgRepo", "$tgConfirm", "$tgLoading", GithubWebhooksDirective]) diff --git a/app/coffee/modules/base.coffee b/app/coffee/modules/base.coffee index 8310d95c..3aeb6e99 100644 --- a/app/coffee/modules/base.coffee +++ b/app/coffee/modules/base.coffee @@ -89,6 +89,7 @@ urls = { "project-admin-project-values-issue-severities": "/project/:project/admin/project-values/issue-severities" "project-admin-memberships": "/project/:project/admin/memberships" "project-admin-roles": "/project/:project/admin/roles" + "project-admin-third-parties-github": "/project/:project/admin/third-parties/github" # User settings "user-settings-user-profile": "/project/:project/user-settings/user-profile" diff --git a/app/coffee/modules/base/repository.coffee b/app/coffee/modules/base/repository.coffee index 05ca4846..9241975a 100644 --- a/app/coffee/modules/base/repository.coffee +++ b/app/coffee/modules/base/repository.coffee @@ -31,6 +31,9 @@ class RepositoryService extends taiga.Service idAttrName = model.getIdAttrName() return "#{@urls.resolve(model.getName())}/#{model[idAttrName]}" + resolveUrlForAttributeModel: (model) -> + return @urls.resolve(model.getName(), model.parent) + create: (name, data, dataTypes={}, extraParams={}) -> defered = @q.defer() url = @urls.resolve(name) @@ -89,6 +92,37 @@ class RepositoryService extends taiga.Service return defered.promise + saveAttribute: (model, attribute, patch=true) -> + defered = @q.defer() + + if not model.isModified() and patch + defered.resolve(model) + return defered.promise + + url = @.resolveUrlForAttributeModel(model) + + data = {} + + data[attribute] = model.getAttrs() + + if patch + promise = @http.patch(url, data) + else + promise = @http.put(url, data) + + promise.success (data, status) => + model._isModified = false + model._attrs = _.extend(model.getAttrs(), data) + model._modifiedAttrs = {} + + model.applyCasts() + defered.resolve(model) + + promise.error (data, status) -> + defered.reject(data) + + return defered.promise + refresh: (model) -> defered = @q.defer() @@ -115,6 +149,19 @@ class RepositoryService extends taiga.Service return @http.get(url, params, httpOptions).then (data) => return _.map(data.data, (x) => @model.make_model(name, x)) + queryOneAttribute: (name, id, attribute, params, options={}) -> + url = @urls.resolve(name, id) + httpOptions = {headers: {}} + + if not options.enablePagination + httpOptions.headers["x-disable-pagination"] = "1" + + return @http.get(url, params, httpOptions).then (data) => + model = @model.make_model(name, data.data[attribute]) + model.parent = id + + return model + queryOne: (name, id, params, options={}) -> url = @urls.resolve(name) url = "#{url}/#{id}" if id diff --git a/app/coffee/modules/common/components.coffee b/app/coffee/modules/common/components.coffee index 4f0e4bf7..f6a57961 100644 --- a/app/coffee/modules/common/components.coffee +++ b/app/coffee/modules/common/components.coffee @@ -132,8 +132,12 @@ CreatedByDisplayDirective = -> link = ($scope, $el, $attrs) -> render = (model) -> + owner = $scope.usersById?[model.owner] or { + full_name_display: "external user" + photo: "/images/unnamed.png" + } html = template({ - owner: $scope.usersById?[model.owner] + owner: owner date: moment(model.created_date).format("DD MMM YYYY HH:mm") }) $el.html(html) diff --git a/app/coffee/modules/common/loader.coffee b/app/coffee/modules/common/loader.coffee index 6d04b025..ac1d745a 100644 --- a/app/coffee/modules/common/loader.coffee +++ b/app/coffee/modules/common/loader.coffee @@ -97,8 +97,6 @@ Loader = () -> startCurrentPageLoader: () -> if config.enabled start() - else - pageLoaded(true) onStart: (fn) -> $rootscope.$on("loader:start", fn) diff --git a/app/coffee/modules/integrations.coffee b/app/coffee/modules/integrations.coffee new file mode 100644 index 00000000..368e313c --- /dev/null +++ b/app/coffee/modules/integrations.coffee @@ -0,0 +1,22 @@ +### +# 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/integrations.coffee +### + +module = angular.module("taigaIntegrations", []) diff --git a/app/coffee/modules/integrations/github.coffee b/app/coffee/modules/integrations/github.coffee new file mode 100644 index 00000000..fa61a86f --- /dev/null +++ b/app/coffee/modules/integrations/github.coffee @@ -0,0 +1,111 @@ +### +# 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/integrations/github.coffee +### + +taiga = @.taiga + +module = angular.module("taigaIntegrations") + +AUTH_URL = "https://github.com/login/oauth/authorize" + + +############################################################################# +## User story team requirements button directive +############################################################################# + +GithubLoginButtonDirective = ($window, $params, $location, $config, $events, $confirm, $auth, $navUrls, $loader) -> + # Login or registar a user with his/her github account. + # + # Example: + # tg-github-login-button() + # + # Requirements: + # - ... + + template = """ + + + Login with Github + + """ #TODO: i18n + + link = ($scope, $el, $attrs) -> + clientId = $config.get("gitHubClientId", null) + return if not clientId + + renderGitHubButton = -> + $el.html(template) if clientId + + loginOnSuccess = (response) -> + if $params.next and $params.next != $navUrls.resolve("login") + nextUrl = $params.next + else + nextUrl = $navUrls.resolve("home") + + $events.setupConnection() + + $location.search("next", null) + $location.search("token", null) + $location.search("state", null) + $location.search("code", null) + $location.path(nextUrl) + + loginOnError = (response) -> + $location.search("state", null) + $location.search("code", null) + $loader.pageLoaded() + + if response.data.error_message + $confirm.notify("light-error", response.data.error_message ) + else + $confirm.notify("light-error", "Our Oompa Loompas have not been able to get you + credentials from GitHub.") #TODO: i18n + + loginWithGitHubAccount = -> + type = $params.state + code = $params.code + token = $params.token + + return if not (type == "github" and code) + $loader.start() + + data = {code: code, token: token} + $auth.login(data, type).then(loginOnSuccess, loginOnError) + + renderGitHubButton() + loginWithGitHubAccount() + + $el.on "click", ".button-github", (event) -> + redirectToUri = $location.absUrl() + url = "#{AUTH_URL}?client_id=#{clientId}&redirect_uri=#{redirectToUri}&state=github&scope=user:email" + $window.location.href = url + + $scope.$on "$destroy", -> + $el.off() + + return { + link: link + restrict: "EA" + template: "" + } + +module.directive("tgGithubLoginButton", ["$window", '$routeParams', "$tgLocation", "$tgConfig", "$tgEvents", + "$tgConfirm", "$tgAuth", "$tgNavUrls", "tgLoader", + GithubLoginButtonDirective]) diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index 18675d29..2bce1f4b 100644 --- a/app/coffee/modules/resources.coffee +++ b/app/coffee/modules/resources.coffee @@ -83,6 +83,7 @@ urls = { "issue-types": "/issue-types" "priorities": "/priorities" "severities": "/severities" + "project-modules": "/projects/%s/modules" # History "history/us": "/history/userstory" @@ -138,5 +139,6 @@ module.run([ "$tgMdRenderResourcesProvider", "$tgHistoryResourcesProvider", "$tgKanbanResourcesProvider", + "$tgModulesResourcesProvider", initResources ]) diff --git a/app/coffee/modules/resources/modules.coffee b/app/coffee/modules/resources/modules.coffee new file mode 100644 index 00000000..fcb6e6a7 --- /dev/null +++ b/app/coffee/modules/resources/modules.coffee @@ -0,0 +1,12 @@ +resourceProvider = ($repo) -> + service = {} + + service.list = (projectId, module) -> + return $repo.queryOneAttribute("project-modules", projectId, module) + + return (instance) -> + instance.modules = service + + +module = angular.module("taigaResources") +module.factory("$tgModulesResourcesProvider", ["$tgRepo", resourceProvider]) diff --git a/app/images/github-help.png b/app/images/github-help.png new file mode 100644 index 00000000..43c64f77 Binary files /dev/null and b/app/images/github-help.png differ diff --git a/app/partials/admin-third-parties-github.jade b/app/partials/admin-third-parties-github.jade new file mode 100644 index 00000000..4f84553e --- /dev/null +++ b/app/partials/admin-third-parties-github.jade @@ -0,0 +1,95 @@ +block head + title Taiga Your agile, free, and open source project management tool + +block content + div.wrapper.roles(tg-github-webhooks, ng-controller="GithubController as ctrl", + ng-init="section='admin'") + sidebar.menu-secondary.sidebar(tg-admin-navigation="third-parties") + include views/modules/admin-menu + sidebar.menu-tertiary.sidebar(tg-admin-navigation="third-parties-github") + include views/modules/admin-submenu-third-parties + + section.main.admin-common.admin-third-parties + include views/components/mainTitle + + form + fieldset + label(for="secret-key") Secret key + input(type="text", name="secret-key", ng-model="github.secret", placeholder="Secret key", id="secret-key") + + fieldset + .select-input-text(tg-select-input-text) + div + label(for="payload-url") Payload URL + .field-with-option + input(type="text", ng-model="github.webhooks_url", name="payload-url", readonly="readonly", placeholder="Payload URL", id="payload-url") + .option-wrapper.select-input-content + .icon.icon-copy + .help-copy Copy to clipboard: Ctrl+C + + input(type="submit", class="hidden") + a.button.button-green(href="") Save + + + .help + h2 How to use it + + h3 Configure Taiga + ol + li Fill + span Secret key + | or use the auto generated one + + li Copy the + span Payload URL field. + + h3 Configure Github + ol + li Go to your github repository. + li Click on + span Settings + | > + span Webhooks & Services + | > + span Add webhook + + li On that screen set the payload url with the payload url of this screen. + li Secret must be filled with the same content as the secret field of this screen. + li Content type must be + span application/json. + li Taiga currently listen for three different kind of events: + ol + li Push events: changing element status via commit message + li Issues: issues created in github appear automatically in Taiga + li Issue comment: issue comments created in github appear automatically in Taiga + + p Just check "send me everything" or just the events you want Taiga to listen for. + + .img + .alt-image Github Webhooke page + img(src="/images/github-help.png", alt="webhook") + + h2 Changing elements status via commit message + + p + | The status of any issue, task or user story can be changed via commit message. + | Just add to your commit message something like: + + code + | TG-REF #STATUS + + ul.code-info + li + span REF: + | US/Issue/Task reference of the element you want to modify + li + span STATUS: + | New status slug to set, you can find all of them in: + a(href="", tg-nav="project-admin-project-values-us-status:project=project.slug") US STATUSES. + + h3 An example please! + + code + | TG-123 #closed + + p In this example, 123 is an issue reference and with this command, the issue will change its status to closed. diff --git a/app/partials/issues-detail.jade b/app/partials/issues-detail.jade index 91448c44..971daecf 100644 --- a/app/partials/issues-detail.jade +++ b/app/partials/issues-detail.jade @@ -23,6 +23,10 @@ block content tg-nav="project-userstories-detail:project=project.slug, ref=us.ref") span(tg-bo-ref="us.ref") + p.external-reference(ng-if="issue.external_reference") This issue has been created from + a(target="_blank", tg-bo-href="issue.external_reference[1]", title="Go to origin") + span {{ issue.external_reference[1] }} + p.block-desc-container(ng-show="issue.is_blocked") span.block-description-title Blocked span.block-description(ng-bind="issue.blocked_note || 'This issue is blocked'") diff --git a/app/partials/task-detail.jade b/app/partials/task-detail.jade index f1bb79c6..c9d4e80e 100644 --- a/app/partials/task-detail.jade +++ b/app/partials/task-detail.jade @@ -21,12 +21,18 @@ block content h2.us-title-text span.us-number(tg-bo-ref="task.ref") span.us-name(tg-editable-subject, ng-model="task", required-perm="modify_task") + h3.us-related-task This task belongs to a(tg-check-permission="view_us", href="", title="Go to user story", tg-nav="project-userstories-detail:project=project.slug, ref=us.ref", ng-if="us") span(tg-bo-ref="us.ref") span(tg-bo-bind="us.subject") + + p.external-reference(ng-if="task.external_reference") This task has been created from + a(target="_blank", tg-bo-href="task.external_reference[1]", title="Go to origin") + span {{ task.external_reference[1] }} + p.block-desc-container(ng-show="task.is_blocked") span.block-description-title Blocked span.block-description(ng-bind="task.blocked_note || 'This task is blocked'") diff --git a/app/partials/us-detail.jade b/app/partials/us-detail.jade index eb66d449..2bb65166 100644 --- a/app/partials/us-detail.jade +++ b/app/partials/us-detail.jade @@ -28,6 +28,10 @@ block content tg-bo-title="'#' + us.origin_issue.ref + ' ' + us.origin_issue.subject") span(tg-bo-ref="us.origin_issue.ref") + p.external-reference(ng-if="us.external_reference") This US has been created from + a(target="_blank", tg-bo-href="us.external_reference[1]", title="Go to origin") + span {{ us.external_reference[1] }} + p.block-desc-container(ng-show="us.is_blocked") span.block-description-title Blocked span.block-description(ng-bind="us.blocked_note || 'This user story is blocked'") diff --git a/app/partials/views/modules/admin-menu.jade b/app/partials/views/modules/admin-menu.jade index 14cfaef4..198667f3 100644 --- a/app/partials/views/modules/admin-menu.jade +++ b/app/partials/views/modules/admin-menu.jade @@ -20,3 +20,7 @@ section.admin-menu a(href="" tg-nav="project-admin-roles:project=project.slug") span.title Roles & Permissions span.icon.icon-arrow-right + li#adminmenu-third-parties + a(href="" tg-nav="project-admin-third-parties-github:project=project.slug") + span.title Third parties + span.icon.icon-arrow-right diff --git a/app/partials/views/modules/admin-submenu-third-parties.jade b/app/partials/views/modules/admin-submenu-third-parties.jade new file mode 100644 index 00000000..95746408 --- /dev/null +++ b/app/partials/views/modules/admin-submenu-third-parties.jade @@ -0,0 +1,10 @@ +section.admin-submenu + header + h1 Third parties + + nav + ul + li#adminmenu-third-parties-github + a(href="", tg-nav="project-admin-third-parties-github:project=project.slug") + span.title Github + span.icon.icon-arrow-right diff --git a/app/partials/views/modules/admin/project-status.jade b/app/partials/views/modules/admin/project-status.jade index aa303a39..d1c5a99a 100644 --- a/app/partials/views/modules/admin/project-status.jade +++ b/app/partials/views/modules/admin/project-status.jade @@ -3,6 +3,7 @@ section.colors-table div.row div.color-column Color div.status-name Name + div.status-slug Slug div.is-closed-column Is closed? div.options-column @@ -17,6 +18,9 @@ section.colors-table div.status-name span {{ value.name }} + div.status-slug + span {{ value.slug }} + div.is-closed-column div.icon.icon-check-square(ng-show="value.is_closed") diff --git a/app/partials/views/modules/admin/project-us-status.jade b/app/partials/views/modules/admin/project-us-status.jade index a20c8c68..50c22641 100644 --- a/app/partials/views/modules/admin/project-us-status.jade +++ b/app/partials/views/modules/admin/project-us-status.jade @@ -3,6 +3,7 @@ section.colors-table div.row div.color-column Color div.status-name Name + div.status-slug Slug div.is-closed-column Is closed? div.status-wip-limit WIP Limit div.options-column @@ -19,6 +20,9 @@ section.colors-table div.status-name span {{ value.name }} + div.status-slug + span {{ value.slug }} + div.is-closed-column div.icon.icon-check-square(ng-show="value.is_closed") diff --git a/app/partials/views/modules/invitation-login-form.jade b/app/partials/views/modules/invitation-login-form.jade index 234866bd..8a0b50b5 100644 --- a/app/partials/views/modules/invitation-login-form.jade +++ b/app/partials/views/modules/invitation-login-form.jade @@ -10,3 +10,5 @@ form.login-form fieldset a.button.button-login.button-gray(href="", title="Log in") Enter input(type="submit", style="display:none") + + fieldset(tg-github-login-button) diff --git a/app/partials/views/modules/invitation-register-form.jade b/app/partials/views/modules/invitation-register-form.jade index 5eec1e53..4005dc73 100644 --- a/app/partials/views/modules/invitation-register-form.jade +++ b/app/partials/views/modules/invitation-register-form.jade @@ -23,4 +23,4 @@ form.register-form a.button.button-register.button-gray(href="", title="Sign up") Sign up input(type="submit", style="display:none") - tg-terms-notice + tg-terms-notice diff --git a/app/partials/views/modules/login-form.jade b/app/partials/views/modules/login-form.jade index 0fe86cae..f5648eab 100644 --- a/app/partials/views/modules/login-form.jade +++ b/app/partials/views/modules/login-form.jade @@ -13,4 +13,6 @@ div.login-form-container(tg-login) a.button.button-login.button-gray(href="", title="Sign in") Sign in input(type="submit", style="display:none") + fieldset(tg-github-login-button) + tg-public-register-message diff --git a/app/partials/views/modules/register-form.jade b/app/partials/views/modules/register-form.jade index 514a476c..f6349c9e 100644 --- a/app/partials/views/modules/register-form.jade +++ b/app/partials/views/modules/register-form.jade @@ -17,13 +17,15 @@ div.register-form-container(tg-register) fieldset input(type="password", name="password", ng-model="data.password", - data-required="true", data-minlength="4", + data-required="true", data-minlength="4", placeholder="Set a password (case sensitive)") fieldset a.button.button-register.button-gray(href="", title="Sign up") Sign up input(type="submit", class="hidden") + fieldset(tg-github-login-button) + // Only displays terms notice when terms plugin is loaded. tg-terms-notice diff --git a/app/styles/components/buttons.scss b/app/styles/components/buttons.scss index f5a060e1..28348313 100755 --- a/app/styles/components/buttons.scss +++ b/app/styles/components/buttons.scss @@ -34,7 +34,7 @@ a.button-green { a.button-gray { background: $button-gray; &:hover { - background: $button-gray-hover; + background: $fresh-taiga; color: $white; } span { @@ -102,3 +102,18 @@ a.button-bulk { background: $fresh-taiga; } } +.button-github { + @extend %button; + background: $grayer; + vertical-align: middle; + .icon { + @extend %large; + color: $white; + margin-right: .5rem; + vertical-align: text-bottom; + } + &:hover { + @include transition (background .3s linear); + background: $black; + } +} diff --git a/app/styles/layout/invitation.scss b/app/styles/layout/invitation.scss index 78a9c63e..8d8f781d 100644 --- a/app/styles/layout/invitation.scss +++ b/app/styles/layout/invitation.scss @@ -44,10 +44,12 @@ } .invitation-form { @include table-flex(); + fieldset { + margin-bottom: .5rem; + } input { background: $white; color: $gray; - margin-bottom: 1rem; position: relative; @include placeholder { color: $gray-light; @@ -80,16 +82,28 @@ background: $fresh-taiga; } } + .button-github { + &:hover { + background: $black; + } + } } .login-form, .register-form { @include table-flex-child(1, 200px, 0, 200px); padding: 1rem; - + text-align: center; .form-header { color: #999; } } + .register-form { + fieldset { + &:last-child { + margin-bottom: 1rem; + } + } + } .register-text { @extend %small; color: $white; diff --git a/app/styles/layout/login.scss b/app/styles/layout/login.scss index 8e62a629..3f53e123 100644 --- a/app/styles/layout/login.scss +++ b/app/styles/layout/login.scss @@ -1,5 +1,3 @@ - - .login-main { //@include table-flex(center, center, flex, row, wrap, center); @include display(flex); @@ -60,11 +58,7 @@ .button { color: $white; display: block; - margin-bottom: .5rem; text-align: center; - &:hover { - background: $fresh-taiga; - } } a { &:hover { diff --git a/app/styles/modules/admin/third-parties.scss b/app/styles/modules/admin/third-parties.scss new file mode 100644 index 00000000..1baf83ea --- /dev/null +++ b/app/styles/modules/admin/third-parties.scss @@ -0,0 +1,91 @@ +.admin-third-parties { + form { + margin-top: 1rem; + max-width: 700px; + width: 100%; + } + input[type="text"], + textarea { + @extend %title; + } + fieldset { + margin-bottom: 1rem; + } + label { + @extend %title; + display: block; + margin-bottom: .2rem; + } + textarea { + height: 10rem; + } + .button-green { + color: $white; + display: block; + text-align: center; + } + .select-input-text { + .field-with-option { + @include display(flex); + } + .option-wrapper { + @include display(flex); + @include align-items(center); + border: 1px solid $gray-light; + border-left: 0; + border-radius: 0 5px 5px 0; + cursor: pointer; + padding: 0 1rem; + } + .help-copy { + @extend %small; + opacity: 0; + &.visible { + @include transition(opacity .2s linear); + opacity: 1; + } + } + } + .help { + margin-top: 2rem; + h3 { + font-family: opensans-semibold; + margin-bottom: 1rem; + } + ol { + padding: 0 0 0 2rem; + span { + font-family: opensans-semibold; + } + } + .img { + margin-bottom: 1rem; + } + .alt-image { + @extend %small; + font-style: italic; + } + code { + @extend %small; + background: $whitish; + direction: ltr; + display: block; + font-family: 'courier new', 'monospace'; + line-height: 1.4rem; + margin-bottom: 1rem; + padding: .5rem; + unicode-bidi: embed; + white-space: pre; + width: 100%; + } + .code-info { + padding-left: 1rem; + li { + margin-bottom: .5rem; + } + span { + font-family: opensans-semibold; + } + } + } +} diff --git a/app/styles/modules/auth/login-form.scss b/app/styles/modules/auth/login-form.scss index 432ad477..40d4a36c 100644 --- a/app/styles/modules/auth/login-form.scss +++ b/app/styles/modules/auth/login-form.scss @@ -1,5 +1,4 @@ .login-form-container { - //display: none; .login-password { position: relative; } diff --git a/app/styles/modules/common/colors-table.scss b/app/styles/modules/common/colors-table.scss index ec9b00f1..dbf4a42c 100644 --- a/app/styles/modules/common/colors-table.scss +++ b/app/styles/modules/common/colors-table.scss @@ -50,6 +50,10 @@ display: block; } } + .status-slug { + @include table-flex-child(6, 150px, 0); + padding: 0 10px; + } .options-column { max-width: 100px; opacity: 0; diff --git a/app/styles/modules/common/external-reference.scss b/app/styles/modules/common/external-reference.scss new file mode 100644 index 00000000..c69ed1b7 --- /dev/null +++ b/app/styles/modules/common/external-reference.scss @@ -0,0 +1,29 @@ +.blocked { + .external-reference { + color: $white; + a { + @include transition(color .3s linear); + color: $white; + &:hover { + color: $red-light; + } + } + } +} + +.external-reference { + @extend %small; + color: $gray-light; + margin-top: .5rem; + a { + @include transition(color .3s linear); + border-left: 1px solid $gray-light; + padding: 0 .2rem; + &:hover { + color: $green-taiga; + } + &:first-child { + border: 0; + } + } +} diff --git a/conf/main.example.json b/conf/main.example.json index 5d382492..e40d5dcd 100644 --- a/conf/main.example.json +++ b/conf/main.example.json @@ -6,5 +6,6 @@ "feedbackEnabled": true, "privacyPolicyUrl": null, "termsOfServiceUrl": null, - "maxUploadFileSize": null + "maxUploadFileSize": null, + "gitHubClientId": null } diff --git a/gulpfile.coffee b/gulpfile.coffee index 85d7788b..fb125940 100644 --- a/gulpfile.coffee +++ b/gulpfile.coffee @@ -73,6 +73,7 @@ paths.coffee = [ paths.app + "coffee/modules/base/*.coffee", paths.app + "coffee/modules/resources/*.coffee", paths.app + "coffee/modules/user-settings/*.coffee" + paths.app + "coffee/modules/integrations/*.coffee" paths.app + "plugins/**/*.coffee" ] diff --git a/main-sass.js b/main-sass.js index b3ebd1da..af5a6cda 100644 --- a/main-sass.js +++ b/main-sass.js @@ -71,6 +71,7 @@ exports.files = function () { 'modules/common/related-tasks', 'modules/common/history', 'modules/common/wizard', + 'modules/common/external-reference', //Project modules 'modules/home-projects-list', @@ -120,6 +121,7 @@ exports.files = function () { 'modules/admin/admin-project-profile', 'modules/admin/default-values', 'modules/admin/project-values', + 'modules/admin/third-parties', //Modules user Settings 'modules/user-settings/user-profile',