From 8fd140f4fdd9fd37f45631b345b0d3b491db5095 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 22 May 2015 09:37:31 +0200 Subject: [PATCH 001/366] Issue #2714 - Wiki edition when content is empty --- app/coffee/modules/wiki/main.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/coffee/modules/wiki/main.coffee b/app/coffee/modules/wiki/main.coffee index f8141940..75968e1a 100644 --- a/app/coffee/modules/wiki/main.coffee +++ b/app/coffee/modules/wiki/main.coffee @@ -263,7 +263,7 @@ EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $ if isEditable() $el.addClass('editable') - if not wikiPage.id? + if not wikiPage.id? or $.trim(wikiPage.content).length == 0 switchToEditMode() else disableEdition() From c2e0fbed061e669a297e559c0423b65b99a5f0d5 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 22 May 2015 13:11:08 +0200 Subject: [PATCH 002/366] Fixing no roles can estimate errors --- app/partials/admin/admin-roles.jade | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/partials/admin/admin-roles.jade b/app/partials/admin/admin-roles.jade index 34b6d23d..7edc272c 100644 --- a/app/partials/admin/admin-roles.jade +++ b/app/partials/admin/admin-roles.jade @@ -27,7 +27,8 @@ div.wrapper.roles(ng-controller="RolesController as ctrl", div.any-computable-role(ng-hide="anyComputableRole", translate="ADMIN.ROLES.WARNING_NO_ROLE") - div.general-category(translate="ADMIN.ROLES.HELP_ROLE_ENABLED") + div.general-category + span(translate="ADMIN.ROLES.HELP_ROLE_ENABLED") div.check input(type="checkbox", ng-model="role.computable", ng-change="ctrl.setComputable()") div From fdb9097edd3d234266db7ea79bb913ad693c2cdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Fri, 22 May 2015 15:38:10 +0200 Subject: [PATCH 003/366] Update CHANGELOG --- CHANGELOG.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6c5e017..a5cb39ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,16 @@ # Changelog # -## 1.7.0 Empetrum Nigrum (unreleased) +## 1.8.0 ??? (unreleased) + +### Features +- ... + +### Misc +- Lots of small and not so small bugfixes. + + +## 1.7.0 Empetrum Nigrum (2015-05-21) ### Features - Make Taiga translatable (i18n support). From 82de0addc738e462af4d224791dfcece620f927c Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 25 May 2015 14:46:26 +0200 Subject: [PATCH 004/366] Fixng Jit.si url videoconference generation --- app/coffee/modules/nav.coffee | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/coffee/modules/nav.coffee b/app/coffee/modules/nav.coffee index ec95aa6c..8c34d3ee 100644 --- a/app/coffee/modules/nav.coffee +++ b/app/coffee/modules/nav.coffee @@ -249,22 +249,25 @@ ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $ container.replaceWith(dom) videoConferenceUrl = (project) -> - urlSeparator = "-" + urlFixer = (url) -> return url + if project.videoconferences == "appear-in" baseUrl = "https://appear.in/" else if project.videoconferences == "talky" baseUrl = "https://talky.io/" else if project.videoconferences == "jitsi" - urlSeparator = "" baseUrl = "https://meet.jit.si/" + urlFixer = (url) -> return url.replace(/ /g, "").replace(/-/g, "") else return "" if project.videoconferences_salt - url = "#{project.slug}#{urlSeparator}#{project.videoconferences_salt}" + url = "#{project.slug}-#{project.videoconferences_salt}" else url = "#{project.slug}" + url = urlFixer(url) + return baseUrl + url From a519c79edd2b377c3fb622522dd45f4a24227b6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Mon, 25 May 2015 15:38:28 +0200 Subject: [PATCH 005/366] Update locales --- app/locales/locale-ca.json | 114 +++++++++--------- app/locales/locale-fi.json | 200 ++++++++++++++++---------------- app/locales/locale-fr.json | 102 ++++++++-------- app/locales/locale-zh-hant.json | 86 +++++++------- 4 files changed, 251 insertions(+), 251 deletions(-) diff --git a/app/locales/locale-ca.json b/app/locales/locale-ca.json index b2fd9755..27c422b5 100644 --- a/app/locales/locale-ca.json +++ b/app/locales/locale-ca.json @@ -48,7 +48,7 @@ "TYPE_PHONE": "This value should be a valid phone number.", "NOTNULL": "This value should not be null.", "NOT_BLANK": "This value should not be blank.", - "REQUIRED": "This value is required.", + "REQUIRED": "Aquest valor és necessari.", "REGEXP": "Aquest valor pareix invàlid.", "MIN": "This value should be greater than or equal to %s.", "MAX": "This value should be lower than or equal to %s.", @@ -59,7 +59,7 @@ "MIN_CHECK": "You must select at least %s choices.", "MAX_CHECK": "You must select %s choices or less.", "RANGE_CHECK": "You must select between %s and %s choices.", - "EQUAL_TO": "This value should be the same." + "EQUAL_TO": "Aquest valor hauria de ser el mateix." }, "PICKERDATE": { "FORMAT": "DD MMM YYYY", @@ -102,8 +102,8 @@ }, "TAGS": { "PLACEHOLDER": "Afegir tag", - "DELETE": "Esborrar tag", - "ADD": "Afegit tag" + "DELETE": "Elimina l'etiqueta", + "ADD": "Afegeix l'etiqueta" }, "DESCRIPTION": { "EMPTY": "Els espai buit es molt aburrit. Sé descriptiu!", @@ -118,7 +118,7 @@ "SLUG": "Slug", "COLOR": "Color", "IS_CLOSED": "Està tancat?", - "STATUS": "Estatus", + "STATUS": "Estats", "TYPE": "Tipus", "SEVERITY": "Severitat", "PRIORITY": "Prioritat", @@ -158,10 +158,10 @@ "FILTERS": { "TITLE": "filtres", "INPUT_PLACEHOLDER": "Descripció o referència", - "TITLE_ACTION_FILTER_BUTTON": "buscar", + "TITLE_ACTION_FILTER_BUTTON": "cerca", "BREADCRUMB_TITLE": "tornar a categories", "BREADCRUMB_FILTERS": "Filtres", - "BREADCRUMB_STATUS": "estatus" + "BREADCRUMB_STATUS": "estats" }, "WYSIWYG": { "H1_BUTTON": "Capçcalera de primer nivel", @@ -243,7 +243,7 @@ "SECTION_NAME": "Adjunts", "TITLE": "{{ fileName }} pujat el {{ date }}", "DESCRIPTION": "Escriu una descripció curta", - "DEPRECATED": "(deprecated)", + "DEPRECATED": "(obsolet)", "DEPRECATED_FILE": "Obsolet?", "ADD": "Afegir nou adjunt. <%- maxFileSizeMsg %>", "MAX_FILE_SIZE": "[Max. grandària: {{maxFileSize}}]", @@ -317,7 +317,7 @@ "PROJECT_SLUG": "Slug de projecte", "NUMBER_SPRINTS": "Nombre de sprints", "NUMBER_US_POINTS": "Nombre de punts de US", - "TAGS": "Tags", + "TAGS": "Etiquetes", "DESCRIPTION": "Descripció", "PUBLIC_PROJECT": "Projecte públic", "PRIVATE_PROJECT": "Projecte privat", @@ -372,11 +372,11 @@ "ACTION_ADD": "Add new severity" }, "PROJECT_VALUES_STATUS": { - "TITLE": "Estatus", - "SUBTITLE": "Especifica els estatus del les teues históries d'usuari, tasques e incidències", - "US_TITLE": "Estatus d'US", - "TASK_TITLE": "Estatus de tasques", - "ISSUE_TITLE": "Estatus d'incidències" + "TITLE": "Estat", + "SUBTITLE": "Especifica els estats del les teues históries d'usuari, tasques e incidències", + "US_TITLE": "Estats d'US", + "TASK_TITLE": "Estats de tasques", + "ISSUE_TITLE": "Estats d'incidències" }, "PROJECT_VALUES_TYPES": { "TITLE": "Tipus", @@ -467,24 +467,24 @@ }, "DEFAULT_VALUES": { "LABEL_POINTS": "Valor per defecte per a selector de punts", - "LABEL_US": "Valor per defecte per a selector d'estatus d'US", - "LABEL_TASK_STATUS": "Valor per defecte per a selector d'estatus de tasques", + "LABEL_US": "Valor per defecte per a selector d'estats d'US", + "LABEL_TASK_STATUS": "Valor per defecte per a selector d'estats de tasques", "LABEL_PRIORITY": "Valor per defecte per a selector de prioritat", "LABEL_SEVERITY": "Valor per defecte per a selector de severitat", "LABEL_ISSUE_TYPE": "Valor per defecte per a selector de tipus", - "LABEL_ISSUE_STATUS": "Valor per defecte per a selector de estatus" + "LABEL_ISSUE_STATUS": "Valor per defecte per a selector de estats" }, "STATUS": { - "PLACEHOLDER_WRITE_STATUS_NAME": "Escriu un nom per a nou estatus" + "PLACEHOLDER_WRITE_STATUS_NAME": "Escriu un nom per a nou estat" }, "TYPES": { "PLACEHOLDER_WRITE_NAME": "Escriu un nom per a nou element" }, "US_STATUS": { - "ACTION_ADD_STATUS": "Afegir estatus nou", + "ACTION_ADD_STATUS": "Afegeix un estat nou", "IS_ARCHIVED_COLUMN": "Es arxivat?", "WIP_LIMIT_COLUMN": "Limit WIP", - "PLACEHOLDER_WRITE_NAME": "Escriu un nom per a nou estatus" + "PLACEHOLDER_WRITE_NAME": "Escriviu un nom per al nou estat" }, "MENU": { "TITLE": "Admin", @@ -496,10 +496,10 @@ "PLUGINS": "Plugins" }, "SUBMENU_PROJECT_ATTRIBUTES": { - "TITLE": "Attributes" + "TITLE": "Atributs" }, "SUBMENU_PROJECT_VALUES": { - "STATUS": "Estatus", + "STATUS": "Estats", "POINTS": "Punts", "PRIORITIES": "Prioritats", "SEVERITIES": "severitats", @@ -528,7 +528,7 @@ "CLOSED": "punts
tancats" }, "SECTION": { - "SEARCH": "Buscar", + "SEARCH": "Cerca", "BACKLOG": "Backlog", "KANBAN": "Kanban", "ISSUES": "Incidències", @@ -539,7 +539,7 @@ }, "NAVIGATION": { "SECTION_TITLE": "Els teus projectes", - "PLACEHOLDER_SEARCH": "Buscar en...", + "PLACEHOLDER_SEARCH": "Cerca en...", "ACTION_CREATE_PROJECT": "Crear projecte", "TITLE_ACTION_IMPORT": "Importar projecte", "TITLE_PRVIOUS_PROJECT": "Mostra projectes previs", @@ -564,7 +564,7 @@ "SECTION_NAME": "Esborrar compte de Taiga", "CONFIRM": "Segur que vols borrar el teu compte de Taiga? ", "SUBTITLE": "Te trobarem a faltar! :-(", - "NEWSLETTER_LABEL_TEXT": "I don't wanna receive your newsletter anymore" + "NEWSLETTER_LABEL_TEXT": "No vull rebre més el vostre butlletí de notícies" }, "DELETE_PROJECT": { "TITLE": "Esborrar projecte", @@ -574,7 +574,7 @@ }, "ASSIGNED_TO": { "SELECT": "Selecciona assignació", - "SEARCH": "Buscar usuaris" + "SEARCH": "Cerca usuaris" }, "ADD_MEMBER": { "TITLE": "Nou membre", @@ -589,7 +589,7 @@ "ACTION_SEND": "Enviar sugerències" }, "SEARCH": { - "TITLE": "Buscar", + "TITLE": "Cerca", "PLACEHOLDER_SEARCH": "Què estàs buscant?" }, "ADD_EDIT_SPRINT": { @@ -604,7 +604,7 @@ "CREATE_EDIT_TASK": { "TITLE": "Nova tasca", "PLACEHOLDER_SUBJECT": "Descripció de tasca", - "PLACEHOLDER_STATUS": "Estatus de tasca", + "PLACEHOLDER_STATUS": "Estat de la tasca", "OPTION_UNASSIGNED": "Sense assignar", "PLACEHOLDER_SHORT_DESCRIPTION": "Escriu una descripció curta", "ACTION_EDIT": "Editar tasca" @@ -682,7 +682,7 @@ "NAME": "nom", "DESCRIPTION": "descripció", "CONTENT": "Contingut", - "STATUS": "Estatus", + "STATUS": "Estats", "IS_CLOSED": "tancat", "FINISH_DATE": "Data de finalització", "TYPE": "tipus", @@ -699,7 +699,7 @@ "CLIENT_REQUIREMENT": "requeriment de client", "TEAM_REQUIREMENT": "requeriment d'equip", "IS_IOCAINE": "Es iocaina", - "TAGS": "tags", + "TAGS": "Etiquetes", "ATTACHMENTS": "adjunts", "IS_DEPRECATED": "és obsolet", "ORDER": "ordre", @@ -714,7 +714,7 @@ "SECTION_NAME": "Backlog", "MOVE_US_TO_CURRENT_SPRINT": "Envia al Sprint", "SHOW_FILTERS": "Mostra filtres", - "SHOW_TAGS": "Mostrar tags", + "SHOW_TAGS": "Mostra etiquetes", "EMPTY": "El teu backlog està buit!", "CREATE_NEW_US": "Crea una nova US", "CREATE_NEW_US_EMPTY_HELP": "Potser vols crear una nova història d'usuari", @@ -725,7 +725,7 @@ "GO_TO_TASKBOARD": "Anar al panell de {{::name}}", "EDIT_SPRINT": "Editar sprint", "TOTAL_POINTS": "total", - "STATUS_NAME": "Nom d'estatus", + "STATUS_NAME": "Nom d'estats", "SORTABLE_FILTER_ERROR": "No pots portar al backlog si els filtres estàn oberts", "DOOMLINE": "Extensió de projecte [Doomline]", "CHART": { @@ -738,8 +738,8 @@ }, "TAGS": { "TOGGLE": "Toggle tags visibility", - "SHOW": "Mostrar tags", - "HIDE": "Amaga tags" + "SHOW": "Mostra etiquetes", + "HIDE": "Amaga etiquetes" }, "TABLE": { "COLUMN_US": "Històries d'usuari", @@ -765,8 +765,8 @@ "REMOVE": "Esborra filtres", "HIDE": "Hide Filters", "SHOW": "Mostra filtres", - "FILTER_CATEGORY_STATUS": "Estatus", - "FILTER_CATEGORY_TAGS": "Tags" + "FILTER_CATEGORY_STATUS": "Estats", + "FILTER_CATEGORY_TAGS": "Etiquetes" }, "SPRINTS": { "TITLE": "SPRINTS", @@ -813,11 +813,11 @@ } }, "TASK": { - "SECTION_NAME": "Task details", + "SECTION_NAME": "Detalls de la tasca", "LINK_TASKBOARD": "Panell de tasques", "TITLE_LINK_TASKBOARD": "Anar a panell de tasques", "PLACEHOLDER_SUBJECT": "Afegix la descripció de la tasca", - "TITLE_SELECT_STATUS": "Nom d'estatus", + "TITLE_SELECT_STATUS": "Nom de l'estat", "OWNER_US": "Aquesta tasca pertany a", "TITLE_LINK_GO_OWNER": "Anar a història d'usuari", "ORIGIN_US": "Aquesta tasca ha sigut creada desde", @@ -871,8 +871,8 @@ }, "FORGOT_PASSWORD_FORM": { "TITLE": "Oops, has oblidat la teua contrasenya?", - "SUBTITLE": "Escriu el teu mot d'usuari o correo per a conseguir uno nou", - "PLACEHOLDER_FIELD": "Mot d'usuari i correu", + "SUBTITLE": "Escriviu el vostre nom d'usuari o correu electrònic per a conseguir-ne un de nou", + "PLACEHOLDER_FIELD": "Nom d'usuari o correu electrònic", "ACTION_RESET_PASSWORD": "Resetejar contrasenya", "LINK_CANCEL": "No, portam enrere, crec que ho recorde.", "SUCCESS": "Mira el teu correu!
Hem enviat un correu amb les instrucciones per a setejar una nova contrasenya.", @@ -880,7 +880,7 @@ }, "LOGIN_COMMON": { "HEADER": "Ja tinc un conter de Taiga", - "PLACEHOLDER_AUTH_NAME": "Mot d'usuari i correu (sensible a majúscules i minúscules)", + "PLACEHOLDER_AUTH_NAME": "Nom d'usuari i correu electrònic (sensible a majúscules i minúscules)", "LINK_FORGOT_PASSWORD": "L'has oblidat?", "TITLE_LINK_FORGOT_PASSWORD": "Has oblidat la teua contrasenya?", "ACTION_ENTER": "Entrar", @@ -888,7 +888,7 @@ "PLACEHOLDER_AUTH_PASSWORD": "Contrasenya (sensible a majúscules i minúscules)" }, "LOGIN_FORM": { - "ERROR_AUTH_INCORRECT": "Segons els Oompa Loompas el teu mot d'usuari/correu o contrasenya sòn incorrectes.", + "ERROR_AUTH_INCORRECT": "Segons els Oompa Loompas, el vostre nom d'usuari/correu electrònic o contrasenya són incorrectes.", "ERROR_GENERIC": "Segons els Oompa Loompas ha hagut un error.", "SUCCESS": "Our Oompa Loompas están contents, benvinguts a Taiga." }, @@ -899,7 +899,7 @@ }, "REGISTER_FORM": { "TITLE": "Register a new Taiga account (free)", - "PLACEHOLDER_NAME": "Tria un mot d'usuari (sensible a majúscules i minúscules)", + "PLACEHOLDER_NAME": "Trieu un nom d'usuari (sensible a majúscules i minúscules)", "PLACEHOLDER_FULL_NAME": "Escriu el teu nom complet", "PLACEHOLDER_EMAIL": "El teu correu", "PLACEHOLDER_PASSWORD": "Tria una contrasenya(sensible a majúscules i minúscules)", @@ -934,10 +934,10 @@ "TITLE_BREADCRUMB": "Filtres", "CATEGORIES": { "TYPE": "Tipus", - "STATUS": "Estatus", + "STATUS": "Estats", "SEVERITY": "Severitat", "PRIORITIES": "Prioritats", - "TAGS": "Tags", + "TAGS": "Etiquetes", "ASSIGNED_TO": "Assignat a", "CREATED_BY": "Creat per", "CUSTOM_FILTERS": "Filtres personalitzats" @@ -953,11 +953,11 @@ "SEVERITY": "Severitat", "PRIORITY": "Prioritat", "SUBJECT": "Descripció", - "STATUS": "Estatus", + "STATUS": "Estats", "CREATED": "Creat", "ASSIGNED_TO": "Assignat a" }, - "TITLE_ACTION_CHANGE_STATUS": "Canviar estatus", + "TITLE_ACTION_CHANGE_STATUS": "Canvia l'estat", "TITLE_ACTION_ASSIGNED_TO": "Assignat a", "EMPTY": { "TITLE": "No hi ha incidències a reportar :-)", @@ -976,7 +976,7 @@ "TITLE_ACTION_ADD_BULK": "Afegir en grup", "ACTION_SHOW_ARCHIVED": "Mostrar arxivats", "ACTION_HIDE_ARCHIVED": "Amagar arxivats", - "HIDDEN_USER_STORIES": "Les històries d'usuar en aques estatus estàn amagades", + "HIDDEN_USER_STORIES": "Les històries d'usuari en aquest estats estan amagades", "ARCHIVED": "Has arxivat", "UNDO_ARCHIVED": "Arrastra de nou per desfer" }, @@ -985,8 +985,8 @@ "FILTER_ISSUES": "Incidències", "FILTER_TASKS": "Tasca", "FILTER_WIKI": "Pàgines de Wiki", - "PLACEHOLDER_SEARCH": "Buscar en...", - "TITLE_ACTION_SEARCH": "buscar", + "PLACEHOLDER_SEARCH": "Cerca en...", + "TITLE_ACTION_SEARCH": "cerca", "EMPTY_TITLE": "Pareix que no hem trobat res amb este criteri de recerca", "EMPTY_DESCRIPTION": "Prova amb una de les pestanyes o busca de nou" }, @@ -1012,7 +1012,7 @@ "ACTION_LEAVE_PROJECT": "Abandonar aquest projecte" }, "CHANGE_PASSWORD": { - "SECTION_NAME": "Canviar contrasenya", + "SECTION_NAME": "Canvi de contrasenya", "FIELD_CURRENT_PASSWORD": "Contrasenya actual", "PLACEHOLDER_CURRENT_PASSWORD": "La teua contrasenya actua (buit si no tens contrasenya encara)", "FIELD_NEW_PASSWORD": "Nova contrasenya", @@ -1026,7 +1026,7 @@ "MENU": { "SECTION_TITLE": "Configuració d'usuari", "USER_PROFILE": "Perfil d'usuari", - "CHANGE_PASSWORD": "Canviar contrasenya", + "CHANGE_PASSWORD": "Canvi de contrasenya", "EMAIL_NOTIFICATIONS": "Notificacions de correu" }, "NOTIFICATIONS": { @@ -1041,7 +1041,7 @@ }, "POPOVER": { "USER_PROFILE": "Perfil d'usuari", - "CHANGE_PASSWORD": "Canviar contrasenya", + "CHANGE_PASSWORD": "Canvi de contrasenya", "NOTIFICATIONS": "Notificacions", "FEEDBACK": "Sugerències", "TITLE_AVATAR": "Preferències d'usuari" @@ -1055,14 +1055,14 @@ "CHANGE_EMAIL_SUCCESS": "Mira el teu correu!
Hem enviat un correu al teu conter
amb les instrucciones per a escriure una nova adreça de correu", "CHANGE_PHOTO": "Canviar foto", "FIELD": { - "USERNAME": "Mot d'usuari", - "EMAIL": "Email", + "USERNAME": "Nom d'usuari", + "EMAIL": "Correu electrònic", "FULL_NAME": "Nom complet", "PLACEHOLDER_FULL_NAME": "Esciur el teu nom complet (ex. Íñigo Montoya)", "BIO": "Bio", "PLACEHOLDER_BIO": "Contans algo sobre tu mateix", - "LANGUAGE": "Llengua", - "LANGUAGE_DEFAULT": "-- utilizat llengua per defecte --" + "LANGUAGE": "Idioma", + "LANGUAGE_DEFAULT": "-- utiliza l'idioma per defecte --" } }, "WIZARD": { diff --git a/app/locales/locale-fi.json b/app/locales/locale-fi.json index 4296274d..912ddf9d 100644 --- a/app/locales/locale-fi.json +++ b/app/locales/locale-fi.json @@ -4,8 +4,8 @@ "NO": "Ei", "LOADING": "Ladataan...", "LOADING_PROJECT": "Ladataan projektia...", - "DATE": "DD MMM YYYY", - "DATETIME": "DD MMM YYYY HH:mm", + "DATE": "DD.MM.YY", + "DATETIME": "DD.MM.YY - HH:mm", "SAVE": "Tallenna", "CANCEL": "Peru", "ACCEPT": "Hyväksy", @@ -35,69 +35,69 @@ "LOGOUT": "Kirjaudu ulos", "EXTERNAL_USER": "ulkoinen käyttäjä", "GENERIC_ERROR": "Oompa Loompas havaitsivat virheen {{error}}.", - "IOCAINE_TEXT": "Jos tehtävä ahdistaa, merkitse se hidasteeksi.", + "IOCAINE_TEXT": "Jos tehtävä ahdistaa, merkitse se myrkylliseksi. Ajan mittaa pieninä annoksina saattaa kastokykysi myrkkyä vastaan parantua.", "FORM_ERRORS": { - "DEFAULT_MESSAGE": "This value seems to be invalid.", - "TYPE_EMAIL": "This value should be a valid email.", - "TYPE_URL": "This value should be a valid url.", - "TYPE_URLSTRICT": "This value should be a valid url.", - "TYPE_NUMBER": "This value should be a valid number.", - "TYPE_DIGITS": "This value should be digits.", - "TYPE_DATEISO": "This value should be a valid date (YYYY-MM-DD).", - "TYPE_ALPHANUM": "This value should be alphanumeric.", - "TYPE_PHONE": "This value should be a valid phone number.", - "NOTNULL": "This value should not be null.", - "NOT_BLANK": "This value should not be blank.", - "REQUIRED": "This value is required.", - "REGEXP": "This value seems to be invalid.", - "MIN": "This value should be greater than or equal to %s.", - "MAX": "This value should be lower than or equal to %s.", - "RANGE": "This value should be between %s and %s.", - "MIN_LENGTH": "This value is too short. It should have %s characters or more.", - "MAX_LENGTH": "This value is too long. It should have %s characters or less.", - "RANGE_LENGTH": "This value length is invalid. It should be between %s and %s characters long.", - "MIN_CHECK": "You must select at least %s choices.", - "MAX_CHECK": "You must select %s choices or less.", - "RANGE_CHECK": "You must select between %s and %s choices.", - "EQUAL_TO": "This value should be the same." + "DEFAULT_MESSAGE": "Tämä arvo vaikuttaa virheelliseltä.", + "TYPE_EMAIL": "Tämän pitäisi olla toimiva sähköpostiosoite.", + "TYPE_URL": "Tämän pitäisi olla toimiva web-osoite.", + "TYPE_URLSTRICT": "Tämän pitäisi olla toimiva web-osoite.", + "TYPE_NUMBER": "Arvon pitäisi olla numeerinen.", + "TYPE_DIGITS": "Arvon pitäisi olla numeroita.", + "TYPE_DATEISO": "Arvon pitäisi olla muotoa (vvvv-kk-pp).", + "TYPE_ALPHANUM": "Arvon pitäisi olla alfanumeerinen.", + "TYPE_PHONE": "Arvon pitäisi olla toimiva puhelinnumero.", + "NOTNULL": "Arvo ei saa olla tyhjä.", + "NOT_BLANK": "Arvon ei pitäisi olla tyhjä.", + "REQUIRED": "Arvo vaaditaan.", + "REGEXP": "Tämä arvo vaikuttaa virheelliseltä.", + "MIN": "Arvon pitää olla vähintään %s.", + "MAX": "Arvon pitää olla korkeintaan %s.", + "RANGE": "Arvon pitää olla välissä %s - %s.", + "MIN_LENGTH": "Arvo on liian lyhyt. Tarvitaan vähintään %s merkkiä.", + "MAX_LENGTH": "Arvo on liian pitkä. Pituus saa olla korkeintaan %s merkkiä.", + "RANGE_LENGTH": "Arvo on väärän mittainen. Sen pituus pitäisi olla väliltä %s - %s.", + "MIN_CHECK": "Sinun täytyy valita vähintään %s valintaa.", + "MAX_CHECK": "Valitse korkeintaan %s vaihtoehtoa.", + "RANGE_CHECK": "Valitse %s - %s vaihteohtoa.", + "EQUAL_TO": "Arvojen pitäisi olla samat." }, "PICKERDATE": { - "FORMAT": "DD MMM YYYY", - "IS_RTL": "false", + "FORMAT": "DD.MM.YY", + "IS_RTL": "epätosi", "FIRST_DAY_OF_WEEK": "1", - "PREV_MONTH": "Previous Month", - "NEXT_MONTH": "Next Month", + "PREV_MONTH": "Edellinen kuukausi", + "NEXT_MONTH": "Seuraava kuukausi", "MONTHS": { - "JAN": "January", - "FEB": "February", - "MAR": "March", - "APR": "April", - "MAY": "May", - "JUN": "June", - "JUL": "July", - "AUG": "August", - "SEP": "September", - "OCT": "October", - "NOV": "November", - "DEC": "December" + "JAN": "Tammikuu", + "FEB": "Helmikuu", + "MAR": "Maaliskuu", + "APR": "Huhtikuu", + "MAY": "Toukokuu", + "JUN": "Kesäkuu", + "JUL": "Heinäkuu", + "AUG": "Elokuu", + "SEP": "Syyskuu", + "OCT": "Lokakuu", + "NOV": "Marraskuu", + "DEC": "Joulukuu" }, "WEEK_DAYS": { - "SUN": "Sunday", - "MON": "Monday", - "TUE": "Tuesday", - "WED": "Wednesday", - "THU": "Thursday", - "FRI": "Friday", - "SAT": "Saturday" + "SUN": "Sunnuntai", + "MON": "Maanantai", + "TUE": "Tiistai", + "WED": "Keskiviikko", + "THU": "Torstai", + "FRI": "Perjantai", + "SAT": "Lauantai" }, "WEEK_DAYS_SHORT": { "SUN": "Sun", - "MON": "Mon", - "TUE": "Tue", - "WED": "Wed", - "THU": "Thu", - "FRI": "Fri", - "SAT": "Sat" + "MON": "Maa", + "TUE": "Tii", + "WED": "Kes", + "THU": "Tor", + "FRI": "Per", + "SAT": "Lau" } }, "TAGS": { @@ -115,9 +115,9 @@ "URL": "URL", "DESCRIPTION": "Kuvaus", "VALUE": "Arvo", - "SLUG": "Hitaus", + "SLUG": "Hukka", "COLOR": "Väri", - "IS_CLOSED": "Onko suljettu?", + "IS_CLOSED": "Suljettu", "STATUS": "Tila", "TYPE": "Tyyppi", "SEVERITY": "Vakavuus", @@ -243,15 +243,15 @@ "SECTION_NAME": "liitteet", "TITLE": "{{ fileName }} ladattu {{ date }}\n", "DESCRIPTION": "Kirjoita lyhyt kuvaus", - "DEPRECATED": "(deprecated)", + "DEPRECATED": "(poistettu)", "DEPRECATED_FILE": "Vanhentunut?", "ADD": "Lisää liite. <%- maxFileSizeMsg %>", "MAX_FILE_SIZE": "[Maks. koko: {{maxFileSize}}]", "SHOW_DEPRECATED": "+ näytä vanhentuneet liitteet", "HIDE_DEPRECATED": "- piilota vanhentuneet liitteet", - "COUNT_DEPRECATED": "({{ counter }} deprecated)", + "COUNT_DEPRECATED": "({{ counter }} vanhentunutta)", "MAX_UPLOAD_SIZE": "Maksimi tiedoston koko {{maxFileSize}}", - "DATE": "DD MMM YYYY - hh:mm", + "DATE": "DD.MM.YY - hh:mm", "ERROR_UPLOAD_ATTACHMENT": "Emme onnistuneet lataamaan tiedostoa '{{fileName}}'. {{errorMessage}}", "TITLE_LIGHTBOX_DELETE_ATTACHMENT": "Poista liite...", "MSG_LIGHTBOX_DELETE_ATTACHMENT": "liite '{{fileName}}'", @@ -306,7 +306,7 @@ "WIKI": "Wiki", "WIKI_DESCRIPTION": "Lisää, muokkaa tai poista sisältöä yhteistyössä muiden kanssa. Tämä on oikea paikka projektin dokumentaatiolle.", "MEETUP": "Tapaa", - "MEETUP_DESCRIPTION": "Valitse videoconferenssisysteemi. Jopa kehittäjät tarvitsevat katsekontaktia.", + "MEETUP_DESCRIPTION": "Valitse videoneuvottelu- järjestelmä. Jopa kehittäjät tarvitsevat katsekontaktia.", "SELECT_VIDEOCONFERENCE": "Valitse videoconferenssi-järjestelmä", "SALT_CHAT_ROOM": "Voit halutessasi lisätä suolaan chat-huoneen nimeen" }, @@ -332,9 +332,9 @@ "REGENERATE_SUBTITLE": "Jos muutata CSV-datan URLia, edellien lakkaa toimimasta. Oletko varma?" }, "CSV": { - "SECTION_TITLE_US": "user stories reports", - "SECTION_TITLE_TASK": "tasks reports", - "SECTION_TITLE_ISSUE": "issues reports", + "SECTION_TITLE_US": "käyttäjätarinoiden raportit", + "SECTION_TITLE_TASK": "tehtävien raportit", + "SECTION_TITLE_ISSUE": "pyyntöjen raportit", "DOWNLOAD": "Lataa CSV-tiedosto", "URL_FIELD_PLACEHOLDER": "Tee uusi CSV-url", "TITLE_REGENERATE_URL": "Tee uusi CSV-url", @@ -364,12 +364,12 @@ "PROJECT_VALUES_PRIORITIES": { "TITLE": "Pyyntöjen tärkeydet", "SUBTITLE": "Määrittele tärkeydet pyynnöille", - "ACTION_ADD": "Add new priority" + "ACTION_ADD": "Lisää uusi tärkeys" }, "PROJECT_VALUES_SEVERITIES": { "TITLE": "Pyyntöjen vakavuudet", "SUBTITLE": "Määrittele pyyntöjen vakavuudet", - "ACTION_ADD": "Add new severity" + "ACTION_ADD": "Lisää uusi vakavuus" }, "PROJECT_VALUES_STATUS": { "TITLE": "Tila", @@ -382,7 +382,7 @@ "TITLE": "Tyypit", "SUBTITLE": "Määrittele pyyntöjen tyypit", "ISSUE_TITLE": "Pyyntöjen tyypit", - "ACTION_ADD": "Add new type" + "ACTION_ADD": "Lisää uusi tyyppi" }, "ROLES": { "SECTION_NAME": "Roolit - {{projectName}}", @@ -433,7 +433,7 @@ "HEADERS": "Ylätunnisteet", "PAYLOAD": "Hyötykuorma", "RESPONSE": "Vastaus", - "DATE": "DD MMM YYYY - hh:mm:ss", + "DATE": "DD.MM.YY - hh:mm:ss", "ACTION_HIDE_HISTORY": "(Piilota historia)", "ACTION_HIDE_HISTORY_TITLE": "Piilota historian yksityiskohdat", "ACTION_SHOW_HISTORY": "(Näytä historia)", @@ -467,12 +467,12 @@ }, "DEFAULT_VALUES": { "LABEL_POINTS": "Oletukset pisteiden valintaan", - "LABEL_US": "Oletusarvo Kt:n tilan valintaan", - "LABEL_TASK_STATUS": "Oletusarvo tehtävän tilan valintaan", + "LABEL_US": "Oletukset käyttäjätarinoiden tiloiksi", + "LABEL_TASK_STATUS": "Oletukset tehtävien tilaksi", "LABEL_PRIORITY": "Oletus arvo tärkeyden valiintaan", - "LABEL_SEVERITY": "Oletusarvo vakavuuden valintaan", - "LABEL_ISSUE_TYPE": "Oletusarvo pyynnön tyypin valintaan", - "LABEL_ISSUE_STATUS": "Oletusarvo pyyntöjen statuksen valintaan" + "LABEL_SEVERITY": "Oletukset vakavuudeksi", + "LABEL_ISSUE_TYPE": "Oletukset pyyntöjen tyypeiksi", + "LABEL_ISSUE_STATUS": "Oletukset pyyntöjen statuksiksi" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Anna uuden tilan nimi" @@ -482,7 +482,7 @@ }, "US_STATUS": { "ACTION_ADD_STATUS": "Lisää uusi tila", - "IS_ARCHIVED_COLUMN": "Onko arkistoitu?", + "IS_ARCHIVED_COLUMN": "Arkistoitu", "WIP_LIMIT_COLUMN": "WIP raja", "PLACEHOLDER_WRITE_NAME": "Anna uuden tilan nimi" }, @@ -496,7 +496,7 @@ "PLUGINS": "Pluginit" }, "SUBMENU_PROJECT_ATTRIBUTES": { - "TITLE": "Attributes" + "TITLE": "Attribuutit" }, "SUBMENU_PROJECT_VALUES": { "STATUS": "Tila", @@ -538,7 +538,7 @@ "ADMIN": "Hallinnoi" }, "NAVIGATION": { - "SECTION_TITLE": "Your projects", + "SECTION_TITLE": "Projektisi", "PLACEHOLDER_SEARCH": "Hae täältä...", "ACTION_CREATE_PROJECT": "Luo projekti", "TITLE_ACTION_IMPORT": "Luo projekti tiedostosta", @@ -547,15 +547,15 @@ }, "IMPORT": { "TITLE": "Luetaan sisään projektia", - "UPLOADING_FILE": "Uploading dump file", + "UPLOADING_FILE": "Ladataan tiedostoa", "DESCRIPTION": "Tämä voi kestää hetken, pidä ikkuna auki.", "ASYNC_IN_PROGRESS_TITLE": " Oompa Loompas tuovat projektia", "ASYNC_IN_PROGRESS_MESSAGE": "Tämä voi kestää muutaman minuutin
Lähetämme sähköpostin on valmista", - "UPLOAD_IN_PROGRESS_MESSAGE": "Uploaded {{uploadedSize}} of {{totalSize}}", + "UPLOAD_IN_PROGRESS_MESSAGE": "LAdattu {{uploadedSize}} / {{totalSize}}", "ERROR": "Oompa Loompas eivät onnistuneet tuomaan tiedostoasi. Yritä uudestaan.", "ERROR_TOO_MANY_REQUEST": "Oompa Loompas ovat kiireisiä juuri nyt. Yritä hetken päästä uudelleen.", "ERROR_MESSAGE": "Oompa Loompas eivät pysty lukemaan tiedostoasi: {{error_message}}", - "ERROR_MAX_SIZE_EXCEEDED": "'{{fileName}}' ({{fileSize}}) is too heavy for our Oompa Loompas, try it with a smaller than ({{maxFileSize}})", + "ERROR_MAX_SIZE_EXCEEDED": "'{{fileName}}' ({{fileSize}}) on liian iso Oompa Loompaseille, kokeile pienemmällä kuin ({{maxFileSize}})", "SYNC_SUCCESS": "Projektisi on tuotu sisään onnistuneesti" } }, @@ -564,7 +564,7 @@ "SECTION_NAME": "Poista Taiga-tunnus", "CONFIRM": "Haluatko varmasti poistaa Taiga-tunnuksesi?", "SUBTITLE": "Tulemme kaipaamaan sinua! :-(", - "NEWSLETTER_LABEL_TEXT": "I don't wanna receive your newsletter anymore" + "NEWSLETTER_LABEL_TEXT": "En halua uutiskirjettä enää" }, "DELETE_PROJECT": { "TITLE": "Poista projekti", @@ -624,7 +624,7 @@ } }, "US": { - "SECTION_NAME": "User story details", + "SECTION_NAME": "Käyttäjätarinan tiedot", "LINK_TASKBOARD": "Tehtävätaulu", "TITLE_LINK_TASKBOARD": "Siirry tehtävätauluun", "TOTAL_POINTS": "yhteensä", @@ -660,7 +660,7 @@ }, "ACTIVITY": { "SHOW_ACTIVITY": "Näytä tapahtumat", - "DATETIME": "DD MMM YYYY HH:mm", + "DATETIME": "DD.MM.YY - HH:mm", "SHOW_MORE": "+ Näytä edelliset rivit ({{showMore}} lisää)", "TITLE": "Aktiivisuus", "REMOVED": "poistettu", @@ -698,7 +698,7 @@ "POINTS": "pisteet", "CLIENT_REQUIREMENT": "asiakkaan vaatimus", "TEAM_REQUIREMENT": "tiimin vaatimus", - "IS_IOCAINE": "on hidaste", + "IS_IOCAINE": "myrkyllinen", "TAGS": "avainsanat", "ATTACHMENTS": "liitteet", "IS_DEPRECATED": "on vanhentunut", @@ -737,7 +737,7 @@ "INCREMENT_CLIENT": "Lisätyt asiakkaan vaatimat pisteet kierrokselle {{xval}} ovat {{yval}}" }, "TAGS": { - "TOGGLE": "Toggle tags visibility", + "TOGGLE": "Vaihda avainsanojen näkyvyyttä", "SHOW": "Näytä avainsanat", "HIDE": "Piilota avainsanat" }, @@ -750,7 +750,7 @@ "COMPLETED_POINTS": "valmiina
pistettä", "OPEN_TASKS": "avaa
tehtävät", "CLOSED_TASKS": "suljettu
tehtävää", - "IOCAINE_DOSES": "hidastetia
annosta", + "IOCAINE_DOSES": "myrkkye-
annosta", "SHOW_STATISTICS_TITLE": "Näytä tilastot" }, "SUMMARY": { @@ -760,20 +760,20 @@ "POINTS_PER_SPRINT": "pisettä/
kierros" }, "FILTERS": { - "TOGGLE": "Toggle filters visibility", + "TOGGLE": "Vaihda suodattimien näkyvyyttä", "TITLE": "Suodattimet", "REMOVE": "Poista suodattimet", - "HIDE": "Hide Filters", + "HIDE": "Piilota suodattimet", "SHOW": "Näytä suodattimet", "FILTER_CATEGORY_STATUS": "Tila", "FILTER_CATEGORY_TAGS": "Avainsanat" }, "SPRINTS": { "TITLE": "KIERROKSET", - "DATE": "DD MMM YYYY", + "DATE": "DD.MM.YY", "LINK_TASKBOARD": "Kierroksien tehtävätaulu", "TITLE_LINK_TASKBOARD": "Siirry tehtävätauluun {{name}}", - "NUMBER_SPRINTS": "
kierrokset", + "NUMBER_SPRINTS": "
kierroksia", "TITLE_ACTION_NEW_SPRINT": "+ Uusi kierros", "ACTION_NEW_SPRINT": "+ Uusi kierros", "ACTION_SHOW_CLOSED_SPRINTS": "Näytä suljetut kierrokset", @@ -809,11 +809,11 @@ "YAXIS_LABEL": "Pisteet", "OPTIMAL": "Optimaaliset odottavat pisteet {{formattedDate}} ovat {{roundedValue}}", "REAL": "Todelliset odottavat pisteet {{formattedDate}} ovat {{roundedValue}}", - "DATE": "DD MMMM YYYY" + "DATE": "DD.MM.YY" } }, "TASK": { - "SECTION_NAME": "Task details", + "SECTION_NAME": "Tehtävän tiedot", "LINK_TASKBOARD": "Tehtävätaulu", "TITLE_LINK_TASKBOARD": "Siirry tehtävätauluun", "PLACEHOLDER_SUBJECT": "Anna tehtävän aihe", @@ -830,10 +830,10 @@ "FIELDS": { "MILESTONE": "Kierros", "USER_STORY": "Käyttäjätarina", - "IS_IOCAINE": "On hidaste" + "IS_IOCAINE": "Myrkyllinen" }, - "ACTION_IOCAINE": "Hidaste", - "TITLE_ACTION_IOCAINE": "Rasittaako tehtävä? Kerro muillekin klikkaamalla hidasteen kuvaketta. Sattaa myös helpottaa jatkossa jos kerrot asiasta vähitellen." + "ACTION_IOCAINE": "Myrkky", + "TITLE_ACTION_IOCAINE": "Rasittaako tehtävä? Kerro muillekin klikkaamalla myrkyn kuvaketta. Sattaa myös helpottaa jatkossa jos nautit vähitellen." }, "NOTIFICATION": { "OK": "Kaikki on kunnossa", @@ -875,7 +875,7 @@ "PLACEHOLDER_FIELD": "Käyttäjänimi tai sähköposti", "ACTION_RESET_PASSWORD": "Uusi salsanasi", "LINK_CANCEL": "Vie minut takaisin, muistan sen.", - "SUCCESS": "Check your inbox!
We have sent you an email with the instructions to set a new password", + "SUCCESS": "Tarkista sähköpostisi!
Olemme lähettäneet sinulle sähköpostissa kirjautumisohjeet", "ERROR": "Oompa Loompas sanovat että käyttäjänimesi tai sähköpostisi tai salasanasi on väärä." }, "LOGIN_COMMON": { @@ -898,7 +898,7 @@ "ERROR": "Oompa Loompas sanovat että käyttäjänimesi tai sähköpostisi tai salasanasi on väärä." }, "REGISTER_FORM": { - "TITLE": "Register a new Taiga account (free)", + "TITLE": "Rekisteröi uusi Taiga tunnus (ilmainen)", "PLACEHOLDER_NAME": "Anna käyttäjänimi (kirjainkoko on merkitsevä)", "PLACEHOLDER_FULL_NAME": "Anna koko nimesi", "PLACEHOLDER_EMAIL": "Sähköpostisi", @@ -909,7 +909,7 @@ }, "ISSUES": { "LIST_SECTION_NAME": "Pyynnöt", - "SECTION_NAME": "Issue details", + "SECTION_NAME": "Pyynnön tiedot", "ACTION_NEW_ISSUE": "+ UUSI PYYNTÖ", "ACTION_PROMOTE_TO_US": "Liitä käyttäjätarinaan", "PLACEHOLDER_FILTER_NAME": "Anna suodattimen nimi ja paina enter", @@ -996,8 +996,8 @@ "PLACEHOLDER_INPUT_SEARCH": "Etsi koko nimellä...", "COLUMN_MR_WOLF": "Mr. Wolf", "EXPLANATION_COLUMN_MR_WOLF": "Suljetut pyynnöt", - "COLUMN_IOCAINE": "Hidasteiden syöjä", - "EXPLANATION_COLUMN_IOCAINE": "Kertyneet hidasteet", + "COLUMN_IOCAINE": "Myrkyn syöjä", + "EXPLANATION_COLUMN_IOCAINE": "Kertyneet myrkyt", "COLUMN_CERVANTES": "Cervantes", "EXPLANATION_COLUMN_CERVANTES": "Wiki-sivuja muokattu", "COLUMN_BUG_HUNTER": "Virheiden metsästäjä", @@ -1074,7 +1074,7 @@ "PROGRESS_NAME_DESCRIPTION": "Nimi ja kuvaus" }, "WIKI": { - "DATETIME": "DD MMM YYYY HH:mm", + "DATETIME": "DD.MM.YY - HH:mm", "PLACEHOLDER_PAGE": "Kirjoita wiki-sivu", "REMOVE": "Poista tämä wiki-sivu", "DELETE_LIGHTBOX_TITLE": "Poista wiki-sivu", diff --git a/app/locales/locale-fr.json b/app/locales/locale-fr.json index 0d79d36f..31809fdf 100644 --- a/app/locales/locale-fr.json +++ b/app/locales/locale-fr.json @@ -33,33 +33,33 @@ "NEW_BULK": "Nouvel ajout en bloc", "RELATED_TASKS": "Tâches associées", "LOGOUT": "Déconnexion", - "EXTERNAL_USER": "an external user", + "EXTERNAL_USER": "um usuário externo", "GENERIC_ERROR": "L'un de nos Ooompa Loompas dit {{error}}.", "IOCAINE_TEXT": "Vous vous sentez un peu submergé par une tâche ? Soyez certains d'en informer les autres en cliquant su Iocaine lors de la modification de la tâche. Il est possible de s'immuniser contre ce poison (fictif) en consommant de petites quantités en heures supplémentaires, tout comme il est possible de s'améliorer en acceptant parfois de nouveaux défis !", "FORM_ERRORS": { "DEFAULT_MESSAGE": "Cette valeur semble être invalide.", - "TYPE_EMAIL": "This value should be a valid email.", - "TYPE_URL": "This value should be a valid url.", - "TYPE_URLSTRICT": "This value should be a valid url.", - "TYPE_NUMBER": "This value should be a valid number.", - "TYPE_DIGITS": "This value should be digits.", + "TYPE_EMAIL": "O valor deveria ser um email válido.", + "TYPE_URL": "O valor deveria ser uma url válida.", + "TYPE_URLSTRICT": "O valor deveria ser uma url válida.", + "TYPE_NUMBER": "O valor deveria ser número válido.", + "TYPE_DIGITS": "O valor deveria conter digitos.", "TYPE_DATEISO": "Cette valeur n'est pas une date valide (AAAA-MM-JJ).", "TYPE_ALPHANUM": "Cette valeur doit être alphanumérique.", - "TYPE_PHONE": "This value should be a valid phone number.", - "NOTNULL": "This value should not be null.", + "TYPE_PHONE": "O valor deveria ser um número de telefone válido.", + "NOTNULL": "O valor não deveria ser nulo.", "NOT_BLANK": "Cette valeur ne doit pas être vide.", "REQUIRED": "Cette valeur est obligatoire.", "REGEXP": "Cette valeur semble être invalide.", - "MIN": "This value should be greater than or equal to %s.", - "MAX": "This value should be lower than or equal to %s.", - "RANGE": "This value should be between %s and %s.", - "MIN_LENGTH": "This value is too short. It should have %s characters or more.", - "MAX_LENGTH": "This value is too long. It should have %s characters or less.", - "RANGE_LENGTH": "This value length is invalid. It should be between %s and %s characters long.", - "MIN_CHECK": "You must select at least %s choices.", - "MAX_CHECK": "You must select %s choices or less.", - "RANGE_CHECK": "You must select between %s and %s choices.", - "EQUAL_TO": "This value should be the same." + "MIN": "O valor deveria ser maior ou igual a %s.", + "MAX": "O valor deveria ser menor ou igual a %s.", + "RANGE": "O valor deveria estar entre %s e %s.", + "MIN_LENGTH": "O valor é muito curto. O valor deveria conter %s caracteres ou mais.", + "MAX_LENGTH": "O valor é muito comprido. O valor deveria conter %s caracteres ou menos.", + "RANGE_LENGTH": "La taille de cette valeur est invalide, elle doit être comprise entre %s et %s caractères.", + "MIN_CHECK": "Vous devez sélectionner au moins %s options.", + "MAX_CHECK": "Vous devez sélectionner %s options ou moins.", + "RANGE_CHECK": "Vous devez sélectionner entre %s et %s options.", + "EQUAL_TO": "Cette valeur doit être identique." }, "PICKERDATE": { "FORMAT": "DD MMM YYYY", @@ -243,19 +243,19 @@ "SECTION_NAME": "pièces jointes", "TITLE": "{{ fileName }} transmis le {{ date }}\n", "DESCRIPTION": "Saisissez une description courte", - "DEPRECATED": "(deprecated)", + "DEPRECATED": "(obsolète)", "DEPRECATED_FILE": "Obsolète?", "ADD": "Ajouter une pièce jointe. <%- maxFileSizeMsg %>", "MAX_FILE_SIZE": "[Taille max.: {{maxFileSize}}]", "SHOW_DEPRECATED": "+ montrer les pièces jointes obsolètes", "HIDE_DEPRECATED": "- cacher les pièces jointes obsolètes", - "COUNT_DEPRECATED": "({{ counter }} deprecated)", + "COUNT_DEPRECATED": "({{ counter }} obsolètes)", "MAX_UPLOAD_SIZE": "La taille maximum d'upload est {{maxFileSize}}", "DATE": "DD MMM YYYY [at] hh:mm", - "ERROR_UPLOAD_ATTACHMENT": "We have not been able to upload '{{fileName}}'. {{errorMessage}}", + "ERROR_UPLOAD_ATTACHMENT": "Nous n'avons pas été capable de charger '{{fileName}}'. {{errorMessage}}", "TITLE_LIGHTBOX_DELETE_ATTACHMENT": "Supprimer la pièce jointe...", "MSG_LIGHTBOX_DELETE_ATTACHMENT": "la pièce jointe \"{{fileName}}\"", - "ERROR_DELETE_ATTACHMENT": "We have not been able to delete: {{errorMessage}}", + "ERROR_DELETE_ATTACHMENT": "Nous n'avons pas été capable de supprimer {{errorMessage}}.", "FIELDS": { "IS_DEPRECATED": "est obsolète" } @@ -269,7 +269,7 @@ "TITLE_ACTION_EDIT_VALUE": "Modifier", "TITLE_ACTION_DELETE_VALUE": "Supprimer" }, - "HELP": "Avez-vous besoin d'aide? Consultez notre page de soutien!", + "HELP": "Avez-vous besoin d'aide? Consultez notre page de support!", "PROJECT_DEFAULT_VALUES": { "TITLE": "Valeurs par défaut", "SUBTITLE": "Définir les valeurs par défaut pour toutes les listes de sélection" @@ -332,9 +332,9 @@ "REGENERATE_SUBTITLE": "Vous êtes sur le point de changer l'url d'accès aux données CSV. L'url précédente sera désactivée. Etes-vous sûre ?" }, "CSV": { - "SECTION_TITLE_US": "user stories reports", + "SECTION_TITLE_US": "Rapports des histoires utilisateur", "SECTION_TITLE_TASK": "Rapports des tâches", - "SECTION_TITLE_ISSUE": "issues reports", + "SECTION_TITLE_ISSUE": "Rapports des bugs", "DOWNLOAD": "Télécharger au format CSV", "URL_FIELD_PLACEHOLDER": "Merci de regénérer l'url de téléchargement au format CSV", "TITLE_REGENERATE_URL": "Regénérer l'URL du CSV", @@ -369,7 +369,7 @@ "PROJECT_VALUES_SEVERITIES": { "TITLE": "Sévérités des problèmes", "SUBTITLE": "Spécifiez les sévérités qu'auront vos problèmes", - "ACTION_ADD": "Add new severity" + "ACTION_ADD": "Ajouter un degré de sévérité" }, "PROJECT_VALUES_STATUS": { "TITLE": "Statut", @@ -382,7 +382,7 @@ "TITLE": "Types", "SUBTITLE": "Spécifiez les priorités qu'auront vos bugs", "ISSUE_TITLE": "Types de problèmes", - "ACTION_ADD": "Add new type" + "ACTION_ADD": "Ajouter un nouveau type" }, "ROLES": { "SECTION_NAME": "Rôles - {{projectName}}", @@ -496,7 +496,7 @@ "PLUGINS": "Plugins" }, "SUBMENU_PROJECT_ATTRIBUTES": { - "TITLE": "Attributes" + "TITLE": "Attributs" }, "SUBMENU_PROJECT_VALUES": { "STATUS": "Etats", @@ -547,15 +547,15 @@ }, "IMPORT": { "TITLE": "Import du projet en cours", - "UPLOADING_FILE": "Uploading dump file", + "UPLOADING_FILE": "Chargement du fichier de dump", "DESCRIPTION": "Ce processus peut prendre du temps, veuillez garder cette fenêtre ouverte.", "ASYNC_IN_PROGRESS_TITLE": "Nos Oompas Loompas sont en train d'importer votre projet", "ASYNC_IN_PROGRESS_MESSAGE": "Ce processus pourrait durer plusieurs minutes
Nous vous enverrons un emai quand ce sera prêt", - "UPLOAD_IN_PROGRESS_MESSAGE": "Uploaded {{uploadedSize}} of {{totalSize}}", + "UPLOAD_IN_PROGRESS_MESSAGE": "{{uploadedSize}} de {{totalSize}} uploadés", "ERROR": "Nos Oompas Loompas ont rencontré des problèmes en important votre dump de données. Merci de réessayer.", "ERROR_TOO_MANY_REQUEST": "Nous sommes désolés, nos oompa loompas sont très occupés en ce moment. Veuillez réessayer dans quelques minutes.", "ERROR_MESSAGE": "Nos oompa loompas ont des problèmes pour importer le dump de vos données : {{error_message}}", - "ERROR_MAX_SIZE_EXCEEDED": "'{{fileName}}' ({{fileSize}}) is too heavy for our Oompa Loompas, try it with a smaller than ({{maxFileSize}})", + "ERROR_MAX_SIZE_EXCEEDED": "'{{fileName}}' ({{fileSize}}) est un peu trop lourd pour nos Oompa Loompas, réessayez avec un fichier inférieur à ({{maxFileSize}})", "SYNC_SUCCESS": "Votre projet a été importé avec succès" } }, @@ -624,7 +624,7 @@ } }, "US": { - "SECTION_NAME": "User story details", + "SECTION_NAME": "Détails de l'histoire utilisateur", "LINK_TASKBOARD": "Tableau des tâches", "TITLE_LINK_TASKBOARD": "Aller au tableau des tâches", "TOTAL_POINTS": "total", @@ -670,20 +670,20 @@ "DELETED_ATTACHMENT": "Pièce-jointe supprimée", "UPDATED_ATTACHMENT": "Pièce jointe {{filename}} modifiée", "DELETED_CUSTOM_ATTRIBUTE": "Attribut personnalisé supprimé", - "SIZE_CHANGE": "Made {size, plural, one{one change} other{# changes}}", + "SIZE_CHANGE": "A fait {size, plural, one{une modification} other{# modifications}}", "VALUES": { "YES": "oui", "NO": "no", - "EMPTY": "empty", + "EMPTY": "vide", "UNASSIGNED": "non affecté" }, "FIELDS": { "SUBJECT": "sujet", "NAME": "nom", "DESCRIPTION": "description", - "CONTENT": "content", + "CONTENT": "conteúdo", "STATUS": "état", - "IS_CLOSED": "is closed", + "IS_CLOSED": "está fechado", "FINISH_DATE": "date de fin", "TYPE": "type", "PRIORITY": "priorité", @@ -702,12 +702,12 @@ "TAGS": "étiquettes", "ATTACHMENTS": "pièces jointes", "IS_DEPRECATED": "est obsolète", - "ORDER": "order", - "BACKLOG_ORDER": "backlog order", - "SPRINT_ORDER": "sprint order", - "KANBAN_ORDER": "kanban order", + "ORDER": "Ordre", + "BACKLOG_ORDER": "Ordre du backlog", + "SPRINT_ORDER": "Ordre du sprint", + "KANBAN_ORDER": "Ordre du Kanban", "TASKBOARD_ORDER": "trier le tableau des tâches", - "US_ORDER": "us order" + "US_ORDER": "Ordre des histoires utilisateur" } }, "BACKLOG": { @@ -737,7 +737,7 @@ "INCREMENT_CLIENT": "Le nombre de points ajoutés par les exigences du client pour le sprint {{xval}} est {{yval}}" }, "TAGS": { - "TOGGLE": "Toggle tags visibility", + "TOGGLE": "Afficher/Cacher les tags", "SHOW": "Montrer les étiquettes", "HIDE": "Cacher les étiquettes" }, @@ -760,10 +760,10 @@ "POINTS_PER_SPRINT": "points /
sprint" }, "FILTERS": { - "TOGGLE": "Toggle filters visibility", + "TOGGLE": "Afficher/Cacher les filtres", "TITLE": "Filtres", "REMOVE": "Supprimer les filtres", - "HIDE": "Hide Filters", + "HIDE": "Esconder Filtros", "SHOW": "Afficher les filtres", "FILTER_CATEGORY_STATUS": "Etat", "FILTER_CATEGORY_TAGS": "Labels" @@ -849,7 +849,7 @@ "SUBTITLE": "Nous sommes tristes de vous voir quitter la taiga, et nous espérons que vous avez apprécié votre séjour. :)", "PLACEHOLDER_INPUT_TOKEN": "Annuler le jeton du compte", "ACTION_LEAVING": "Oui, je m'en vais!", - "SUCCESS": "Nos Oompa Loompas ont supprimés votre compte" + "SUCCESS": "Nos Oompa Loompas ont supprimé votre compte" }, "CHANGE_EMAIL_FORM": { "TITLE": "Modifier votre email", @@ -876,7 +876,7 @@ "ACTION_RESET_PASSWORD": "Réinitialiser le mot de passe", "LINK_CANCEL": "Nan, ramenez-moi en arrière. Je crois que je m'en rappelle.", "SUCCESS": "Consultez votre messagerie!
Nous venons d'envoyer un mail avec les instructions pour créer un nouveau mot de passe", - "ERROR": "Selon nos Oompa Loompas, vous n'êtes pas encore enregistré." + "ERROR": "D'après nos Oompa Loompas, vous n'êtes pas encore enregistré." }, "LOGIN_COMMON": { "HEADER": "J'ai déjà un login Taiga", @@ -888,17 +888,17 @@ "PLACEHOLDER_AUTH_PASSWORD": "Mot de passe (sensible à la casse)" }, "LOGIN_FORM": { - "ERROR_AUTH_INCORRECT": "Selon nos Oompa Loompas, votre nom d'utilisateur / email ou votre mot de passe sont incorrects.", - "ERROR_GENERIC": "Selon nos Oompa Loompas, une erreur est survenue.", + "ERROR_AUTH_INCORRECT": "D'après nos Oompa Loompas, votre nom d'utilisateur / email ou votre mot de passe sont incorrects.", + "ERROR_GENERIC": "D'après nos Oompa Loompas, une erreur est survenue.", "SUCCESS": "Nos Oompa Loompas sont heureux, bienvenue sur Taiga." }, "INVITATION_LOGIN_FORM": { "NOT_FOUND": "Ooops, nous avons un problème
Nos Oompa Loompas trouvent pas votre invitation.", "SUCCESS": "Vous avez rejoint ce projet, Bienvenue sur {{project_name}}", - "ERROR": "Selon nos Oompa Loompas, vous n'êtes pas encore enregistrés ou vous avez saisi un mot de passe incorrecte." + "ERROR": "D'après nos Oompa Loompas, vous n'êtes pas encore enregistrés ou vous avez saisi un mot de passe incorrect." }, "REGISTER_FORM": { - "TITLE": "Register a new Taiga account (free)", + "TITLE": "Créer un compte Taiga (gratuit)", "PLACEHOLDER_NAME": "Choisissez un nom d'utilisateur (sensible à la casse)", "PLACEHOLDER_FULL_NAME": "Choisissez votre nom complet", "PLACEHOLDER_EMAIL": "Votre email", @@ -909,7 +909,7 @@ }, "ISSUES": { "LIST_SECTION_NAME": "Problèmes", - "SECTION_NAME": "Issue details", + "SECTION_NAME": "Detalhes do problema", "ACTION_NEW_ISSUE": "+ NOUVEAU PROBLÈME", "ACTION_PROMOTE_TO_US": "Transformer en User Story", "PLACEHOLDER_FILTER_NAME": "Écrivez le nom du filtre et appuyez sur \"Entrée\"", diff --git a/app/locales/locale-zh-hant.json b/app/locales/locale-zh-hant.json index 9d7cc7ab..ebe2feb3 100644 --- a/app/locales/locale-zh-hant.json +++ b/app/locales/locale-zh-hant.json @@ -63,41 +63,41 @@ }, "PICKERDATE": { "FORMAT": "DD MMM YYYY", - "IS_RTL": "false", + "IS_RTL": "錯誤", "FIRST_DAY_OF_WEEK": "1", - "PREV_MONTH": "Previous Month", - "NEXT_MONTH": "Next Month", + "PREV_MONTH": "上個月", + "NEXT_MONTH": "下個月", "MONTHS": { - "JAN": "January", - "FEB": "February", - "MAR": "March", - "APR": "April", - "MAY": "May", - "JUN": "June", - "JUL": "July", - "AUG": "August", - "SEP": "September", - "OCT": "October", - "NOV": "November", - "DEC": "December" + "JAN": "1月份", + "FEB": "2月份", + "MAR": "三月", + "APR": "四月份", + "MAY": "五月份", + "JUN": "六月份", + "JUL": "七月份", + "AUG": "八月份", + "SEP": "九月份", + "OCT": "十月份", + "NOV": "十一月份", + "DEC": "十二月份" }, "WEEK_DAYS": { - "SUN": "Sunday", - "MON": "Monday", - "TUE": "Tuesday", - "WED": "Wednesday", - "THU": "Thursday", - "FRI": "Friday", - "SAT": "Saturday" + "SUN": "週日", + "MON": "週一", + "TUE": "週二", + "WED": "週三", + "THU": "週四", + "FRI": "週五", + "SAT": "週六" }, "WEEK_DAYS_SHORT": { - "SUN": "Sun", - "MON": "Mon", - "TUE": "Tue", - "WED": "Wed", - "THU": "Thu", - "FRI": "Fri", - "SAT": "Sat" + "SUN": "週日", + "MON": "週一", + "TUE": "週二", + "WED": "週三", + "THU": "週四", + "FRI": "週五", + "SAT": "週六" } }, "TAGS": { @@ -243,13 +243,13 @@ "SECTION_NAME": "附件", "TITLE": "{{ fileName }} 上傳於 {{ date }}", "DESCRIPTION": "輸入一段簡短描述", - "DEPRECATED": "(deprecated)", + "DEPRECATED": "(被棄用)", "DEPRECATED_FILE": "棄用?", "ADD": "加入新附件 <%- maxFileSizeMsg %>", "MAX_FILE_SIZE": "[Max. size: {{maxFileSize}}]", "SHOW_DEPRECATED": "+ 顯示棄用的附件", "HIDE_DEPRECATED": "+ 隱藏棄用的附件", - "COUNT_DEPRECATED": "({{ counter }} deprecated)", + "COUNT_DEPRECATED": "({{ counter }} 遭棄用)", "MAX_UPLOAD_SIZE": "上傳檔案最大容量限制 {{maxFileSize}}", "DATE": "DD MMM YYYY [at] hh:mm", "ERROR_UPLOAD_ATTACHMENT": "無法成功上傳 '{{fileName}}'. {{errorMessage}}", @@ -364,12 +364,12 @@ "PROJECT_VALUES_PRIORITIES": { "TITLE": "問題優先性", "SUBTITLE": "指明你將遇到的問題優先程度", - "ACTION_ADD": "Add new priority" + "ACTION_ADD": "新增優先性" }, "PROJECT_VALUES_SEVERITIES": { "TITLE": "問題急迫性", "SUBTITLE": "指明你將遇到問題的嚴重程度", - "ACTION_ADD": "Add new severity" + "ACTION_ADD": "新增急迫性" }, "PROJECT_VALUES_STATUS": { "TITLE": "狀態", @@ -382,7 +382,7 @@ "TITLE": "類型", "SUBTITLE": "指定你的問題類型可能是", "ISSUE_TITLE": "問題類型", - "ACTION_ADD": "Add new type" + "ACTION_ADD": "新增類別" }, "ROLES": { "SECTION_NAME": "角色- {{projectName}}", @@ -496,7 +496,7 @@ "PLUGINS": "外掛" }, "SUBMENU_PROJECT_ATTRIBUTES": { - "TITLE": "Attributes" + "TITLE": "屬性" }, "SUBMENU_PROJECT_VALUES": { "STATUS": "狀態", @@ -547,7 +547,7 @@ }, "IMPORT": { "TITLE": "滙入專案中", - "UPLOADING_FILE": "Uploading dump file", + "UPLOADING_FILE": "上傳傾倒檔案", "DESCRIPTION": "這個過桯要花點時間,請保持视窗開啟", "ASYNC_IN_PROGRESS_TITLE": "我們的工程師對你的專案很重要哦", "ASYNC_IN_PROGRESS_MESSAGE": "這個過程要花上一點時間
當弄好時我們會發給你一封郵件", @@ -564,7 +564,7 @@ "SECTION_NAME": "刪除Taiga帳戶", "CONFIRM": "你確定要刪除Taiga帳戶嗎?", "SUBTITLE": "我們會想念你:-(", - "NEWSLETTER_LABEL_TEXT": "I don't wanna receive your newsletter anymore" + "NEWSLETTER_LABEL_TEXT": "不願再收到電子月報" }, "DELETE_PROJECT": { "TITLE": "刪除專案", @@ -624,7 +624,7 @@ } }, "US": { - "SECTION_NAME": "User story details", + "SECTION_NAME": "使用者故事細節", "LINK_TASKBOARD": "任務板", "TITLE_LINK_TASKBOARD": "到任務板去", "TOTAL_POINTS": "全部", @@ -737,7 +737,7 @@ "INCREMENT_CLIENT": "客戶要求的任務衝刺逐步新增點數 {{xval}} 為 {{yval}}" }, "TAGS": { - "TOGGLE": "Toggle tags visibility", + "TOGGLE": "切換標籤可見度", "SHOW": "顯示標籤", "HIDE": "隱藏標籤" }, @@ -760,10 +760,10 @@ "POINTS_PER_SPRINT": "點數 /
衝刺任務" }, "FILTERS": { - "TOGGLE": "Toggle filters visibility", + "TOGGLE": "切換過濾器可見度", "TITLE": "過濾器", "REMOVE": "移除過濾器", - "HIDE": "Hide Filters", + "HIDE": "隱藏過濾器", "SHOW": "顯示過濾器", "FILTER_CATEGORY_STATUS": "狀態", "FILTER_CATEGORY_TAGS": "標籤" @@ -813,7 +813,7 @@ } }, "TASK": { - "SECTION_NAME": "Task details", + "SECTION_NAME": "任務細節", "LINK_TASKBOARD": "任務板", "TITLE_LINK_TASKBOARD": "到任務板去", "PLACEHOLDER_SUBJECT": "鍵入新任務主旨", @@ -909,7 +909,7 @@ }, "ISSUES": { "LIST_SECTION_NAME": "問題 ", - "SECTION_NAME": "Issue details", + "SECTION_NAME": "問題細節", "ACTION_NEW_ISSUE": "+ 新問題 ", "ACTION_PROMOTE_TO_US": "提昇到使用者故事", "PLACEHOLDER_FILTER_NAME": "寫入過濾器名稱後按下enter ", From 36564b37e7585a563479f0eb579809f481130a3a Mon Sep 17 00:00:00 2001 From: Chris Wilson Date: Tue, 26 May 2015 13:06:15 +0100 Subject: [PATCH 006/366] Search results table now shows the Ref for each item --- AUTHORS.rst | 1 + CHANGELOG.md | 2 +- app/locales/locale-en.json | 3 ++- app/partials/includes/modules/search-result-table.jade | 6 ++++++ app/styles/modules/search/search-result-table.scss | 5 +++++ 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index c1066e92..8f30a52f 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -26,3 +26,4 @@ answer newbie questions, and generally made Taiga that much better: - Daniel Koch - Florian Bezagu - Ryan Swanstrom +- Chris Wilson diff --git a/CHANGELOG.md b/CHANGELOG.md index a5cb39ed..3264fc46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ## 1.8.0 ??? (unreleased) ### Features -- ... +- Show the reference of entities in search results (thanks to [@artlepool](https://github.com/artlepool)) ### Misc - Lots of small and not so small bugfixes. diff --git a/app/locales/locale-en.json b/app/locales/locale-en.json index 1b1f3779..0a91ff9c 100644 --- a/app/locales/locale-en.json +++ b/app/locales/locale-en.json @@ -125,7 +125,8 @@ "ASSIGNED_TO": "Assigned to", "POINTS": "Points", "BLOCKED_NOTE": "blocked note", - "IS_BLOCKED": "is blocked" + "IS_BLOCKED": "is blocked", + "REF": "Ref" }, "ROLES": { "ALL": "All" diff --git a/app/partials/includes/modules/search-result-table.jade b/app/partials/includes/modules/search-result-table.jade index aaebd465..be95d40c 100644 --- a/app/partials/includes/modules/search-result-table.jade +++ b/app/partials/includes/modules/search-result-table.jade @@ -4,11 +4,13 @@ script(type="text/ng-template", id="search-issues") div.search-result-table-container(ng-class="{'hidden': !issues.length}", tg-bind-scope) div.search-result-table-header div.row.title + div.ref(translate="COMMON.FIELDS.REF") div.user-stories(translate="SEARCH.FILTER_ISSUES") div.status(translate="COMMON.FIELDS.STATUS") div.assigned-to(translate="COMMON.FIELDS.ASSIGNED_TO") div.search-result-table-body div.row.table-main(ng-repeat="issue in issues track by issue.id") + div.ref(tg-bo-ref="issue.ref") div.user-stories div.user-story-name a(href="", tg-nav="project-issues-detail:project=project.slug,ref=issue.ref", @@ -26,11 +28,13 @@ script(type="text/ng-template", id="search-userstories") div.search-result-table-container(ng-class="{'hidden': !userstories.length}", tg-bind-scope) div.search-result-table-header div.row.title + div.ref(translate="COMMON.FIELDS.REF") div.user-stories(translate="SEARCH.FILTER_USER_STORIES") div.status(translate="COMMON.FIELDS.STATUS") div.points(translate="COMMON.FIELDS.POINTS") div.search-result-table-body div.row.table-main(ng-repeat="us in userstories track by us.id") + div.ref(tg-bo-ref="us.ref") div.user-stories div.user-story-name a(href="", tg-nav="project-userstories-detail:project=project.slug,ref=us.ref", @@ -47,11 +51,13 @@ script(type="text/ng-template", id="search-tasks") div.search-result-table-container(ng-class="{'hidden': !tasks.length}", tg-bind-scope) div.search-result-table-header div.row.title + div.ref(translate="COMMON.FIELDS.REF") div.user-stories(translate="SEARCH.FILTER_TASKS") div.status(translate="COMMON.FIELDS.STATUS") div.assigned-to(translate="COMMON.FIELDS.ASSIGNED_TO") div.search-result-table-body div.row.table-main(ng-repeat="task in tasks track by task.id") + div.ref(tg-bo-ref="task.ref") div.user-stories div.user-story-name a(href="", tg-nav="project-tasks-detail:project=project.slug,ref=task.ref", diff --git a/app/styles/modules/search/search-result-table.scss b/app/styles/modules/search/search-result-table.scss index ed37282e..ebbf8884 100644 --- a/app/styles/modules/search/search-result-table.scss +++ b/app/styles/modules/search/search-result-table.scss @@ -16,6 +16,11 @@ background: lighten($green-taiga, 60%); transition: background .2s ease-in; } + .ref { + flex-basis: 30px; + flex-grow: 1; + padding: 0 1rem; + } .user-stories { flex-basis: 300px; flex-grow: 10; From 63a0a78eac3760749b54a55c8abe9067186357e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Tue, 26 May 2015 13:18:58 +0200 Subject: [PATCH 007/366] Remove some unneeded api calls --- CHANGELOG.md | 1 + app/coffee/modules/admin/memberships.coffee | 17 ++--- .../modules/admin/project-profile.coffee | 17 ++--- .../modules/admin/project-values.coffee | 10 ++- app/coffee/modules/admin/roles.coffee | 51 ++++++--------- app/coffee/modules/admin/third-parties.coffee | 65 +++++++++---------- app/coffee/modules/base/contrib.coffee | 9 +-- app/coffee/modules/projects/main.coffee | 19 ++---- app/coffee/modules/taskboard/main.coffee | 2 +- .../user-settings/change-password.coffee | 12 ++-- app/coffee/modules/user-settings/main.coffee | 15 ++--- .../user-settings/notifications.coffee | 13 ++-- 12 files changed, 97 insertions(+), 134 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3264fc46..34c371b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Show the reference of entities in search results (thanks to [@artlepool](https://github.com/artlepool)) ### Misc +- Improve performance: remove some unnecessary calls to the api. - Lots of small and not so small bugfixes. diff --git a/app/coffee/modules/admin/memberships.coffee b/app/coffee/modules/admin/memberships.coffee index d5b6deec..1d5953d4 100644 --- a/app/coffee/modules/admin/memberships.coffee +++ b/app/coffee/modules/admin/memberships.coffee @@ -65,10 +65,11 @@ class MembershipsController extends mixOf(taiga.Controller, taiga.PageMixin, tai @analytics.trackEvent("membership", "create", "create memberships on admin", 1) loadProject: -> - return @rs.projects.get(@scope.projectId).then (project) => + return @rs.projects.getBySlug(@params.pslug).then (project) => if not project.i_am_owner @location.path(@navUrls.resolve("permission-denied")) + @scope.projectId = project.id @scope.project = project @scope.$emit('project:loaded', project) return project @@ -76,20 +77,20 @@ class MembershipsController extends mixOf(taiga.Controller, taiga.PageMixin, tai loadMembers: -> httpFilters = @.getUrlFilters() return @rs.memberships.list(@scope.projectId, httpFilters).then (data) => - @scope.memberships = _.filter(data.models, (membership) -> membership.user == null or membership.is_user_active) + @scope.memberships = _.filter(data.models, (membership) -> + membership.user == null or membership.is_user_active) @scope.page = data.current @scope.count = data.count @scope.paginatedBy = data.paginatedBy return data loadInitialData: -> - promise = @repo.resolve({pslug: @params.pslug}).then (data) => - @scope.projectId = data.project - return data + promise = @.loadProject() + promise.then => + @.loadUsersAndRoles() + @.loadMembers() - return promise.then(=> @.loadProject()) - .then(=> @.loadUsersAndRoles()) - .then(=> @.loadMembers()) + return promise getUrlFilters: -> filters = _.pick(@location.search(), "page") diff --git a/app/coffee/modules/admin/project-profile.coffee b/app/coffee/modules/admin/project-profile.coffee index 207734b4..8d2bd6dd 100644 --- a/app/coffee/modules/admin/project-profile.coffee +++ b/app/coffee/modules/admin/project-profile.coffee @@ -71,10 +71,11 @@ class ProjectProfileController extends mixOf(taiga.Controller, taiga.PageMixin) @appTitle.set(appTitle) loadProject: -> - return @rs.projects.get(@scope.projectId).then (project) => + return @rs.projects.getBySlug(@params.pslug).then (project) => if not project.i_am_owner @location.path(@navUrls.resolve("permission-denied")) + @scope.projectId = project.id @scope.project = project @scope.pointsList = _.sortBy(project.points, "order") @scope.usStatusList = _.sortBy(project.us_statuses, "order") @@ -90,18 +91,10 @@ class ProjectProfileController extends mixOf(taiga.Controller, taiga.PageMixin) return @rs.projects.tagsColors(@scope.projectId).then (tags_colors) => @scope.project.tags_colors = tags_colors - loadProjectProfile: -> - return @q.all([ - @.loadProject(), - @.loadTagsColors() - ]) - loadInitialData: -> - promise = @repo.resolve({pslug: @params.pslug}).then (data) => - @scope.projectId = data.project - return data - - return promise.then(=> @.loadProjectProfile()) + promise = @.loadProject() + promise.then => @.loadTagsColors() + return promise openDeleteLightbox: -> @rootscope.$broadcast("deletelightbox:new", @scope.project) diff --git a/app/coffee/modules/admin/project-values.coffee b/app/coffee/modules/admin/project-values.coffee index c86c762d..22f7b7ce 100644 --- a/app/coffee/modules/admin/project-values.coffee +++ b/app/coffee/modules/admin/project-values.coffee @@ -68,20 +68,18 @@ class ProjectValuesSectionController extends mixOf(taiga.Controller, taiga.PageM promise.then null, @.onInitialDataError.bind(@) loadProject: -> - return @rs.projects.get(@scope.projectId).then (project) => + return @rs.projects.getBySlug(@params.pslug).then (project) => if not project.i_am_owner @location.path(@navUrls.resolve("permission-denied")) + @scope.projectId = project.id @scope.project = project @scope.$emit('project:loaded', project) return project loadInitialData: -> - promise = @repo.resolve({pslug: @params.pslug}).then (data) => - @scope.projectId = data.project - return data - - return promise.then => @.loadProject() + promise = @.loadProject() + return promise module.controller("ProjectValuesSectionController", ProjectValuesSectionController) diff --git a/app/coffee/modules/admin/roles.coffee b/app/coffee/modules/admin/roles.coffee index 9e3b1669..f466c90d 100644 --- a/app/coffee/modules/admin/roles.coffee +++ b/app/coffee/modules/admin/roles.coffee @@ -65,10 +65,11 @@ class RolesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fil promise.then null, @.onInitialDataError.bind(@) loadProject: -> - return @rs.projects.get(@scope.projectId).then (project) => + return @rs.projects.getBySlug(@params.pslug).then (project) => if not project.i_am_owner @location.path(@navUrls.resolve("permission-denied")) + @scope.projectId = project.id @scope.project = project @scope.$emit('project:loaded', project) @@ -76,39 +77,29 @@ class RolesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fil return project - loadExternalUserRole: (roles) -> - roles = roles.map (role) -> - role.external_user = false - - return role - - public_permission = { - "name": @translate.instant("ADMIN.ROLES.EXTERNAL_USER"), - "permissions": @scope.project.public_permissions, - "external_user": true - } - - roles.push(public_permission) - - return roles - loadRoles: -> - return @rs.roles.list(@scope.projectId) - .then @loadExternalUserRole - .then (roles) => - @scope.roles = roles - @scope.role = @scope.roles[0] + return @rs.roles.list(@scope.projectId).then (roles) => + roles = roles.map (role) -> + role.external_user = false - return roles + return role + + public_permission = { + "name": @translate.instant("ADMIN.ROLES.EXTERNAL_USER"), + "permissions": @scope.project.public_permissions, + "external_user": true + } + + roles.push(public_permission) + + @scope.roles = roles + @scope.role = @scope.roles[0] + return roles loadInitialData: -> - promise = @repo.resolve({pslug: @params.pslug}).then (data) => - @scope.projectId = data.project - return data - - return promise.then(=> @.loadProject()) - .then(=> @.loadUsersAndRoles()) - .then(=> @.loadRoles()) + promise = @.loadProject() + promise.then(=> @.loadRoles()) + return promise setRole: (role) -> @scope.role = role diff --git a/app/coffee/modules/admin/third-parties.coffee b/app/coffee/modules/admin/third-parties.coffee index 06bb946e..b05318c3 100644 --- a/app/coffee/modules/admin/third-parties.coffee +++ b/app/coffee/modules/admin/third-parties.coffee @@ -28,6 +28,7 @@ timeout = @.taiga.timeout module = angular.module("taigaAdmin") + ############################################################################# ## Webhooks ############################################################################# @@ -65,24 +66,25 @@ class WebhooksController extends mixOf(taiga.Controller, taiga.PageMixin, taiga. @scope.webhooks = webhooks loadProject: -> - return @rs.projects.get(@scope.projectId).then (project) => + return @rs.projects.getBySlug(@params.pslug).then (project) => if not project.i_am_owner @location.path(@navUrls.resolve("permission-denied")) + @scope.projectId = project.id @scope.project = project @scope.$emit('project:loaded', project) return project loadInitialData: -> - promise = @repo.resolve({pslug: @params.pslug}).then (data) => - @scope.projectId = data.project - return data + promise = @.loadProject() + promise.then => + @.loadWebhooks() - return promise.then(=> @.loadProject()) - .then(=> @.loadWebhooks()) + return promise module.controller("WebhooksController", WebhooksController) + ############################################################################# ## Webhook Directive ############################################################################# @@ -213,7 +215,8 @@ WebhookDirective = ($rs, $repo, $confirm, $loading, $translate) -> return {link:link} -module.directive("tgWebhook", ["$tgResources", "$tgRepo", "$tgConfirm", "$tgLoading", "$translate", WebhookDirective]) +module.directive("tgWebhook", ["$tgResources", "$tgRepo", "$tgConfirm", "$tgLoading", "$translate", + WebhookDirective]) ############################################################################# @@ -312,19 +315,16 @@ class GithubController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi @scope.github = github loadProject: -> - return @rs.projects.get(@scope.projectId).then (project) => + return @rs.projects.getBySlug(@params.pslug).then (project) => + @scope.projectId = project.id @scope.project = project @scope.$emit('project:loaded', project) return project loadInitialData: -> - promise = @repo.resolve({pslug: @params.pslug}).then (data) => - @scope.projectId = data.project - return data - - return promise.then(=> @.loadProject()) - .then(=> @.loadModules()) - + promise = @.loadProject() + promise.then(=> @.loadModules()) + return promise module.controller("GithubController", GithubController) @@ -364,19 +364,16 @@ class GitlabController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi @scope.gitlab = gitlab loadProject: -> - return @rs.projects.get(@scope.projectId).then (project) => + return @rs.projects.getBySlug(@params.pslug).then (project) => + @scope.projectId = project.id @scope.project = project @scope.$emit('project:loaded', project) return project loadInitialData: -> - promise = @repo.resolve({pslug: @params.pslug}).then (data) => - @scope.projectId = data.project - return data - - return promise.then(=> @.loadProject()) - .then(=> @.loadModules()) - + promise = @.loadProject() + promise.then(=> @.loadModules()) + return promise module.controller("GitlabController", GitlabController) @@ -416,18 +413,16 @@ class BitbucketController extends mixOf(taiga.Controller, taiga.PageMixin, taiga @scope.bitbucket = bitbucket loadProject: -> - return @rs.projects.get(@scope.projectId).then (project) => + return @rs.projects.getBySlug(@params.pslug).then (project) => + @scope.projectId = project.id @scope.project = project @scope.$emit('project:loaded', project) return project loadInitialData: -> - promise = @repo.resolve({pslug: @params.pslug}).then (data) => - @scope.projectId = data.project - return data - - return promise.then(=> @.loadProject()) - .then(=> @.loadModules()) + promise = @.loadProject() + promise.then(=> @.loadModules()) + return promise module.controller("BitbucketController", BitbucketController) @@ -552,12 +547,12 @@ module.directive("tgBitbucketWebhooks", ["$tgRepo", "$tgConfirm", "$tgLoading", ############################################################################# ValidOriginIpsDirective = -> link = ($scope, $el, $attrs, $ngModel) -> - $ngModel.$parsers.push (value) -> - value = $.trim(value) - if value == "" - return [] + $ngModel.$parsers.push (value) -> + value = $.trim(value) + if value == "" + return [] - return value.split(",") + return value.split(",") return { link: link diff --git a/app/coffee/modules/base/contrib.coffee b/app/coffee/modules/base/contrib.coffee index 1c148bb9..a5e3d695 100644 --- a/app/coffee/modules/base/contrib.coffee +++ b/app/coffee/modules/base/contrib.coffee @@ -39,18 +39,15 @@ class ContribController extends taiga.Controller @confirm.notify("error") loadProject: -> - return @rs.projects.get(@scope.projectId).then (project) => + return @rs.projects.getBySlug(@params.pslug).then (project) => + @scope.projectId = project.id @scope.project = project @scope.$emit('project:loaded', project) @scope.$broadcast('project:loaded', project) return project loadInitialData: -> - promise = @repo.resolve({pslug: @params.pslug}).then (data) => - @scope.projectId = data.project - return data - - return promise.then(=> @.loadProject()) + return @.loadProject() module = angular.module("taigaBase") module.controller("ContribController", ContribController) diff --git a/app/coffee/modules/projects/main.coffee b/app/coffee/modules/projects/main.coffee index 8fda3a20..c041d588 100644 --- a/app/coffee/modules/projects/main.coffee +++ b/app/coffee/modules/projects/main.coffee @@ -98,22 +98,15 @@ class ProjectController extends taiga.Controller promise.then null, @.onInitialDataError.bind(@) loadInitialData: -> - # Resolve project slug - promise = @repo.resolve({pslug: @params.pslug}).then (data) => - @scope.projectId = data.project - return data - - return promise.then(=> @.loadPageData()) - .then(=> @scope.$emit("project:loaded", @scope.project)) - - loadPageData: -> - return @q.all([ - @.loadProjectStats(), - @.loadProject()]) + promise = @.loadProject() + promise.then(=> @.loadProjectStats()) + return promise loadProject: -> - return @rs.projects.get(@scope.projectId).then (project) => + return @rs.projects.getBySlug(@params.pslug).then (project) => + @scope.projectId = project.id @scope.project = project + @scope.$emit("project:loaded", @scope.project) return project loadProjectStats: -> diff --git a/app/coffee/modules/taskboard/main.coffee b/app/coffee/modules/taskboard/main.coffee index 75018b97..f2bd605f 100644 --- a/app/coffee/modules/taskboard/main.coffee +++ b/app/coffee/modules/taskboard/main.coffee @@ -136,7 +136,7 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin) @scope.stats.remainingPointsSum = remainingPointsSum @scope.stats.remainingTasks = remainingTasks if stats.totalPointsSum - @scope.stats.completedPercentage = Math.round(100 * stats.completedPointsSum / stats.totalPointsSum) + @scope.stats.completedPercentage = Math.round(100*stats.completedPointsSum/stats.totalPointsSum) else @scope.stats.completedPercentage = 0 diff --git a/app/coffee/modules/user-settings/change-password.coffee b/app/coffee/modules/user-settings/change-password.coffee index 387b92d6..9957000d 100644 --- a/app/coffee/modules/user-settings/change-password.coffee +++ b/app/coffee/modules/user-settings/change-password.coffee @@ -46,7 +46,8 @@ class UserChangePasswordController extends mixOf(taiga.Controller, taiga.PageMix "$translate" ] - constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @auth, @translate) -> + constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, + @auth, @translate) -> @scope.sectionName = @translate.instant("CHANGE_PASSWORD.SECTION_NAME") @scope.project = {} @scope.user = @auth.getUser() @@ -56,17 +57,14 @@ class UserChangePasswordController extends mixOf(taiga.Controller, taiga.PageMix promise.then null, @.onInitialDataError.bind(@) loadProject: -> - return @rs.projects.get(@scope.projectId).then (project) => + return @rs.projects.getBySlug(@params.pslug).then (project) => + @scope.projectId = project.id @scope.project = project @scope.$emit('project:loaded', project) return project loadInitialData: -> - promise = @repo.resolve({pslug: @params.pslug}).then (data) => - @scope.projectId = data.project - return data - - return promise.then(=> @.loadProject()) + return @.loadProject() module.controller("UserChangePasswordController", UserChangePasswordController) diff --git a/app/coffee/modules/user-settings/main.coffee b/app/coffee/modules/user-settings/main.coffee index 620236b3..39257d78 100644 --- a/app/coffee/modules/user-settings/main.coffee +++ b/app/coffee/modules/user-settings/main.coffee @@ -45,7 +45,8 @@ class UserSettingsController extends mixOf(taiga.Controller, taiga.PageMixin) "$translate" ] - constructor: (@scope, @rootscope, @config, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @auth, @translate) -> + constructor: (@scope, @rootscope, @config, @repo, @confirm, @rs, @params, @q, @location, @navUrls, + @auth, @translate) -> @scope.sectionName = "USER_SETTINGS.MENU.SECTION_TITLE" @scope.project = {} @@ -62,7 +63,8 @@ class UserSettingsController extends mixOf(taiga.Controller, taiga.PageMixin) promise.then null, @.onInitialDataError.bind(@) loadProject: -> - return @rs.projects.get(@scope.projectId).then (project) => + return @rs.projects.getBySlug(@params.pslug).then (project) => + @scope.projectId = project.id @scope.project = project @scope.$emit('project:loaded', project) return project @@ -73,12 +75,9 @@ class UserSettingsController extends mixOf(taiga.Controller, taiga.PageMixin) return locales loadInitialData: -> - promise = @repo.resolve({pslug: @params.pslug}).then (data) => - @scope.projectId = data.project - return data - - return @q.all([promise.then(=> @.loadProject()), - @.loadLocales()]) + promise = @.loadProject() + promise.then => @.loadLocales() + return promise openDeleteLightbox: -> @rootscope.$broadcast("deletelightbox:new", @scope.user) diff --git a/app/coffee/modules/user-settings/notifications.coffee b/app/coffee/modules/user-settings/notifications.coffee index ca299c71..5f77599f 100644 --- a/app/coffee/modules/user-settings/notifications.coffee +++ b/app/coffee/modules/user-settings/notifications.coffee @@ -54,7 +54,8 @@ class UserNotificationsController extends mixOf(taiga.Controller, taiga.PageMixi promise.then null, @.onInitialDataError.bind(@) loadProject: -> - return @rs.projects.get(@scope.projectId).then (project) => + return @rs.projects.getBySlug(@params.pslug).then (project) => + @scope.projectId = project.id @scope.project = project @scope.$emit('project:loaded', project) return project @@ -65,13 +66,9 @@ class UserNotificationsController extends mixOf(taiga.Controller, taiga.PageMixi return notifyPolicies loadInitialData: -> - promise = @repo.resolve({pslug: @params.pslug}).then (data) => - @scope.projectId = data.project - return data - - return promise.then(=> @.loadProject()) - .then(=> @.loadNotifyPolicies()) - + promise = @.loadProject() + promise.then(=> @.loadNotifyPolicies()) + return promise module.controller("UserNotificationsController", UserNotificationsController) From fa23f22475548798ec8ee74515f76bb7134a088e Mon Sep 17 00:00:00 2001 From: Juanfran Date: Thu, 28 May 2015 09:59:09 +0200 Subject: [PATCH 008/366] fix projects scroll --- app/partials/project/project-navigation-base.jade | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/partials/project/project-navigation-base.jade b/app/partials/project/project-navigation-base.jade index c360b971..39b125d9 100644 --- a/app/partials/project/project-navigation-base.jade +++ b/app/partials/project/project-navigation-base.jade @@ -21,5 +21,5 @@ div.projects-pagination(tg-projects-pagination) title="{{'PROJECT.NAVIGATION.TITLE_PRVIOUS_PROJECT' | translate}}") div.v-pagination-list ul.projects-list - a.v-pagination-next.icon.icon-arrow-bottom(href="", - title="{{'PROJECT.NAVIGATION.TITLE_NEXT_PROJECT' | translate}}") + + a.v-pagination-next.icon.icon-arrow-bottom(href="", title="{{'PROJECT.NAVIGATION.TITLE_NEXT_PROJECT' | translate}}") From 13e5f59ee7bd3192271899b66992bac6adbbc257 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Thu, 28 May 2015 12:41:58 +0200 Subject: [PATCH 009/366] Issue - 2742 - hide wiki edit button in public wiki --- app/partials/wiki/editable-wiki-content.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/partials/wiki/editable-wiki-content.jade b/app/partials/wiki/editable-wiki-content.jade index 9c9533ed..f1009bc3 100644 --- a/app/partials/wiki/editable-wiki-content.jade +++ b/app/partials/wiki/editable-wiki-content.jade @@ -1,6 +1,6 @@ .view-wiki-content section.wysiwyg(tg-bind-html='wiki.html') - span.edit.icon.icon-edit(title="{{'COMMON.EDIT' | translate}}") + span.edit.icon.icon-edit(title="{{'COMMON.EDIT' | translate}}", ng-if="wiki") .edit-wiki-content(style='display: none;') textarea(ng-attr-placeholder="{{'WIKI.PLACEHOLDER_PAGE' | translate}}", ng-model='wiki.content', tg-markitup='tg-markitup') a.help-markdown(href='https://taiga.io/support/taiga-markdown-syntax/', target='_blank', title="{{'COMMON.WYSIWYG.MARKDOWN_HELP' | translate}}") From 948c5f303683ca506ce3fce62b388962e16fb428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 24 Mar 2015 13:02:54 +0100 Subject: [PATCH 010/366] Home Loggedin basic structure --- app/coffee/app.coffee | 2 +- app/partials/home/home-logged-in.jade | 10 ++ app/styles/core/base.scss | 7 + app/styles/layout/profile.scss | 32 ++++ app/styles/modules/create-project.scss | 58 ------- app/styles/modules/home-project.scss | 34 ---- app/styles/modules/home-projects-list.scss | 189 --------------------- 7 files changed, 50 insertions(+), 282 deletions(-) create mode 100644 app/partials/home/home-logged-in.jade create mode 100644 app/styles/layout/profile.scss delete mode 100644 app/styles/modules/create-project.scss delete mode 100644 app/styles/modules/home-project.scss delete mode 100644 app/styles/modules/home-projects-list.scss diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 32807348..482fe1bf 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -39,7 +39,7 @@ taiga.sessionId = taiga.generateUniqueSessionIdentifier() configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEventsProvider, tgLoaderProvider, $compileProvider, $translateProvider) -> $routeProvider.when("/", - {templateUrl: "project/projects.html", resolve: {loader: tgLoaderProvider.add()}}) + {templateUrl: "home/home-logged-in.html"}) $routeProvider.when("/project/:pslug/", {templateUrl: "project/project.html"}) diff --git a/app/partials/home/home-logged-in.jade b/app/partials/home/home-logged-in.jade new file mode 100644 index 00000000..89b347d1 --- /dev/null +++ b/app/partials/home/home-logged-in.jade @@ -0,0 +1,10 @@ +include ../includes/components/beta +div.profile.centered + div.profile-bar User bar + div.main + div.hero + div.content-wrapper + div.content + div.sidebar Sidebar + + diff --git a/app/styles/core/base.scss b/app/styles/core/base.scss index a98ce08e..2be99cef 100644 --- a/app/styles/core/base.scss +++ b/app/styles/core/base.scss @@ -82,6 +82,13 @@ body { position: relative; } +.centered { + margin: 0 auto; + max-width: 1200px; + min-width: 960px; + width: 90%; +} + .wrapper { display: flex; min-height: 100vh; diff --git a/app/styles/layout/profile.scss b/app/styles/layout/profile.scss new file mode 100644 index 00000000..6bdccd15 --- /dev/null +++ b/app/styles/layout/profile.scss @@ -0,0 +1,32 @@ +.profile { + display: flex; + min-height: 100vh; + padding: 2rem 0; + .profile-bar { + background: #f0f; + flex: 1; + } + .main { + background: #00f; + display: flex; + flex: 4; + flex-direction: column; + padding: 0; + } + .content-wrapper { + display: flex; + min-height: 100vh; + } + .hero { + background: #0ff; + min-height: 10vh; + } + .content { + background: #5dcd42; + flex: 4; + } + .sidebar { + background: #121212; + flex: .5; + } +} diff --git a/app/styles/modules/create-project.scss b/app/styles/modules/create-project.scss deleted file mode 100644 index c3a8b5e6..00000000 --- a/app/styles/modules/create-project.scss +++ /dev/null @@ -1,58 +0,0 @@ -.create-project { - @extend %triangled-bg; - align-content: center; - align-items: center; - bottom: 0; - justify-content: center; - left: 0; - position: fixed; - right: 0; - top: 0; - z-index: 999; - fieldset { - margin-bottom: 1rem; - } - .create-project-container { - flex-basis: 400px; - flex-grow: 0; - } - h1 { - color: $white; - text-align: center; - } - .logo, - .tagline { - margin-bottom: 1rem; - text-align: center; - } - .tagline { - @extend %xlarge; - @extend %title; - color: $white; - line-height: 2rem; - text-transform: uppercase; - } - form { - margin-bottom: 2rem; - } - input { - background: $white; - @include placeholder { - color: $gray-light; - } - } - .button { - color: $white; - display: block; - margin-bottom: .5rem; - text-align: center; - &:hover { - background: $fresh-taiga; - } - } - a { - &:hover { - color: $white; - } - } -} diff --git a/app/styles/modules/home-project.scss b/app/styles/modules/home-project.scss deleted file mode 100644 index b093b945..00000000 --- a/app/styles/modules/home-project.scss +++ /dev/null @@ -1,34 +0,0 @@ -.summary-stats { - align-items: flex-start; - display: flex; - .info-num { - @extend %xlarge; - @extend %bold; - float: left; - margin-right: .3rem; - position: relative; - top: 5px; - } - .info-text { - @extend %small; - float: left; - line-height: .9rem; - } -} - -.project-data-container { - display: flex; - justify-content: space-between; - ul { - flex-grow: 0; - max-width: 33%; - } - li { - display: inline-block; - margin-right: .1rem; - width: 10%; - figure { - width: 100%; - } - } -} diff --git a/app/styles/modules/home-projects-list.scss b/app/styles/modules/home-projects-list.scss deleted file mode 100644 index 40962a31..00000000 --- a/app/styles/modules/home-projects-list.scss +++ /dev/null @@ -1,189 +0,0 @@ -.home-projects-list, -.home-project { - @extend %background-taiga; - align-content: center; - align-items: center; - background-color: $black; - background-position: center center; - background-size: cover; - display: flex; - height: 100%; - justify-content: center; - left: 0; - padding: 0; - position: fixed; - top: 0; - width: 100%; - .welcome-user { - display: flex; - position: absolute; - right: 1rem; - top: 1rem; - p { - color: $whitish; - margin-bottom: 0; - span:before { - content: ' '; - } - } - .logout { - @extend %small; - float: right; - &:hover { - color: $red-light; - } - } - .info { - padding-right: 1rem; - } - img { - width: 40px; - } - } -} -.home-projects-wrapper { - width: 1200px; -} - -.home-projects-list-inner { - display: flex; -} - -.recent-projects { - flex-grow: 8; - max-width: 800px; - ul { - display: flex; - flex-wrap: wrap; - margin: 0; - padding: 0; - } - a { - height: 100%; - left: 0; - padding: 1rem; - position: absolute; - top: 0; - width: 100%; - } - li { - background-color: rgba($white, .5); - color: $whitish; - flex-basis: 230px; - flex-grow: 1; - flex-shrink: 0; - height: 130px; - margin-bottom: 1rem; - margin-right: 1rem; - overflow: hidden; - position: relative; - transition: background-color .3s linear; - width: 23.5%; - &:hover { - background-color: rgba($fresh-taiga, .5); - cursor: pointer; - transition: background-color .3s linear; - p { - color: $gray-light; - transition: color .3s linear; - } - } - } - h2 { - color: $whitish; - line-height: 2rem; - } - p { - color: $grayer; - transition: color .3s linear; - } -} - -.project-content { - h2 { - margin-bottom: .5rem; - } - p { - @extend %small; - line-height: 1rem; - } -} - -.all-projects { - background-color: rgba(0, 0, 0, .5); - display: flex; - flex-direction: column; - flex-grow: 1; - margin-left: 1rem; - max-height: 422px; - padding: 1rem; - width: 285px; - h1 { - color: $whitish; - flex-shrink: 0; - text-align: center; - } - .v-pagination-list { - max-height: 221px; - } - ul { - left: 0; - margin-bottom: 0; - position: relative; - top: 0; - width: 100%; - } - li { - border-bottom: 2px solid $gray; - a { - @extend %large; - @extend %title; - color: $whitish; - display: block; - padding: 1rem; - text-transform: uppercase; - width: 100%; - &:hover { - background-color: $gray; - transition: background-color .3s linear; - } - } - .active { - background-color: $gray; - transition: background-color .3s linear; - } - } - .projects-pagination { - width: 100%; - } - .create-project-button-wrapper { - display: flex; - flex-shrink: 0; - .create-project-button { - flex-grow: 8; - margin-right: .2rem; - text-align: center; - } - .import-project-button { - flex-grow: 1; - padding-left: .5rem; - padding-right: .5rem; - text-align: center; - .icon { - color: $grayer; - margin: 0; - } - } - } - .button-green { - color: $whitish; - text-align: center; - width: 100%; - &:hover { - color: $whitish; - } - } - .v-pagination-next { - margin-bottom: 1rem; - } -} From b8af0bf52429bfde46ae870fe7b3d2f03fbbca32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 24 Mar 2015 14:08:18 +0100 Subject: [PATCH 011/366] Logged in profile sidebar --- app/images/quote.png | Bin 0 -> 402 bytes app/partials/home/home-logged-in.jade | 2 +- .../includes/modules/profile/profile-bar.jade | 38 ++++++++++++++++++ app/styles/components/buttons.scss | 2 + app/styles/layout/profile.scss | 4 +- 5 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 app/images/quote.png create mode 100644 app/partials/includes/modules/profile/profile-bar.jade diff --git a/app/images/quote.png b/app/images/quote.png new file mode 100644 index 0000000000000000000000000000000000000000..884fdd8ee3703223887f1c1e8a912c98f39d0263 GIT binary patch literal 402 zcmV;D0d4+?P)y?o8O2~R8 zWW5ryUinwDNkmLP$;TLsF-)5*%R)-&a9mYUwf(E_y_?qLoT)j_$5mBC#B``y>!~O} zO6hXWcSOYea9V4&Ydpp ze4y@B{o3R=xmy(`W$#wCyoWRVq$*6xdZ-E$vRR)r6;UaJDU0hQ9oDIDkE#{d8T07*qoM6N<$g7aIbi~s-t literal 0 HcmV?d00001 diff --git a/app/partials/home/home-logged-in.jade b/app/partials/home/home-logged-in.jade index 89b347d1..017a5dd6 100644 --- a/app/partials/home/home-logged-in.jade +++ b/app/partials/home/home-logged-in.jade @@ -1,6 +1,6 @@ include ../includes/components/beta div.profile.centered - div.profile-bar User bar + include ../includes/modules/profile/profile-bar div.main div.hero div.content-wrapper diff --git a/app/partials/includes/modules/profile/profile-bar.jade b/app/partials/includes/modules/profile/profile-bar.jade new file mode 100644 index 00000000..ae397850 --- /dev/null +++ b/app/partials/includes/modules/profile/profile-bar.jade @@ -0,0 +1,38 @@ +div.profile-bar + img.profile-img(src="http://i.imgur.com/gPiHzHN.jpg", alt="{{ user.nickname }}") + a.button-green + span Follow + h1 Silvia Rodríguez + // If user has no defined role in its profile use all its project defined roles followed by an & + h2 Backend Developer & Stackeholder + div.location + // span.icon.icon-location + span Madrid + // These values in profile stats are not defined yet in UX. Please ask + div.profile-stats + div.stat + span.stat-number 3 + span.stat-name projects + div.stat + span.stat-number 67 + span.stat-name us done + div.stat + span.stat-number 34 + span.stat-name contacts + // These badges are not defined yet in UX. Please ask + div.profile-badges + div.badge + div.badge + div.badge + div.badge + + div.profile-organizations + h3 Organizations + div.profile-organizations-wrapper + div.organization + div.organization + div.organization + div.organization + + div.profile-quote + span "Small minds talk about people, average minds discuss events, great minds discuss ideas" diff --git a/app/styles/components/buttons.scss b/app/styles/components/buttons.scss index e961ce8f..52fb1402 100755 --- a/app/styles/components/buttons.scss +++ b/app/styles/components/buttons.scss @@ -5,8 +5,10 @@ background: transparent; border: 0; color: $white; + cursor: pointer; display: inline-block; padding: .4rem 2.5rem; + text-align: center; text-transform: uppercase; transition: all .3s linear; vertical-align: middle; diff --git a/app/styles/layout/profile.scss b/app/styles/layout/profile.scss index 6bdccd15..dc0fdde3 100644 --- a/app/styles/layout/profile.scss +++ b/app/styles/layout/profile.scss @@ -3,8 +3,8 @@ min-height: 100vh; padding: 2rem 0; .profile-bar { - background: #f0f; - flex: 1; + margin-right: 1rem; + width: 200px; } .main { background: #00f; From 0f1da5101aaac5229e0dbf0feca415bc4ea5aa45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 24 Mar 2015 16:11:37 +0100 Subject: [PATCH 012/366] Navigation bar in projects list --- app/partials/home/home-logged-in.jade | 5 +- .../includes/modules/profile/profile-bar.jade | 2 +- .../modules/profile/profile-content-tabs.jade | 13 +++ app/styles/layout/profile.scss | 19 ++-- app/styles/modules/profile/profile-bar.scss | 92 +++++++++++++++++++ .../modules/profile/profile-content-tabs.scss | 25 +++++ 6 files changed, 144 insertions(+), 12 deletions(-) create mode 100644 app/partials/includes/modules/profile/profile-content-tabs.jade create mode 100644 app/styles/modules/profile/profile-bar.scss create mode 100644 app/styles/modules/profile/profile-content-tabs.scss diff --git a/app/partials/home/home-logged-in.jade b/app/partials/home/home-logged-in.jade index 017a5dd6..39d87b3a 100644 --- a/app/partials/home/home-logged-in.jade +++ b/app/partials/home/home-logged-in.jade @@ -3,8 +3,9 @@ div.profile.centered include ../includes/modules/profile/profile-bar div.main div.hero + include ../includes/modules/profile/profile-content-tabs div.content-wrapper - div.content - div.sidebar Sidebar + section.content + section.sidebar Sidebar diff --git a/app/partials/includes/modules/profile/profile-bar.jade b/app/partials/includes/modules/profile/profile-bar.jade index ae397850..740ab986 100644 --- a/app/partials/includes/modules/profile/profile-bar.jade +++ b/app/partials/includes/modules/profile/profile-bar.jade @@ -1,4 +1,4 @@ -div.profile-bar +section.profile-bar img.profile-img(src="http://i.imgur.com/gPiHzHN.jpg", alt="{{ user.nickname }}") a.button-green span Follow diff --git a/app/partials/includes/modules/profile/profile-content-tabs.jade b/app/partials/includes/modules/profile/profile-content-tabs.jade new file mode 100644 index 00000000..f28d9c3e --- /dev/null +++ b/app/partials/includes/modules/profile/profile-content-tabs.jade @@ -0,0 +1,13 @@ +nav.profile-content-tabs + a.tab.active(href="", title="Activity Tab") + span.icon.icon-plus + span activity + a.tab(href="", title="Projects Tab") + span.icon.icon-plus + span projects + a.tab(href="", title="Contacts Tab") + span.icon.icon-plus + span contacts + a.tab(href="", title="Favorites Tab") + span.icon.icon-plus + span favorites diff --git a/app/styles/layout/profile.scss b/app/styles/layout/profile.scss index dc0fdde3..f0beb45c 100644 --- a/app/styles/layout/profile.scss +++ b/app/styles/layout/profile.scss @@ -7,26 +7,27 @@ width: 200px; } .main { - background: #00f; display: flex; - flex: 4; + flex: 1; flex-direction: column; padding: 0; } + .hero { + background: lighten($whitish, 10%); + margin-bottom: .3rem; + min-height: 200px; + } .content-wrapper { display: flex; min-height: 100vh; } - .hero { - background: #0ff; - min-height: 10vh; - } .content { - background: #5dcd42; - flex: 4; + background: lighten($whitish, 10%); + flex: 1; + margin-right: 1rem; } .sidebar { background: #121212; - flex: .5; + width: 150px; } } diff --git a/app/styles/modules/profile/profile-bar.scss b/app/styles/modules/profile/profile-bar.scss new file mode 100644 index 00000000..83ddc082 --- /dev/null +++ b/app/styles/modules/profile/profile-bar.scss @@ -0,0 +1,92 @@ +.profile-bar { + .profile-img { + max-width: 100%; + } + .button-green { + display: block; + margin-bottom: 1rem; + } + h1, + h2 { + @extend %bold; + @extend %small; + line-height: 1; + margin-bottom: .5rem; + } + h1 { + @extend %xlarge; + text-transform: none; + } + h2 { + @extend %large; + color: $gray-light; + } + .location { + color: $gray-light; + margin-bottom: 1rem; + } + .profile-stats { + background: $whitish; + border-radius: 5px; + display: flex; + justify-content: space-between; + margin-bottom: 1rem; + padding: 1rem; + .stat { + padding: 0 .2rem; + text-align: center; + } + .stat-number { + @extend %xlarge; + @extend %title; + display: block; + margin-bottom: .3rem; + } + .stat-name { + @extend %title; + display: block; + } + } + .profile-badges { + display: flex; + justify-content: space-between; + margin-bottom: 1rem; + .badge { + background: $gray-light; + border-radius: 5px; + height: 45px; + margin-right: .2rem; + width: 45px; + } + } + + .profile-organizations { + border-bottom: 1px solid $whitish; + border-top: 1px solid $whitish; + margin-bottom: 1rem; + padding: 1rem 0; + h3 { + @extend %bold; + margin-bottom: .5rem; + } + .profile-organizations-wrapper { + display: flex; + justify-content: space-between; + } + .organization { + background: $gray-light; + border-radius: 5px; + height: 45px; + margin-right: .2rem; + width: 45px; + } + } + + .profile-quote { + @extend %title; + @extend %xlarge; + background: url('/images/quote.png') no-repeat top left; + line-height: 1.2; + padding: .5rem; + } +} diff --git a/app/styles/modules/profile/profile-content-tabs.scss b/app/styles/modules/profile/profile-content-tabs.scss new file mode 100644 index 00000000..b5905eb5 --- /dev/null +++ b/app/styles/modules/profile/profile-content-tabs.scss @@ -0,0 +1,25 @@ +.profile-content-tabs { + border-top: 1px solid $whitish; + .tab { + background: $white; + color: $gray-light; + display: inline-block; + padding: .3rem 1rem; + &:hover, + &.active { + color: $grayer; + .icon { + color: $green-taiga; + } + } + &.active { + border-left: 1px solid $whitish; + border-right: 1px solid $whitish; + position: relative; + top: 1px; + } + } + .icon { + margin-right: .3rem; + } +} From 2458dfbade13b4dbd130e4b4f44cba5aaf042b3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 25 Mar 2015 09:39:11 +0100 Subject: [PATCH 013/366] Profile sidebar and icons --- app/fonts/taiga.eot | Bin 10152 -> 10012 bytes app/fonts/taiga.svg | 9 +++--- app/fonts/taiga.ttf | Bin 9996 -> 9856 bytes app/fonts/taiga.woff | Bin 7312 -> 7212 bytes app/partials/home/home-logged-in.jade | 4 +-- .../modules/profile/profile-content-tabs.jade | 8 +++--- .../modules/profile/profile-sidebar.jade | 14 +++++++++ app/styles/core/typography.scss | 27 ++++++++++-------- app/styles/layout/profile.scss | 3 +- .../modules/profile/profile-content-tabs.scss | 4 +-- .../modules/profile/profile-sidebar.scss | 20 +++++++++++++ 11 files changed, 62 insertions(+), 27 deletions(-) create mode 100644 app/partials/includes/modules/profile/profile-sidebar.jade create mode 100644 app/styles/modules/profile/profile-sidebar.scss diff --git a/app/fonts/taiga.eot b/app/fonts/taiga.eot index 39b73c707a168cef53c4a491576f9b3d4f590ded..f8cdfc90c9bfc24340534c3967586c34d1d6c547 100644 GIT binary patch delta 1382 zcmZ{kTWl0%6vuyOW_I>6)7|ZMx4X1kwzu7umUg@MEl{Z8Ccy|vsn&AQ4b<|`LIGm{ zquW$scu~MHng9>HfFx>SQqw1*#6%N)kU(NInl$P&X?PG#&~(jAL5-1tFli5@;_#;`_rBl|$;Av#(k~ ziC4{(b|2(p;N;ZNgOwk5-+2qvyTI}3%E=jC=RJ_JO?;#>J@MEt^@Hq{0jsEtRKKE3 zk|cY`kt6QpS7};#F)%|9@La8iP-c`tLX0DX2!UO*v*L~vxPj_;aWt*~K8Mt1A7Gr* zwiw5Mf{Nq2t4_eK19a@FZ4oW3CP6C&+GwXp2it9heDR58Q?M z8(P3`O*Smfe-U{{o!{SdR-La;?9u19hG(U!GtRhNlWX{CriT*S7=dI*roBZ8ub1qQ zl5gv1ZxP3re5&;LlZV%PCDYy_U5}EALz0(E3x=^^EZ;90M$vTt@i(g4aGl}cP4=P7 z{k|y$!zhSpuHf1K;EkqP5W^7DPIVOxr^AF1smVtKp`k5WYAfYRxni_291yilmAsx~e|8d|sYf_^vsr)O)HehOvClX~T>x$?tT4gaZCl}R+V!T zhH5C0In$hr*iEbuTH&kWr5BQ=v;-vTE=5Z#0MrwwUO9gChV%{~4*&zJCl~7r?6#lw zW0YxzcW@P;j{&@V>ddkFcY8O^0+N_Fmg;94cpYy8G%gaKt}iYA(K_P;*>!+UH_j}- z$UouN09&kI1OZ}fUPwotjZXfBFoU3*w?Bw($yN(s2<$2t{12%_p8q2qrhUH44mLg9 zz&PT_A%{6sQN;mdf+$U-+TjCgU>LxBs|CV1p1?$4m4IMmf>MALNP(7tj0Le5K~h0a z|XnCt&6^-8I9#wYpni8JiRu6tosjfFj3S1+ro;TozMGh8Ef zSycmLfQV5^0NT2$hIeew+OAu1eBSlT+Wg=Ot^L+@MQTbo0MHr}8*5<>@W!g$+~M9( z&9TMjxt?1U)g~0KL`dNZcX%?<&65em6y|=`M?xk8QFT4n+snp;a6Q+thb-mOi@B_I z@~tk@G&?(`P`)(xWFgN<*E&PZ1n=lHzLX>)Da2%ABuHYCOk4_4(HJ(0WJp|QibBK~ zQ(szEP1o%vdfv8dvpYMKoyr#q`Ki8iI;r$3Q7f}aS|~)CN=#uABgQDiLNe1Nm8OcL zlhi3|GRfg6lXWemEiM9UuO;71!R-&D2RW26g#(z!2_QFQ*}Yv1?Q0 zR+0%*VODUUl|(O@_v_LFs1dSjo?GdsgsE_go8l(41BOHpZjF%L6bQOC9f`*yf*sdW znyweMw5IFtXX5d#e^pEAnpV^WFBLUSPiZB=S=%i#9idcF*R`}JcQ)+VvqlW-Rrm@$iayS5|0 - - - @@ -49,7 +46,6 @@ - @@ -59,4 +55,9 @@ + + + + + diff --git a/app/fonts/taiga.ttf b/app/fonts/taiga.ttf index 6b27df58eba18584528856374e9dffbee3d87eda..67c906cc0890a72714a082892c4744a70d03e7f1 100644 GIT binary patch delta 1317 zcmZ{kU1%It6vuyeW_I>tCcB$WHk&5dY_i$id}p&?vzw-AE3K)5wSlA>(pp{Gm^>s+ z`k^Eh-6ewLL2c=XLhD0ctPvE1NI(#cK8RJYSP%szs82qG&0_Ut{0PJ- z@W|=u;_T~h-?#%(8;DOPiWg?Lw~-;S4{%LP%|F@y+*HePL3DP$S;+j1-=K2 z$;t6jQR$H!AgY1beuS45MKhW+QkWY(pRWTq6=6-QP z^qSYiaQ!U`#ECOPh71jMK#4j)n0_f#3NVHsKme>#?bTp2!3W5;)quhcz=I13i?o+9 zE^TkyxAk%#Mca1T*m?p`v1+my!!#11i5$&DF=(NU7#+k(&^kyL-L%t54@r7Skw!+r zfr=9sZaiq{RN%#jpGpE$QB9Br1-q%7lF0q_TVe>^2d7?<>jXu zN7UuFs>-6IGRBy=DQ@DWo(}R1G7RySR5UDA_lxv^lx=Q_hJ|g5Y$CtA=g;GvA{7mb z*1v*`0g)BgS2S%!+uAfV&Cs2{{f$Z%+^gC6faP>J-_%7;({e)BcX0VXc)zaagr*7I z3UwHo)i4N##kP0{KU6h`^3C~7K4XMxs(nIk7UEp0Z|epBPr>t*6{;*huysW|wDL`T zT&i@H!kV^q*Y2~F*bu+cV$Fz8go4#hwI!MqmTpbtvIm5%B_kb?YQ%xRvF<0X;C7tWem_NtQiGn$n$PJw2v7K?C&7OrWdgvuhhAc_)q$rTXppU)uvyTB{ ioS=hls=)j#5pK;RQ0$T|u}ARssZZ}&;Ggdyvib)P%;^yT delta 1461 zcmZ8hO>7%g5dP-vdjI{Kjh)!ePdD~Djeio`$!^@HNh=_TLsF!M(m$Y9lO~|1Nz{Tu zIXFTE3B;kwRswP8p;Do}AS4S34hSJ}XgKA-EtMJt4jedCLcP}Lty@BQyE`*)_WS0| z&U>>PGoQ^iKmdZ+fPh!0r{`wYGTq&P{Rlw%>R0#A-vQVlKp!vGudKFO5CHo=0A=yg`xkF*310wWKhS@7X`w#P_Pi8;2LPO< zrG-XaEU^GUCV{OHu&(7QlO#RxZ?ko7`Ck1U_F~uCK4+LwpX+%c={xB*VDtAk{Es8x;YX>^Tj7bv&>8sH-n$oWYj!WZNk_=_Ch064 z^S-1L4HsJ&M+{kHF^4Lum_gd7HIM}Sn0%~a2*Bx93xsjJhMMmc0RKrp#qq-O5~m?z zo)-lQ+DV+jwN}gL97DZrwQhqZ0A6tbJ?KRr`f&gj29QA(McBwAhY|*H5JMP283hcZ zf+|Ka2FLq@Io^g4J=D|N*MGno$YhIlK35t%I5bi&3|Fe7V~!shf*6owMb&g87z#(C zu@18{p6KdMrqbS;aNXMw4DYc}s?Eu_IQ^jUpV|3?4Q(2Nn| zP2*;8C^#Lw9=scx568j}B6eglGS@sWhC>Y(JG4cwAchQFprg~2r94?V`v{FXhpCc? zC;snx-s5sq{Enh&uC6E%-PJT@Q&9}ZP?U(_7?Djy@tr;*LM0Ap+lr!h?a$iOXK}oD zV!t3Az%ieCgX*-FbB9})lMlVxdWp)n_sTuI#r%+T$bbz$&xJPq(r<^ zO2lQ8yZcx#ah>p0)o~qnl8tfYxK7R|9tGWkhp2Mq8ak~K_rgCl77Xebs7adA;gs+v|*Q>vzYosPwx-FMZbrm97a z%TiHQwWM0&lC^1}qajKbHBC*aTqbwh#A_U)wc$_J_o0{nH49IE*k@^brTt}I@R$Ce zol23Xh2~=Nq~z5ykK{6HD5C6*WHVy9RxEp`vTNjezh=j3XK@a1_|0o0_q1*ED4>Lq jr)@h7myR$LCS%>39 diff --git a/app/fonts/taiga.woff b/app/fonts/taiga.woff index 48f74d8f614b82b0404fedaba089931501387a19..5c346f3a23653fbe926725ed6cdcd7c5a9ea6200 100644 GIT binary patch delta 7121 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 E1>qQ`7ytkO diff --git a/app/partials/home/home-logged-in.jade b/app/partials/home/home-logged-in.jade index 39d87b3a..01d37d8f 100644 --- a/app/partials/home/home-logged-in.jade +++ b/app/partials/home/home-logged-in.jade @@ -6,6 +6,4 @@ div.profile.centered include ../includes/modules/profile/profile-content-tabs div.content-wrapper section.content - section.sidebar Sidebar - - + include ../includes/modules/profile/profile-sidebar diff --git a/app/partials/includes/modules/profile/profile-content-tabs.jade b/app/partials/includes/modules/profile/profile-content-tabs.jade index f28d9c3e..c5c956d5 100644 --- a/app/partials/includes/modules/profile/profile-content-tabs.jade +++ b/app/partials/includes/modules/profile/profile-content-tabs.jade @@ -1,13 +1,13 @@ nav.profile-content-tabs a.tab.active(href="", title="Activity Tab") - span.icon.icon-plus + span.icon.icon-timeline span activity a.tab(href="", title="Projects Tab") - span.icon.icon-plus + span.icon.icon-project span projects a.tab(href="", title="Contacts Tab") - span.icon.icon-plus + span.icon.icon-team span contacts a.tab(href="", title="Favorites Tab") - span.icon.icon-plus + span.icon.icon-star-fill span favorites diff --git a/app/partials/includes/modules/profile/profile-sidebar.jade b/app/partials/includes/modules/profile/profile-sidebar.jade new file mode 100644 index 00000000..a9edb1c0 --- /dev/null +++ b/app/partials/includes/modules/profile/profile-sidebar.jade @@ -0,0 +1,14 @@ +section.profile-sidebar + h4 Your profile + p Lots of people could see what do you do and what are you working on. Add tags so you will give an enhanced version of your information. + a.trans-button + span Add info + + h4 + span.icon.icon-help + span Hint + + p Did you know you can archive user Stories? + p Archived User Stories help you organize better your columns and remove old cards. + a(href="", title="visit our archived user stories support page") If you want to know how to use it visit our archived user stories support page + diff --git a/app/styles/core/typography.scss b/app/styles/core/typography.scss index 67e91155..20de8e7d 100755 --- a/app/styles/core/typography.scss +++ b/app/styles/core/typography.scss @@ -198,18 +198,12 @@ a:visited { .icon-move:before { content: 'w'; } -.icon-drag-h:before { - content: 'x'; -} .icon-drag-v:before { content: 'y'; } .icon-filter:before { content: 'z'; } -.icon-github:before { - content: 'A'; -} .icon-help:before { content: 'B'; } @@ -225,9 +219,6 @@ a:visited { .icon-floppy:before { content: 'F'; } -.icon-idea:before { - content: 'G'; -} .icon-warning-alt:before { content: 'H'; } @@ -255,9 +246,6 @@ a:visited { .icon-team:before { content: 'T'; } -.icon-spinner:before { - content: 'J'; -} .icon-vfold:before { content: 'N'; } @@ -285,3 +273,18 @@ a:visited { .icon-upload:before { content: 'Z'; } +.icon-github:before { + content: 'A'; +} +.icon-timeline:before { + content: 'x'; +} +.icon-project:before { + content: 'G'; +} +.icon-star-fill:before { + content: 'J'; +} +.icon-star:before { + content: '0'; +} diff --git a/app/styles/layout/profile.scss b/app/styles/layout/profile.scss index f0beb45c..76a860fc 100644 --- a/app/styles/layout/profile.scss +++ b/app/styles/layout/profile.scss @@ -26,8 +26,7 @@ flex: 1; margin-right: 1rem; } - .sidebar { - background: #121212; + .profile-sidebar { width: 150px; } } diff --git a/app/styles/modules/profile/profile-content-tabs.scss b/app/styles/modules/profile/profile-content-tabs.scss index b5905eb5..8c26c58e 100644 --- a/app/styles/modules/profile/profile-content-tabs.scss +++ b/app/styles/modules/profile/profile-content-tabs.scss @@ -4,7 +4,7 @@ background: $white; color: $gray-light; display: inline-block; - padding: .3rem 1rem; + padding: .4rem 1rem; &:hover, &.active { color: $grayer; @@ -20,6 +20,6 @@ } } .icon { - margin-right: .3rem; + margin-right: .4rem; } } diff --git a/app/styles/modules/profile/profile-sidebar.scss b/app/styles/modules/profile/profile-sidebar.scss new file mode 100644 index 00000000..299821cb --- /dev/null +++ b/app/styles/modules/profile/profile-sidebar.scss @@ -0,0 +1,20 @@ +.profile-sidebar { + h4 { + @extend %bold; + background: $whitish; + color: $gray; + margin-bottom: .5rem; + padding: .5rem; + .icon { + color: $gray-light; + margin-right: .3rem; + } + } + p { + @extend %small; + color: $gray-light; + } + .trans-button { + margin-bottom: 1rem; + } +} From e3030b694803e317e7e4c470350f1f1165a640e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 25 Mar 2015 15:05:06 +0100 Subject: [PATCH 014/366] Timeline --- app/partials/home/home-logged-in.jade | 3 +- .../modules/profile/profile-sidebar.jade | 2 +- .../modules/profile/profile-timeline.jade | 89 +++++++++++++++++++ app/styles/layout/profile.scss | 1 + .../modules/profile/profile-timeline.scss | 87 ++++++++++++++++++ 5 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 app/partials/includes/modules/profile/profile-timeline.jade create mode 100644 app/styles/modules/profile/profile-timeline.scss diff --git a/app/partials/home/home-logged-in.jade b/app/partials/home/home-logged-in.jade index 01d37d8f..81f8e879 100644 --- a/app/partials/home/home-logged-in.jade +++ b/app/partials/home/home-logged-in.jade @@ -5,5 +5,6 @@ div.profile.centered div.hero include ../includes/modules/profile/profile-content-tabs div.content-wrapper - section.content + div.content + include ../includes/modules/profile/profile-timeline include ../includes/modules/profile/profile-sidebar diff --git a/app/partials/includes/modules/profile/profile-sidebar.jade b/app/partials/includes/modules/profile/profile-sidebar.jade index a9edb1c0..dc351a25 100644 --- a/app/partials/includes/modules/profile/profile-sidebar.jade +++ b/app/partials/includes/modules/profile/profile-sidebar.jade @@ -1,4 +1,4 @@ -section.profile-sidebar +aside.profile-sidebar h4 Your profile p Lots of people could see what do you do and what are you working on. Add tags so you will give an enhanced version of your information. a.trans-button diff --git a/app/partials/includes/modules/profile/profile-timeline.jade b/app/partials/includes/modules/profile/profile-timeline.jade new file mode 100644 index 00000000..0fedd998 --- /dev/null +++ b/app/partials/includes/modules/profile/profile-timeline.jade @@ -0,0 +1,89 @@ +section.profile-timeline + - for (var x = 0; x < 3; x++) + // Simple message for favorites, updates, etc. + div.activity-simple + div.activity-info + picture + a(href="", title="{{ user.nickname }}") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/gerrenlamson/128.jpg", alt="{{ user.nickname }}") + p + a(href="", title="See {{ user.nickname }} profile") Jesús Espino + span has updated the status of the US + a(href="", title="See #{{ us.id }}{{ us.title }}") #23 Web comercial/Ayuda from "UX" to "UX Done" + + // Added comment in us, task or issue. + div.activity-comment + div.activity-info + picture + a(href="", title="{{ user.nickname }}") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/tonystubblebine/128.jpg", alt="{{ user.nickname }}") + p + a(href="", title="See {{ user.nickname }} profile") JuanFrancisco Alcántara + span has commented in the task + a(href="", title="See #{{ us.id }}{{ us.title }}") #15 Revisar el contraste de los grises + div.activity-comment-quote + p "He subido a GitLab unos wireframes. Echadle un vistazo por favor, a ver si falta algo o tenéis al" + + // Added attachment type image in us, task or issue. + div.activity-image + div.activity-info + picture + a(href="", title="{{ user.nickname }}") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/jina/128.jpg", alt="{{ user.nickname }}") + p + a(href="", title="See {{ user.nickname }} profile") Alejandro Alonso + span has uploaded an image in the US + a(href="", title="See #{{ us.id }}{{ us.title }}") US #23 Web comercial/Ayuda + div.activity-comment-attachment + p "Eh! Look at this amazing Taiga picture!" + img(src="https://ununsplash.imgix.net/photo-1423753623104-718aaace6772?q=75&fm=jpg&w=1080&fit=max&s=f655534aa0fe8bae35c687e80a2ed399", alt="{{ attachment.name }}") + + // Multiple update message, etc. + div.activity-notification + div.activity-info + picture + a(href="", title="{{ user.nickname }}") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/idiot/128.jpg", alt="{{ user.nickname }}") + p + a(href="", title="See {{ user.nickname }} profile") Jesús Espino + span closed + ul.activity-notification-list + li + a(href="", title="See #{{ us.id }}{{ us.title }}") US #23 Web comercial/Ayuda + li + a(href="", title="See #{{ us.id }}{{ us.title }}") #2156 Search Page UX is hardly understandable + li + a(href="", title="See #{{ us.id }}{{ us.title }}") #456 Search for users + li + a(href="", title="See #{{ us.id }}{{ us.title }}") #2140 Las notificaciones de cambios están fallando + + // Added attachment type image in us, task or issue. + div.activity-member + div.activity-info + picture + a(href="", title="{{ organization.nickname }}") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/tofslie/128.jpg", alt="{{ organization.nickname }}") + p + a(href="", title="See {{ organization.nickname }} profile") Mozilla + span has a new member + div.activity-member-view + picture + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/BillSKenney/128.jpg", alt="{{ organization.nickname }}") + div.activity-member-info + a(href="", title="See {{ user.nickname }} profile") + span Andrés González + p Back-end developer & Stake + + // Added comment in us, task or issue. + div.activity-project + div.activity-info + picture + a(href="", title="{{ organization.nickname }}") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/ekvium/128.jpg", alt="{{ organization.nickname }}") + p + a(href="", title="See {{ user.nickname }} profile") Redhat + span has a new project + a(href="", title="See {{ project.name }}") Nanatubos + div.activity-comment-quote + p We plan to build a hundred of so called "telehubs" so people from all over the world can immediately relocate their physical self at any other telehub in microseconds. + diff --git a/app/styles/layout/profile.scss b/app/styles/layout/profile.scss index 76a860fc..90d3d1b7 100644 --- a/app/styles/layout/profile.scss +++ b/app/styles/layout/profile.scss @@ -25,6 +25,7 @@ background: lighten($whitish, 10%); flex: 1; margin-right: 1rem; + max-width: 786px; } .profile-sidebar { width: 150px; diff --git a/app/styles/modules/profile/profile-timeline.scss b/app/styles/modules/profile/profile-timeline.scss new file mode 100644 index 00000000..124979dd --- /dev/null +++ b/app/styles/modules/profile/profile-timeline.scss @@ -0,0 +1,87 @@ + +.profile-timeline { + border-top: 1px solid $whitish; + %profile-activity { + border-bottom: 1px solid $whitish; + padding: .8rem 1rem; + .activity-info { + align-items: center; + display: flex; + } + picture { + margin-right: .5rem; + vertical-align: center; + width: 35px; + img { + width: 100%; + } + } + p { + margin: 0; + a { + color: $green-taiga; + } + } + } + .activity-simple, + .activity-comment, + .activity-image, + .activity-notification, + .activity-member, + .activity-project { + @extend %profile-activity; + } + .activity-comment, + .activity-project, + .activity-image { + .activity-comment-quote, + .activity-comment-attachment { + @extend %small; + border-left: 2px solid $whitish; + color: $gray-light; + margin-bottom: .5rem; + margin-left: calc(35px + .5rem); + margin-top: .5rem; + padding: .2rem 1rem; + img { + max-height: 640px; + max-width: 100%; + } + } + } + .activity-notification { + .activity-notification-list { + border-left: 2px solid $whitish; + margin-bottom: .5rem; + margin-left: calc(30px + .5rem); + margin-top: .5rem; + padding: .2rem 1rem; + li { + margin-bottom: .5rem; + } + } + } + .activity-member { + .activity-member-view { + display: flex; + margin-bottom: .5rem; + margin-left: calc(30px + .5rem); + margin-top: .5rem; + picture { + margin-right: 1rem; + max-width: 64px; + min-width: 32px; + width: 100%; + } + .activity-member-info { + flex: 1; + } + a { + @extend %bold; + } + p { + color: $gray-light; + } + } + } +} From 3bf00e2701deccf543bfdfaecc35033d3d98d5be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 26 Mar 2015 08:16:39 +0100 Subject: [PATCH 015/366] Style fixes in timeline --- app/partials/home/home-logged-in.jade | 3 ++- .../includes/modules/profile/profile-sidebar.jade | 2 +- .../modules/profile/profile-timeline.jade | 6 ++++++ app/styles/core/typography.scss | 5 +++-- app/styles/dependencies/helpers.scss | 6 +++--- app/styles/modules/profile/profile-timeline.scss | 15 ++++++++++++--- 6 files changed, 27 insertions(+), 10 deletions(-) diff --git a/app/partials/home/home-logged-in.jade b/app/partials/home/home-logged-in.jade index 81f8e879..1104a300 100644 --- a/app/partials/home/home-logged-in.jade +++ b/app/partials/home/home-logged-in.jade @@ -6,5 +6,6 @@ div.profile.centered include ../includes/modules/profile/profile-content-tabs div.content-wrapper div.content - include ../includes/modules/profile/profile-timeline + //include ../includes/modules/profile/profile-timeline + include ../includes/modules/profile/profile-projects include ../includes/modules/profile/profile-sidebar diff --git a/app/partials/includes/modules/profile/profile-sidebar.jade b/app/partials/includes/modules/profile/profile-sidebar.jade index dc351a25..d21cc246 100644 --- a/app/partials/includes/modules/profile/profile-sidebar.jade +++ b/app/partials/includes/modules/profile/profile-sidebar.jade @@ -10,5 +10,5 @@ aside.profile-sidebar p Did you know you can archive user Stories? p Archived User Stories help you organize better your columns and remove old cards. - a(href="", title="visit our archived user stories support page") If you want to know how to use it visit our archived user stories support page + a(href="", title="visit our archived user stories support page") If you want to know how to use it visit our archived user stories support page diff --git a/app/partials/includes/modules/profile/profile-timeline.jade b/app/partials/includes/modules/profile/profile-timeline.jade index 0fedd998..72ecb54b 100644 --- a/app/partials/includes/modules/profile/profile-timeline.jade +++ b/app/partials/includes/modules/profile/profile-timeline.jade @@ -2,6 +2,7 @@ section.profile-timeline - for (var x = 0; x < 3; x++) // Simple message for favorites, updates, etc. div.activity-simple + span.activity-date Yesterday 12.30h div.activity-info picture a(href="", title="{{ user.nickname }}") @@ -13,6 +14,7 @@ section.profile-timeline // Added comment in us, task or issue. div.activity-comment + span.activity-date 3 days ago div.activity-info picture a(href="", title="{{ user.nickname }}") @@ -26,6 +28,7 @@ section.profile-timeline // Added attachment type image in us, task or issue. div.activity-image + span.activity-date 5 days ago div.activity-info picture a(href="", title="{{ user.nickname }}") @@ -40,6 +43,7 @@ section.profile-timeline // Multiple update message, etc. div.activity-notification + span.activity-date 6 days ago div.activity-info picture a(href="", title="{{ user.nickname }}") @@ -59,6 +63,7 @@ section.profile-timeline // Added attachment type image in us, task or issue. div.activity-member + span.activity-date a week ago div.activity-info picture a(href="", title="{{ organization.nickname }}") @@ -76,6 +81,7 @@ section.profile-timeline // Added comment in us, task or issue. div.activity-project + span.activity-date a week ago div.activity-info picture a(href="", title="{{ organization.nickname }}") diff --git a/app/styles/core/typography.scss b/app/styles/core/typography.scss index 20de8e7d..65a21a8e 100755 --- a/app/styles/core/typography.scss +++ b/app/styles/core/typography.scss @@ -29,12 +29,11 @@ h6 { h1 { @extend %xxlarge; @extend %title; - line-height: 3.4rem; + line-height: 1.5; margin-bottom: 1rem; text-transform: uppercase; span { @extend %xxlarge; - line-height: 3.4rem; margin-right: .5rem; overflow: hidden; text-overflow: ellipsis; @@ -65,10 +64,12 @@ h1 { h2 { @extend %xlarge; @extend %title; + line-height: 1.2; margin-bottom: 1rem; } p { + line-height: 1.5; margin: 0 0 20px; img { margin: 0; diff --git a/app/styles/dependencies/helpers.scss b/app/styles/dependencies/helpers.scss index e1d484f5..d78b24d9 100644 --- a/app/styles/dependencies/helpers.scss +++ b/app/styles/dependencies/helpers.scss @@ -8,9 +8,9 @@ %xxlarge {font-size: 3rem;} // __Font Types__ // -%title {font-family: 'OpenSans-CondLight', Arial, Helvetica, sans-serif;} -%text {font-family: 'opensans-regular', Arial, Helvetica, sans-serif; line-height: 1.3rem;} -%bold {font-family: 'opensans-semibold', Arial, Helvetica, sans-serif;} +%title {font-family: 'OpenSans-CondLight', Arial, Helvetica, sans-serif; line-height: 1.5;} +%text {font-family: 'opensans-regular', Arial, Helvetica, sans-serif; line-height: 1.5;} +%bold {font-family: 'opensans-semibold', Arial, Helvetica, sans-serif; line-height: 1.5;} %taiga {font-family: 'taiga';} %mono {font-family: 'courier new', 'monospace';} diff --git a/app/styles/modules/profile/profile-timeline.scss b/app/styles/modules/profile/profile-timeline.scss index 124979dd..63c10102 100644 --- a/app/styles/modules/profile/profile-timeline.scss +++ b/app/styles/modules/profile/profile-timeline.scss @@ -4,9 +4,18 @@ %profile-activity { border-bottom: 1px solid $whitish; padding: .8rem 1rem; + position: relative; .activity-info { align-items: center; display: flex; + margin-right: 100px; + } + .activity-date { + @extend %xsmall; + color: $gray-light; + position: absolute; + right: .5rem; + top: .5rem; } picture { margin-right: .5rem; @@ -17,7 +26,7 @@ } } p { - margin: 0; + margin-bottom: .5rem; a { color: $green-taiga; } @@ -53,7 +62,7 @@ .activity-notification-list { border-left: 2px solid $whitish; margin-bottom: .5rem; - margin-left: calc(30px + .5rem); + margin-left: calc(35px + .5rem); margin-top: .5rem; padding: .2rem 1rem; li { @@ -65,7 +74,7 @@ .activity-member-view { display: flex; margin-bottom: .5rem; - margin-left: calc(30px + .5rem); + margin-left: calc(35px + .5rem); margin-top: .5rem; picture { margin-right: 1rem; From d506c0909908225da506463df68971f2fa90002d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 26 Mar 2015 11:54:05 +0100 Subject: [PATCH 016/366] Profile projects --- .../modules/profile/profile-projects.jade | 131 ++++++++++++++++++ app/styles/dependencies/colors.scss | 2 + .../modules/profile/profile-projects.scss | 78 +++++++++++ 3 files changed, 211 insertions(+) create mode 100644 app/partials/includes/modules/profile/profile-projects.jade create mode 100644 app/styles/modules/profile/profile-projects.scss diff --git a/app/partials/includes/modules/profile/profile-projects.jade b/app/partials/includes/modules/profile/profile-projects.jade new file mode 100644 index 00000000..496ed6f6 --- /dev/null +++ b/app/partials/includes/modules/profile/profile-projects.jade @@ -0,0 +1,131 @@ +section.profile-projects + - for (var x = 0; x < 3; x++) + div.profile-project-single + div.profile-projects-left + + div.profile-project-title + h1 + a(href="", title="View {{ project.title }}") My Side Project + p We plan to build a hundred of so called "telehubs" so people from all over the world can immediately relocate their physical self at any other telehub in microseconds. The technology and science behind this project is sound and we are now ready to build the first telehub and then massproduce them. + + div.profile-project-tags.tags-container + // Tag border style has to be set by JS + span.tag(style='border-left: 5px solid #73d216;') + span.tag-name python + span.tag(style='border-left: 5px solid #cc0000;') + span.tag-name groovy + span.tag(style='border-left: 5px solid #25f45c;') + span.tag-name opensource + + div.profile-projects-right + + div.profile-project-stats + div.stat-comments(title="2 comments") + span.icon.icon-comment + span.stat-num 2 + div.stat-favorite.active(title="2 favorites") + span.icon.icon-star-fill + span.stat-num 4 + div.stat-viewer(title="2 followers") + span.icon.icon-open-eye + span.stat-num 4 + + div.profile-project-members + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/mantia/128.jpg") + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/annapickard/128.jpg") + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/nexy_dre/128.jpg", alt="{{ user.nickname }}") + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/pixeliris/128.jpg", alt="{{ user.nickname }}") + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/cbillins/128.jpg", alt="{{ user.nickname }}") + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/allisongrayce/128.jpg", alt="{{ user.nickname }}") + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/uxceo/128.jpg", alt="{{ user.nickname }}") + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/syswarren/128.jpg", alt="{{ user.nickname }}") + + div.profile-project-single + div.profile-projects-left + div.profile-project-title + h1 + a(href="", title="View {{ project.title }}") Teletransportation hubs + + div.profile-project-tags.tags-container + // Tag border style has to be set by JS + span.tag(style='border-left: 5px solid #43d56f;') + span.tag-name javascript + span.tag(style='border-left: 5px solid #0000cc;') + span.tag-name css + span.tag(style='border-left: 5px solid #cc43fd;') + span.tag-name design + + div.profile-projects-right + div.profile-project-stats + div.stat-comments(title="2 comments") + span.icon.icon-comment + span.stat-num 2 + div.stat-favorite(title="2 favorites") + span.icon.icon-star-fill + span.stat-num 4 + div.stat-viewer(title="2 followers") + span.icon.icon-open-eye + span.stat-num 4 + div.profile-project-members + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/mantia/128.jpg") + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/annapickard/128.jpg") + + div.profile-project-single + div.profile-projects-left + + div.profile-project-title + h1 + a(href="", title="View {{ project.title }}") Taiga + p Una plataforma social para crear comunidad entorno a la tienda. Esta comunidad está pensada para quedar a hacer deporte, compartir iniciativas. + + div.profile-project-tags.tags-container + // Tag border style has to be set by JS + span.tag(style='border-left: 5px solid #11cd00;') + span.tag-name PHP + span.tag(style='border-left: 5px solid #ff00dd;') + span.tag-name marketing + span.tag(style='border-left: 5px solid #cdcd54;') + span.tag-name wordpress + div.profile-projects-right + + div.profile-project-stats + div.stat-comments + span.icon.icon-comment + span.stat-num 2 + div.stat-favorite.active + span.icon.icon-star-fill + span.stat-num 4 + div.stat-viewer + span.icon.icon-open-eye + span.stat-num 4 + + div.profile-project-members + - for (var x = 0; x < 2; x++) + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/mantia/128.jpg") + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/annapickard/128.jpg") + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/nexy_dre/128.jpg", alt="{{ user.nickname }}") + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/pixeliris/128.jpg", alt="{{ user.nickname }}") + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/cbillins/128.jpg", alt="{{ user.nickname }}") + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/allisongrayce/128.jpg", alt="{{ user.nickname }}") + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/uxceo/128.jpg", alt="{{ user.nickname }}") + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/syswarren/128.jpg", alt="{{ user.nickname }}") + + diff --git a/app/styles/dependencies/colors.scss b/app/styles/dependencies/colors.scss index 3275443a..3b73fad4 100755 --- a/app/styles/dependencies/colors.scss +++ b/app/styles/dependencies/colors.scss @@ -20,6 +20,7 @@ $postit: #fff8e4; $postit-hover: #f1e8cd; $postit-dark-hover: #cfc29b; + //Loading bar $green-japanese-laurel: #237400; $green-olive: #618000; @@ -28,3 +29,4 @@ $purple-eggplant: #810061; $yellow-pear: #bbe831; $menu: #232323; +$star-fill: #edd400; diff --git a/app/styles/modules/profile/profile-projects.scss b/app/styles/modules/profile/profile-projects.scss new file mode 100644 index 00000000..b2fae443 --- /dev/null +++ b/app/styles/modules/profile/profile-projects.scss @@ -0,0 +1,78 @@ +.profile-projects { + border-top: 1px solid $whitish; + .profile-project-single { + border-bottom: 1px solid $whitish; + display: flex; + justify-content: center; + min-height: 10rem; + padding: .8rem 1rem; + position: relative; + } + .profile-projects-left, + .profile-projects-right { + display: flex; + flex-direction: column; + } +} + +.profile-projects-left { + align-content: space-between; + flex: 4; + h1 { + @extend %bold; + @extend %xlarge; + margin-bottom: 0; + text-transform: none; + } + p { + color: $gray-light; + max-width: 70%; + } + .profile-project-tags { + align-content: flex-end; + display: flex; + flex: 3; + flex-wrap: wrap; + justify-content: flex-start; + } + .tag { + align-self: flex-end; + margin-bottom: .3rem; + } +} + +.profile-projects-right { + justify-content: space-between; + .profile-project-stats { + align-self: flex-end; + display: flex; + div { + color: $gray-light; + margin-right: .5rem; + .icon { + margin-right: .2rem; + vertical-align: center; + } + } + .active { + .icon { + color: $star-fill; + } + } + } + .profile-project-members { + align-self: flex-end; + display: flex; + flex-grow: 0; + flex-wrap: wrap; + margin-top: 1rem; + max-width: 160px; + a { + display: block; + } + img { + margin-right: .3rem; + width: 34px; + } + } +} From 78ebae1c86f6f4eb83aaa14ced1651f7d366e89b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 26 Mar 2015 12:41:02 +0100 Subject: [PATCH 017/366] Profile contacts --- app/partials/home/home-logged-in.jade | 3 +- .../modules/profile/profile-contacts.jade | 40 ++++++++++++ .../modules/profile/profile-contacts.scss | 62 +++++++++++++++++++ 3 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 app/partials/includes/modules/profile/profile-contacts.jade create mode 100644 app/styles/modules/profile/profile-contacts.scss diff --git a/app/partials/home/home-logged-in.jade b/app/partials/home/home-logged-in.jade index 1104a300..fa21139b 100644 --- a/app/partials/home/home-logged-in.jade +++ b/app/partials/home/home-logged-in.jade @@ -7,5 +7,6 @@ div.profile.centered div.content-wrapper div.content //include ../includes/modules/profile/profile-timeline - include ../includes/modules/profile/profile-projects + //include ../includes/modules/profile/profile-projects + include ../includes/modules/profile/profile-contacts include ../includes/modules/profile/profile-sidebar diff --git a/app/partials/includes/modules/profile/profile-contacts.jade b/app/partials/includes/modules/profile/profile-contacts.jade new file mode 100644 index 00000000..3939e29c --- /dev/null +++ b/app/partials/includes/modules/profile/profile-contacts.jade @@ -0,0 +1,40 @@ +section.profile-contacts + - for (var x = 0; x < 3; x++) + div.profile-contact-single + picture + a(href="", title="See {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/koridhandy/128.jpg", alt="{{ user.nickname }}") + div.profile-contact-data + h1 + a(href="", title="See {{ user.nickname}} profile") Sebastián Molina + span.your-contact Your contact + p Chief GIF Officer at myamazingpage.com. Tweet me at @sebas + div.extra-info + span.position Back-end developer & Stakeholder + span.location Madrid + div.profile-project-stats + div.stat-comments(title="2 comments") + span.icon.icon-project + span.stat-num 2 + div.stat-viewer(title="2 followers") + span.icon.icon-open-eye + span.stat-num 4 + + div.profile-contact-single + picture + a(href="", title="See {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/marktimemedia/128.jpg", alt="{{ user.nickname }}") + div.profile-contact-data + h1 + a(href="", title="See {{ user.nickname}} profile") Ane Moreno + p Have some experience working with sock monkeys in Orlando, FL. Crossed the country licensing bongos in Miami, FL. What gets me going now is deploying catfish in Bethesda, MD. Prior to my current job I was selling dandruff in Minneapolis, MN. In 2009 I was marketing Elvis Presley in Hanford, CA. + div.extra-info + span.position Monkey Socker & Deployer + span.location Miami + div.profile-project-stats + div.stat-comments(title="2 comments") + span.icon.icon-project + span.stat-num 2 + div.stat-viewer(title="2 followers") + span.icon.icon-open-eye + span.stat-num 4 diff --git a/app/styles/modules/profile/profile-contacts.scss b/app/styles/modules/profile/profile-contacts.scss new file mode 100644 index 00000000..4f4d66de --- /dev/null +++ b/app/styles/modules/profile/profile-contacts.scss @@ -0,0 +1,62 @@ +.profile-contacts { + border-top: 1px solid $whitish; +} + +.profile-contact-single { + border-bottom: 1px solid $whitish; + display: flex; + flex-wrap: wrap; + padding: .8rem 1rem; + picture { + flex-grow: 0; + margin-right: 1rem; + max-width: 100px; + img { + width: 100%; + } + } + .profile-contact-data { + flex: 1; + h1 { + @extend %text; + @extend %xlarge; + align-items: center; + display: flex; + margin-bottom: 0; + text-transform: none; + span { + @extend %text; + @extend %small; + background: $whitish; + color: $gray; + margin-left: 1rem; + padding: .1rem .3rem; + } + } + p { + color: $gray; + margin-bottom: .3rem; + } + .extra-info { + color: $gray-light; + } + .position { + @extend %bold; + margin-right: .3rem; + } + } + .profile-project-stats { + display: flex; + flex-grow: 0; + margin-left: auto; + width: 100px; + div { + color: $gray-light; + margin-right: .5rem; + .icon { + margin-right: .2rem; + vertical-align: center; + } + } + } +} From d576016e9c47146974323ae67150c0c43b7746c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 26 Mar 2015 12:53:03 +0100 Subject: [PATCH 018/366] Contacts filters --- .../modules/profile/profile-contacts.jade | 5 +++++ .../modules/profile/profile-content-tabs.jade | 4 ++-- .../modules/profile/profile-contacts.scss | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/app/partials/includes/modules/profile/profile-contacts.jade b/app/partials/includes/modules/profile/profile-contacts.jade index 3939e29c..111fc8f3 100644 --- a/app/partials/includes/modules/profile/profile-contacts.jade +++ b/app/partials/includes/modules/profile/profile-contacts.jade @@ -1,4 +1,9 @@ section.profile-contacts + nav.profile-contacts-filters + a.active(href="", title="No Filter") all + a(href="", title="Only show your team") team + a(href="", title="Only show people you follow") following + a(href="", title="Only show people follow you") followers - for (var x = 0; x < 3; x++) div.profile-contact-single picture diff --git a/app/partials/includes/modules/profile/profile-content-tabs.jade b/app/partials/includes/modules/profile/profile-content-tabs.jade index c5c956d5..968440f0 100644 --- a/app/partials/includes/modules/profile/profile-content-tabs.jade +++ b/app/partials/includes/modules/profile/profile-content-tabs.jade @@ -1,11 +1,11 @@ nav.profile-content-tabs - a.tab.active(href="", title="Activity Tab") + a.tab(href="", title="Activity Tab") span.icon.icon-timeline span activity a.tab(href="", title="Projects Tab") span.icon.icon-project span projects - a.tab(href="", title="Contacts Tab") + a.tab.active(href="", title="Contacts Tab") span.icon.icon-team span contacts a.tab(href="", title="Favorites Tab") diff --git a/app/styles/modules/profile/profile-contacts.scss b/app/styles/modules/profile/profile-contacts.scss index 4f4d66de..2638f6fc 100644 --- a/app/styles/modules/profile/profile-contacts.scss +++ b/app/styles/modules/profile/profile-contacts.scss @@ -1,5 +1,23 @@ .profile-contacts { border-top: 1px solid $whitish; + display: flex; + flex-direction: column; + .profile-contacts-filters { + align-self: center; + display: flex; + a { + border-bottom: 2px solid $white; + color: $gray-light; + display: inline-block; + padding: 1rem 1.5rem; + transition: all .2s linear; + &:hover, + &.active { + border-bottom: 2px solid $gray-light; + color: $green-taiga; + } + } + } } .profile-contact-single { From f1108202034e44d22728563861221a8c0e447c9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 26 Mar 2015 13:31:31 +0100 Subject: [PATCH 019/366] Favorites tab --- app/partials/home/home-logged-in.jade | 5 +++-- .../modules/profile/profile-contacts.jade | 2 +- .../modules/profile/profile-favorites.jade | 6 ++++++ .../modules/profile/profile-favorites.scss | 21 +++++++++++++++++++ 4 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 app/partials/includes/modules/profile/profile-favorites.jade create mode 100644 app/styles/modules/profile/profile-favorites.scss diff --git a/app/partials/home/home-logged-in.jade b/app/partials/home/home-logged-in.jade index fa21139b..78f71368 100644 --- a/app/partials/home/home-logged-in.jade +++ b/app/partials/home/home-logged-in.jade @@ -6,7 +6,8 @@ div.profile.centered include ../includes/modules/profile/profile-content-tabs div.content-wrapper div.content - //include ../includes/modules/profile/profile-timeline + include ../includes/modules/profile/profile-timeline //include ../includes/modules/profile/profile-projects - include ../includes/modules/profile/profile-contacts + //include ../includes/modules/profile/profile-contacts + //include ../includes/modules/profile/profile-favorites include ../includes/modules/profile/profile-sidebar diff --git a/app/partials/includes/modules/profile/profile-contacts.jade b/app/partials/includes/modules/profile/profile-contacts.jade index 111fc8f3..e4f79ef7 100644 --- a/app/partials/includes/modules/profile/profile-contacts.jade +++ b/app/partials/includes/modules/profile/profile-contacts.jade @@ -1,5 +1,5 @@ section.profile-contacts - nav.profile-contacts-filters + nav.profile-contact-filters a.active(href="", title="No Filter") all a(href="", title="Only show your team") team a(href="", title="Only show people you follow") following diff --git a/app/partials/includes/modules/profile/profile-favorites.jade b/app/partials/includes/modules/profile/profile-favorites.jade new file mode 100644 index 00000000..f0a723c7 --- /dev/null +++ b/app/partials/includes/modules/profile/profile-favorites.jade @@ -0,0 +1,6 @@ +section.profile-favorites + nav.profile-favorites-filters + a.active(href="", title="No Filter") all + a(href="", title="Only show your team") projects + a(href="", title="Only show people you follow") US + a(href="", title="Only show people follow you") tasks diff --git a/app/styles/modules/profile/profile-favorites.scss b/app/styles/modules/profile/profile-favorites.scss new file mode 100644 index 00000000..23cb806b --- /dev/null +++ b/app/styles/modules/profile/profile-favorites.scss @@ -0,0 +1,21 @@ +.profile-favorites { + border-top: 1px solid $whitish; + display: flex; + flex-direction: column; + .profile-favorites-filters { + align-self: flex-start; + display: flex; + a { + border-bottom: 2px solid $white; + color: $gray-light; + display: inline-block; + padding: 1rem 1.5rem; + transition: all .2s linear; + &:hover, + &.active { + border-bottom: 2px solid $gray-light; + color: $green-taiga; + } + } + } +} From 0bb4664921b1d51db5ef8f6bf6278d973d93615a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 26 Mar 2015 14:38:25 +0100 Subject: [PATCH 020/366] Minor style fixes --- app/partials/home/home-logged-in.jade | 4 +-- .../modules/profile/profile-contacts.jade | 2 +- .../modules/profile/profile-contacts.scss | 30 +++++++++---------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/app/partials/home/home-logged-in.jade b/app/partials/home/home-logged-in.jade index 78f71368..f700c8d0 100644 --- a/app/partials/home/home-logged-in.jade +++ b/app/partials/home/home-logged-in.jade @@ -6,8 +6,8 @@ div.profile.centered include ../includes/modules/profile/profile-content-tabs div.content-wrapper div.content - include ../includes/modules/profile/profile-timeline + //clude ../includes/modules/profile/profile-timeline //include ../includes/modules/profile/profile-projects - //include ../includes/modules/profile/profile-contacts + include ../includes/modules/profile/profile-contacts //include ../includes/modules/profile/profile-favorites include ../includes/modules/profile/profile-sidebar diff --git a/app/partials/includes/modules/profile/profile-contacts.jade b/app/partials/includes/modules/profile/profile-contacts.jade index e4f79ef7..ed0bce30 100644 --- a/app/partials/includes/modules/profile/profile-contacts.jade +++ b/app/partials/includes/modules/profile/profile-contacts.jade @@ -18,7 +18,7 @@ section.profile-contacts span.position Back-end developer & Stakeholder span.location Madrid div.profile-project-stats - div.stat-comments(title="2 comments") + div.stat-projects(title="2 projects") span.icon.icon-project span.stat-num 2 div.stat-viewer(title="2 followers") diff --git a/app/styles/modules/profile/profile-contacts.scss b/app/styles/modules/profile/profile-contacts.scss index 2638f6fc..568ae0a0 100644 --- a/app/styles/modules/profile/profile-contacts.scss +++ b/app/styles/modules/profile/profile-contacts.scss @@ -2,24 +2,24 @@ border-top: 1px solid $whitish; display: flex; flex-direction: column; - .profile-contacts-filters { - align-self: center; - display: flex; - a { - border-bottom: 2px solid $white; - color: $gray-light; - display: inline-block; - padding: 1rem 1.5rem; - transition: all .2s linear; - &:hover, - &.active { - border-bottom: 2px solid $gray-light; - color: $green-taiga; - } +} + +.profile-contact-filters { + align-self: center; + display: flex; + a { + border-bottom: 2px solid $white; + color: $gray-light; + display: inline-block; + padding: 1rem 1.5rem; + transition: all .2s linear; + &:hover, + &.active { + border-bottom: 2px solid $gray-light; + color: $green-taiga; } } } - .profile-contact-single { border-bottom: 1px solid $whitish; display: flex; From 3d8a8b29cdf696df04fc7d3fef1bdc421e637ada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 27 Mar 2015 08:10:44 +0100 Subject: [PATCH 021/366] Profile Tabs Directive --- app/coffee/app.coffee | 1 + app/coffee/modules/profile.coffee | 22 +++++++++++++++ app/coffee/modules/profile/main.coffee | 37 ++++++++++++++++++++++++++ app/partials/home/home-logged-in.jade | 2 +- gulpfile.js | 1 + 5 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 app/coffee/modules/profile.coffee create mode 100644 app/coffee/modules/profile/main.coffee diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 482fe1bf..26fd1c73 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -333,6 +333,7 @@ modules = [ "taigaPlugins", "taigaIntegrations", "taigaComponents", + "taigaProfile", # template cache "templates", diff --git a/app/coffee/modules/profile.coffee b/app/coffee/modules/profile.coffee new file mode 100644 index 00000000..a32f6001 --- /dev/null +++ b/app/coffee/modules/profile.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/team.coffee +### + +module = angular.module("taigaProfile", []) diff --git a/app/coffee/modules/profile/main.coffee b/app/coffee/modules/profile/main.coffee new file mode 100644 index 00000000..d78c5c45 --- /dev/null +++ b/app/coffee/modules/profile/main.coffee @@ -0,0 +1,37 @@ +### +# 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/team/main.coffee +### + +taiga = @.taiga + +module = angular.module("taigaProfile") + +############################################################################# +## Profile Tabs +############################################################################# + +ProfileTabsDirective = () -> + link = ($scope, $el, $attrs) -> + console.log $el + + return {link:link} + + +module.directive("tgProfileTabs", ProfileTabsDirective) diff --git a/app/partials/home/home-logged-in.jade b/app/partials/home/home-logged-in.jade index f700c8d0..42b5ded5 100644 --- a/app/partials/home/home-logged-in.jade +++ b/app/partials/home/home-logged-in.jade @@ -1,7 +1,7 @@ include ../includes/components/beta div.profile.centered include ../includes/modules/profile/profile-bar - div.main + div.main(tg-profile-tabs) div.hero include ../includes/modules/profile/profile-content-tabs div.content-wrapper diff --git a/gulpfile.js b/gulpfile.js index b436c775..f7e83029 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -103,6 +103,7 @@ paths.coffee_order = [ paths.app + "coffee/modules/admin/*.coffee", paths.app + "coffee/modules/projects/*.coffee", paths.app + "coffee/modules/locales/*.coffee", + paths.app + "coffee/modules/profile/*.js", paths.app + "coffee/modules/base/*.coffee", paths.app + "coffee/modules/resources/*.coffee", paths.app + "coffee/modules/user-settings/*.coffee", From d3a38b9fb5199a85de870a2f8aa2e9817efdb799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 27 Mar 2015 08:32:25 +0100 Subject: [PATCH 022/366] CrossBrowser fixes --- app/partials/home/home-logged-in.jade | 2 +- .../modules/profile/profile-contacts.jade | 4 ++-- .../modules/profile/profile-timeline.jade | 14 +++++++------- app/styles/modules/profile/profile-bar.scss | 4 ++-- app/styles/modules/profile/profile-contacts.scss | 2 +- app/styles/modules/profile/profile-timeline.scss | 16 +++++++++------- 6 files changed, 22 insertions(+), 20 deletions(-) diff --git a/app/partials/home/home-logged-in.jade b/app/partials/home/home-logged-in.jade index 42b5ded5..20760956 100644 --- a/app/partials/home/home-logged-in.jade +++ b/app/partials/home/home-logged-in.jade @@ -6,7 +6,7 @@ div.profile.centered include ../includes/modules/profile/profile-content-tabs div.content-wrapper div.content - //clude ../includes/modules/profile/profile-timeline + //include ../includes/modules/profile/profile-timeline //include ../includes/modules/profile/profile-projects include ../includes/modules/profile/profile-contacts //include ../includes/modules/profile/profile-favorites diff --git a/app/partials/includes/modules/profile/profile-contacts.jade b/app/partials/includes/modules/profile/profile-contacts.jade index ed0bce30..e3ed4c1f 100644 --- a/app/partials/includes/modules/profile/profile-contacts.jade +++ b/app/partials/includes/modules/profile/profile-contacts.jade @@ -6,7 +6,7 @@ section.profile-contacts a(href="", title="Only show people follow you") followers - for (var x = 0; x < 3; x++) div.profile-contact-single - picture + div.profile-contact-picture a(href="", title="See {{ user.nickname }} profile") img(src="https://s3.amazonaws.com/uifaces/faces/twitter/koridhandy/128.jpg", alt="{{ user.nickname }}") div.profile-contact-data @@ -26,7 +26,7 @@ section.profile-contacts span.stat-num 4 div.profile-contact-single - picture + div.profile-contact-picture a(href="", title="See {{ user.nickname }} profile") img(src="https://s3.amazonaws.com/uifaces/faces/twitter/marktimemedia/128.jpg", alt="{{ user.nickname }}") div.profile-contact-data diff --git a/app/partials/includes/modules/profile/profile-timeline.jade b/app/partials/includes/modules/profile/profile-timeline.jade index 72ecb54b..f4daf1fb 100644 --- a/app/partials/includes/modules/profile/profile-timeline.jade +++ b/app/partials/includes/modules/profile/profile-timeline.jade @@ -4,7 +4,7 @@ section.profile-timeline div.activity-simple span.activity-date Yesterday 12.30h div.activity-info - picture + div.profile-contact-picture a(href="", title="{{ user.nickname }}") img(src="https://s3.amazonaws.com/uifaces/faces/twitter/gerrenlamson/128.jpg", alt="{{ user.nickname }}") p @@ -16,7 +16,7 @@ section.profile-timeline div.activity-comment span.activity-date 3 days ago div.activity-info - picture + div.profile-contact-picture a(href="", title="{{ user.nickname }}") img(src="https://s3.amazonaws.com/uifaces/faces/twitter/tonystubblebine/128.jpg", alt="{{ user.nickname }}") p @@ -30,7 +30,7 @@ section.profile-timeline div.activity-image span.activity-date 5 days ago div.activity-info - picture + div.profile-contact-picture a(href="", title="{{ user.nickname }}") img(src="https://s3.amazonaws.com/uifaces/faces/twitter/jina/128.jpg", alt="{{ user.nickname }}") p @@ -45,7 +45,7 @@ section.profile-timeline div.activity-notification span.activity-date 6 days ago div.activity-info - picture + div.profile-contact-picture a(href="", title="{{ user.nickname }}") img(src="https://s3.amazonaws.com/uifaces/faces/twitter/idiot/128.jpg", alt="{{ user.nickname }}") p @@ -65,14 +65,14 @@ section.profile-timeline div.activity-member span.activity-date a week ago div.activity-info - picture + div.profile-contact-picture a(href="", title="{{ organization.nickname }}") img(src="https://s3.amazonaws.com/uifaces/faces/twitter/tofslie/128.jpg", alt="{{ organization.nickname }}") p a(href="", title="See {{ organization.nickname }} profile") Mozilla span has a new member div.activity-member-view - picture + div.profile-member-picture img(src="https://s3.amazonaws.com/uifaces/faces/twitter/BillSKenney/128.jpg", alt="{{ organization.nickname }}") div.activity-member-info a(href="", title="See {{ user.nickname }} profile") @@ -83,7 +83,7 @@ section.profile-timeline div.activity-project span.activity-date a week ago div.activity-info - picture + div.profile-contact-picture a(href="", title="{{ organization.nickname }}") img(src="https://s3.amazonaws.com/uifaces/faces/twitter/ekvium/128.jpg", alt="{{ organization.nickname }}") p diff --git a/app/styles/modules/profile/profile-bar.scss b/app/styles/modules/profile/profile-bar.scss index 83ddc082..882781ac 100644 --- a/app/styles/modules/profile/profile-bar.scss +++ b/app/styles/modules/profile/profile-bar.scss @@ -31,7 +31,7 @@ display: flex; justify-content: space-between; margin-bottom: 1rem; - padding: 1rem; + padding: .5rem 1rem; .stat { padding: 0 .2rem; text-align: center; @@ -40,7 +40,7 @@ @extend %xlarge; @extend %title; display: block; - margin-bottom: .3rem; + line-height: 1; } .stat-name { @extend %title; diff --git a/app/styles/modules/profile/profile-contacts.scss b/app/styles/modules/profile/profile-contacts.scss index 568ae0a0..bd76eee4 100644 --- a/app/styles/modules/profile/profile-contacts.scss +++ b/app/styles/modules/profile/profile-contacts.scss @@ -25,7 +25,7 @@ display: flex; flex-wrap: wrap; padding: .8rem 1rem; - picture { + .profile-contact-picture { flex-grow: 0; margin-right: 1rem; max-width: 100px; diff --git a/app/styles/modules/profile/profile-timeline.scss b/app/styles/modules/profile/profile-timeline.scss index 63c10102..e660bb4a 100644 --- a/app/styles/modules/profile/profile-timeline.scss +++ b/app/styles/modules/profile/profile-timeline.scss @@ -17,7 +17,7 @@ right: .5rem; top: .5rem; } - picture { + .profile-contact-picture { margin-right: .5rem; vertical-align: center; width: 35px; @@ -76,12 +76,6 @@ margin-bottom: .5rem; margin-left: calc(35px + .5rem); margin-top: .5rem; - picture { - margin-right: 1rem; - max-width: 64px; - min-width: 32px; - width: 100%; - } .activity-member-info { flex: 1; } @@ -92,5 +86,13 @@ color: $gray-light; } } + .profile-member-picture { + img { + margin-right: 1rem; + max-width: 64px; + min-width: 32px; + width: 100%; + } + } } } From a38f8fcb869141ba6bd88886cb70978092fc6095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 27 Mar 2015 12:52:05 +0100 Subject: [PATCH 023/366] Tabs behavior --- app/coffee/modules/profile/main.coffee | 11 ++++++++++- app/partials/home/home-logged-in.jade | 6 +++--- .../includes/modules/profile/profile-contacts.jade | 2 +- .../modules/profile/profile-content-tabs.jade | 8 ++++---- .../includes/modules/profile/profile-favorites.jade | 2 +- .../includes/modules/profile/profile-projects.jade | 2 +- .../includes/modules/profile/profile-timeline.jade | 2 +- app/styles/layout/profile.scss | 10 ++++++++++ app/styles/modules/profile/profile-content-tabs.scss | 12 ++++++++---- app/styles/modules/profile/profile-timeline.scss | 1 - 10 files changed, 39 insertions(+), 17 deletions(-) diff --git a/app/coffee/modules/profile/main.coffee b/app/coffee/modules/profile/main.coffee index d78c5c45..87b70efa 100644 --- a/app/coffee/modules/profile/main.coffee +++ b/app/coffee/modules/profile/main.coffee @@ -28,8 +28,17 @@ module = angular.module("taigaProfile") ############################################################################# ProfileTabsDirective = () -> + link = ($scope, $el, $attrs) -> - console.log $el + + $scope.tabSelected = 'profile-timeline' + + $scope.toggleTab = -> + target = angular.element(event.currentTarget) + tab = target.data("selected") + target.siblings().removeClass('active') + target.addClass('active') + $scope.tabSelected = tab return {link:link} diff --git a/app/partials/home/home-logged-in.jade b/app/partials/home/home-logged-in.jade index 20760956..90037a43 100644 --- a/app/partials/home/home-logged-in.jade +++ b/app/partials/home/home-logged-in.jade @@ -6,8 +6,8 @@ div.profile.centered include ../includes/modules/profile/profile-content-tabs div.content-wrapper div.content - //include ../includes/modules/profile/profile-timeline - //include ../includes/modules/profile/profile-projects + include ../includes/modules/profile/profile-timeline + include ../includes/modules/profile/profile-projects include ../includes/modules/profile/profile-contacts - //include ../includes/modules/profile/profile-favorites + include ../includes/modules/profile/profile-favorites include ../includes/modules/profile/profile-sidebar diff --git a/app/partials/includes/modules/profile/profile-contacts.jade b/app/partials/includes/modules/profile/profile-contacts.jade index e3ed4c1f..3c647ed9 100644 --- a/app/partials/includes/modules/profile/profile-contacts.jade +++ b/app/partials/includes/modules/profile/profile-contacts.jade @@ -1,4 +1,4 @@ -section.profile-contacts +section.profile-contacts(ng-show="tabSelected == 'profile-contacts'") nav.profile-contact-filters a.active(href="", title="No Filter") all a(href="", title="Only show your team") team diff --git a/app/partials/includes/modules/profile/profile-content-tabs.jade b/app/partials/includes/modules/profile/profile-content-tabs.jade index 968440f0..f55c1c4d 100644 --- a/app/partials/includes/modules/profile/profile-content-tabs.jade +++ b/app/partials/includes/modules/profile/profile-content-tabs.jade @@ -1,13 +1,13 @@ nav.profile-content-tabs - a.tab(href="", title="Activity Tab") + a.tab.active(href="", title="Activity Tab", ng-click="toggleTab()", data-selected="profile-timeline") span.icon.icon-timeline span activity - a.tab(href="", title="Projects Tab") + a.tab(href="", title="Projects Tab", ng-click="toggleTab()", data-selected="profile-projects") span.icon.icon-project span projects - a.tab.active(href="", title="Contacts Tab") + a.tab(href="", title="Contacts Tab", ng-click="toggleTab()", data-selected="profile-contacts") span.icon.icon-team span contacts - a.tab(href="", title="Favorites Tab") + a.tab(href="", title="Favorites Tab", ng-click="toggleTab()", data-selected="profile-favorites") span.icon.icon-star-fill span favorites diff --git a/app/partials/includes/modules/profile/profile-favorites.jade b/app/partials/includes/modules/profile/profile-favorites.jade index f0a723c7..4ecae7bd 100644 --- a/app/partials/includes/modules/profile/profile-favorites.jade +++ b/app/partials/includes/modules/profile/profile-favorites.jade @@ -1,4 +1,4 @@ -section.profile-favorites +section.profile-favorites(ng-show="tabSelected == 'profile-favorites'") nav.profile-favorites-filters a.active(href="", title="No Filter") all a(href="", title="Only show your team") projects diff --git a/app/partials/includes/modules/profile/profile-projects.jade b/app/partials/includes/modules/profile/profile-projects.jade index 496ed6f6..dac39552 100644 --- a/app/partials/includes/modules/profile/profile-projects.jade +++ b/app/partials/includes/modules/profile/profile-projects.jade @@ -1,4 +1,4 @@ -section.profile-projects +section.profile-projects(ng-show="tabSelected == 'profile-projects'") - for (var x = 0; x < 3; x++) div.profile-project-single div.profile-projects-left diff --git a/app/partials/includes/modules/profile/profile-timeline.jade b/app/partials/includes/modules/profile/profile-timeline.jade index f4daf1fb..509569c1 100644 --- a/app/partials/includes/modules/profile/profile-timeline.jade +++ b/app/partials/includes/modules/profile/profile-timeline.jade @@ -1,4 +1,4 @@ -section.profile-timeline +section.profile-timeline(ng-show="tabSelected == 'profile-timeline'") - for (var x = 0; x < 3; x++) // Simple message for favorites, updates, etc. div.activity-simple diff --git a/app/styles/layout/profile.scss b/app/styles/layout/profile.scss index 90d3d1b7..59e64a5b 100644 --- a/app/styles/layout/profile.scss +++ b/app/styles/layout/profile.scss @@ -26,6 +26,16 @@ flex: 1; margin-right: 1rem; max-width: 786px; + section { + opacity: 1; + padding-top: 0; + position: relative; + transition: all .3s cubic-bezier(.09, .43, .35, .95); + &.ng-hide { + opacity: 0; + padding-top: .5vh; + } + } } .profile-sidebar { width: 150px; diff --git a/app/styles/modules/profile/profile-content-tabs.scss b/app/styles/modules/profile/profile-content-tabs.scss index 8c26c58e..67cc0e79 100644 --- a/app/styles/modules/profile/profile-content-tabs.scss +++ b/app/styles/modules/profile/profile-content-tabs.scss @@ -1,22 +1,26 @@ .profile-content-tabs { border-top: 1px solid $whitish; + z-index: 9; .tab { - background: $white; color: $gray-light; display: inline-block; padding: .4rem 1rem; &:hover, &.active { color: $grayer; - .icon { - color: $green-taiga; - } + transition: color .2s linear; } &.active { + background: $white; border-left: 1px solid $whitish; border-right: 1px solid $whitish; position: relative; top: 1px; + transition: color .2s linear; + .icon { + color: $green-taiga; + transition: color .2s linear; + } } } .icon { diff --git a/app/styles/modules/profile/profile-timeline.scss b/app/styles/modules/profile/profile-timeline.scss index e660bb4a..ebd93633 100644 --- a/app/styles/modules/profile/profile-timeline.scss +++ b/app/styles/modules/profile/profile-timeline.scss @@ -1,4 +1,3 @@ - .profile-timeline { border-top: 1px solid $whitish; %profile-activity { From ddbaffe1c2fd9a7fb195b6ac06d825efbb84c9f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 27 Mar 2015 13:50:13 +0100 Subject: [PATCH 024/366] Abuse Flag --- .../includes/modules/profile/profile-bar.jade | 17 +++++++++----- app/styles/modules/profile/profile-bar.scss | 22 ++++++++++++++++++- app/svg/flag.svg | 1 + 3 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 app/svg/flag.svg diff --git a/app/partials/includes/modules/profile/profile-bar.jade b/app/partials/includes/modules/profile/profile-bar.jade index 740ab986..3e130b95 100644 --- a/app/partials/includes/modules/profile/profile-bar.jade +++ b/app/partials/includes/modules/profile/profile-bar.jade @@ -2,12 +2,17 @@ section.profile-bar img.profile-img(src="http://i.imgur.com/gPiHzHN.jpg", alt="{{ user.nickname }}") a.button-green span Follow - h1 Silvia Rodríguez - // If user has no defined role in its profile use all its project defined roles followed by an & - h2 Backend Developer & Stackeholder - div.location - // span.icon.icon-location - span Madrid + div.profile-data + h1 Silvia Rodríguez + // If user has no defined role in its profile use all its project defined roles followed by an & + h2 Backend Developer & Stackeholder + div.location + // span.icon.icon-location + span Madrid + // Remove Abuse Flag when a user is seeing itself + a.flag(href="", title="Report Abuse") + svg(xmlns:svg="http://www.w3.org/2000/svg", xmlns="http://www.w3.org/2000/svg", xml:space="preserve", enable-background="new 0 0 100 100" viewBox="0 0 14.7 20.3", y="0", x="0", version="1.1") + path(d="M2.9 4C2 4.5 1 5.4 0.8 5.6L0.7 5.5 0 5.9 8.3 20.3 9.1 19.9 5.4 13.6c0.2-0.2 1.2-1.1 2.1-1.6 1.8-1.1 2.7-0.8 4.5-1.9 1.8-1 2.6-2.3 2.6-2.3L10.2 0C10.2 0 9.3 1.2 7.5 2.2 5.7 3.3 4.7 2.9 2.9 4Z") // These values in profile stats are not defined yet in UX. Please ask div.profile-stats div.stat diff --git a/app/styles/modules/profile/profile-bar.scss b/app/styles/modules/profile/profile-bar.scss index 882781ac..93376634 100644 --- a/app/styles/modules/profile/profile-bar.scss +++ b/app/styles/modules/profile/profile-bar.scss @@ -6,20 +6,40 @@ display: block; margin-bottom: 1rem; } + .profile-data { + position: relative; + } + .flag { + position: absolute; + right: 0; + top: 0; + width: 12px; + path { + fill: $gray-light; + transition: all .2s linear; + } + &:hover { + path { + fill: $red; + transition: all .2s linear; + } + } + } h1, h2 { @extend %bold; @extend %small; - line-height: 1; margin-bottom: .5rem; } h1 { @extend %xlarge; + line-height: 1; text-transform: none; } h2 { @extend %large; color: $gray-light; + line-height: 1.2; } .location { color: $gray-light; diff --git a/app/svg/flag.svg b/app/svg/flag.svg new file mode 100644 index 00000000..2612a5cf --- /dev/null +++ b/app/svg/flag.svg @@ -0,0 +1 @@ + \ No newline at end of file From 226e5c444d6bc59767550ad4df4e91e6cb8500dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 31 Mar 2015 14:37:23 +0200 Subject: [PATCH 025/366] Add edit icon to timeline profile --- .../includes/modules/profile/profile-bar.jade | 5 ++++- app/styles/modules/profile/profile-bar.scss | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/partials/includes/modules/profile/profile-bar.jade b/app/partials/includes/modules/profile/profile-bar.jade index 3e130b95..873fa152 100644 --- a/app/partials/includes/modules/profile/profile-bar.jade +++ b/app/partials/includes/modules/profile/profile-bar.jade @@ -1,5 +1,8 @@ section.profile-bar - img.profile-img(src="http://i.imgur.com/gPiHzHN.jpg", alt="{{ user.nickname }}") + div.profile-image-wrapper + img.profile-img(src="http://i.imgur.com/gPiHzHN.jpg", alt="{{ user.nickname }}") + a.edit-profile(href="", title="Edit profile") + span.icon.icon-edit a.button-green span Follow div.profile-data diff --git a/app/styles/modules/profile/profile-bar.scss b/app/styles/modules/profile/profile-bar.scss index 93376634..59aba130 100644 --- a/app/styles/modules/profile/profile-bar.scss +++ b/app/styles/modules/profile/profile-bar.scss @@ -1,7 +1,20 @@ .profile-bar { + .profile-image-wrapper { + position: relative; + } .profile-img { max-width: 100%; } + .edit-profile { + @extend %large; + background: rgba(255, 255, 255, .6); + border-radius: 50%; + color: $grayer; + left: calc(50% - 1rem); + padding: 1rem; + position: absolute; + top: calc(50% - 1rem); + } .button-green { display: block; margin-bottom: 1rem; From d8303544b4ced4e5045cde5755ecff8e3e11fde7 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 1 Apr 2015 12:34:59 +0200 Subject: [PATCH 026/366] recover project home --- app/coffee/app.coffee | 6 +- app/partials/project/project-list.jade | 2 +- app/styles/modules/create-project.scss | 58 +++++++ app/styles/modules/home-project.scss | 34 ++++ app/styles/modules/home-projects-list.scss | 191 +++++++++++++++++++++ 5 files changed, 289 insertions(+), 2 deletions(-) create mode 100644 app/styles/modules/create-project.scss create mode 100644 app/styles/modules/home-project.scss create mode 100644 app/styles/modules/home-projects-list.scss diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 26fd1c73..a21baeae 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -39,7 +39,7 @@ taiga.sessionId = taiga.generateUniqueSessionIdentifier() configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEventsProvider, tgLoaderProvider, $compileProvider, $translateProvider) -> $routeProvider.when("/", - {templateUrl: "home/home-logged-in.html"}) + {templateUrl: "project/projects.html", resolve: {loader: tgLoaderProvider.add()}}) $routeProvider.when("/project/:pslug/", {templateUrl: "project/project.html"}) @@ -138,6 +138,10 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven $routeProvider.when("/cancel-account/:cancel_token", {templateUrl: "user/cancel-account.html"}) + # User profile + $routeProvider.when("/profile", + {templateUrl: "home/home-logged-in.html"}) + # Auth $routeProvider.when("/login", {templateUrl: "auth/login.html"}) diff --git a/app/partials/project/project-list.jade b/app/partials/project/project-list.jade index 3e6d48d5..9e52dbd8 100644 --- a/app/partials/project/project-list.jade +++ b/app/partials/project/project-list.jade @@ -5,7 +5,7 @@ div(tg-projects-pagination) ul.projects-list <% _.each(projects, function(project) { %> li - a.button(href!='<%- project.url %>') + a(href!='<%- project.url %>') <%- project.name %> <% }) %> a.v-pagination-next.icon.icon-arrow-bottom(href='') \ No newline at end of file diff --git a/app/styles/modules/create-project.scss b/app/styles/modules/create-project.scss new file mode 100644 index 00000000..f7958a33 --- /dev/null +++ b/app/styles/modules/create-project.scss @@ -0,0 +1,58 @@ +.create-project { + @extend %triangled-bg; + align-content: center; + align-items: center; + bottom: 0; + justify-content: center; + left: 0; + position: fixed; + right: 0; + top: 0; + z-index: 999; + fieldset { + margin-bottom: 1rem; + } + .create-project-container { + flex-basis: 400px; + flex-grow: 0; + } + h1 { + color: $white; + text-align: center; + } + .logo, + .tagline { + margin-bottom: 1rem; + text-align: center; + } + .tagline { + @extend %xlarge; + @extend %title; + color: $white; + line-height: 2rem; + text-transform: uppercase; + } + form { + margin-bottom: 2rem; + } + input { + background: $white; + @include placeholder { + color: $gray-light; + } + } + .button { + color: $white; + display: block; + margin-bottom: .5rem; + text-align: center; + &:hover { + background: $fresh-taiga; + } + } + a { + &:hover { + color: $white; + } + } +} diff --git a/app/styles/modules/home-project.scss b/app/styles/modules/home-project.scss new file mode 100644 index 00000000..b093b945 --- /dev/null +++ b/app/styles/modules/home-project.scss @@ -0,0 +1,34 @@ +.summary-stats { + align-items: flex-start; + display: flex; + .info-num { + @extend %xlarge; + @extend %bold; + float: left; + margin-right: .3rem; + position: relative; + top: 5px; + } + .info-text { + @extend %small; + float: left; + line-height: .9rem; + } +} + +.project-data-container { + display: flex; + justify-content: space-between; + ul { + flex-grow: 0; + max-width: 33%; + } + li { + display: inline-block; + margin-right: .1rem; + width: 10%; + figure { + width: 100%; + } + } +} diff --git a/app/styles/modules/home-projects-list.scss b/app/styles/modules/home-projects-list.scss new file mode 100644 index 00000000..bd805c70 --- /dev/null +++ b/app/styles/modules/home-projects-list.scss @@ -0,0 +1,191 @@ +.home-projects-list, +.home-project { + @extend %background-taiga; + align-content: center; + align-items: center; + background-color: $black; + background-position: center center; + background-size: cover; + display: flex; + height: 100%; + justify-content: center; + left: 0; + padding: 0; + position: fixed; + top: 0; + width: 100%; + .welcome-user { + display: flex; + position: absolute; + right: 1rem; + top: 1rem; + p { + color: $whitish; + margin-bottom: 0; + span:before { + content: ' '; + } + } + .logout { + @extend %small; + float: right; + &:hover { + color: $red-light; + } + } + .info { + padding-right: 1rem; + } + img { + width: 40px; + } + } +} +.home-projects-wrapper { + width: 1200px; +} + +.home-projects-list-inner { + display: flex; +} + +.recent-projects { + flex-grow: 8; + max-width: 800px; + ul { + display: flex; + flex-wrap: wrap; + margin: 0; + padding: 0; + } + a { + height: 100%; + left: 0; + padding: 1rem; + position: absolute; + top: 0; + width: 100%; + } + li { + background-color: rgba($white, .5); + color: $whitish; + flex-basis: 230px; + flex-grow: 1; + flex-shrink: 0; + height: 130px; + margin-bottom: 1rem; + margin-right: 1rem; + overflow: hidden; + position: relative; + transition: background-color .3s linear; + width: 23.5%; + &:hover { + background-color: rgba($fresh-taiga, .5); + cursor: pointer; + transition: background-color .3s linear; + p { + color: $gray-light; + transition: color .3s linear; + } + } + } + h2 { + color: $whitish; + line-height: 2rem; + } + p { + color: $grayer; + transition: color .3s linear; + } +} + +.project-content { + h2 { + margin-bottom: .5rem; + } + p { + @extend %small; + line-height: 1rem; + } +} + +.all-projects { + background-color: rgba(0, 0, 0, .5); + display: flex; + flex-direction: column; + flex-grow: 1; + margin-left: 1rem; + max-height: 422px; + padding: 1rem; + width: 285px; + h1 { + color: $whitish; + flex-shrink: 0; + line-height: 1; + text-align: center; + } + .v-pagination-list { + max-height: 221px; + } + ul { + left: 0; + margin-bottom: 0; + position: relative; + top: 0; + width: 100%; + } + li { + border-bottom: 2px solid $gray; + a { + @extend %large; + @extend %title; + color: $whitish; + display: block; + line-height: 1.2rem; + padding: 1rem; + text-transform: uppercase; + width: 100%; + &:hover { + background-color: $gray; + transition: background-color .3s linear; + } + } + .active { + background-color: $gray; + transition: background-color .3s linear; + } + } + .projects-pagination { + width: 100%; + } + .create-project-button-wrapper { + display: flex; + flex-shrink: 0; + .create-project-button { + flex-grow: 8; + margin-right: .2rem; + text-align: center; + } + .import-project-button { + flex-grow: 1; + padding-left: .5rem; + padding-right: .5rem; + text-align: center; + .icon { + color: $grayer; + margin: 0; + } + } + } + .button-green { + color: $whitish; + text-align: center; + width: 100%; + &:hover { + color: $whitish; + } + } + .v-pagination-next { + margin-bottom: 1rem; + } +} From ee1ba35b3de7a4849dc31f57af62b2b79ab4c4b1 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 1 Apr 2015 14:10:14 +0200 Subject: [PATCH 027/366] rename home-logged to profile --- app/coffee/app.coffee | 2 +- .../{home/home-logged-in.jade => profile/public-profile.jade} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename app/partials/{home/home-logged-in.jade => profile/public-profile.jade} (100%) diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index a21baeae..1de71f3f 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -140,7 +140,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven # User profile $routeProvider.when("/profile", - {templateUrl: "home/home-logged-in.html"}) + {templateUrl: "profile/public-profile.html"}) # Auth $routeProvider.when("/login", diff --git a/app/partials/home/home-logged-in.jade b/app/partials/profile/public-profile.jade similarity index 100% rename from app/partials/home/home-logged-in.jade rename to app/partials/profile/public-profile.jade From 80fa5b7db33408766ca2ed9d6ea19b5e19720e07 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Mon, 6 Apr 2015 13:44:31 +0200 Subject: [PATCH 028/366] refactor profile tabs --- app/coffee/modules/profile/main.coffee | 22 ---------------- .../profile/profile-tab.directive.coffee | 26 +++++++++++++++++++ .../profile/profile-tabs.directive.coffee | 26 +++++++++++++++++++ .../modules/profile/profile-contacts.jade | 2 +- .../modules/profile/profile-content-tabs.jade | 13 ---------- .../modules/profile/profile-favorites.jade | 2 +- .../modules/profile/profile-projects.jade | 4 +-- .../modules/profile/profile-timeline.jade | 3 +-- app/partials/profile/profile-tabs.jade | 7 +++++ app/partials/profile/public-profile.jade | 26 ++++++++++++------- app/styles/layout/profile.scss | 2 +- 11 files changed, 81 insertions(+), 52 deletions(-) create mode 100644 app/coffee/modules/profile/profile-tab.directive.coffee create mode 100644 app/coffee/modules/profile/profile-tabs.directive.coffee delete mode 100644 app/partials/includes/modules/profile/profile-content-tabs.jade create mode 100644 app/partials/profile/profile-tabs.jade diff --git a/app/coffee/modules/profile/main.coffee b/app/coffee/modules/profile/main.coffee index 87b70efa..6a97f450 100644 --- a/app/coffee/modules/profile/main.coffee +++ b/app/coffee/modules/profile/main.coffee @@ -22,25 +22,3 @@ taiga = @.taiga module = angular.module("taigaProfile") - -############################################################################# -## Profile Tabs -############################################################################# - -ProfileTabsDirective = () -> - - link = ($scope, $el, $attrs) -> - - $scope.tabSelected = 'profile-timeline' - - $scope.toggleTab = -> - target = angular.element(event.currentTarget) - tab = target.data("selected") - target.siblings().removeClass('active') - target.addClass('active') - $scope.tabSelected = tab - - return {link:link} - - -module.directive("tgProfileTabs", ProfileTabsDirective) diff --git a/app/coffee/modules/profile/profile-tab.directive.coffee b/app/coffee/modules/profile/profile-tab.directive.coffee new file mode 100644 index 00000000..ed6859e1 --- /dev/null +++ b/app/coffee/modules/profile/profile-tab.directive.coffee @@ -0,0 +1,26 @@ +ProfileTabDirective = () -> + link = ($scope, $el, $attrs, $ctrl) -> + $scope.tab = {} + + $scope.tab.name = $attrs.tgProfileTab + $scope.tab.title = $attrs.tabTitle + $scope.tab.icon = $attrs.tabIcon + $scope.tab.active = !!$attrs.tabActive + + $ctrl.addTab($scope.tab) + + return { + scope: {} + require: "^tgProfileTabs" + link: link + transclude: true + replace: true + template: """ +
+ +
+ """ + } + +angular.module("taigaProfile") + .directive("tgProfileTab", ProfileTabDirective) diff --git a/app/coffee/modules/profile/profile-tabs.directive.coffee b/app/coffee/modules/profile/profile-tabs.directive.coffee new file mode 100644 index 00000000..9df742dd --- /dev/null +++ b/app/coffee/modules/profile/profile-tabs.directive.coffee @@ -0,0 +1,26 @@ +class ProfileTabsController + constructor: (@scope) -> + @scope.tabs = [] + + addTab: (tab) -> + @scope.tabs.push(tab) + +ProfileTabsController.$inject = ["$scope"] + +ProfileTabsDirective = () -> + link = ($scope, $el, $attrs) -> + $scope.toggleTab = (tab) -> + _.map $scope.tabs, (tab) => tab.active = false + + tab.active = true + + return { + scope: {} + controller: ProfileTabsController + templateUrl: "profile/profile-tabs.html" + link: link + transclude: true + } + +angular.module("taigaProfile") + .directive("tgProfileTabs", ProfileTabsDirective) diff --git a/app/partials/includes/modules/profile/profile-contacts.jade b/app/partials/includes/modules/profile/profile-contacts.jade index 3c647ed9..e3ed4c1f 100644 --- a/app/partials/includes/modules/profile/profile-contacts.jade +++ b/app/partials/includes/modules/profile/profile-contacts.jade @@ -1,4 +1,4 @@ -section.profile-contacts(ng-show="tabSelected == 'profile-contacts'") +section.profile-contacts nav.profile-contact-filters a.active(href="", title="No Filter") all a(href="", title="Only show your team") team diff --git a/app/partials/includes/modules/profile/profile-content-tabs.jade b/app/partials/includes/modules/profile/profile-content-tabs.jade deleted file mode 100644 index f55c1c4d..00000000 --- a/app/partials/includes/modules/profile/profile-content-tabs.jade +++ /dev/null @@ -1,13 +0,0 @@ -nav.profile-content-tabs - a.tab.active(href="", title="Activity Tab", ng-click="toggleTab()", data-selected="profile-timeline") - span.icon.icon-timeline - span activity - a.tab(href="", title="Projects Tab", ng-click="toggleTab()", data-selected="profile-projects") - span.icon.icon-project - span projects - a.tab(href="", title="Contacts Tab", ng-click="toggleTab()", data-selected="profile-contacts") - span.icon.icon-team - span contacts - a.tab(href="", title="Favorites Tab", ng-click="toggleTab()", data-selected="profile-favorites") - span.icon.icon-star-fill - span favorites diff --git a/app/partials/includes/modules/profile/profile-favorites.jade b/app/partials/includes/modules/profile/profile-favorites.jade index 4ecae7bd..f0a723c7 100644 --- a/app/partials/includes/modules/profile/profile-favorites.jade +++ b/app/partials/includes/modules/profile/profile-favorites.jade @@ -1,4 +1,4 @@ -section.profile-favorites(ng-show="tabSelected == 'profile-favorites'") +section.profile-favorites nav.profile-favorites-filters a.active(href="", title="No Filter") all a(href="", title="Only show your team") projects diff --git a/app/partials/includes/modules/profile/profile-projects.jade b/app/partials/includes/modules/profile/profile-projects.jade index dac39552..ab7f3e37 100644 --- a/app/partials/includes/modules/profile/profile-projects.jade +++ b/app/partials/includes/modules/profile/profile-projects.jade @@ -1,4 +1,4 @@ -section.profile-projects(ng-show="tabSelected == 'profile-projects'") +section.profile-projects - for (var x = 0; x < 3; x++) div.profile-project-single div.profile-projects-left @@ -127,5 +127,3 @@ section.profile-projects(ng-show="tabSelected == 'profile-projects'") img(src="https://s3.amazonaws.com/uifaces/faces/twitter/uxceo/128.jpg", alt="{{ user.nickname }}") a(href="", title="View {{ user.nickname }} profile") img(src="https://s3.amazonaws.com/uifaces/faces/twitter/syswarren/128.jpg", alt="{{ user.nickname }}") - - diff --git a/app/partials/includes/modules/profile/profile-timeline.jade b/app/partials/includes/modules/profile/profile-timeline.jade index 509569c1..5a051585 100644 --- a/app/partials/includes/modules/profile/profile-timeline.jade +++ b/app/partials/includes/modules/profile/profile-timeline.jade @@ -1,4 +1,4 @@ -section.profile-timeline(ng-show="tabSelected == 'profile-timeline'") +section.profile-timeline - for (var x = 0; x < 3; x++) // Simple message for favorites, updates, etc. div.activity-simple @@ -92,4 +92,3 @@ section.profile-timeline(ng-show="tabSelected == 'profile-timeline'") a(href="", title="See {{ project.name }}") Nanatubos div.activity-comment-quote p We plan to build a hundred of so called "telehubs" so people from all over the world can immediately relocate their physical self at any other telehub in microseconds. - diff --git a/app/partials/profile/profile-tabs.jade b/app/partials/profile/profile-tabs.jade new file mode 100644 index 00000000..9a4d3674 --- /dev/null +++ b/app/partials/profile/profile-tabs.jade @@ -0,0 +1,7 @@ +div + nav.profile-content-tabs + a.tab(ng-repeat="tab in tabs", href="", title="{{::tab.title}}", ng-class="{active: tab.active}" ng-click="toggleTab(tab)") + span.icon(ng-class="::tab.icon") + span {{::tab.name}} + + ng-transclude \ No newline at end of file diff --git a/app/partials/profile/public-profile.jade b/app/partials/profile/public-profile.jade index 90037a43..298a6637 100644 --- a/app/partials/profile/public-profile.jade +++ b/app/partials/profile/public-profile.jade @@ -1,13 +1,21 @@ include ../includes/components/beta div.profile.centered include ../includes/modules/profile/profile-bar - div.main(tg-profile-tabs) + div.main div.hero - include ../includes/modules/profile/profile-content-tabs - div.content-wrapper - div.content - include ../includes/modules/profile/profile-timeline - include ../includes/modules/profile/profile-projects - include ../includes/modules/profile/profile-contacts - include ../includes/modules/profile/profile-favorites - include ../includes/modules/profile/profile-sidebar + div(tg-profile-tabs) + div.content-wrapper + div.content + div(tg-profile-tab="activity", tab-title="Activity Tab", tab-icon="icon-timeline", tab-active) + include ../includes/modules/profile/profile-timeline + + div(tg-profile-tab="projects", tab-title="Projects Tab", tab-icon="icon-project") + include ../includes/modules/profile/profile-projects + + div(tg-profile-tab="contacts", tab-title="Contacts Tab", tab-icon="icon-team") + include ../includes/modules/profile/profile-contacts + + div(tg-profile-tab="favorities", tab-title="Favorites Tab", tab-icon="icon-star-fill") + include ../includes/modules/profile/profile-favorites + + include ../includes/modules/profile/profile-sidebar diff --git a/app/styles/layout/profile.scss b/app/styles/layout/profile.scss index 59e64a5b..cb9a6610 100644 --- a/app/styles/layout/profile.scss +++ b/app/styles/layout/profile.scss @@ -26,7 +26,7 @@ flex: 1; margin-right: 1rem; max-width: 786px; - section { + > div { opacity: 1; padding-top: 0; position: relative; From 2c1de83d85b2729380ea23db1363b14b3ce388cf Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 8 Apr 2015 09:37:10 +0200 Subject: [PATCH 029/366] comments in timelines --- app/coffee/modules/base.coffee | 1 + app/coffee/modules/base/repository.coffee | 15 ++ .../profile/profileTimeline.controller.coffee | 45 +++++ .../profile/timeline-item.directive.coffee | 58 ++++++ app/coffee/modules/resources.coffee | 8 + app/coffee/modules/resources/timeline.coffee | 35 ++++ .../modules/profile/profile-timeline.jade | 179 +++++++++--------- .../profile/timeline/comment-timeline.jade | 12 ++ 8 files changed, 264 insertions(+), 89 deletions(-) create mode 100644 app/coffee/modules/profile/profileTimeline.controller.coffee create mode 100644 app/coffee/modules/profile/timeline-item.directive.coffee create mode 100644 app/coffee/modules/resources/timeline.coffee create mode 100644 app/partials/profile/timeline/comment-timeline.jade diff --git a/app/coffee/modules/base.coffee b/app/coffee/modules/base.coffee index 8bdf9917..d91446ac 100644 --- a/app/coffee/modules/base.coffee +++ b/app/coffee/modules/base.coffee @@ -58,6 +58,7 @@ urls = { "create-project": "/create-project" "profile": "/:user" + "user-profile": "profile/:username" "project": "/project/:project" "project-backlog": "/project/:project/backlog" diff --git a/app/coffee/modules/base/repository.coffee b/app/coffee/modules/base/repository.coffee index 866435d7..0089ad3d 100644 --- a/app/coffee/modules/base/repository.coffee +++ b/app/coffee/modules/base/repository.coffee @@ -194,6 +194,21 @@ class RepositoryService extends taiga.Service result.paginatedBy = parseInt(headers["x-paginated-by"], 10) return result + queryOnePaginatedRaw: (name, id, params, options={}) -> + url = @urls.resolve(name) + url = "#{url}/#{id}" if id + httpOptions = _.merge({headers: {}}, options) + + return @http.get(url, params, httpOptions).then (data) => + headers = data.headers() + result = {} + result.data = data.data + result.count = parseInt(headers["x-pagination-count"], 10) + result.current = parseInt(headers["x-pagination-current"] or 1, 10) + result.paginatedBy = parseInt(headers["x-paginated-by"], 10) + + return result + resolve: (options) -> params = {} params.project = options.pslug if options.pslug? diff --git a/app/coffee/modules/profile/profileTimeline.controller.coffee b/app/coffee/modules/profile/profileTimeline.controller.coffee new file mode 100644 index 00000000..ca8c20f0 --- /dev/null +++ b/app/coffee/modules/profile/profileTimeline.controller.coffee @@ -0,0 +1,45 @@ +### +# 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/backlog/main.coffee +### + +taiga = @.taiga + +mixOf = @.taiga.mixOf + +class ProfileTimelineController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.FiltersMixin) + @.$inject = [ + "$scope", + "$tgResources", + "$tgAuth" + ] + + constructor: (@scope, @rs, @auth) -> + promise = @.loadTimeline() + promise.then null, @.onInitialDataError.bind(@) + + loadTimeline: () -> + user = @auth.getUser() + + return @rs.timeline.profile(user.id).then (result) => + @scope.result = result + console.log @scope.result.data + +angular.module("taigaProfile") + .controller("ProfileTimeline", ProfileTimelineController) diff --git a/app/coffee/modules/profile/timeline-item.directive.coffee b/app/coffee/modules/profile/timeline-item.directive.coffee new file mode 100644 index 00000000..b6cb3e9c --- /dev/null +++ b/app/coffee/modules/profile/timeline-item.directive.coffee @@ -0,0 +1,58 @@ +TimelineItemDirective = ($tgTemplate, $compile, $navUrls) -> + parseEventType = (event_type) -> + event_type = event_type.split(".") + + return { + section: event_type[0], + obj: event_type[1], + type: event_type[2] + } + + getUrl = (timeline, event) -> + url = { + "issue": "project-issues-detail", + "wiki": "project-wiki-page", + "task": "project-tasks-detail", + "userstories": "project-userstories-detail" + } + + params = {project: timeline.data.project.slug, ref: timeline.data[event.obj].ref} + + return $navUrls.resolve(url[event.obj], params) + + getTemplate = (timeline, event) -> + template = "" + + if event.type == 'change' + if timeline.data.comment.length + template = "profile/timeline/comment-timeline.html" + + return $tgTemplate.get(template) + + link = ($scope, $el, $attrs) -> + event = parseEventType($scope.timeline.event_type) + template = getTemplate($scope.timeline, event) + + if !template + return "" + + obj = $scope.timeline.data[event.obj] + + $scope.timeline.subject = obj.subject + $scope.timeline.ref = obj.ref + $scope.timeline.type = event.obj + $scope.timeline.created_formated = moment($scope.timeline.created).fromNow() + $scope.timeline.detail_url = getUrl($scope.timeline, event) + + $el.html(template) + $compile($el.contents())($scope) + + return { + link: link + scope: { + timeline: "=tgTimelineItem" + } + } + +angular.module("taigaProfile") + .directive("tgTimelineItem", ["$tgTemplate", "$compile", "$tgNavUrls", TimelineItemDirective]) diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index 52028c90..a3f05dbb 100644 --- a/app/coffee/modules/resources.coffee +++ b/app/coffee/modules/resources.coffee @@ -36,6 +36,8 @@ urls = { "users-change-password": "/users/change_password" "users-change-email": "/users/change_email" "users-cancel-account": "/users/cancel" + "contacts": "/users/%s/contacts" + "stats": "/users/%s/stats" # User - Notification "notify-policies": "/notify-policies" @@ -58,6 +60,7 @@ urls = { "projects": "/projects" "project-templates": "/project-templates" "project-modules": "/projects/%s/modules" + "bulk-update-projects-order": "/projects/bulk_update_order" # Project Values - Choises "userstory-statuses": "/userstory-statuses" @@ -125,6 +128,10 @@ urls = { "tasks-csv": "/tasks/csv?uuid=%s" "issues-csv": "/issues/csv?uuid=%s" + # Timeline + "timeline-profile": "/timeline/profile" + "timeline-project": "/timeline/project" + # Search "search": "/search" @@ -183,5 +190,6 @@ module.run([ "$tgWebhooksResourcesProvider", "$tgWebhookLogsResourcesProvider", "$tgLocalesResourcesProvider", + "$tgTimelineResourcesProvider", initResources ]) diff --git a/app/coffee/modules/resources/timeline.coffee b/app/coffee/modules/resources/timeline.coffee new file mode 100644 index 00000000..abbff0ae --- /dev/null +++ b/app/coffee/modules/resources/timeline.coffee @@ -0,0 +1,35 @@ +### +# 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/resources/timeline.coffee +### + +taiga = @.taiga + +resourceProvider = ($repo) -> + service = {} + + service.profile = (userId) -> + return $repo.queryOnePaginatedRaw("timeline-profile", userId) + + return (instance) -> + instance.timeline = service + + +module = angular.module("taigaResources") +module.factory("$tgTimelineResourcesProvider", ["$tgRepo", resourceProvider]) diff --git a/app/partials/includes/modules/profile/profile-timeline.jade b/app/partials/includes/modules/profile/profile-timeline.jade index 5a051585..9ee99a1f 100644 --- a/app/partials/includes/modules/profile/profile-timeline.jade +++ b/app/partials/includes/modules/profile/profile-timeline.jade @@ -1,94 +1,95 @@ -section.profile-timeline - - for (var x = 0; x < 3; x++) - // Simple message for favorites, updates, etc. - div.activity-simple - span.activity-date Yesterday 12.30h - div.activity-info - div.profile-contact-picture - a(href="", title="{{ user.nickname }}") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/gerrenlamson/128.jpg", alt="{{ user.nickname }}") - p - a(href="", title="See {{ user.nickname }} profile") Jesús Espino - span has updated the status of the US - a(href="", title="See #{{ us.id }}{{ us.title }}") #23 Web comercial/Ayuda from "UX" to "UX Done" +section.profile-timeline(ng-controller="ProfileTimeline as ctrl") + div(ng-repeat="timeline in result.data", tg-timeline-item="timeline") + // - for (var x = 0; x < 3; x++) + // // Simple message for favorites, updates, etc. + // div.activity-simple) + // span.activity-date Yesterday 12.30h + // div.activity-info + // div.profile-contact-picture + // a(href="", title="{{ user.nickname }}") + // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/gerrenlamson/128.jpg", alt="{{ user.nickname }}") + // p + // a(href="", title="See {{ user.nickname }} profile") Jesús Espino + // span has updated the status of the US + // a(href="", title="See #{{ us.id }}{{ us.title }}") #23 Web comercial/Ayuda from "UX" to "UX Done" - // Added comment in us, task or issue. - div.activity-comment - span.activity-date 3 days ago - div.activity-info - div.profile-contact-picture - a(href="", title="{{ user.nickname }}") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/tonystubblebine/128.jpg", alt="{{ user.nickname }}") - p - a(href="", title="See {{ user.nickname }} profile") JuanFrancisco Alcántara - span has commented in the task - a(href="", title="See #{{ us.id }}{{ us.title }}") #15 Revisar el contraste de los grises - div.activity-comment-quote - p "He subido a GitLab unos wireframes. Echadle un vistazo por favor, a ver si falta algo o tenéis al" + // // Added comment in us, task or issue. + // div.activity-comment + // span.activity-date 3 days ago + // div.activity-info + // div.profile-contact-picture + // a(href="", title="{{ user.nickname }}") + // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/tonystubblebine/128.jpg", alt="{{ user.nickname }}") + // p + // a(href="", title="See {{ user.nickname }} profile") JuanFrancisco Alcántara + // span has commented in the task + // a(href="", title="See #{{ us.id }}{{ us.title }}") #15 Revisar el contraste de los grises + // div.activity-comment-quote + // p "He subido a GitLab unos wireframes. Echadle un vistazo por favor, a ver si falta algo o tenéis al" - // Added attachment type image in us, task or issue. - div.activity-image - span.activity-date 5 days ago - div.activity-info - div.profile-contact-picture - a(href="", title="{{ user.nickname }}") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/jina/128.jpg", alt="{{ user.nickname }}") - p - a(href="", title="See {{ user.nickname }} profile") Alejandro Alonso - span has uploaded an image in the US - a(href="", title="See #{{ us.id }}{{ us.title }}") US #23 Web comercial/Ayuda - div.activity-comment-attachment - p "Eh! Look at this amazing Taiga picture!" - img(src="https://ununsplash.imgix.net/photo-1423753623104-718aaace6772?q=75&fm=jpg&w=1080&fit=max&s=f655534aa0fe8bae35c687e80a2ed399", alt="{{ attachment.name }}") + // // Added attachment type image in us, task or issue. + // div.activity-image + // span.activity-date 5 days ago + // div.activity-info + // div.profile-contact-picture + // a(href="", title="{{ user.nickname }}") + // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/jina/128.jpg", alt="{{ user.nickname }}") + // p + // a(href="", title="See {{ user.nickname }} profile") Alejandro Alonso + // span has uploaded an image in the US + // a(href="", title="See #{{ us.id }}{{ us.title }}") US #23 Web comercial/Ayuda + // div.activity-comment-attachment + // p "Eh! Look at this amazing Taiga picture!" + // img(src="https://ununsplash.imgix.net/photo-1423753623104-718aaace6772?q=75&fm=jpg&w=1080&fit=max&s=f655534aa0fe8bae35c687e80a2ed399", alt="{{ attachment.name }}") - // Multiple update message, etc. - div.activity-notification - span.activity-date 6 days ago - div.activity-info - div.profile-contact-picture - a(href="", title="{{ user.nickname }}") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/idiot/128.jpg", alt="{{ user.nickname }}") - p - a(href="", title="See {{ user.nickname }} profile") Jesús Espino - span closed - ul.activity-notification-list - li - a(href="", title="See #{{ us.id }}{{ us.title }}") US #23 Web comercial/Ayuda - li - a(href="", title="See #{{ us.id }}{{ us.title }}") #2156 Search Page UX is hardly understandable - li - a(href="", title="See #{{ us.id }}{{ us.title }}") #456 Search for users - li - a(href="", title="See #{{ us.id }}{{ us.title }}") #2140 Las notificaciones de cambios están fallando + // // Multiple update message, etc. + // div.activity-notification + // span.activity-date 6 days ago + // div.activity-info + // div.profile-contact-picture + // a(href="", title="{{ user.nickname }}") + // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/idiot/128.jpg", alt="{{ user.nickname }}") + // p + // a(href="", title="See {{ user.nickname }} profile") Jesús Espino + // span closed + // ul.activity-notification-list + // li + // a(href="", title="See #{{ us.id }}{{ us.title }}") US #23 Web comercial/Ayuda + // li + // a(href="", title="See #{{ us.id }}{{ us.title }}") #2156 Search Page UX is hardly understandable + // li + // a(href="", title="See #{{ us.id }}{{ us.title }}") #456 Search for users + // li + // a(href="", title="See #{{ us.id }}{{ us.title }}") #2140 Las notificaciones de cambios están fallando - // Added attachment type image in us, task or issue. - div.activity-member - span.activity-date a week ago - div.activity-info - div.profile-contact-picture - a(href="", title="{{ organization.nickname }}") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/tofslie/128.jpg", alt="{{ organization.nickname }}") - p - a(href="", title="See {{ organization.nickname }} profile") Mozilla - span has a new member - div.activity-member-view - div.profile-member-picture - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/BillSKenney/128.jpg", alt="{{ organization.nickname }}") - div.activity-member-info - a(href="", title="See {{ user.nickname }} profile") - span Andrés González - p Back-end developer & Stake + // // Added attachment type image in us, task or issue. + // div.activity-member + // span.activity-date a week ago + // div.activity-info + // div.profile-contact-picture + // a(href="", title="{{ organization.nickname }}") + // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/tofslie/128.jpg", alt="{{ organization.nickname }}") + // p + // a(href="", title="See {{ organization.nickname }} profile") Mozilla + // span has a new member + // div.activity-member-view + // div.profile-member-picture + // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/BillSKenney/128.jpg", alt="{{ organization.nickname }}") + // div.activity-member-info + // a(href="", title="See {{ user.nickname }} profile") + // span Andrés González + // p Back-end developer & Stake - // Added comment in us, task or issue. - div.activity-project - span.activity-date a week ago - div.activity-info - div.profile-contact-picture - a(href="", title="{{ organization.nickname }}") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/ekvium/128.jpg", alt="{{ organization.nickname }}") - p - a(href="", title="See {{ user.nickname }} profile") Redhat - span has a new project - a(href="", title="See {{ project.name }}") Nanatubos - div.activity-comment-quote - p We plan to build a hundred of so called "telehubs" so people from all over the world can immediately relocate their physical self at any other telehub in microseconds. + // // Added comment in us, task or issue. + // div.activity-project + // span.activity-date a week ago + // div.activity-info + // div.profile-contact-picture + // a(href="", title="{{ organization.nickname }}") + // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/ekvium/128.jpg", alt="{{ organization.nickname }}") + // p + // a(href="", title="See {{ user.nickname }} profile") Redhat + // span has a new project + // a(href="", title="See {{ project.name }}") Nanatubos + // div.activity-comment-quote + // p We plan to build a hundred of so called "telehubs" so people from all over the world can immediately relocate their physical self at any other telehub in microseconds. diff --git a/app/partials/profile/timeline/comment-timeline.jade b/app/partials/profile/timeline/comment-timeline.jade new file mode 100644 index 00000000..101aabca --- /dev/null +++ b/app/partials/profile/timeline/comment-timeline.jade @@ -0,0 +1,12 @@ +div.activity-comment + span.activity-date {{::timeline.created_formated}} + div.activity-info + div.profile-contact-picture + a(tg-nav="user-profile:username=timeline.data.user.username", title="{{::timeline.data.user.name }}") + img(ng-src="{{::timeline.data.user.photo}}", alt="{{::timeline.data.user.name}}") + p + a(tg-nav="user-profile:username=timeline.data.user.username", title="See {{::timeline.data.user.name }} profile") {{::timeline.data.user.name}} + span has commented in the {{::timeline.type }} + a(href="{{::timeline.detail_url}}", title="See #{{::timeline.ref}} {{::timeline.subject}}") \#{{::timeline.ref}} {{::timeline.subject}} + div.activity-comment-quote + p "{{::timeline.data.comment}}" From c439ca7e3fc26ac1eead4999d0d9e730efd587dc Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 8 Apr 2015 12:39:41 +0200 Subject: [PATCH 030/366] attachment image --- .../timeline-attachment.directive.coffee | 27 +++ .../profile/timeline-item.directive.coffee | 2 + .../modules/profile/profile-timeline.jade | 176 +++++++++--------- .../profile/timeline/comment-timeline.jade | 2 +- .../timeline/timeline-attachment-image.jade | 3 + .../profile/timeline/timeline-attachment.jade | 12 ++ 6 files changed, 133 insertions(+), 89 deletions(-) create mode 100644 app/coffee/modules/profile/timeline-attachment.directive.coffee create mode 100644 app/partials/profile/timeline/timeline-attachment-image.jade create mode 100644 app/partials/profile/timeline/timeline-attachment.jade diff --git a/app/coffee/modules/profile/timeline-attachment.directive.coffee b/app/coffee/modules/profile/timeline-attachment.directive.coffee new file mode 100644 index 00000000..7bbe12fa --- /dev/null +++ b/app/coffee/modules/profile/timeline-attachment.directive.coffee @@ -0,0 +1,27 @@ +TimelineAttachmentDirective = ($tgTemplate, $compile) -> + validFileExtensions = [".jpg", ".jpeg", ".bmp", ".gif", ".png"] + + isImage = (url) -> + url = url.toLowerCase() + + return _.some validFileExtensions, (extension) => + return url.indexOf(extension, url - extension.length) != -1 + + link = ($scope, $el, $attrs) -> + is_image = isImage($scope.attachment.url) + + if is_image + template = $tgTemplate.get("profile/timeline/timeline-attachment-image.html") + + $el.html(template) + $compile($el.contents())($scope) + + return { + link: link + scope: { + attachment: "=tgTimelineAttachment" + } + } + +angular.module("taigaProfile") + .directive("tgTimelineAttachment", ["$tgTemplate", "$compile", TimelineAttachmentDirective]) diff --git a/app/coffee/modules/profile/timeline-item.directive.coffee b/app/coffee/modules/profile/timeline-item.directive.coffee index b6cb3e9c..6ac4726f 100644 --- a/app/coffee/modules/profile/timeline-item.directive.coffee +++ b/app/coffee/modules/profile/timeline-item.directive.coffee @@ -26,6 +26,8 @@ TimelineItemDirective = ($tgTemplate, $compile, $navUrls) -> if event.type == 'change' if timeline.data.comment.length template = "profile/timeline/comment-timeline.html" + else if timeline.data.values_diff.attachments + template = "profile/timeline/attachment-timeline.html" return $tgTemplate.get(template) diff --git a/app/partials/includes/modules/profile/profile-timeline.jade b/app/partials/includes/modules/profile/profile-timeline.jade index 9ee99a1f..4580cc0b 100644 --- a/app/partials/includes/modules/profile/profile-timeline.jade +++ b/app/partials/includes/modules/profile/profile-timeline.jade @@ -1,95 +1,95 @@ section.profile-timeline(ng-controller="ProfileTimeline as ctrl") div(ng-repeat="timeline in result.data", tg-timeline-item="timeline") - // - for (var x = 0; x < 3; x++) - // // Simple message for favorites, updates, etc. - // div.activity-simple) - // span.activity-date Yesterday 12.30h - // div.activity-info - // div.profile-contact-picture - // a(href="", title="{{ user.nickname }}") - // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/gerrenlamson/128.jpg", alt="{{ user.nickname }}") - // p - // a(href="", title="See {{ user.nickname }} profile") Jesús Espino - // span has updated the status of the US - // a(href="", title="See #{{ us.id }}{{ us.title }}") #23 Web comercial/Ayuda from "UX" to "UX Done" + - for (var x = 0; x < 3; x++) + // Simple message for favorites, updates, etc. + div.activity-simple + span.activity-date Yesterday 12.30h + div.activity-info + div.profile-contact-picture + a(href="", title="{{ user.nickname }}") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/gerrenlamson/128.jpg", alt="{{ user.nickname }}") + p + a(href="", title="See {{ user.nickname }} profile") Jesús Espino + span has updated the status of the US + a(href="", title="See #{{ us.id }}{{ us.title }}") #23 Web comercial/Ayuda from "UX" to "UX Done" - // // Added comment in us, task or issue. - // div.activity-comment - // span.activity-date 3 days ago - // div.activity-info - // div.profile-contact-picture - // a(href="", title="{{ user.nickname }}") - // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/tonystubblebine/128.jpg", alt="{{ user.nickname }}") - // p - // a(href="", title="See {{ user.nickname }} profile") JuanFrancisco Alcántara - // span has commented in the task - // a(href="", title="See #{{ us.id }}{{ us.title }}") #15 Revisar el contraste de los grises - // div.activity-comment-quote - // p "He subido a GitLab unos wireframes. Echadle un vistazo por favor, a ver si falta algo o tenéis al" + // Added comment in us, task or issue. + div.activity-comment + span.activity-date 3 days ago + div.activity-info + div.profile-contact-picture + a(href="", title="{{ user.nickname }}") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/tonystubblebine/128.jpg", alt="{{ user.nickname }}") + p + a(href="", title="See {{ user.nickname }} profile") JuanFrancisco Alcántara + span has commented in the task + a(href="", title="See #{{ us.id }}{{ us.title }}") #15 Revisar el contraste de los grises + div.activity-comment-quote + p "He subido a GitLab unos wireframes. Echadle un vistazo por favor, a ver si falta algo o tenéis al" - // // Added attachment type image in us, task or issue. - // div.activity-image - // span.activity-date 5 days ago - // div.activity-info - // div.profile-contact-picture - // a(href="", title="{{ user.nickname }}") - // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/jina/128.jpg", alt="{{ user.nickname }}") - // p - // a(href="", title="See {{ user.nickname }} profile") Alejandro Alonso - // span has uploaded an image in the US - // a(href="", title="See #{{ us.id }}{{ us.title }}") US #23 Web comercial/Ayuda - // div.activity-comment-attachment - // p "Eh! Look at this amazing Taiga picture!" - // img(src="https://ununsplash.imgix.net/photo-1423753623104-718aaace6772?q=75&fm=jpg&w=1080&fit=max&s=f655534aa0fe8bae35c687e80a2ed399", alt="{{ attachment.name }}") + // Added attachment type image in us, task or issue. + div.activity-image + span.activity-date 5 days ago + div.activity-info + div.profile-contact-picture + a(href="", title="{{ user.nickname }}") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/jina/128.jpg", alt="{{ user.nickname }}") + p + a(href="", title="See {{ user.nickname }} profile") Alejandro Alonso + span has uploaded an image in the US + a(href="", title="See #{{ us.id }}{{ us.title }}") US #23 Web comercial/Ayuda + div.activity-comment-attachment + p "Eh! Look at this amazing Taiga picture!" + img(src="https://ununsplash.imgix.net/photo-1423753623104-718aaace6772?q=75&fm=jpg&w=1080&fit=max&s=f655534aa0fe8bae35c687e80a2ed399", alt="{{ attachment.name }}") - // // Multiple update message, etc. - // div.activity-notification - // span.activity-date 6 days ago - // div.activity-info - // div.profile-contact-picture - // a(href="", title="{{ user.nickname }}") - // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/idiot/128.jpg", alt="{{ user.nickname }}") - // p - // a(href="", title="See {{ user.nickname }} profile") Jesús Espino - // span closed - // ul.activity-notification-list - // li - // a(href="", title="See #{{ us.id }}{{ us.title }}") US #23 Web comercial/Ayuda - // li - // a(href="", title="See #{{ us.id }}{{ us.title }}") #2156 Search Page UX is hardly understandable - // li - // a(href="", title="See #{{ us.id }}{{ us.title }}") #456 Search for users - // li - // a(href="", title="See #{{ us.id }}{{ us.title }}") #2140 Las notificaciones de cambios están fallando + // Multiple update message, etc. + div.activity-notification + span.activity-date 6 days ago + div.activity-info + div.profile-contact-picture + a(href="", title="{{ user.nickname }}") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/idiot/128.jpg", alt="{{ user.nickname }}") + p + a(href="", title="See {{ user.nickname }} profile") Jesús Espino + span closed + ul.activity-notification-list + li + a(href="", title="See #{{ us.id }}{{ us.title }}") US #23 Web comercial/Ayuda + li + a(href="", title="See #{{ us.id }}{{ us.title }}") #2156 Search Page UX is hardly understandable + li + a(href="", title="See #{{ us.id }}{{ us.title }}") #456 Search for users + li + a(href="", title="See #{{ us.id }}{{ us.title }}") #2140 Las notificaciones de cambios están fallando - // // Added attachment type image in us, task or issue. - // div.activity-member - // span.activity-date a week ago - // div.activity-info - // div.profile-contact-picture - // a(href="", title="{{ organization.nickname }}") - // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/tofslie/128.jpg", alt="{{ organization.nickname }}") - // p - // a(href="", title="See {{ organization.nickname }} profile") Mozilla - // span has a new member - // div.activity-member-view - // div.profile-member-picture - // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/BillSKenney/128.jpg", alt="{{ organization.nickname }}") - // div.activity-member-info - // a(href="", title="See {{ user.nickname }} profile") - // span Andrés González - // p Back-end developer & Stake + // Added attachment type image in us, task or issue. + div.activity-member + span.activity-date a week ago + div.activity-info + div.profile-contact-picture + a(href="", title="{{ organization.nickname }}") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/tofslie/128.jpg", alt="{{ organization.nickname }}") + p + a(href="", title="See {{ organization.nickname }} profile") Mozilla + span has a new member + div.activity-member-view + div.profile-member-picture + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/BillSKenney/128.jpg", alt="{{ organization.nickname }}") + div.activity-member-info + a(href="", title="See {{ user.nickname }} profile") + span Andrés González + p Back-end developer & Stake - // // Added comment in us, task or issue. - // div.activity-project - // span.activity-date a week ago - // div.activity-info - // div.profile-contact-picture - // a(href="", title="{{ organization.nickname }}") - // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/ekvium/128.jpg", alt="{{ organization.nickname }}") - // p - // a(href="", title="See {{ user.nickname }} profile") Redhat - // span has a new project - // a(href="", title="See {{ project.name }}") Nanatubos - // div.activity-comment-quote - // p We plan to build a hundred of so called "telehubs" so people from all over the world can immediately relocate their physical self at any other telehub in microseconds. + // Added comment in us, task or issue. + div.activity-project + span.activity-date a week ago + div.activity-info + div.profile-contact-picture + a(href="", title="{{ organization.nickname }}") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/ekvium/128.jpg", alt="{{ organization.nickname }}") + p + a(href="", title="See {{ user.nickname }} profile") Redhat + span has a new project + a(href="", title="See {{ project.name }}") Nanatubos + div.activity-comment-quote + p We plan to build a hundred of so called "telehubs" so people from all over the world can immediately relocate their physical self at any other telehub in microseconds. diff --git a/app/partials/profile/timeline/comment-timeline.jade b/app/partials/profile/timeline/comment-timeline.jade index 101aabca..13b8dc76 100644 --- a/app/partials/profile/timeline/comment-timeline.jade +++ b/app/partials/profile/timeline/comment-timeline.jade @@ -9,4 +9,4 @@ div.activity-comment span has commented in the {{::timeline.type }} a(href="{{::timeline.detail_url}}", title="See #{{::timeline.ref}} {{::timeline.subject}}") \#{{::timeline.ref}} {{::timeline.subject}} div.activity-comment-quote - p "{{::timeline.data.comment}}" + p(ng-if="timeline.data.comment") "{{::timeline.data.comment}}" diff --git a/app/partials/profile/timeline/timeline-attachment-image.jade b/app/partials/profile/timeline/timeline-attachment-image.jade new file mode 100644 index 00000000..dbdaa186 --- /dev/null +++ b/app/partials/profile/timeline/timeline-attachment-image.jade @@ -0,0 +1,3 @@ +// timeline-attachment directive +div.activity-comment-attachment + img(ng-src="{{::attachment.url}}", alt="{{::attachment.filename}}") \ No newline at end of file diff --git a/app/partials/profile/timeline/timeline-attachment.jade b/app/partials/profile/timeline/timeline-attachment.jade new file mode 100644 index 00000000..94f92d5a --- /dev/null +++ b/app/partials/profile/timeline/timeline-attachment.jade @@ -0,0 +1,12 @@ +div.activity-image + span.activity-date {{::timeline.created_formated}} + div.activity-info + div.profile-contact-picture + a(tg-nav="user-profile:username=timeline.data.user.username", title="{{::timeline.data.user.name }}") + img(ng-src="{{::timeline.data.user.photo}}", alt="{{::timeline.data.user.name}}") + p + a(tg-nav="user-profile:username=timeline.data.user.username", title="See {{::timeline.data.user.name }} profile") {{::timeline.data.user.name}} + span has uploaded an image in the {{::timeline.type }} + a(href="{{::timeline.detail_url}}", title="See #{{::timeline.ref}} {{::timeline.subject}}") \#{{::timeline.ref}} {{::timeline.subject}} + div(ng-repeat="attachment in timeline.data.values_diff.attachments.new") + div(tg-timeline-attachment="attachment") \ No newline at end of file From 8cc04a7f58adc520d26c5110913a60eededdd339 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 14 Apr 2015 12:25:20 +0200 Subject: [PATCH 031/366] timeline items header --- .../common/compile-html.directive.coffee | 13 + .../profile/profileTimeline.controller.coffee | 21 +- .../timeline-attachment.directive.coffee | 2 + .../profile/timeline-item.directive.coffee | 252 ++++++++++++++++-- app/locales/locale-en.json | 32 ++- .../modules/profile/profile-timeline.jade | 2 +- .../profile/timeline/comment-timeline.jade | 12 - .../profile/timeline/timeline-attachment.jade | 15 +- .../profile/timeline/timeline-item.jade | 11 + 9 files changed, 303 insertions(+), 57 deletions(-) create mode 100644 app/coffee/modules/common/compile-html.directive.coffee delete mode 100644 app/partials/profile/timeline/comment-timeline.jade create mode 100644 app/partials/profile/timeline/timeline-item.jade diff --git a/app/coffee/modules/common/compile-html.directive.coffee b/app/coffee/modules/common/compile-html.directive.coffee new file mode 100644 index 00000000..195d6a2c --- /dev/null +++ b/app/coffee/modules/common/compile-html.directive.coffee @@ -0,0 +1,13 @@ +CompileHtmlDirective = ($compile) -> + link = (scope, element, attrs) -> + scope.$watch attrs.tgCompileHtml, (newValue, oldValue) -> + element.html(newValue) + $compile(element.contents())(scope) + + return { + link: link + } + +CompileHtmlDirective.$inject = ["$compile"] + +angular.module("taigaCommon").directive("tgCompileHtml", CompileHtmlDirective) diff --git a/app/coffee/modules/profile/profileTimeline.controller.coffee b/app/coffee/modules/profile/profileTimeline.controller.coffee index ca8c20f0..a8656c55 100644 --- a/app/coffee/modules/profile/profileTimeline.controller.coffee +++ b/app/coffee/modules/profile/profileTimeline.controller.coffee @@ -30,16 +30,33 @@ class ProfileTimelineController extends mixOf(taiga.Controller, taiga.PageMixin, "$tgAuth" ] + valid_fields: ['status', 'subject', 'description', 'assigned_to', 'points', 'severity', 'priority', 'type', 'attachments', 'milestone', 'is_blocked', 'is_iocaine', 'content_diff', 'name', 'estimated_finish', 'estimated_start'] + constructor: (@scope, @rs, @auth) -> promise = @.loadTimeline() promise.then null, @.onInitialDataError.bind(@) + isValid: (values) => + return _.some values, (value) => @valid_fields.indexOf(value) != -1 + + filterValidTimelineItems: (timeline) => + if timeline.data.values_diff + values = Object.keys(timeline.data.values_diff) + + if values && values.length + if !@isValid(values) + return false + else if values[0] == 'attachments' && timeline.data.values_diff.attachments.new.length == 0 + return false + + return true + loadTimeline: () -> user = @auth.getUser() return @rs.timeline.profile(user.id).then (result) => - @scope.result = result - console.log @scope.result.data + console.log result.data + @scope.timelineList = _.filter result.data, @filterValidTimelineItems angular.module("taigaProfile") .controller("ProfileTimeline", ProfileTimelineController) diff --git a/app/coffee/modules/profile/timeline-attachment.directive.coffee b/app/coffee/modules/profile/timeline-attachment.directive.coffee index 7bbe12fa..88669bdd 100644 --- a/app/coffee/modules/profile/timeline-attachment.directive.coffee +++ b/app/coffee/modules/profile/timeline-attachment.directive.coffee @@ -12,6 +12,8 @@ TimelineAttachmentDirective = ($tgTemplate, $compile) -> if is_image template = $tgTemplate.get("profile/timeline/timeline-attachment-image.html") + else + template = $tgTemplate.get("profile/timeline/timeline-attachment.html") $el.html(template) $compile($el.contents())($scope) diff --git a/app/coffee/modules/profile/timeline-item.directive.coffee b/app/coffee/modules/profile/timeline-item.directive.coffee index 6ac4726f..4a3a69b8 100644 --- a/app/coffee/modules/profile/timeline-item.directive.coffee +++ b/app/coffee/modules/profile/timeline-item.directive.coffee @@ -1,4 +1,151 @@ -TimelineItemDirective = ($tgTemplate, $compile, $navUrls) -> +timelineTitle = (timeline, event) -> + title = [ + { # NewMember + check: (timeline, event) -> + return event.obj == 'membership' + key: 'TIMELINE.NEW_MEMBER', + translate_params: ['project_name'] + }, + { # NewProject + check: (timeline, event) -> + return event.obj == 'project' && event.type == 'create' + key: 'TIMELINE.NEW_PROJECT', + translate_params: ['username', 'project_name'] + }, + { # NewAttachment + check: (timeline, event) -> + return event.type == 'change' && timeline.data.values_diff.attachments + key: 'TIMELINE.UPLOAD_ATTACHMENT', + translate_params: ['username', 'obj_name'] + }, + { # NewUs + check: (timeline, event) -> + return event.obj == 'userstory' && event.type == 'create' + key: 'TIMELINE.US_CREATED', + translate_params: ['username', 'project_name', 'obj_name'] + }, + { # NewIssue + check: (timeline, event) -> + return event.obj == 'issue' && event.type == 'create' + key: 'TIMELINE.ISSUE_CREATED', + translate_params: ['username', 'project_name', 'obj_name'] + }, + { # NewWiki + check: (timeline, event) -> + return event.obj == 'wikipage' && event.type == 'create' + key: 'TIMELINE.WIKI_CREATED', + translate_params: ['username', 'project_name', 'obj_name'] + }, + { # NewTask + check: (timeline, event) -> + return event.obj == 'task' && event.type == 'create' + key: 'TIMELINE.TASK_CREATED', + translate_params: ['username', 'project_name', 'obj_name'] + }, + { # NewUsComment + check: (timeline, event) -> + return timeline.data.comment && event.obj == 'userstory' + key: 'TIMELINE.NEW_COMMENT_US', + translate_params: ['username', 'obj_name'] + }, + { # NewIssueComment + check: (timeline, event) -> + return timeline.data.comment && event.obj == 'issue' + key: 'TIMELINE.NEW_COMMENT_ISSUE', + translate_params: ['username', 'obj_name'] + }, + { # NewTask + check: (timeline, event) -> + return timeline.data.comment && event.obj == 'task' + key: 'TIMELINE.NEW_COMMENT_TASK' + translate_params: ['username', 'obj_name'] + }, + { # UsToMilestone + check: (timeline, event, field_name) -> + if field_name == 'milestone' && event.type == 'change' + return timeline.data.values_diff.milestone[0] == null + + return false + key: 'TIMELINE.US_ADDED_MILESTONE', + translate_params: ['username', 'obj_name', 'sprint_name'] + }, + { # UsToBacklog + check: (timeline, event, field_name) -> + if field_name == 'milestone' && event.type == 'change' + return timeline.data.values_diff.milestone[1] == null + + return false + key: 'TIMELINE.US_REMOVED_FROM_MILESTONE', + translate_params: ['username', 'obj_name'] + }, + { # Blocked + check: (timeline, event) -> + if event.type == 'change' && timeline.data.values_diff.is_blocked + return timeline.data.values_diff.is_blocked[1] == true + + return false + key: 'TIMELINE.BLOCKED', + translate_params: ['username', 'obj_name'] + }, + { # UnBlocked + check: (timeline, event) -> + if event.type == 'change' && timeline.data.values_diff.is_blocked + return timeline.data.values_diff.is_blocked[1] == false + + return false + key: 'TIMELINE.UNBLOCKED', + translate_params: ['username', 'obj_name'] + }, + { # MilestoneUpdated + check: (timeline, event) -> + return event.obj == 'milestone' && event.type == 'change' + key: 'TIMELINE.MILESTONE_UPDATED', + translate_params: ['username', 'obj_name'] + }, + { # WikiUpdated + check: (timeline, event) -> + return event.obj == 'wikipage' && event.type == 'change' + key: 'TIMELINE.WIKI_UPDATED', + translate_params: ['username', 'obj_name'] + }, + { # UsUpdated + check: (timeline, event) -> + return event.obj == 'userstory' && event.type == 'change' + key: 'TIMELINE.US_UPDATED', + translate_params: ['username', 'field_name', 'obj_name'] + }, + { # IssueUpdated + check: (timeline, event) -> + return event.obj == 'issue' && event.type == 'change' + key: 'TIMELINE.ISSUE_UPDATED', + translate_params: ['username', 'field_name', 'obj_name'] + }, + { # TaskUpdated + check: (timeline, event) -> + return event.obj == 'task' && event.type == 'change' + key: 'TIMELINE.TASK_UPDATED', + translate_params: ['username', 'field_name', 'obj_name'] + } + ] + + if timeline.data.values_diff + field_name = Object.keys(timeline.data.values_diff)[0] + + return _.find title, (obj) -> + return obj.check(timeline, event, field_name) + +TimelineItemDirective = ($tgTemplate, $compile, $navUrls, $translate, $sce) -> + fieldTranslationKey = { + 'status': 'COMMON.FIELDS.STATUS', + 'subject': 'COMMON.FIELDS.SUBJECT', + 'description': 'COMMON.FIELDS.DESCRIPTION', + 'points': 'COMMON.FIELDS.POINTS', + 'severity': 'ISSUES.FIELDS.SEVERITY', + 'priority': 'ISSUES.FIELDS.PRIORITY', + 'type': 'ISSUES.FIELDS.TYPE', + 'is_iocaine': 'TASK.FIELDS.IS_IOCAINE' + } + parseEventType = (event_type) -> event_type = event_type.split(".") @@ -8,53 +155,100 @@ TimelineItemDirective = ($tgTemplate, $compile, $navUrls) -> type: event_type[2] } - getUrl = (timeline, event) -> + getDetailObjUrl = (event) -> url = { - "issue": "project-issues-detail", - "wiki": "project-wiki-page", - "task": "project-tasks-detail", - "userstories": "project-userstories-detail" + "issue": ["project-issues-detail", ":project=activity.project.slug,ref=activity.obj.ref"], + "wikipage": ["project-wiki-page", ":project=activity.project.slug,slug=activity.obj.slug"], + "task": ["project-tasks-detail", ":project=activity.project.slug,ref=activity.obj.ref"], + "userstory": ["project-userstories-detail", ":project=activity.project.slug,ref=activity.obj.ref"], + "milestone": ["project-taskboard", ":project=activity.project.slug,sprint=activity.obj.slug"] } - params = {project: timeline.data.project.slug, ref: timeline.data[event.obj].ref} + return url[event.obj][0] + url[event.obj][1] - return $navUrls.resolve(url[event.obj], params) + getLink = (url, text, title) -> + title = title || text - getTemplate = (timeline, event) -> - template = "" + return $('') + .attr('tg-nav', url) + .text(text) + .attr('title', title) + .prop('outerHTML') - if event.type == 'change' - if timeline.data.comment.length - template = "profile/timeline/comment-timeline.html" - else if timeline.data.values_diff.attachments - template = "profile/timeline/attachment-timeline.html" + translate_params = { + username: (timeline) -> + user = timeline.data.user + title_attr = $translate.instant('COMMON.SEE_USER_PROFILE', {username: user.username}) + url = 'user-profile:username=activity.user.username' + return getLink(url, user.username, title_attr) - return $tgTemplate.get(template) + field_name: (timeline) -> + field_name = Object.keys(timeline.data.values_diff)[0] + + return $translate.instant(fieldTranslationKey[field_name]) + + project_name: (timeline) -> + url = 'project:project=activity.project.slug' + + return getLink(url, timeline.data.project.name) + + sprint_name: (timeline) -> + url = 'project-taskboard:project=activity.project.slug,sprint=activity.sprint.slug' + + return getLink(url, timeline.data.milestone.name) + + obj_name: (timeline, event) -> + obj = getTimelineObj(timeline, event) + url = getDetailObjUrl(event) + + if event.obj == 'wikipage' + text = obj.slug + else if event.obj == 'milestone' + text = obj.name + else + text = '#' + obj.ref + ' ' + obj.subject + + return getLink(url, text) + } + + getTimelineObj = (timeline, event) -> + return timeline.data[event.obj] + + getParams = (timeline, event, timeline_type) -> + params = {} + + timeline_type.translate_params.forEach (param) -> + params[param] = translate_params[param](timeline, event) + + return params + + getTitle = (timeline, event) -> + type = timelineTitle(timeline, event) + + return $translate.instant(type.key, getParams(timeline, event, type)) link = ($scope, $el, $attrs) -> event = parseEventType($scope.timeline.event_type) - template = getTemplate($scope.timeline, event) - if !template - return "" + $scope.activity = {} - obj = $scope.timeline.data[event.obj] + $scope.activity.obj = getTimelineObj($scope.timeline, event) + $scope.activity.user = $scope.timeline.data.user + $scope.activity.project = $scope.timeline.data.project + $scope.activity.sprint = $scope.timeline.data.milestone + $scope.activity.title = getTitle($scope.timeline, event) + $scope.activity.created_formated = moment($scope.timeline.created).fromNow() - $scope.timeline.subject = obj.subject - $scope.timeline.ref = obj.ref - $scope.timeline.type = event.obj - $scope.timeline.created_formated = moment($scope.timeline.created).fromNow() - $scope.timeline.detail_url = getUrl($scope.timeline, event) - - $el.html(template) - $compile($el.contents())($scope) + if $scope.timeline.data.values_diff?.attachments + $scope.activity.attachments = $scope.timeline.data.values_diff.attachments.new return { link: link + templateUrl: "profile/timeline/timeline-item.html" scope: { timeline: "=tgTimelineItem" } } angular.module("taigaProfile") - .directive("tgTimelineItem", ["$tgTemplate", "$compile", "$tgNavUrls", TimelineItemDirective]) + .directive("tgTimelineItem", ["$tgTemplate", "$compile", "$tgNavUrls", "$translate", "$sce", TimelineItemDirective]) diff --git a/app/locales/locale-en.json b/app/locales/locale-en.json index 0a91ff9c..1f2634d5 100644 --- a/app/locales/locale-en.json +++ b/app/locales/locale-en.json @@ -100,6 +100,7 @@ "SAT": "Sat" } }, + "SEE_USER_PROFILE": "See {{username }} profile", "TAGS": { "PLACEHOLDER": "I'm it! Tag me...", "DELETE": "Delete tag", @@ -383,7 +384,7 @@ "TITLE": "Types", "SUBTITLE": "Specify the types your issues could be", "ISSUE_TITLE": "Issues types", - "ACTION_ADD": "Add new type" + "ACTION_ADD": "Add new type" }, "ROLES": { "SECTION_NAME": "Roles - {{projectName}}", @@ -924,6 +925,11 @@ "TITLE_NEXT_ISSUE": "next issue", "ACTION_DELETE": "Delete issue", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Blocking issue", + "FIELDS": { + "PRIORITY": "Priority", + "SEVERITY": "Severity", + "TYPE": "Type" + }, "CONFIRM_PROMOTE": { "TITLE": "Promote this issue to a new user story", "MESSAGE": "Are you sure you want to create a new US from this Issue?" @@ -1090,5 +1096,29 @@ "LAST_EDIT": "last
edit", "LAST_MODIFICATION": "last modification" } + }, + "TIMELINE": { + "UPLOAD_ATTACHMENT": "{{username}} has uploaded a new attachment in {{obj_name}}", + "US_CREATED": "{{username}} has created a new US in {{project_name}} {{obj_name}}", + "ISSUE_CREATED": "{{username}} has created a new Issue in {{project_name}} {{obj_name}}", + "TASK_CREATED": "{{username}} has created a new Task in {{project_name}} {{obj_name}}", + "WIKI_CREATED": "{{username}} has created a new Wiki page in {{project_name}} {{obj_name}}", + "NEW_PROJECT": "{{username}} has a new project {{project_name}}", + "MILESTONE_UPDATED": "{{username}} has updated the milestone {{obj_name}}", + "US_UPDATED": "{{username}} has updated the field \"{{field_name}}\" of the US {{obj_name}}", + "ISSUE_UPDATED": "{{username}} has updated the field \"{{field_name}}\" of the Issue {{obj_name}}", + "TASK_UPDATED": "{{username}} has updated the field \"{{field_name}}\" of the Task {{obj_name}}", + "WIKI_UPDATED": "{{username}} has update the Wiki page {{obj_name}}", + "NEW_COMMENT_US": "{{username}} has commented in the US {{obj_name}}", + "NEW_COMMENT_ISSUE": "{{username}} has commented in the Issue {{obj_name}}", + "NEW_COMMENT_TASK": "{{username}} has commented in the Task {{obj_name}}", + "NEW_MEMBER": "{{project_name}} has a new member", + "US_ADDED_MILESTONE": "{{username}} has added the US {{obj_name}} to {{sprint_name}}", + "US_REMOVED_FROM_MILESTONE": "{{username}} has added the US {{obj_name}} to the Backlog", + "BLOCKED": "{{username}} has blocked {{obj_name}}", + "UNBLOCKED": "{{username}} has unblocked {{obj_name}}" + }, + "LANGUAGES": { + "ENGLISH": "English" } } diff --git a/app/partials/includes/modules/profile/profile-timeline.jade b/app/partials/includes/modules/profile/profile-timeline.jade index 4580cc0b..09ac8855 100644 --- a/app/partials/includes/modules/profile/profile-timeline.jade +++ b/app/partials/includes/modules/profile/profile-timeline.jade @@ -1,5 +1,5 @@ section.profile-timeline(ng-controller="ProfileTimeline as ctrl") - div(ng-repeat="timeline in result.data", tg-timeline-item="timeline") + div(ng-repeat="timeline in timelineList", tg-timeline-item="timeline") - for (var x = 0; x < 3; x++) // Simple message for favorites, updates, etc. div.activity-simple diff --git a/app/partials/profile/timeline/comment-timeline.jade b/app/partials/profile/timeline/comment-timeline.jade deleted file mode 100644 index 13b8dc76..00000000 --- a/app/partials/profile/timeline/comment-timeline.jade +++ /dev/null @@ -1,12 +0,0 @@ -div.activity-comment - span.activity-date {{::timeline.created_formated}} - div.activity-info - div.profile-contact-picture - a(tg-nav="user-profile:username=timeline.data.user.username", title="{{::timeline.data.user.name }}") - img(ng-src="{{::timeline.data.user.photo}}", alt="{{::timeline.data.user.name}}") - p - a(tg-nav="user-profile:username=timeline.data.user.username", title="See {{::timeline.data.user.name }} profile") {{::timeline.data.user.name}} - span has commented in the {{::timeline.type }} - a(href="{{::timeline.detail_url}}", title="See #{{::timeline.ref}} {{::timeline.subject}}") \#{{::timeline.ref}} {{::timeline.subject}} - div.activity-comment-quote - p(ng-if="timeline.data.comment") "{{::timeline.data.comment}}" diff --git a/app/partials/profile/timeline/timeline-attachment.jade b/app/partials/profile/timeline/timeline-attachment.jade index 94f92d5a..22edecd8 100644 --- a/app/partials/profile/timeline/timeline-attachment.jade +++ b/app/partials/profile/timeline/timeline-attachment.jade @@ -1,12 +1,3 @@ -div.activity-image - span.activity-date {{::timeline.created_formated}} - div.activity-info - div.profile-contact-picture - a(tg-nav="user-profile:username=timeline.data.user.username", title="{{::timeline.data.user.name }}") - img(ng-src="{{::timeline.data.user.photo}}", alt="{{::timeline.data.user.name}}") - p - a(tg-nav="user-profile:username=timeline.data.user.username", title="See {{::timeline.data.user.name }} profile") {{::timeline.data.user.name}} - span has uploaded an image in the {{::timeline.type }} - a(href="{{::timeline.detail_url}}", title="See #{{::timeline.ref}} {{::timeline.subject}}") \#{{::timeline.ref}} {{::timeline.subject}} - div(ng-repeat="attachment in timeline.data.values_diff.attachments.new") - div(tg-timeline-attachment="attachment") \ No newline at end of file +p TODO: ATTACHMENT +p + a(ng-href="attachment.url"){{attachment.filename}} \ No newline at end of file diff --git a/app/partials/profile/timeline/timeline-item.jade b/app/partials/profile/timeline/timeline-item.jade new file mode 100644 index 00000000..da03224a --- /dev/null +++ b/app/partials/profile/timeline/timeline-item.jade @@ -0,0 +1,11 @@ +div.activity-image + span.activity-date {{::activity.created_formated}} + div.activity-info + div.profile-contact-picture + a(tg-nav="user-profile:username=activity.user.username", title="{{::activity.user.name }}") + img(ng-src="{{::activity.user.photo}}", alt="{{::activity.user.name}}") + + p(tg-compile-html="activity.title") + + div(ng-repeat="attachment in activity.attachments") + div(tg-timeline-attachment="attachment") \ No newline at end of file From 0c00ea31ebd67d13883917a8d1486d11f4f2535b Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 14 Apr 2015 15:06:11 +0200 Subject: [PATCH 032/366] timeline item comment --- .../profile/timeline-item.directive.coffee | 45 ++++++++++++++----- app/coffee/utils.coffee | 19 ++++++++ .../profile/timeline/timeline-item.jade | 13 +++++- .../modules/profile/profile-timeline.scss | 42 +++++++++-------- 4 files changed, 84 insertions(+), 35 deletions(-) diff --git a/app/coffee/modules/profile/timeline-item.directive.coffee b/app/coffee/modules/profile/timeline-item.directive.coffee index 4a3a69b8..88e7083f 100644 --- a/app/coffee/modules/profile/timeline-item.directive.coffee +++ b/app/coffee/modules/profile/timeline-item.directive.coffee @@ -1,16 +1,23 @@ -timelineTitle = (timeline, event) -> - title = [ +timelineType = (timeline, event) -> + types = [ { # NewMember check: (timeline, event) -> return event.obj == 'membership' key: 'TIMELINE.NEW_MEMBER', translate_params: ['project_name'] + member: (timeline) -> + return { + user: timeline.data.user, + role: timeline.data.role + } }, { # NewProject check: (timeline, event) -> return event.obj == 'project' && event.type == 'create' key: 'TIMELINE.NEW_PROJECT', - translate_params: ['username', 'project_name'] + translate_params: ['username', 'project_name'], + description: (timeline) -> + return timeline.data.project.description }, { # NewAttachment check: (timeline, event) -> @@ -46,19 +53,26 @@ timelineTitle = (timeline, event) -> check: (timeline, event) -> return timeline.data.comment && event.obj == 'userstory' key: 'TIMELINE.NEW_COMMENT_US', - translate_params: ['username', 'obj_name'] + translate_params: ['username', 'obj_name'], + description: (timeline) -> + return taiga.stripTags(timeline.data.comment_html, 'br|p') }, { # NewIssueComment check: (timeline, event) -> return timeline.data.comment && event.obj == 'issue' key: 'TIMELINE.NEW_COMMENT_ISSUE', - translate_params: ['username', 'obj_name'] + translate_params: ['username', 'obj_name'], + description: (timeline) -> + text = taiga.replaceTags(timeline.data.comment_html, 'h1|h2|h3', 'p') + return taiga.stripTags(text, 'br|p') }, { # NewTask check: (timeline, event) -> return timeline.data.comment && event.obj == 'task' key: 'TIMELINE.NEW_COMMENT_TASK' - translate_params: ['username', 'obj_name'] + translate_params: ['username', 'obj_name'], + description: (timeline) -> + return taiga.stripTags(timeline.data.comment_html, 'br|p') }, { # UsToMilestone check: (timeline, event, field_name) -> @@ -85,7 +99,9 @@ timelineTitle = (timeline, event) -> return false key: 'TIMELINE.BLOCKED', - translate_params: ['username', 'obj_name'] + translate_params: ['username', 'obj_name'], + description: (timeline) -> + return taiga.stripTags(timeline.data.values_diff.blocked_note_html[1], 'br') }, { # UnBlocked check: (timeline, event) -> @@ -131,7 +147,7 @@ timelineTitle = (timeline, event) -> if timeline.data.values_diff field_name = Object.keys(timeline.data.values_diff)[0] - return _.find title, (obj) -> + return _.find types, (obj) -> return obj.check(timeline, event, field_name) TimelineItemDirective = ($tgTemplate, $compile, $navUrls, $translate, $sce) -> @@ -222,13 +238,12 @@ TimelineItemDirective = ($tgTemplate, $compile, $navUrls, $translate, $sce) -> return params - getTitle = (timeline, event) -> - type = timelineTitle(timeline, event) - + getTitle = (timeline, event, type) -> return $translate.instant(type.key, getParams(timeline, event, type)) link = ($scope, $el, $attrs) -> event = parseEventType($scope.timeline.event_type) + type = timelineType($scope.timeline, event) $scope.activity = {} @@ -236,9 +251,15 @@ TimelineItemDirective = ($tgTemplate, $compile, $navUrls, $translate, $sce) -> $scope.activity.user = $scope.timeline.data.user $scope.activity.project = $scope.timeline.data.project $scope.activity.sprint = $scope.timeline.data.milestone - $scope.activity.title = getTitle($scope.timeline, event) + $scope.activity.title = getTitle($scope.timeline, event, type) $scope.activity.created_formated = moment($scope.timeline.created).fromNow() + if type.description + $scope.activity.description = $sce.trustAsHtml(type.description($scope.timeline)) + + if type.member + $scope.activity.member = type.member($scope.timeline) + if $scope.timeline.data.values_diff?.attachments $scope.activity.attachments = $scope.timeline.data.values_diff.attachments.new diff --git a/app/coffee/utils.coffee b/app/coffee/utils.coffee index dc399ab8..7a0ed706 100644 --- a/app/coffee/utils.coffee +++ b/app/coffee/utils.coffee @@ -140,6 +140,23 @@ sizeFormat = (input, precision=1) -> size = (input / Math.pow(1024, number)).toFixed(precision) return "#{size} #{units[number]}" +stripTags = (str, exception) -> + if exception + pattern = new RegExp('<(?!' + exception + '\s*\/?)[^>]+>', 'gi') + return String(str).replace(pattern, '') + else + return String(str).replace(/<\/?[^>]+>/g, '') + +replaceTags = (str, tags, replace) -> + # open tag + pattern = new RegExp('<(' + tags + ')>', 'gi') + str = str.replace(pattern, '<' + replace + '>') + + # close tag + pattern = new RegExp('<\/(' + tags + ')>', 'gi') + str = str.replace(pattern, '') + + return str taiga = @.taiga taiga.nl2br = nl2br @@ -160,3 +177,5 @@ taiga.debounce = debounce taiga.debounceLeading = debounceLeading taiga.startswith = startswith taiga.sizeFormat = sizeFormat +taiga.stripTags = stripTags +taiga.replaceTags = replaceTags diff --git a/app/partials/profile/timeline/timeline-item.jade b/app/partials/profile/timeline/timeline-item.jade index da03224a..ebf30734 100644 --- a/app/partials/profile/timeline/timeline-item.jade +++ b/app/partials/profile/timeline/timeline-item.jade @@ -7,5 +7,16 @@ div.activity-image p(tg-compile-html="activity.title") - div(ng-repeat="attachment in activity.attachments") + .activity-comment-quote(ng-if="::activity.description") + p(ng-bind-html="activity.description") + + .activity-member-view(ng-if="::activity.member") + .profile-member-picture + img(ng-src="{{::activity.member.user.photo}}", alt="{{::activity.member.user.name}}") + .activity-member-info + a(tg-nav="user-profile:username=activity.member.user.username", title="{{::activity.member.user.name }}") + span {{::activity.member.user.name}} + p {{::activity.member.role.name}} + +div(ng-repeat="attachment in activity.attachments") div(tg-timeline-attachment="attachment") \ No newline at end of file diff --git a/app/styles/modules/profile/profile-timeline.scss b/app/styles/modules/profile/profile-timeline.scss index ebd93633..fcb51bd6 100644 --- a/app/styles/modules/profile/profile-timeline.scss +++ b/app/styles/modules/profile/profile-timeline.scss @@ -69,29 +69,27 @@ } } } - .activity-member { - .activity-member-view { - display: flex; - margin-bottom: .5rem; - margin-left: calc(35px + .5rem); - margin-top: .5rem; - .activity-member-info { - flex: 1; - } - a { - @extend %bold; - } - p { - color: $gray-light; - } + .activity-member-view { + display: flex; + margin-bottom: .5rem; + margin-left: calc(35px + .5rem); + margin-top: .5rem; + .activity-member-info { + flex: 1; } - .profile-member-picture { - img { - margin-right: 1rem; - max-width: 64px; - min-width: 32px; - width: 100%; - } + a { + @extend %bold; + } + p { + color: $gray-light; + } + } + .profile-member-picture { + img { + margin-right: 1rem; + max-width: 64px; + min-width: 32px; + width: 100%; } } } From 8f70c26165eff22c19ee2e818e81aced636f01cb Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 15 Apr 2015 08:55:34 +0200 Subject: [PATCH 033/366] timeline infinite scroll --- app/coffee/app.coffee | 3 +- ...fee => profile-timeline.controller.coffee} | 22 +++-- app/coffee/modules/resources/timeline.coffee | 4 +- .../modules/profile/profile-timeline.jade | 96 +------------------ bower.json | 3 +- gulpfile.js | 1 + 6 files changed, 22 insertions(+), 107 deletions(-) rename app/coffee/modules/profile/{profileTimeline.controller.coffee => profile-timeline.controller.coffee} (80%) diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 1de71f3f..30bc4e3e 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -345,7 +345,8 @@ modules = [ # Vendor modules "ngRoute", "ngAnimate", - "pascalprecht.translate" + "pascalprecht.translate", + "infinite-scroll" ].concat(_.map(@.taigaContribPlugins, (plugin) -> plugin.module)) # Main module definition diff --git a/app/coffee/modules/profile/profileTimeline.controller.coffee b/app/coffee/modules/profile/profile-timeline.controller.coffee similarity index 80% rename from app/coffee/modules/profile/profileTimeline.controller.coffee rename to app/coffee/modules/profile/profile-timeline.controller.coffee index a8656c55..acaaa365 100644 --- a/app/coffee/modules/profile/profileTimeline.controller.coffee +++ b/app/coffee/modules/profile/profile-timeline.controller.coffee @@ -25,18 +25,17 @@ mixOf = @.taiga.mixOf class ProfileTimelineController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.FiltersMixin) @.$inject = [ - "$scope", "$tgResources", "$tgAuth" ] valid_fields: ['status', 'subject', 'description', 'assigned_to', 'points', 'severity', 'priority', 'type', 'attachments', 'milestone', 'is_blocked', 'is_iocaine', 'content_diff', 'name', 'estimated_finish', 'estimated_start'] - constructor: (@scope, @rs, @auth) -> - promise = @.loadTimeline() - promise.then null, @.onInitialDataError.bind(@) + constructor: (@rs, @auth) -> + @timelineList = [] + @pagination = {page: 1} - isValid: (values) => + isValidField: (values) => return _.some values, (value) => @valid_fields.indexOf(value) != -1 filterValidTimelineItems: (timeline) => @@ -44,7 +43,7 @@ class ProfileTimelineController extends mixOf(taiga.Controller, taiga.PageMixin, values = Object.keys(timeline.data.values_diff) if values && values.length - if !@isValid(values) + if !@isValidField(values) return false else if values[0] == 'attachments' && timeline.data.values_diff.attachments.new.length == 0 return false @@ -54,9 +53,14 @@ class ProfileTimelineController extends mixOf(taiga.Controller, taiga.PageMixin, loadTimeline: () -> user = @auth.getUser() - return @rs.timeline.profile(user.id).then (result) => - console.log result.data - @scope.timelineList = _.filter result.data, @filterValidTimelineItems + @loadingData = true + + return @rs.timeline.profile(user.id, @pagination).then (result) => + newTimelineList = _.filter result.data, @filterValidTimelineItems + + @timelineList = @timelineList.concat(newTimelineList) + @pagination.page++ + @loadingData = false angular.module("taigaProfile") .controller("ProfileTimeline", ProfileTimelineController) diff --git a/app/coffee/modules/resources/timeline.coffee b/app/coffee/modules/resources/timeline.coffee index abbff0ae..61c91805 100644 --- a/app/coffee/modules/resources/timeline.coffee +++ b/app/coffee/modules/resources/timeline.coffee @@ -24,8 +24,8 @@ taiga = @.taiga resourceProvider = ($repo) -> service = {} - service.profile = (userId) -> - return $repo.queryOnePaginatedRaw("timeline-profile", userId) + service.profile = (userId, params) -> + return $repo.queryOnePaginatedRaw("timeline-profile", userId, params) return (instance) -> instance.timeline = service diff --git a/app/partials/includes/modules/profile/profile-timeline.jade b/app/partials/includes/modules/profile/profile-timeline.jade index 09ac8855..8bc2c435 100644 --- a/app/partials/includes/modules/profile/profile-timeline.jade +++ b/app/partials/includes/modules/profile/profile-timeline.jade @@ -1,95 +1,3 @@ section.profile-timeline(ng-controller="ProfileTimeline as ctrl") - div(ng-repeat="timeline in timelineList", tg-timeline-item="timeline") - - for (var x = 0; x < 3; x++) - // Simple message for favorites, updates, etc. - div.activity-simple - span.activity-date Yesterday 12.30h - div.activity-info - div.profile-contact-picture - a(href="", title="{{ user.nickname }}") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/gerrenlamson/128.jpg", alt="{{ user.nickname }}") - p - a(href="", title="See {{ user.nickname }} profile") Jesús Espino - span has updated the status of the US - a(href="", title="See #{{ us.id }}{{ us.title }}") #23 Web comercial/Ayuda from "UX" to "UX Done" - - // Added comment in us, task or issue. - div.activity-comment - span.activity-date 3 days ago - div.activity-info - div.profile-contact-picture - a(href="", title="{{ user.nickname }}") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/tonystubblebine/128.jpg", alt="{{ user.nickname }}") - p - a(href="", title="See {{ user.nickname }} profile") JuanFrancisco Alcántara - span has commented in the task - a(href="", title="See #{{ us.id }}{{ us.title }}") #15 Revisar el contraste de los grises - div.activity-comment-quote - p "He subido a GitLab unos wireframes. Echadle un vistazo por favor, a ver si falta algo o tenéis al" - - // Added attachment type image in us, task or issue. - div.activity-image - span.activity-date 5 days ago - div.activity-info - div.profile-contact-picture - a(href="", title="{{ user.nickname }}") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/jina/128.jpg", alt="{{ user.nickname }}") - p - a(href="", title="See {{ user.nickname }} profile") Alejandro Alonso - span has uploaded an image in the US - a(href="", title="See #{{ us.id }}{{ us.title }}") US #23 Web comercial/Ayuda - div.activity-comment-attachment - p "Eh! Look at this amazing Taiga picture!" - img(src="https://ununsplash.imgix.net/photo-1423753623104-718aaace6772?q=75&fm=jpg&w=1080&fit=max&s=f655534aa0fe8bae35c687e80a2ed399", alt="{{ attachment.name }}") - - // Multiple update message, etc. - div.activity-notification - span.activity-date 6 days ago - div.activity-info - div.profile-contact-picture - a(href="", title="{{ user.nickname }}") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/idiot/128.jpg", alt="{{ user.nickname }}") - p - a(href="", title="See {{ user.nickname }} profile") Jesús Espino - span closed - ul.activity-notification-list - li - a(href="", title="See #{{ us.id }}{{ us.title }}") US #23 Web comercial/Ayuda - li - a(href="", title="See #{{ us.id }}{{ us.title }}") #2156 Search Page UX is hardly understandable - li - a(href="", title="See #{{ us.id }}{{ us.title }}") #456 Search for users - li - a(href="", title="See #{{ us.id }}{{ us.title }}") #2140 Las notificaciones de cambios están fallando - - // Added attachment type image in us, task or issue. - div.activity-member - span.activity-date a week ago - div.activity-info - div.profile-contact-picture - a(href="", title="{{ organization.nickname }}") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/tofslie/128.jpg", alt="{{ organization.nickname }}") - p - a(href="", title="See {{ organization.nickname }} profile") Mozilla - span has a new member - div.activity-member-view - div.profile-member-picture - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/BillSKenney/128.jpg", alt="{{ organization.nickname }}") - div.activity-member-info - a(href="", title="See {{ user.nickname }} profile") - span Andrés González - p Back-end developer & Stake - - // Added comment in us, task or issue. - div.activity-project - span.activity-date a week ago - div.activity-info - div.profile-contact-picture - a(href="", title="{{ organization.nickname }}") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/ekvium/128.jpg", alt="{{ organization.nickname }}") - p - a(href="", title="See {{ user.nickname }} profile") Redhat - span has a new project - a(href="", title="See {{ project.name }}") Nanatubos - div.activity-comment-quote - p We plan to build a hundred of so called "telehubs" so people from all over the world can immediately relocate their physical self at any other telehub in microseconds. + div(infinite-scroll="ctrl.loadTimeline()", infinite-scroll-distance="3", infinite-scroll-disabled='ctrl.loadingData') + div(ng-repeat="timeline in ctrl.timelineList", tg-timeline-item="timeline") diff --git a/bower.json b/bower.json index 3b1f68c8..6ea6daad 100644 --- a/bower.json +++ b/bower.json @@ -76,7 +76,8 @@ "l.js": "~0.1.0", "angular-translate": "~2.6.1", "angular-translate-loader-static-files": "~2.6.1", - "angular-translate-interpolation-messageformat": "~2.6.1" + "angular-translate-interpolation-messageformat": "~2.6.1", + "ngInfiniteScroll": "1.0.0" }, "resolutions": { "lodash": "~2.4.1", diff --git a/gulpfile.js b/gulpfile.js index f7e83029..3838ea8a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -141,6 +141,7 @@ paths.libs = [ paths.vendor + "raven-js/dist/raven.js", paths.vendor + "l.js/l.js", paths.vendor + "messageformat/locale/*.js", + paths.vendor + "ngInfiniteScroll/build/ng-infinite-scroll.js", paths.app + "js/jquery.ui.git-custom.js", paths.app + "js/jquery-ui.drag-multiple-custom.js", paths.app + "js/jquery.ui.touch-punch.min.js", From 67fb2a6dd0e2598441f0f3120e6de2328ad98b1c Mon Sep 17 00:00:00 2001 From: Juanfran Date: Mon, 20 Apr 2015 15:22:46 +0200 Subject: [PATCH 034/366] profile timeline with the new front architecture --- app/coffee/app.coffee | 2 +- app/coffee/modules/profile.coffee | 22 -- app/coffee/modules/profile/main.coffee | 24 -- .../profile/profile-tabs.directive.coffee | 26 -- .../timeline-attachment.directive.coffee | 29 --- .../profile/includes}/profile-bar.jade | 0 .../profile/includes}/profile-contacts.jade | 0 .../profile/includes}/profile-favorites.jade | 0 .../profile/includes}/profile-projects.jade | 0 .../profile/includes}/profile-sidebar.jade | 0 .../profile/includes}/profile-timeline.jade | 2 +- .../profile-tab}/profile-tab.directive.coffee | 0 .../profile-tabs.controller.coffee | 14 + .../profile-tabs.controller.spec.coffee | 41 +++ .../profile-tabs.directive.coffee | 11 + .../profile/profile-tabs/profile-tabs.jade | 7 + .../profile-timeline-attachment-image.jade} | 0 ...ofile-timeline-attachment.directive.coffee | 34 +++ ...-timeline-attachment.directive.spec.coffee | 58 +++++ .../profile-timeline-attachment.jade | 3 + ...profile-timeline-item-title.service.coffee | 90 +++++++ ...le-timeline-item-title.service.spec.coffee | 244 ++++++++++++++++++ ...profile-timeline-item-type.service.coffee} | 124 +-------- ...ile-timeline-item-type.service.spec.coffee | 31 +++ .../profile-timeline-item.controller.coffee | 40 +++ ...ofile-timeline-item.controller.spec.coffee | 101 ++++++++ .../profile-timeline-item.directive.coffee | 13 + .../profile-timeline-item.jade | 22 ++ .../profile-timeline.controller.coffee | 45 +++- .../profile-timeline.controller.spec.coffee | 116 +++++++++ .../profile-timeline}/profile-timeline.scss | 0 .../profile/profile.jade} | 14 +- app/modules/profile/profile.module.coffee | 1 + .../layout => modules/profile}/profile.scss | 0 .../profile/styles}/profile-bar.scss | 0 .../profile/styles}/profile-contacts.scss | 0 .../profile/styles}/profile-content-tabs.scss | 0 .../profile/styles}/profile-favorites.scss | 0 .../profile/styles}/profile-projects.scss | 0 .../profile/styles}/profile-sidebar.scss | 0 app/partials/profile/profile-tabs.jade | 7 - .../profile/timeline/timeline-attachment.jade | 3 - .../profile/timeline/timeline-item.jade | 22 -- gulpfile.js | 3 +- 44 files changed, 871 insertions(+), 278 deletions(-) delete mode 100644 app/coffee/modules/profile.coffee delete mode 100644 app/coffee/modules/profile/main.coffee delete mode 100644 app/coffee/modules/profile/profile-tabs.directive.coffee delete mode 100644 app/coffee/modules/profile/timeline-attachment.directive.coffee rename app/{partials/includes/modules/profile => modules/profile/includes}/profile-bar.jade (100%) rename app/{partials/includes/modules/profile => modules/profile/includes}/profile-contacts.jade (100%) rename app/{partials/includes/modules/profile => modules/profile/includes}/profile-favorites.jade (100%) rename app/{partials/includes/modules/profile => modules/profile/includes}/profile-projects.jade (100%) rename app/{partials/includes/modules/profile => modules/profile/includes}/profile-sidebar.jade (100%) rename app/{partials/includes/modules/profile => modules/profile/includes}/profile-timeline.jade (67%) rename app/{coffee/modules/profile => modules/profile/profile-tab}/profile-tab.directive.coffee (100%) create mode 100644 app/modules/profile/profile-tabs/profile-tabs.controller.coffee create mode 100644 app/modules/profile/profile-tabs/profile-tabs.controller.spec.coffee create mode 100644 app/modules/profile/profile-tabs/profile-tabs.directive.coffee create mode 100644 app/modules/profile/profile-tabs/profile-tabs.jade rename app/{partials/profile/timeline/timeline-attachment-image.jade => modules/profile/profile-timeline-attachment/profile-timeline-attachment-image.jade} (100%) create mode 100644 app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.directive.coffee create mode 100644 app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.directive.spec.coffee create mode 100644 app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.jade create mode 100644 app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.coffee create mode 100644 app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.spec.coffee rename app/{coffee/modules/profile/timeline-item.directive.coffee => modules/profile/profile-timeline-item/profile-timeline-item-type.service.coffee} (58%) create mode 100644 app/modules/profile/profile-timeline-item/profile-timeline-item-type.service.spec.coffee create mode 100644 app/modules/profile/profile-timeline-item/profile-timeline-item.controller.coffee create mode 100644 app/modules/profile/profile-timeline-item/profile-timeline-item.controller.spec.coffee create mode 100644 app/modules/profile/profile-timeline-item/profile-timeline-item.directive.coffee create mode 100644 app/modules/profile/profile-timeline-item/profile-timeline-item.jade rename app/{coffee/modules/profile => modules/profile/profile-timeline}/profile-timeline.controller.coffee (62%) create mode 100644 app/modules/profile/profile-timeline/profile-timeline.controller.spec.coffee rename app/{styles/modules/profile => modules/profile/profile-timeline}/profile-timeline.scss (100%) rename app/{partials/profile/public-profile.jade => modules/profile/profile.jade} (54%) create mode 100644 app/modules/profile/profile.module.coffee rename app/{styles/layout => modules/profile}/profile.scss (100%) rename app/{styles/modules/profile => modules/profile/styles}/profile-bar.scss (100%) rename app/{styles/modules/profile => modules/profile/styles}/profile-contacts.scss (100%) rename app/{styles/modules/profile => modules/profile/styles}/profile-content-tabs.scss (100%) rename app/{styles/modules/profile => modules/profile/styles}/profile-favorites.scss (100%) rename app/{styles/modules/profile => modules/profile/styles}/profile-projects.scss (100%) rename app/{styles/modules/profile => modules/profile/styles}/profile-sidebar.scss (100%) delete mode 100644 app/partials/profile/profile-tabs.jade delete mode 100644 app/partials/profile/timeline/timeline-attachment.jade delete mode 100644 app/partials/profile/timeline/timeline-item.jade diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 30bc4e3e..efacc87a 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -140,7 +140,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven # User profile $routeProvider.when("/profile", - {templateUrl: "profile/public-profile.html"}) + {templateUrl: "profile/profile.html"}) # Auth $routeProvider.when("/login", diff --git a/app/coffee/modules/profile.coffee b/app/coffee/modules/profile.coffee deleted file mode 100644 index a32f6001..00000000 --- a/app/coffee/modules/profile.coffee +++ /dev/null @@ -1,22 +0,0 @@ -### -# 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/team.coffee -### - -module = angular.module("taigaProfile", []) diff --git a/app/coffee/modules/profile/main.coffee b/app/coffee/modules/profile/main.coffee deleted file mode 100644 index 6a97f450..00000000 --- a/app/coffee/modules/profile/main.coffee +++ /dev/null @@ -1,24 +0,0 @@ -### -# 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/team/main.coffee -### - -taiga = @.taiga - -module = angular.module("taigaProfile") diff --git a/app/coffee/modules/profile/profile-tabs.directive.coffee b/app/coffee/modules/profile/profile-tabs.directive.coffee deleted file mode 100644 index 9df742dd..00000000 --- a/app/coffee/modules/profile/profile-tabs.directive.coffee +++ /dev/null @@ -1,26 +0,0 @@ -class ProfileTabsController - constructor: (@scope) -> - @scope.tabs = [] - - addTab: (tab) -> - @scope.tabs.push(tab) - -ProfileTabsController.$inject = ["$scope"] - -ProfileTabsDirective = () -> - link = ($scope, $el, $attrs) -> - $scope.toggleTab = (tab) -> - _.map $scope.tabs, (tab) => tab.active = false - - tab.active = true - - return { - scope: {} - controller: ProfileTabsController - templateUrl: "profile/profile-tabs.html" - link: link - transclude: true - } - -angular.module("taigaProfile") - .directive("tgProfileTabs", ProfileTabsDirective) diff --git a/app/coffee/modules/profile/timeline-attachment.directive.coffee b/app/coffee/modules/profile/timeline-attachment.directive.coffee deleted file mode 100644 index 88669bdd..00000000 --- a/app/coffee/modules/profile/timeline-attachment.directive.coffee +++ /dev/null @@ -1,29 +0,0 @@ -TimelineAttachmentDirective = ($tgTemplate, $compile) -> - validFileExtensions = [".jpg", ".jpeg", ".bmp", ".gif", ".png"] - - isImage = (url) -> - url = url.toLowerCase() - - return _.some validFileExtensions, (extension) => - return url.indexOf(extension, url - extension.length) != -1 - - link = ($scope, $el, $attrs) -> - is_image = isImage($scope.attachment.url) - - if is_image - template = $tgTemplate.get("profile/timeline/timeline-attachment-image.html") - else - template = $tgTemplate.get("profile/timeline/timeline-attachment.html") - - $el.html(template) - $compile($el.contents())($scope) - - return { - link: link - scope: { - attachment: "=tgTimelineAttachment" - } - } - -angular.module("taigaProfile") - .directive("tgTimelineAttachment", ["$tgTemplate", "$compile", TimelineAttachmentDirective]) diff --git a/app/partials/includes/modules/profile/profile-bar.jade b/app/modules/profile/includes/profile-bar.jade similarity index 100% rename from app/partials/includes/modules/profile/profile-bar.jade rename to app/modules/profile/includes/profile-bar.jade diff --git a/app/partials/includes/modules/profile/profile-contacts.jade b/app/modules/profile/includes/profile-contacts.jade similarity index 100% rename from app/partials/includes/modules/profile/profile-contacts.jade rename to app/modules/profile/includes/profile-contacts.jade diff --git a/app/partials/includes/modules/profile/profile-favorites.jade b/app/modules/profile/includes/profile-favorites.jade similarity index 100% rename from app/partials/includes/modules/profile/profile-favorites.jade rename to app/modules/profile/includes/profile-favorites.jade diff --git a/app/partials/includes/modules/profile/profile-projects.jade b/app/modules/profile/includes/profile-projects.jade similarity index 100% rename from app/partials/includes/modules/profile/profile-projects.jade rename to app/modules/profile/includes/profile-projects.jade diff --git a/app/partials/includes/modules/profile/profile-sidebar.jade b/app/modules/profile/includes/profile-sidebar.jade similarity index 100% rename from app/partials/includes/modules/profile/profile-sidebar.jade rename to app/modules/profile/includes/profile-sidebar.jade diff --git a/app/partials/includes/modules/profile/profile-timeline.jade b/app/modules/profile/includes/profile-timeline.jade similarity index 67% rename from app/partials/includes/modules/profile/profile-timeline.jade rename to app/modules/profile/includes/profile-timeline.jade index 8bc2c435..7ec2c7ee 100644 --- a/app/partials/includes/modules/profile/profile-timeline.jade +++ b/app/modules/profile/includes/profile-timeline.jade @@ -1,3 +1,3 @@ section.profile-timeline(ng-controller="ProfileTimeline as ctrl") div(infinite-scroll="ctrl.loadTimeline()", infinite-scroll-distance="3", infinite-scroll-disabled='ctrl.loadingData') - div(ng-repeat="timeline in ctrl.timelineList", tg-timeline-item="timeline") + div(ng-repeat="timeline in ctrl.timelineList", tg-profile-timeline-item="timeline") diff --git a/app/coffee/modules/profile/profile-tab.directive.coffee b/app/modules/profile/profile-tab/profile-tab.directive.coffee similarity index 100% rename from app/coffee/modules/profile/profile-tab.directive.coffee rename to app/modules/profile/profile-tab/profile-tab.directive.coffee diff --git a/app/modules/profile/profile-tabs/profile-tabs.controller.coffee b/app/modules/profile/profile-tabs/profile-tabs.controller.coffee new file mode 100644 index 00000000..00ac82f5 --- /dev/null +++ b/app/modules/profile/profile-tabs/profile-tabs.controller.coffee @@ -0,0 +1,14 @@ +class ProfileTabsController + constructor: () -> + @tabs = [] + + addTab: (tab) -> + @tabs.push(tab) + + toggleTab: (tab) -> + _.map @tabs, (tab) -> tab.active = false + + tab.active = true + +angular.module("taigaProfile") + .controller("ProfileTabs", ProfileTabsController) diff --git a/app/modules/profile/profile-tabs/profile-tabs.controller.spec.coffee b/app/modules/profile/profile-tabs/profile-tabs.controller.spec.coffee new file mode 100644 index 00000000..59312095 --- /dev/null +++ b/app/modules/profile/profile-tabs/profile-tabs.controller.spec.coffee @@ -0,0 +1,41 @@ +describe "ProfileTabsController", -> + myCtrl = scope = null + + beforeEach -> + module "taigaProfile" + + inject ($controller) -> + scope = {} + myCtrl = $controller "ProfileTabs", + $scope: scope + + it "tabs should be an array", () -> + expect(myCtrl.tabs).is.an("array") + + it "add new tab", () -> + tab = {"fakeTab": true} + + myCtrl.addTab(tab) + + expect(myCtrl.tabs[0]).to.be.eql(tab) + + it "toggleTab, mark the tab passed as parameter to active", () -> + fakeTabs = [ + {id: 1}, + {id: 2}, + {id: 3} + ] + + myCtrl.tabs = fakeTabs + + myCtrl.toggleTab(fakeTabs[1]) + + expect(myCtrl.tabs[0].active).to.be.false + expect(myCtrl.tabs[1].active).to.be.true + expect(myCtrl.tabs[2].active).to.be.false + + myCtrl.toggleTab(fakeTabs[0]) + + expect(myCtrl.tabs[0].active).to.be.true + expect(myCtrl.tabs[1].active).to.be.false + expect(myCtrl.tabs[2].active).to.be.false diff --git a/app/modules/profile/profile-tabs/profile-tabs.directive.coffee b/app/modules/profile/profile-tabs/profile-tabs.directive.coffee new file mode 100644 index 00000000..999e2900 --- /dev/null +++ b/app/modules/profile/profile-tabs/profile-tabs.directive.coffee @@ -0,0 +1,11 @@ +ProfileTabsDirective = () -> + return { + scope: {} + controller: "ProfileTabs" + controllerAs: "vm" + templateUrl: "profile/profile-tabs/profile-tabs.html" + transclude: true + } + +angular.module("taigaProfile") + .directive("tgProfileTabs", ProfileTabsDirective) diff --git a/app/modules/profile/profile-tabs/profile-tabs.jade b/app/modules/profile/profile-tabs/profile-tabs.jade new file mode 100644 index 00000000..727a4d3e --- /dev/null +++ b/app/modules/profile/profile-tabs/profile-tabs.jade @@ -0,0 +1,7 @@ +div + nav.profile-content-tabs + a.tab(ng-repeat="tab in vm.tabs", href="", title="{{::tab.title}}", ng-class="{active: tab.active}" ng-click="vm.toggleTab(tab)") + span.icon(ng-class="::tab.icon") + span {{::tab.name}} + + ng-transclude \ No newline at end of file diff --git a/app/partials/profile/timeline/timeline-attachment-image.jade b/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment-image.jade similarity index 100% rename from app/partials/profile/timeline/timeline-attachment-image.jade rename to app/modules/profile/profile-timeline-attachment/profile-timeline-attachment-image.jade diff --git a/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.directive.coffee b/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.directive.coffee new file mode 100644 index 00000000..268bcd73 --- /dev/null +++ b/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.directive.coffee @@ -0,0 +1,34 @@ +ProfileTimelineAttachmentDirective = (template, $compile) -> + validFileExtensions = [".jpg", ".jpeg", ".bmp", ".gif", ".png"] + + isImage = (url) -> + url = url.toLowerCase() + + return _.some validFileExtensions, (extension) -> + return url.indexOf(extension, url - extension.length) != -1 + + link = (scope, el) -> + is_image = isImage(scope.attachment.url) + + if is_image + template = template.get("profile/profile-timeline-attachment/profile-timeline-attachment-image.html") + else + template = template.get("profile/profile-timeline-attachment/profile-timeline-attachment.html") + + el.html(template) + $compile(el.contents())(scope) + + return { + link: link + scope: { + attachment: "=tgProfileTimelineAttachment" + } + } + +ProfileTimelineAttachmentDirective.$inject = [ + "$tgTemplate", + "$compile" +] + +angular.module("taigaProfile") + .directive("tgProfileTimelineAttachment", ProfileTimelineAttachmentDirective) diff --git a/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.directive.spec.coffee b/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.directive.spec.coffee new file mode 100644 index 00000000..c2e24a9d --- /dev/null +++ b/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.directive.spec.coffee @@ -0,0 +1,58 @@ +describe "profileTimelineAttachmentDirective", () -> + element = scope = compile = provide = null + mockTgTemplate = null + template = "
" + + _mockTgTemplate= () -> + mockTgTemplate = { + get: sinon.stub() + } + + provide.value "$tgTemplate", mockTgTemplate + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgTemplate() + + return null + + createDirective = () -> + elm = compile(template)(scope) + + return elm + + beforeEach -> + module "taigaProfile" + + _mocks() + + inject ($rootScope, $compile) -> + scope = $rootScope.$new() + compile = $compile + + it "attachment image template", () -> + scope.attachment = { + url: "path/path/file.jpg" + } + + mockTgTemplate.get + .withArgs("profile/profile-timeline-attachment/profile-timeline-attachment-image.html") + .returns("
") + + elm = createDirective() + + expect(elm.find('#image')).to.have.length(1) + + it "attachment file template", () -> + scope.attachment = { + url: "path/path/file.pdf" + } + + mockTgTemplate.get + .withArgs("profile/profile-timeline-attachment/profile-timeline-attachment.html") + .returns("
") + + elm = createDirective() + + expect(elm.find('#file')).to.have.length(1) diff --git a/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.jade b/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.jade new file mode 100644 index 00000000..8deca77a --- /dev/null +++ b/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.jade @@ -0,0 +1,3 @@ +p TODO: ATTACHMENT +p + a(ng-href="attachment.url") {{attachment.filename}} \ No newline at end of file diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.coffee b/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.coffee new file mode 100644 index 00000000..a4079994 --- /dev/null +++ b/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.coffee @@ -0,0 +1,90 @@ +class ProfileTimelineItemTitle + @.$inject = [ + "$translate" + ] + + _fieldTranslationKey: { + 'status': 'COMMON.FIELDS.STATUS', + 'subject': 'COMMON.FIELDS.SUBJECT', + 'description': 'COMMON.FIELDS.DESCRIPTION', + 'points': 'COMMON.FIELDS.POINTS', + 'severity': 'ISSUES.FIELDS.SEVERITY', + 'priority': 'ISSUES.FIELDS.PRIORITY', + 'type': 'ISSUES.FIELDS.TYPE', + 'is_iocaine': 'TASK.FIELDS.IS_IOCAINE' + } + + constructor: (@translate) -> + + + _translateTitleParams: (param, timeline, event) -> + if param == "username" + user = timeline.data.user + title_attr = @translate.instant('COMMON.SEE_USER_PROFILE', {username: user.username}) + url = 'user-profile:username=activity.user.username' + return @._getLink(url, user.username, title_attr) + + else if param == 'field_name' + field_name = Object.keys(timeline.data.values_diff)[0] + + return @translate.instant(@._fieldTranslationKey[field_name]) + + else if param == 'project_name' + url = 'project:project=activity.project.slug' + + return @._getLink(url, timeline.data.project.name) + + else if param == 'sprint_name' + url = 'project-taskboard:project=activity.project.slug,sprint=activity.sprint.slug' + + return @._getLink(url, timeline.data.milestone.name) + + else if param == 'obj_name' + obj = @._getTimelineObj(timeline, event) + url = @._getDetailObjUrl(event) + + if event.obj == 'wikipage' + text = obj.slug + else if event.obj == 'milestone' + text = obj.name + else + text = '#' + obj.ref + ' ' + obj.subject + + return @._getLink(url, text) + + _getTimelineObj: (timeline, event) -> + return timeline.data[event.obj] + + _getDetailObjUrl: (event) -> + url = { + "issue": ["project-issues-detail", ":project=vm.activity.project.slug,ref=vm.activity.obj.ref"], + "wikipage": ["project-wiki-page", ":project=vm.activity.project.slug,slug=vm.activity.obj.slug"], + "task": ["project-tasks-detail", ":project=vm.activity.project.slug,ref=vm.activity.obj.ref"], + "userstory": ["project-userstories-detail", ":project=vm.activity.project.slug,ref=vm.activity.obj.ref"], + "milestone": ["project-taskboard", ":project=vm.activity.project.slug,sprint=vm.activity.obj.slug"] + } + + return url[event.obj][0] + url[event.obj][1] + + _getLink: (url, text, title) -> + title = title || text + + return $('
') + .attr('tg-nav', url) + .text(text) + .attr('title', title) + .prop('outerHTML') + + _getParams: (timeline, event, timeline_type) -> + params = {} + + timeline_type.translate_params.forEach (param) => + params[param] = @._translateTitleParams(param, timeline, event) + + return params + + getTitle: (timeline, event, type) -> + return @translate.instant(type.key, @._getParams(timeline, event, type)) + +angular.module("taigaProfile") + .service("tgProfileTimelineItemTitle", ProfileTimelineItemTitle) diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.spec.coffee b/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.spec.coffee new file mode 100644 index 00000000..133dde8b --- /dev/null +++ b/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.spec.coffee @@ -0,0 +1,244 @@ +describe "tgProfileTimelineItemTitle", -> + mySvc = null + mockTranslate = null + timeline = event = type = null + + _mockTranslate = () -> + _provide (provide) -> + mockTranslate = { + instant: sinon.stub() + } + + provide.value "$translate", mockTranslate + + _provide = (callback) -> + module ($provide) -> + callback($provide) + return null + + _mocks = () -> + _mockTranslate() + + _inject = -> + inject (_tgProfileTimelineItemTitle_) -> + mySvc = _tgProfileTimelineItemTitle_ + + _setup = -> + _mocks() + _inject() + + beforeEach -> + module "taigaProfile" + _setup() + + it "title with username", () -> + timeline = { + data: { + user: { + username: 'xx' + } + } + } + + event = {} + + type = { + key: 'TITLE_USER_NAME', + translate_params: ['username'] + } + + mockTranslate.instant + .withArgs('COMMON.SEE_USER_PROFILE', {username: timeline.data.user.username}) + .returns('user-param') + + usernamelink = sinon.match ((value) -> + return value.username == 'xx' + ), "usernamelink" + + mockTranslate.instant + .withArgs('TITLE_USER_NAME', usernamelink) + .returns('title_ok') + + title = mySvc.getTitle(timeline, event, type) + + expect(title).to.be.equal("title_ok") + + it "title with a field name", () -> + timeline = { + data: { + values_diff: { + status: {} + } + } + } + + event = {} + + type = { + key: 'TITLE_FIELD', + translate_params: ['field_name'] + } + + mockTranslate.instant + .withArgs('COMMON.FIELDS.STATUS') + .returns('field-params') + + fieldparam = sinon.match ((value) -> + return value.field_name == 'field-params' + ), "fieldparam" + + mockTranslate.instant + .withArgs('TITLE_FIELD', fieldparam) + .returns('title_ok') + + + title = mySvc.getTitle(timeline, event, type) + + expect(title).to.be.equal("title_ok") + + it "title with project name", () -> + timeline = { + data: { + project: { + name: "project_name" + } + } + } + + event = {} + + type = { + key: 'TITLE_PROJECT', + translate_params: ['project_name'] + } + + projectparam = sinon.match ((value) -> + return value.project_name == 'project_name' + ), "projectparam" + + mockTranslate.instant + .withArgs('TITLE_PROJECT', projectparam) + .returns('title_ok') + + title = mySvc.getTitle(timeline, event, type) + + expect(title).to.be.equal("title_ok") + + it "title with sprint name", () -> + timeline = { + data: { + milestone: { + name: "milestone_name" + } + } + } + + event = {} + + type = { + key: 'TITLE_MILESTONE', + translate_params: ['sprint_name'] + } + + milestoneparam = sinon.match ((value) -> + return value.sprint_name == 'milestone_name' + ), "milestoneparam" + + mockTranslate.instant + .withArgs('TITLE_MILESTONE', milestoneparam) + .returns('title_ok') + + title = mySvc.getTitle(timeline, event, type) + + expect(title).to.be.equal("title_ok") + + it "title with object", () -> + timeline = { + data: { + issue: { + ref: '123', + subject: 'subject' + } + } + } + + event = { + obj: 'issue', + } + + type = { + key: 'TITLE_OBJ', + translate_params: ['obj_name'] + } + + objparam = sinon.match ((value) -> + return value.obj_name == '#123 subject' + ), "objparam" + + mockTranslate.instant + .withArgs('TITLE_OBJ', objparam) + .returns('title_ok') + + title = mySvc.getTitle(timeline, event, type) + + expect(title).to.be.equal("title_ok") + + it "title obj wiki", () -> + timeline = { + data: { + wikipage: { + slug: 'slugwiki', + } + } + } + + event = { + obj: 'wikipage', + } + + type = { + key: 'TITLE_OBJ', + translate_params: ['obj_name'] + } + + objparam = sinon.match ((value) -> + return value.obj_name == 'slugwiki' + ), "objparam" + + mockTranslate.instant + .withArgs('TITLE_OBJ', objparam) + .returns('title_ok') + + title = mySvc.getTitle(timeline, event, type) + + expect(title).to.be.equal("title_ok") + + it "title obj milestone", () -> + timeline = { + data: { + milestone: { + name: 'milestone_name', + } + } + } + + event = { + obj: 'milestone', + } + + type = { + key: 'TITLE_OBJ', + translate_params: ['obj_name'] + } + + objparam = sinon.match ((value) -> + return value.obj_name == 'milestone_name' + ), "objparam" + + mockTranslate.instant + .withArgs('TITLE_OBJ', objparam) + .returns('title_ok') + + title = mySvc.getTitle(timeline, event, type) + + expect(title).to.be.equal("title_ok") diff --git a/app/coffee/modules/profile/timeline-item.directive.coffee b/app/modules/profile/profile-timeline-item/profile-timeline-item-type.service.coffee similarity index 58% rename from app/coffee/modules/profile/timeline-item.directive.coffee rename to app/modules/profile/profile-timeline-item/profile-timeline-item-type.service.coffee index 88e7083f..29987438 100644 --- a/app/coffee/modules/profile/timeline-item.directive.coffee +++ b/app/modules/profile/profile-timeline-item/profile-timeline-item-type.service.coffee @@ -150,126 +150,8 @@ timelineType = (timeline, event) -> return _.find types, (obj) -> return obj.check(timeline, event, field_name) -TimelineItemDirective = ($tgTemplate, $compile, $navUrls, $translate, $sce) -> - fieldTranslationKey = { - 'status': 'COMMON.FIELDS.STATUS', - 'subject': 'COMMON.FIELDS.SUBJECT', - 'description': 'COMMON.FIELDS.DESCRIPTION', - 'points': 'COMMON.FIELDS.POINTS', - 'severity': 'ISSUES.FIELDS.SEVERITY', - 'priority': 'ISSUES.FIELDS.PRIORITY', - 'type': 'ISSUES.FIELDS.TYPE', - 'is_iocaine': 'TASK.FIELDS.IS_IOCAINE' - } - - parseEventType = (event_type) -> - event_type = event_type.split(".") - - return { - section: event_type[0], - obj: event_type[1], - type: event_type[2] - } - - getDetailObjUrl = (event) -> - url = { - "issue": ["project-issues-detail", ":project=activity.project.slug,ref=activity.obj.ref"], - "wikipage": ["project-wiki-page", ":project=activity.project.slug,slug=activity.obj.slug"], - "task": ["project-tasks-detail", ":project=activity.project.slug,ref=activity.obj.ref"], - "userstory": ["project-userstories-detail", ":project=activity.project.slug,ref=activity.obj.ref"], - "milestone": ["project-taskboard", ":project=activity.project.slug,sprint=activity.obj.slug"] - } - - return url[event.obj][0] + url[event.obj][1] - - getLink = (url, text, title) -> - title = title || text - - return $('') - .attr('tg-nav', url) - .text(text) - .attr('title', title) - .prop('outerHTML') - - translate_params = { - username: (timeline) -> - user = timeline.data.user - title_attr = $translate.instant('COMMON.SEE_USER_PROFILE', {username: user.username}) - url = 'user-profile:username=activity.user.username' - return getLink(url, user.username, title_attr) - - field_name: (timeline) -> - field_name = Object.keys(timeline.data.values_diff)[0] - - return $translate.instant(fieldTranslationKey[field_name]) - - project_name: (timeline) -> - url = 'project:project=activity.project.slug' - - return getLink(url, timeline.data.project.name) - - sprint_name: (timeline) -> - url = 'project-taskboard:project=activity.project.slug,sprint=activity.sprint.slug' - - return getLink(url, timeline.data.milestone.name) - - obj_name: (timeline, event) -> - obj = getTimelineObj(timeline, event) - url = getDetailObjUrl(event) - - if event.obj == 'wikipage' - text = obj.slug - else if event.obj == 'milestone' - text = obj.name - else - text = '#' + obj.ref + ' ' + obj.subject - - return getLink(url, text) - } - - getTimelineObj = (timeline, event) -> - return timeline.data[event.obj] - - getParams = (timeline, event, timeline_type) -> - params = {} - - timeline_type.translate_params.forEach (param) -> - params[param] = translate_params[param](timeline, event) - - return params - - getTitle = (timeline, event, type) -> - return $translate.instant(type.key, getParams(timeline, event, type)) - - link = ($scope, $el, $attrs) -> - event = parseEventType($scope.timeline.event_type) - type = timelineType($scope.timeline, event) - - $scope.activity = {} - - $scope.activity.obj = getTimelineObj($scope.timeline, event) - $scope.activity.user = $scope.timeline.data.user - $scope.activity.project = $scope.timeline.data.project - $scope.activity.sprint = $scope.timeline.data.milestone - $scope.activity.title = getTitle($scope.timeline, event, type) - $scope.activity.created_formated = moment($scope.timeline.created).fromNow() - - if type.description - $scope.activity.description = $sce.trustAsHtml(type.description($scope.timeline)) - - if type.member - $scope.activity.member = type.member($scope.timeline) - - if $scope.timeline.data.values_diff?.attachments - $scope.activity.attachments = $scope.timeline.data.values_diff.attachments.new - - return { - link: link - templateUrl: "profile/timeline/timeline-item.html" - scope: { - timeline: "=tgTimelineItem" - } - } +class ProfileTimelineType + getType: (timeline, event) -> timelineType(timeline, event) angular.module("taigaProfile") - .directive("tgTimelineItem", ["$tgTemplate", "$compile", "$tgNavUrls", "$translate", "$sce", TimelineItemDirective]) + .service("tgProfileTimelineItemType", ProfileTimelineType) diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item-type.service.spec.coffee b/app/modules/profile/profile-timeline-item/profile-timeline-item-type.service.spec.coffee new file mode 100644 index 00000000..ecc15084 --- /dev/null +++ b/app/modules/profile/profile-timeline-item/profile-timeline-item-type.service.spec.coffee @@ -0,0 +1,31 @@ +describe "tgProfileTimelineItemType", -> + mySvc = null + + _provide = (callback) -> + module ($provide) -> + callback($provide) + return null + + _inject = -> + inject (_tgProfileTimelineItemType_) -> + mySvc = _tgProfileTimelineItemType_ + + _setup = -> + _inject() + + beforeEach -> + module "taigaProfile" + _setup() + + it "get the timeline type", () -> + timeline = { + data: {} + } + + event = { + obj: 'membership' + } + + type = mySvc.getType(timeline, event) + + expect(type.key).to.be.equal("TIMELINE.NEW_MEMBER") diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.coffee b/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.coffee new file mode 100644 index 00000000..e0cf1141 --- /dev/null +++ b/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.coffee @@ -0,0 +1,40 @@ +class ProfileTimelineItemController + @.$inject = [ + "$scope", + "$sce", + "tgProfileTimelineItemType", + "tgProfileTimelineItemTitle" + ] + + constructor: (@scope, @sce, @profileTimelineItemType, @profileTimelineItemTitle) -> + event = @parseEventType(@scope.vm.timeline.event_type) + type = @profileTimelineItemType.getType(@scope.vm.timeline, event) + + @.activity = {} + + @.activity.user = @scope.vm.timeline.data.user + @.activity.project = @scope.vm.timeline.data.project + @.activity.sprint = @scope.vm.timeline.data.milestone + @.activity.title = @profileTimelineItemTitle.getTitle(@scope.vm.timeline, event, type) + @.activity.created_formated = moment(@scope.vm.timeline.created).fromNow() + + if type.description + @.activity.description = @sce.trustAsHtml(type.description(@scope.vm.timeline)) + + if type.member + @.activity.member = type.member(@scope.vm.timeline) + + if @scope.vm.timeline.data.values_diff?.attachments + @.activity.attachments = @scope.vm.timeline.data.values_diff.attachments.new + + parseEventType: (event_type) -> + event_type = event_type.split(".") + + return { + section: event_type[0], + obj: event_type[1], + type: event_type[2] + } + +angular.module("taigaProfile") + .controller("ProfileTimelineItem", ProfileTimelineItemController) diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.spec.coffee b/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.spec.coffee new file mode 100644 index 00000000..5f59fbd0 --- /dev/null +++ b/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.spec.coffee @@ -0,0 +1,101 @@ +describe "ProfileTimelineItemController", -> + controller = scope = provide = null + timeline = event = null + mockTgProfileTimelineItemType = null + mockTgProfileTimelineItemTitle = null + mockType = null + + _mockTgProfileTimelineItemType = () -> + mockTgProfileTimelineItemType = { + getType: sinon.stub() + } + + mockType = { + description: sinon.stub(), + member: sinon.stub() + } + + mockTgProfileTimelineItemType.getType.withArgs(timeline).returns(mockType) + + provide.value "tgProfileTimelineItemType", mockTgProfileTimelineItemType + + _mockTgProfileTimelineItemTitle = () -> + mockTgProfileTimelineItemTitle = { + getTitle: sinon.stub() + } + + mockTgProfileTimelineItemTitle.getTitle.withArgs(timeline, event, mockType).returns("fakeTitle") + + provide.value "tgProfileTimelineItemTitle", mockTgProfileTimelineItemTitle + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgProfileTimelineItemType() + _mockTgProfileTimelineItemTitle() + + return null + + _setup = () -> + event = { + section: 'issues', + obj: 'issue', + type: 'created' + } + + timeline = { + event_type: 'issues.issue.created', + data: { + user: 'user_fake', + project: 'project_fake', + milestone: 'milestone_fake', + created: new Date().getTime(), + values_diff: {} + } + } + + scope = { + vm: { + timeline: timeline + } + } + + beforeEach -> + module "taigaProfile" + + _setup() + _mocks() + + inject ($controller) -> + controller = $controller + + it "basic activity fields filled", () -> + timeline = scope.vm.timeline + + myCtrl = controller("ProfileTimelineItem", {$scope: scope}) + + expect(myCtrl.activity.user).to.be.equal(timeline.data.user) + expect(myCtrl.activity.project).to.be.equal(timeline.data.project) + expect(myCtrl.activity.sprint).to.be.equal(timeline.data.milestone) + expect(myCtrl.activity.title).to.be.equal("fakeTitle") + expect(myCtrl.activity.created_formated).to.have.length.above(1) + + it "all activity fields filled", () -> + timeline = scope.vm.timeline + + attachment = "fakeAttachment" + timeline.data.values_diff.attachments = { + new: attachment + } + + description = "fakeDescription" + member = "fakeMember" + + mockType.description.withArgs(timeline).returns(description) + mockType.member.withArgs(timeline).returns(member) + + myCtrl = controller("ProfileTimelineItem", {$scope: scope}) + + expect(myCtrl.activity.description).to.be.an('object') # $sce.trustAsHtml + expect(myCtrl.activity.member).to.be.equal(member) + expect(myCtrl.activity.attachments).to.be.equal(attachment) diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item.directive.coffee b/app/modules/profile/profile-timeline-item/profile-timeline-item.directive.coffee new file mode 100644 index 00000000..d15cf6b9 --- /dev/null +++ b/app/modules/profile/profile-timeline-item/profile-timeline-item.directive.coffee @@ -0,0 +1,13 @@ +ProfileTimelineItemDirective = () -> + return { + controllerAs: "vm" + controller: "ProfileTimelineItem" + bindToController: true + templateUrl: "profile/profile-timeline-item/profile-timeline-item.html" + scope: { + timeline: "=tgProfileTimelineItem" + } + } + +angular.module("taigaProfile") + .directive("tgProfileTimelineItem", ProfileTimelineItemDirective) diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item.jade b/app/modules/profile/profile-timeline-item/profile-timeline-item.jade new file mode 100644 index 00000000..64471945 --- /dev/null +++ b/app/modules/profile/profile-timeline-item/profile-timeline-item.jade @@ -0,0 +1,22 @@ +div.activity-image + span.activity-date {{::vm.activity.created_formated}} + div.activity-info + div.profile-contact-picture + a(tg-nav="user-profile:username=vm.activity.user.username", title="{{::vm.activity.user.name }}") + img(ng-src="{{::vm.activity.user.photo}}", alt="{{::vm.activity.user.name}}") + + p(tg-compile-html="vm.activity.title") + + .activity-comment-quote(ng-if="::vm.activity.description") + p(ng-bind-html="vm.activity.description") + + .activity-member-view(ng-if="::vm.activity.member") + .profile-member-picture + img(ng-src="{{::vm.activity.member.user.photo}}", alt="{{::vm.activity.member.user.name}}") + .activity-member-info + a(tg-nav="user-profile:username=activity.member.user.username", title="{{::vm.activity.member.user.name }}") + span {{::vm.activity.member.user.name}} + p {{::vm.activity.member.role.name}} + +div(ng-repeat="attachment in vm.activity.attachments") + div(tg-profile-timeline-attachment="vm.attachment") \ No newline at end of file diff --git a/app/coffee/modules/profile/profile-timeline.controller.coffee b/app/modules/profile/profile-timeline/profile-timeline.controller.coffee similarity index 62% rename from app/coffee/modules/profile/profile-timeline.controller.coffee rename to app/modules/profile/profile-timeline/profile-timeline.controller.coffee index acaaa365..714d89ae 100644 --- a/app/coffee/modules/profile/profile-timeline.controller.coffee +++ b/app/modules/profile/profile-timeline/profile-timeline.controller.coffee @@ -29,21 +29,39 @@ class ProfileTimelineController extends mixOf(taiga.Controller, taiga.PageMixin, "$tgAuth" ] - valid_fields: ['status', 'subject', 'description', 'assigned_to', 'points', 'severity', 'priority', 'type', 'attachments', 'milestone', 'is_blocked', 'is_iocaine', 'content_diff', 'name', 'estimated_finish', 'estimated_start'] + _valid_fields: [ + 'status', + 'subject', + 'description', + 'assigned_to', + 'points', + 'severity', + 'priority', + 'type', + 'attachments', + 'milestone', + 'is_blocked', + 'is_iocaine', + 'content_diff', + 'name', + 'estimated_finish', + 'estimated_start' + ] constructor: (@rs, @auth) -> - @timelineList = [] - @pagination = {page: 1} + @.timelineList = [] + @.pagination = {page: 1} + @.loadingData = false - isValidField: (values) => - return _.some values, (value) => @valid_fields.indexOf(value) != -1 + _isValidField: (values) => + return _.some values, (value) => @._valid_fields.indexOf(value) != -1 - filterValidTimelineItems: (timeline) => + _filterValidTimelineItems: (timeline) => if timeline.data.values_diff values = Object.keys(timeline.data.values_diff) if values && values.length - if !@isValidField(values) + if !@._isValidField(values) return false else if values[0] == 'attachments' && timeline.data.values_diff.attachments.new.length == 0 return false @@ -53,14 +71,13 @@ class ProfileTimelineController extends mixOf(taiga.Controller, taiga.PageMixin, loadTimeline: () -> user = @auth.getUser() - @loadingData = true + @.loadingData = true - return @rs.timeline.profile(user.id, @pagination).then (result) => - newTimelineList = _.filter result.data, @filterValidTimelineItems - - @timelineList = @timelineList.concat(newTimelineList) - @pagination.page++ - @loadingData = false + return @rs.timeline.profile(user.id, @.pagination).then (result) => + newTimelineList = _.filter result.data, @._filterValidTimelineItems + @.timelineList = @timelineList.concat(newTimelineList) + @.pagination.page++ + @.loadingData = false angular.module("taigaProfile") .controller("ProfileTimeline", ProfileTimelineController) diff --git a/app/modules/profile/profile-timeline/profile-timeline.controller.spec.coffee b/app/modules/profile/profile-timeline/profile-timeline.controller.spec.coffee new file mode 100644 index 00000000..18669f00 --- /dev/null +++ b/app/modules/profile/profile-timeline/profile-timeline.controller.spec.coffee @@ -0,0 +1,116 @@ +describe "ProfileTimelineController", -> + myCtrl = scope = $q = provide = null + + mockUser = {id: 3} + + _mockTgResources = () -> + provide.value "$tgResources", { + timeline: {} + } + + _mockTgAuth = () -> + provide.value "$tgAuth", { + getUser: () -> + return mockUser + } + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgResources() + _mockTgAuth() + + return null + + + beforeEach -> + module "taigaProfile" + _mocks() + + inject ($controller, _$q_) -> + $q = _$q_ + myCtrl = $controller "ProfileTimeline" + + it "timelineList should be an array", () -> + expect(myCtrl.timelineList).is.an("array") + + + it "pagination starts at 1", () -> + expect(myCtrl.pagination.page).to.be.equal(1) + + describe "load timeline", () -> + thenStub = timelineList = null + + beforeEach () -> + timelineList = { + data: [ + { # valid item + data: { + values_diff: { + "status": "xx", + "subject": "xx" + } + } + }, + { # invalid item + data: { + values_diff: { + "fake": "xx" + } + } + }, + { # invalid item + data: { + values_diff: { + "fake2": "xx" + } + } + }, + { # valid item + data: { + values_diff: { + "fake2": "xx", + "milestone": "xx" + } + } + } + ] + } + + thenStub = sinon.stub() + + profileStub = sinon.stub() + .withArgs(mockUser.id, myCtrl.pagination) + .returns({ + then: thenStub + }) + + myCtrl.rs.timeline.profile = profileStub + + it "the loadingData variable must be true during the timeline load", () -> + expect(myCtrl.loadingData).to.be.false + + myCtrl.loadTimeline() + + expect(myCtrl.loadingData).to.be.true + + thenStub.callArgWith(0, timelineList) + + expect(myCtrl.loadingData).to.be.false + + it "pagiantion increase one every call to loadTimeline", () -> + expect(myCtrl.pagination.page).to.equal(1) + + myCtrl.loadTimeline() + + thenStub.callArgWith(0, timelineList) + + expect(myCtrl.pagination.page).to.equal(2) + + it "filter the invalid timeline items", () -> + myCtrl.loadTimeline() + + thenStub.callArgWith(0, timelineList) + + expect(myCtrl.timelineList[0]).to.be.equal(timelineList.data[0]) + expect(myCtrl.timelineList[1]).to.be.equal(timelineList.data[3]) diff --git a/app/styles/modules/profile/profile-timeline.scss b/app/modules/profile/profile-timeline/profile-timeline.scss similarity index 100% rename from app/styles/modules/profile/profile-timeline.scss rename to app/modules/profile/profile-timeline/profile-timeline.scss diff --git a/app/partials/profile/public-profile.jade b/app/modules/profile/profile.jade similarity index 54% rename from app/partials/profile/public-profile.jade rename to app/modules/profile/profile.jade index 298a6637..ceede2d0 100644 --- a/app/partials/profile/public-profile.jade +++ b/app/modules/profile/profile.jade @@ -1,21 +1,21 @@ -include ../includes/components/beta +include ../../partials/includes/components/beta div.profile.centered - include ../includes/modules/profile/profile-bar + include includes/profile-bar div.main div.hero div(tg-profile-tabs) div.content-wrapper div.content div(tg-profile-tab="activity", tab-title="Activity Tab", tab-icon="icon-timeline", tab-active) - include ../includes/modules/profile/profile-timeline + include includes/profile-timeline div(tg-profile-tab="projects", tab-title="Projects Tab", tab-icon="icon-project") - include ../includes/modules/profile/profile-projects + include includes/profile-projects div(tg-profile-tab="contacts", tab-title="Contacts Tab", tab-icon="icon-team") - include ../includes/modules/profile/profile-contacts + include includes/profile-contacts div(tg-profile-tab="favorities", tab-title="Favorites Tab", tab-icon="icon-star-fill") - include ../includes/modules/profile/profile-favorites + include includes/profile-favorites - include ../includes/modules/profile/profile-sidebar + include includes/profile-sidebar diff --git a/app/modules/profile/profile.module.coffee b/app/modules/profile/profile.module.coffee new file mode 100644 index 00000000..8c55b9f8 --- /dev/null +++ b/app/modules/profile/profile.module.coffee @@ -0,0 +1 @@ +module = angular.module("taigaProfile", []) diff --git a/app/styles/layout/profile.scss b/app/modules/profile/profile.scss similarity index 100% rename from app/styles/layout/profile.scss rename to app/modules/profile/profile.scss diff --git a/app/styles/modules/profile/profile-bar.scss b/app/modules/profile/styles/profile-bar.scss similarity index 100% rename from app/styles/modules/profile/profile-bar.scss rename to app/modules/profile/styles/profile-bar.scss diff --git a/app/styles/modules/profile/profile-contacts.scss b/app/modules/profile/styles/profile-contacts.scss similarity index 100% rename from app/styles/modules/profile/profile-contacts.scss rename to app/modules/profile/styles/profile-contacts.scss diff --git a/app/styles/modules/profile/profile-content-tabs.scss b/app/modules/profile/styles/profile-content-tabs.scss similarity index 100% rename from app/styles/modules/profile/profile-content-tabs.scss rename to app/modules/profile/styles/profile-content-tabs.scss diff --git a/app/styles/modules/profile/profile-favorites.scss b/app/modules/profile/styles/profile-favorites.scss similarity index 100% rename from app/styles/modules/profile/profile-favorites.scss rename to app/modules/profile/styles/profile-favorites.scss diff --git a/app/styles/modules/profile/profile-projects.scss b/app/modules/profile/styles/profile-projects.scss similarity index 100% rename from app/styles/modules/profile/profile-projects.scss rename to app/modules/profile/styles/profile-projects.scss diff --git a/app/styles/modules/profile/profile-sidebar.scss b/app/modules/profile/styles/profile-sidebar.scss similarity index 100% rename from app/styles/modules/profile/profile-sidebar.scss rename to app/modules/profile/styles/profile-sidebar.scss diff --git a/app/partials/profile/profile-tabs.jade b/app/partials/profile/profile-tabs.jade deleted file mode 100644 index 9a4d3674..00000000 --- a/app/partials/profile/profile-tabs.jade +++ /dev/null @@ -1,7 +0,0 @@ -div - nav.profile-content-tabs - a.tab(ng-repeat="tab in tabs", href="", title="{{::tab.title}}", ng-class="{active: tab.active}" ng-click="toggleTab(tab)") - span.icon(ng-class="::tab.icon") - span {{::tab.name}} - - ng-transclude \ No newline at end of file diff --git a/app/partials/profile/timeline/timeline-attachment.jade b/app/partials/profile/timeline/timeline-attachment.jade deleted file mode 100644 index 22edecd8..00000000 --- a/app/partials/profile/timeline/timeline-attachment.jade +++ /dev/null @@ -1,3 +0,0 @@ -p TODO: ATTACHMENT -p - a(ng-href="attachment.url"){{attachment.filename}} \ No newline at end of file diff --git a/app/partials/profile/timeline/timeline-item.jade b/app/partials/profile/timeline/timeline-item.jade deleted file mode 100644 index ebf30734..00000000 --- a/app/partials/profile/timeline/timeline-item.jade +++ /dev/null @@ -1,22 +0,0 @@ -div.activity-image - span.activity-date {{::activity.created_formated}} - div.activity-info - div.profile-contact-picture - a(tg-nav="user-profile:username=activity.user.username", title="{{::activity.user.name }}") - img(ng-src="{{::activity.user.photo}}", alt="{{::activity.user.name}}") - - p(tg-compile-html="activity.title") - - .activity-comment-quote(ng-if="::activity.description") - p(ng-bind-html="activity.description") - - .activity-member-view(ng-if="::activity.member") - .profile-member-picture - img(ng-src="{{::activity.member.user.photo}}", alt="{{::activity.member.user.name}}") - .activity-member-info - a(tg-nav="user-profile:username=activity.member.user.username", title="{{::activity.member.user.name }}") - span {{::activity.member.user.name}} - p {{::activity.member.role.name}} - -div(ng-repeat="attachment in activity.attachments") - div(tg-timeline-attachment="attachment") \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index 3838ea8a..9a315e9b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -42,7 +42,8 @@ paths.htmlPartials = [ paths.tmp + "partials/**/*.html", paths.tmp + "plugins/**/*.html", paths.tmp + "modules/**/*.html", - "!" + paths.tmp + "partials/includes/**/*.html" + "!" + paths.tmp + "partials/includes/**/*.html", + "!" + paths.tmp + "/modules/**/includes/**/*.html" ]; paths.images = paths.app + "images/**/*"; From 07a2834861bdcca25f3401823960cca397ec5321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 14 Apr 2015 14:59:57 +0200 Subject: [PATCH 035/366] Move menu inside every file since it is a new layout element --- app/coffee/app.coffee | 2 - app/index.jade | 11 ++--- app/partials/admin/admin-memberships.jade | 2 + .../admin/admin-project-default-values.jade | 1 + app/partials/admin/admin-project-export.jade | 1 + app/partials/admin/admin-project-modules.jade | 1 + app/partials/admin/admin-project-profile.jade | 1 + app/partials/admin/admin-project-reports.jade | 1 + .../admin-project-values-custom-fields.jade | 3 ++ .../admin/admin-project-values-points.jade | 3 ++ .../admin-project-values-priorities.jade | 2 + .../admin-project-values-severities.jade | 2 + .../admin/admin-project-values-status.jade | 1 + .../admin/admin-project-values-types.jade | 2 + app/partials/admin/admin-roles.jade | 3 ++ .../admin/admin-third-parties-bitbucket.jade | 1 + .../admin/admin-third-parties-github.jade | 1 + .../admin/admin-third-parties-gitlab.jade | 1 + .../admin/admin-third-parties-webhooks.jade | 1 + app/partials/backlog/backlog.jade | 2 +- app/partials/contrib/main.jade | 1 + app/partials/issue/issues-detail.jade | 1 + app/partials/issue/issues.jade | 1 + app/partials/kanban/kanban.jade | 1 + app/partials/project/project.jade | 1 + app/partials/search/search.jade | 2 +- app/partials/task/task-detail.jade | 1 + app/partials/taskboard/taskboard.jade | 1 + app/partials/team/team.jade | 1 + app/partials/us/us-detail.jade | 1 + app/partials/user/mail-notifications.jade | 2 + app/partials/user/user-change-password.jade | 1 + app/partials/user/user-profile.jade | 1 + app/partials/wiki/wiki.jade | 1 + app/styles/core/base.scss | 44 ------------------- app/styles/modules/common/nav.scss | 4 +- 36 files changed, 50 insertions(+), 56 deletions(-) diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index efacc87a..66065784 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -129,8 +129,6 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven {templateUrl: "user/user-profile.html"}) $routeProvider.when("/project/:pslug/user-settings/user-change-password", {templateUrl: "user/user-change-password.html"}) - $routeProvider.when("/project/:pslug/user-settings/user-avatar", - {templateUrl: "user/user-avatar.html"}) $routeProvider.when("/project/:pslug/user-settings/mail-notifications", {templateUrl: "user/mail-notifications.html"}) $routeProvider.when("/change-email/:email_token", diff --git a/app/index.jade b/app/index.jade index e1ff61b8..6829df65 100644 --- a/app/index.jade +++ b/app/index.jade @@ -12,14 +12,15 @@ html(lang="en") body(tg-main) - include partials/includes/modules/projects-nav - - //- the content of nav.menu is in coffe.modules.base TaigaMain directive - nav.menu.hidden(tg-project-menu) - + //include partials/includes/modules/projects-nav include partials/includes/components/notification-message + //- the content of nav.menu is in coffe.modules.base TaigaMain directive + //nav.menu.hidden(tg-project-menu) + + div.master(ng-view) + include partials/includes/modules/navigation-bar/navbar div.lightbox.lightbox-generic-ask include partials/includes/modules/lightbox-generic-ask diff --git a/app/partials/admin/admin-memberships.jade b/app/partials/admin/admin-memberships.jade index dd0d8510..035b825e 100644 --- a/app/partials/admin/admin-memberships.jade +++ b/app/partials/admin/admin-memberships.jade @@ -2,6 +2,8 @@ doctype html div.wrapper.memberships(ng-controller="MembershipsController as ctrl", ng-init="section='admin'; sectionName='ADMIN.MEMBERSHIPS.TITLE'", tg-memberships) + nav.menu.hidden(tg-project-menu) + sidebar.menu-secondary.sidebar(tg-admin-navigation="memberships") include ../includes/modules/admin-menu diff --git a/app/partials/admin/admin-project-default-values.jade b/app/partials/admin/admin-project-default-values.jade index 886b2bac..c53c9e76 100644 --- a/app/partials/admin/admin-project-default-values.jade +++ b/app/partials/admin/admin-project-default-values.jade @@ -2,6 +2,7 @@ doctype html div.wrapper(tg-project-default-values, ng-controller="ProjectProfileController as ctrl", ng-init="section='admin'; sectionName='ADMIN.PROJECT_DEFAULT_VALUES.TITLE'") + nav.menu.hidden(tg-project-menu) sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile") include ../includes/modules/admin-menu diff --git a/app/partials/admin/admin-project-export.jade b/app/partials/admin/admin-project-export.jade index f92a33c3..dc605827 100644 --- a/app/partials/admin/admin-project-export.jade +++ b/app/partials/admin/admin-project-export.jade @@ -2,6 +2,7 @@ doctype html div.wrapper(ng-controller="ProjectProfileController as ctrl", ng-init="section='admin'; sectionName='ADMIN.PROJECT_EXPORT.TITLE'") + nav.menu.hidden(tg-project-menu) sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile") include ../includes/modules/admin-menu diff --git a/app/partials/admin/admin-project-modules.jade b/app/partials/admin/admin-project-modules.jade index 11e440b7..f28c3440 100644 --- a/app/partials/admin/admin-project-modules.jade +++ b/app/partials/admin/admin-project-modules.jade @@ -2,6 +2,7 @@ doctype html div.wrapper(tg-project-modules, ng-controller="ProjectProfileController as ctrl", ng-init="section='admin'; sectionName='ADMIN.MODULES.TITLE'") + nav.menu.hidden(tg-project-menu) sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile") include ../includes/modules/admin-menu diff --git a/app/partials/admin/admin-project-profile.jade b/app/partials/admin/admin-project-profile.jade index fdb89da0..ebfbcb91 100644 --- a/app/partials/admin/admin-project-profile.jade +++ b/app/partials/admin/admin-project-profile.jade @@ -2,6 +2,7 @@ doctype html div.wrapper(tg-project-profile, ng-controller="ProjectProfileController as ctrl", ng-init="section='admin'; sectionName='ADMIN.PROJECT_PROFILE.PROJECT_DETAILS'") + nav.menu.hidden(tg-project-menu) sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile") include ../includes/modules/admin-menu diff --git a/app/partials/admin/admin-project-reports.jade b/app/partials/admin/admin-project-reports.jade index a4dffd1f..234200db 100644 --- a/app/partials/admin/admin-project-reports.jade +++ b/app/partials/admin/admin-project-reports.jade @@ -2,6 +2,7 @@ doctype html div.wrapper(ng-controller="ProjectProfileController as ctrl", ng-init="section='admin'; sectionName='ADMIN.REPORTS.TITLE'") + nav.menu.hidden(tg-project-menu) sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile") include ../includes/modules/admin-menu diff --git a/app/partials/admin/admin-project-values-custom-fields.jade b/app/partials/admin/admin-project-values-custom-fields.jade index d8b93d59..548f2e8d 100644 --- a/app/partials/admin/admin-project-values-custom-fields.jade +++ b/app/partials/admin/admin-project-values-custom-fields.jade @@ -2,6 +2,9 @@ doctype html div.wrapper(ng-controller="ProjectValuesSectionController", ng-init="sectionName='ADMIN.CUSTOM_FIELDS.TITLE'") + + nav.menu.hidden(tg-project-menu) + sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values") include ../includes/modules/admin-menu diff --git a/app/partials/admin/admin-project-values-points.jade b/app/partials/admin/admin-project-values-points.jade index b47bd8ef..33dceff8 100644 --- a/app/partials/admin/admin-project-values-points.jade +++ b/app/partials/admin/admin-project-values-points.jade @@ -1,6 +1,9 @@ doctype html div.wrapper(ng-controller="ProjectValuesSectionController") + + nav.menu.hidden(tg-project-menu) + sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values") include ../includes/modules/admin-menu diff --git a/app/partials/admin/admin-project-values-priorities.jade b/app/partials/admin/admin-project-values-priorities.jade index 0658f0d1..213b29c6 100644 --- a/app/partials/admin/admin-project-values-priorities.jade +++ b/app/partials/admin/admin-project-values-priorities.jade @@ -1,6 +1,8 @@ doctype html div.wrapper(ng-controller="ProjectValuesSectionController") + nav.menu.hidden(tg-project-menu) + sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values") include ../includes/modules/admin-menu diff --git a/app/partials/admin/admin-project-values-severities.jade b/app/partials/admin/admin-project-values-severities.jade index 93101293..eb5b7686 100644 --- a/app/partials/admin/admin-project-values-severities.jade +++ b/app/partials/admin/admin-project-values-severities.jade @@ -1,6 +1,8 @@ doctype html div.wrapper(ng-controller="ProjectValuesSectionController") + nav.menu.hidden(tg-project-menu) + sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values") include ../includes/modules/admin-menu diff --git a/app/partials/admin/admin-project-values-status.jade b/app/partials/admin/admin-project-values-status.jade index 5d7e6343..66fb5a9c 100644 --- a/app/partials/admin/admin-project-values-status.jade +++ b/app/partials/admin/admin-project-values-status.jade @@ -2,6 +2,7 @@ doctype html div.wrapper(ng-controller="ProjectValuesSectionController", ng-init="section='admin'; sectionName='ADMIN.PROJECT_VALUES_STATUS.TITLE'") + nav.menu.hidden(tg-project-menu) sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values") include ../includes/modules/admin-menu diff --git a/app/partials/admin/admin-project-values-types.jade b/app/partials/admin/admin-project-values-types.jade index 8cb5092f..f4e8cd58 100644 --- a/app/partials/admin/admin-project-values-types.jade +++ b/app/partials/admin/admin-project-values-types.jade @@ -2,6 +2,8 @@ doctype html div.wrapper(ng-controller="ProjectValuesSectionController" ng-init="sectionName='ADMIN.PROJECT_VALUES_TYPES.TITLE'") + nav.menu.hidden(tg-project-menu) + sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values") include ../includes/modules/admin-menu diff --git a/app/partials/admin/admin-roles.jade b/app/partials/admin/admin-roles.jade index 7edc272c..0117516e 100644 --- a/app/partials/admin/admin-roles.jade +++ b/app/partials/admin/admin-roles.jade @@ -2,8 +2,11 @@ doctype html div.wrapper.roles(ng-controller="RolesController as ctrl", ng-init="section='admin'", tg-roles) + nav.menu.hidden(tg-project-menu) + sidebar.menu-secondary.sidebar(tg-admin-navigation="roles") include ../includes/modules/admin-menu + sidebar.menu-tertiary.sidebar include ../includes/modules/admin-submenu-roles diff --git a/app/partials/admin/admin-third-parties-bitbucket.jade b/app/partials/admin/admin-third-parties-bitbucket.jade index 992da283..b65f8d17 100644 --- a/app/partials/admin/admin-third-parties-bitbucket.jade +++ b/app/partials/admin/admin-third-parties-bitbucket.jade @@ -2,6 +2,7 @@ doctype html div.wrapper.roles(tg-bitbucket-webhooks, ng-controller="BitbucketController as ctrl", ng-init="section='admin'") + nav.menu.hidden(tg-project-menu) sidebar.menu-secondary.sidebar(tg-admin-navigation="third-parties") include ../includes/modules/admin-menu sidebar.menu-tertiary.sidebar(tg-admin-navigation="third-parties-bitbucket") diff --git a/app/partials/admin/admin-third-parties-github.jade b/app/partials/admin/admin-third-parties-github.jade index 0b3efe32..478ab0ac 100644 --- a/app/partials/admin/admin-third-parties-github.jade +++ b/app/partials/admin/admin-third-parties-github.jade @@ -2,6 +2,7 @@ doctype html div.wrapper.roles(tg-github-webhooks, ng-controller="GithubController as ctrl", ng-init="section='admin'") + nav.menu.hidden(tg-project-menu) sidebar.menu-secondary.sidebar(tg-admin-navigation="third-parties") include ../includes/modules/admin-menu sidebar.menu-tertiary.sidebar(tg-admin-navigation="third-parties-github") diff --git a/app/partials/admin/admin-third-parties-gitlab.jade b/app/partials/admin/admin-third-parties-gitlab.jade index 9a552bbc..289d0025 100644 --- a/app/partials/admin/admin-third-parties-gitlab.jade +++ b/app/partials/admin/admin-third-parties-gitlab.jade @@ -2,6 +2,7 @@ doctype html div.wrapper.roles(tg-gitlab-webhooks, ng-controller="GitlabController as ctrl", ng-init="section='admin'") + nav.menu.hidden(tg-project-menu) sidebar.menu-secondary.sidebar(tg-admin-navigation="third-parties") include ../includes/modules/admin-menu sidebar.menu-tertiary.sidebar(tg-admin-navigation="third-parties-gitlab") diff --git a/app/partials/admin/admin-third-parties-webhooks.jade b/app/partials/admin/admin-third-parties-webhooks.jade index 345ffd8a..1a0c6545 100644 --- a/app/partials/admin/admin-third-parties-webhooks.jade +++ b/app/partials/admin/admin-third-parties-webhooks.jade @@ -2,6 +2,7 @@ doctype html div.wrapper.roles(ng-controller="WebhooksController as ctrl", ng-init="section='admin'") + nav.menu.hidden(tg-project-menu) sidebar.menu-secondary.sidebar(tg-admin-navigation="third-parties") include ../includes/modules/admin-menu sidebar.menu-tertiary.sidebar(tg-admin-navigation="third-parties-webhooks") diff --git a/app/partials/backlog/backlog.jade b/app/partials/backlog/backlog.jade index 29edba68..9bf5254a 100644 --- a/app/partials/backlog/backlog.jade +++ b/app/partials/backlog/backlog.jade @@ -2,7 +2,7 @@ doctype html div.wrapper(tg-backlog, ng-controller="BacklogController as ctrl", ng-init="section='backlog'") - + nav.menu.hidden(tg-project-menu) sidebar.menu-secondary.extrabar.filters-bar(tg-backlog-filters) include ../includes/modules/backlog-filters section.main.backlog diff --git a/app/partials/contrib/main.jade b/app/partials/contrib/main.jade index cc9a2b5e..26aed247 100644 --- a/app/partials/contrib/main.jade +++ b/app/partials/contrib/main.jade @@ -1,6 +1,7 @@ doctype html div.wrapper.roles(ng-init="section='admin'", ng-controller="ContribController as ctrl") + nav.menu.hidden(tg-project-menu) sidebar.menu-secondary.sidebar(tg-admin-navigation="contrib") include ../includes/modules/admin-menu diff --git a/app/partials/issue/issues-detail.jade b/app/partials/issue/issues-detail.jade index e1e780b6..f53641da 100644 --- a/app/partials/issue/issues-detail.jade +++ b/app/partials/issue/issues-detail.jade @@ -2,6 +2,7 @@ doctype html div.wrapper(ng-controller="IssueDetailController as ctrl", ng-init="section='issues'") + nav.menu.hidden(tg-project-menu) div.main.us-detail div.us-detail-header.header-with-actions include ../includes/components/mainTitle diff --git a/app/partials/issue/issues.jade b/app/partials/issue/issues.jade index ed8f5ff0..62fe9ec8 100644 --- a/app/partials/issue/issues.jade +++ b/app/partials/issue/issues.jade @@ -1,6 +1,7 @@ doctype html div.wrapper.issues(tg-issues, ng-controller="IssuesController as ctrl", ng-init="section='issues'") + nav.menu.hidden(tg-project-menu) sidebar.menu-secondary.extrabar.filters-bar(tg-issues-filters) include ../includes/modules/issues-filters diff --git a/app/partials/kanban/kanban.jade b/app/partials/kanban/kanban.jade index 7a85f542..96b47004 100644 --- a/app/partials/kanban/kanban.jade +++ b/app/partials/kanban/kanban.jade @@ -2,6 +2,7 @@ doctype html div.wrapper(tg-kanban, ng-controller="KanbanController as ctrl" ng-init="section='kanban'") + nav.menu.hidden(tg-project-menu) section.main.kanban include ../includes/components/mainTitle include ../includes/modules/kanban-table diff --git a/app/partials/project/project.jade b/app/partials/project/project.jade index d639d50d..5cba01e1 100644 --- a/app/partials/project/project.jade +++ b/app/partials/project/project.jade @@ -1,6 +1,7 @@ doctype html div.wrapper(ng-controller="ProjectController as ctrl") + nav.menu.hidden(tg-project-menu) section.main.single-project h1 span.green(tg-bo-bind="project.name", class="project-name") diff --git a/app/partials/search/search.jade b/app/partials/search/search.jade index 68836dc4..0b3c2916 100644 --- a/app/partials/search/search.jade +++ b/app/partials/search/search.jade @@ -2,7 +2,7 @@ doctype html div.wrapper(tg-search, ng-controller="SearchController as ctrl", ng-init="section='search'") - + nav.menu.hidden(tg-project-menu) sidebar.menu-secondary.sidebar include ../includes/modules/search-in diff --git a/app/partials/task/task-detail.jade b/app/partials/task/task-detail.jade index a1f43b5d..89e5541e 100644 --- a/app/partials/task/task-detail.jade +++ b/app/partials/task/task-detail.jade @@ -2,6 +2,7 @@ doctype html div.wrapper(ng-controller="TaskDetailController as ctrl", ng-init="section='backlog-kanban'") + nav.menu.hidden(tg-project-menu) div.main.us-detail div.us-detail-header.header-with-actions include ../includes/components/mainTitle diff --git a/app/partials/taskboard/taskboard.jade b/app/partials/taskboard/taskboard.jade index 5f4cfda1..8f757855 100644 --- a/app/partials/taskboard/taskboard.jade +++ b/app/partials/taskboard/taskboard.jade @@ -2,6 +2,7 @@ doctype html div.wrapper(tg-taskboard, ng-controller="TaskboardController as ctrl", ng-init="section='backlog'") + nav.menu.hidden(tg-project-menu) section.main.taskboard .taskboard-inner h1 diff --git a/app/partials/team/team.jade b/app/partials/team/team.jade index 96323560..ac949f81 100644 --- a/app/partials/team/team.jade +++ b/app/partials/team/team.jade @@ -1,6 +1,7 @@ doctype html div.wrapper(ng-controller="TeamController as ctrl", ng-init="section='team'") + nav.menu.hidden(tg-project-menu) sidebar.menu-secondary include ../includes/modules/team/team-filters section.main.team diff --git a/app/partials/us/us-detail.jade b/app/partials/us/us-detail.jade index f930e9e7..3d1d5f67 100644 --- a/app/partials/us/us-detail.jade +++ b/app/partials/us/us-detail.jade @@ -2,6 +2,7 @@ doctype html div.wrapper(ng-controller="UserStoryDetailController as ctrl", ng-init="section='backlog-kanban'") + nav.menu.hidden(tg-project-menu) div.main.us-detail div.us-detail-header.header-with-actions include ../includes/components/mainTitle diff --git a/app/partials/user/mail-notifications.jade b/app/partials/user/mail-notifications.jade index a798aad6..c4aa3f9f 100644 --- a/app/partials/user/mail-notifications.jade +++ b/app/partials/user/mail-notifications.jade @@ -2,6 +2,8 @@ doctype html div.wrapper(tg-user-notifications, ng-controller="UserNotificationsController as ctrl", ng-init="section='mail-notifications'") + nav.menu.hidden(tg-project-menu) + sidebar.menu-secondary.sidebar(tg-user-settings-navigation="mail-notifications") include ../includes/modules/user-settings-menu diff --git a/app/partials/user/user-change-password.jade b/app/partials/user/user-change-password.jade index 625993ad..655b592b 100644 --- a/app/partials/user/user-change-password.jade +++ b/app/partials/user/user-change-password.jade @@ -2,6 +2,7 @@ doctype html div.wrapper(tg-user-change-password, ng-controller="UserChangePasswordController as ctrl", ng-init="section='user-settings'") + nav.menu.hidden(tg-project-menu) sidebar.menu-secondary.sidebar(tg-user-settings-navigation="change-password") include ../includes/modules/user-settings-menu diff --git a/app/partials/user/user-profile.jade b/app/partials/user/user-profile.jade index 7709cf60..6a4cc03e 100644 --- a/app/partials/user/user-profile.jade +++ b/app/partials/user/user-profile.jade @@ -2,6 +2,7 @@ doctype html div.wrapper(tg-user-profile, ng-controller="UserSettingsController as ctrl", ng-init="section='user-settings'") + nav.menu.hidden(tg-project-menu) sidebar.menu-secondary.sidebar(tg-user-settings-navigation="user-profile") include ../includes/modules/user-settings-menu diff --git a/app/partials/wiki/wiki.jade b/app/partials/wiki/wiki.jade index 34463d42..c90bc5a8 100644 --- a/app/partials/wiki/wiki.jade +++ b/app/partials/wiki/wiki.jade @@ -2,6 +2,7 @@ doctype html div.wrapper(ng-controller="WikiDetailController as ctrl", ng-init="section='wiki'") + nav.menu.hidden(tg-project-menu) sidebar.menu-secondary.extrabar(tg-check-permission="view_wiki_links") section.wiki-nav(tg-wiki-nav, ng-model="wikiLinks") section.main.wiki diff --git a/app/styles/core/base.scss b/app/styles/core/base.scss index 2be99cef..79504e0c 100644 --- a/app/styles/core/base.scss +++ b/app/styles/core/base.scss @@ -14,49 +14,6 @@ body { transition: 0; } } - .menu { - transform: translate3d(0, 0, 0); - transition: transform 1s ease; - } - &.open-projects-nav { - .projects-nav { - transform: translate3d(0, 0, 0); - transition: transform 1s ease; - } - .master { - transform: translate3d(300px, 0, 0); - transition: transform 1s ease; - &.ng-animate { - transition: 0; - } - } - .menu { - transform: translate3d(300px, 0, 0); - transition: transform 1s ease; - } - .projects-nav-overlay { - opacity: .9; - transform: translate3d(300px, 0, 0); - transition: all 1s ease; - } - &.closed-projects-nav { - .projects-nav { - transform: translate3d(-300px, 0, 0); - transition: transform 1s ease; - } - .projects-nav-overlay { - opacity: 0; - transform: translate3d(0, 0, 0); - transition: all 1s ease; - } - .master { - transform: translate3d(0, 0, 0); - } - .menu { - transform: translate3d(0, 0, 0); - } - } - } &.loading-project { overflow: hidden; .projects-nav-overlay { @@ -92,7 +49,6 @@ body { .wrapper { display: flex; min-height: 100vh; - padding-left: 90px; } .menu-secondary { diff --git a/app/styles/modules/common/nav.scss b/app/styles/modules/common/nav.scss index dd1a1415..cf87eec0 100644 --- a/app/styles/modules/common/nav.scss +++ b/app/styles/modules/common/nav.scss @@ -2,13 +2,11 @@ @extend %title; background-image: url('../images/menu.png'); background-position: left bottom; - flex-wrap: wrap; height: 100%; + min-height: 100vh; padding: 0 .3rem; - position: fixed; text-transform: uppercase; width: 90px; - z-index: 999; } .logo-container { cursor: pointer; From 9432ca39c289aa7c8bac6751e83de9e200040980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 14 Apr 2015 15:02:43 +0200 Subject: [PATCH 036/366] Top navigation bar --- .../modules/navigation-bar/navbar.jade | 22 +++++++++++++++++++ app/styles/modules/common/navbar.scss | 6 +++++ 2 files changed, 28 insertions(+) create mode 100644 app/partials/includes/modules/navigation-bar/navbar.jade create mode 100644 app/styles/modules/common/navbar.scss diff --git a/app/partials/includes/modules/navigation-bar/navbar.jade b/app/partials/includes/modules/navigation-bar/navbar.jade new file mode 100644 index 00000000..2a38732b --- /dev/null +++ b/app/partials/includes/modules/navigation-bar/navbar.jade @@ -0,0 +1,22 @@ +nav.navbar + div.nav-left + img.logo(src="svg/logo-nav.svg", alt="Taiga") + a(href="#", title="Discover trending projects") Discover + a(href="#", title="Taiga Support Page") Help + div.nav-right + a(href="", title="Dashboard") Dashboard + a(href="", title="Projects") Projects + //ul.dropdown.projects-list + // li + // a(href="#", title="{{ project-name }}") Project Name + // li + // a(href="#", title="{{ project-name }}") Project Name + // li + // a(href="#", title="{{ project-name }}") Project Name + // li + // a(href="#", title="{{ project-name }}") Project Name + + a(href="#", title="Organizations") Organizations + a(href="#", title="{{ user.fullname }} profile") + span Sebastián Sanchís + img(src="#", alt="{{ user.fullname }} picture") diff --git a/app/styles/modules/common/navbar.scss b/app/styles/modules/common/navbar.scss new file mode 100644 index 00000000..64672e1c --- /dev/null +++ b/app/styles/modules/common/navbar.scss @@ -0,0 +1,6 @@ +.navbar { + background: $black; + .logo { + max-width: 35px; + } +} From 0b4cf71c6737cc135c1a58004b5474c841972f75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 15 Apr 2015 12:41:20 +0200 Subject: [PATCH 037/366] Vertical Navigation bar styles --- app/images/menu-vert.png | Bin 0 -> 6729 bytes .../modules/navigation-bar/navbar.jade | 36 ++++------ app/styles/modules/common/navbar.scss | 64 +++++++++++++++++- app/svg/dashboard.svg | 5 ++ app/svg/organizations.svg | 5 ++ app/svg/projects.svg | 7 ++ 6 files changed, 94 insertions(+), 23 deletions(-) create mode 100644 app/images/menu-vert.png create mode 100644 app/svg/dashboard.svg create mode 100644 app/svg/organizations.svg create mode 100644 app/svg/projects.svg diff --git a/app/images/menu-vert.png b/app/images/menu-vert.png new file mode 100644 index 0000000000000000000000000000000000000000..e7d8425dcacdd0d439c720171cc2fda15350b982 GIT binary patch literal 6729 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 literal 0 HcmV?d00001 diff --git a/app/partials/includes/modules/navigation-bar/navbar.jade b/app/partials/includes/modules/navigation-bar/navbar.jade index 2a38732b..fa10a591 100644 --- a/app/partials/includes/modules/navigation-bar/navbar.jade +++ b/app/partials/includes/modules/navigation-bar/navbar.jade @@ -1,22 +1,16 @@ nav.navbar - div.nav-left - img.logo(src="svg/logo-nav.svg", alt="Taiga") - a(href="#", title="Discover trending projects") Discover - a(href="#", title="Taiga Support Page") Help - div.nav-right - a(href="", title="Dashboard") Dashboard - a(href="", title="Projects") Projects - //ul.dropdown.projects-list - // li - // a(href="#", title="{{ project-name }}") Project Name - // li - // a(href="#", title="{{ project-name }}") Project Name - // li - // a(href="#", title="{{ project-name }}") Project Name - // li - // a(href="#", title="{{ project-name }}") Project Name - - a(href="#", title="Organizations") Organizations - a(href="#", title="{{ user.fullname }} profile") - span Sebastián Sanchís - img(src="#", alt="{{ user.fullname }} picture") + div.nav-left + a.logo(href="#", title="Dashboard") + include ../../../../svg/logo.svg + a(href="#", title="Discover trending projects") Discover + a(href="#", title="Taiga Support Page") Help + div.nav-right + a(href="", title="Dashboard") + include ../../../../svg/dashboard.svg + a(href="", title="Projects") + include ../../../../svg/projects.svg + a(href="#", title="Organizations") + include ../../../../svg/organizations.svg + a.user-avatar(href="#", title="{{ user.fullname }} profile") + span Sebastián Sanchís + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/brad_frost/128.jpg", alt="{{ user.fullname }} picture") diff --git a/app/styles/modules/common/navbar.scss b/app/styles/modules/common/navbar.scss index 64672e1c..add48ee0 100644 --- a/app/styles/modules/common/navbar.scss +++ b/app/styles/modules/common/navbar.scss @@ -1,6 +1,66 @@ .navbar { - background: $black; + background: rgba($black, .2); + display: flex; + justify-content: space-between; + overflow: hidden; + position: relative; + &:after { + background: url('../images/menu-vert.png') center center repeat; + background-size: cover; + bottom: 0; + content: ''; + height: 100%; + left: 0; + position: absolute; + right: 0; + top: 0; + width: 200%; + z-index: -1; + } + .nav-left, + .nav-right { + align-content: center; + align-items: center; + display: flex; + } + a { + color: $gray-light; + display: inline-block; + padding: .5rem .75rem; + &:hover { + background: rgba($gray, .3); + color: $fresh-taiga; + svg path { + fill: $gray-light; + } + } + &.user-avatar { + padding: 0; + padding-left: .75rem; + } + + } .logo { - max-width: 35px; + padding: 0 2rem; + svg { + height: 2rem; + max-width: 2rem; + } + path { + fill: $white; + } + } + img { + height: 2.5rem; + padding-left: .3rem; + vertical-align: middle; + } + svg { + height: 1.2rem; + max-width: 1.2rem; + path { + fill: $gray; + transition: all .2s; + } } } diff --git a/app/svg/dashboard.svg b/app/svg/dashboard.svg new file mode 100644 index 00000000..1b2061a2 --- /dev/null +++ b/app/svg/dashboard.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/app/svg/organizations.svg b/app/svg/organizations.svg new file mode 100644 index 00000000..4dca4867 --- /dev/null +++ b/app/svg/organizations.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/app/svg/projects.svg b/app/svg/projects.svg new file mode 100644 index 00000000..e8c66014 --- /dev/null +++ b/app/svg/projects.svg @@ -0,0 +1,7 @@ + + + + + + + From 66f67f9f7e6fbb0c9a658caca5cd6b06589beca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 15 Apr 2015 12:41:41 +0200 Subject: [PATCH 038/366] Basic code clean for project navigation --- app/coffee/modules/nav.coffee | 179 +----------------------- app/coffee/modules/projects/main.coffee | 126 ----------------- app/index.jade | 4 +- app/partials/project/project-list.jade | 16 +-- 4 files changed, 12 insertions(+), 313 deletions(-) diff --git a/app/coffee/modules/nav.coffee b/app/coffee/modules/nav.coffee index 8c34d3ee..a9ebffc3 100644 --- a/app/coffee/modules/nav.coffee +++ b/app/coffee/modules/nav.coffee @@ -27,151 +27,6 @@ timeout = @.taiga.timeout module = angular.module("taigaNavMenu", []) -############################################################################# -## Projects Navigation -############################################################################# - -class ProjectsNavigationController extends taiga.Controller - @.$inject = ["$scope", "$rootScope", "$tgResources", "$tgNavUrls", "$projectUrl"] - - constructor: (@scope, @rootscope, @rs, @navurls, @projectUrl) -> - promise = @.loadInitialData() - promise.then null, -> - console.log "FAIL" - # TODO - - # Listen when someone wants to reload all the projects - @scope.$on "projects:reload", => - @.loadInitialData() - - # Listen when someone has reloaded a project - @scope.$on "project:loaded", (ctx, project) => - @.loadInitialData() - - loadInitialData: -> - return @rs.projects.listByMember(@rootscope.user?.id).then (projects) => - for project in projects - project.url = @projectUrl.get(project) - @scope.projects = projects - @scope.filteredProjects = projects - @scope.filterText = "" - return projects - - newProject: -> - @scope.$apply () => - @rootscope.$broadcast("projects:create") - - filterProjects: (text) -> - @scope.filteredProjects = _.filter @scope.projects, (project) -> - project.name.toLowerCase().indexOf(text) > -1 - - @scope.filterText = text - @rootscope.$broadcast("projects:filtered") - -module.controller("ProjectsNavigationController", ProjectsNavigationController) - - -ProjectsNavigationDirective = ($rootscope, animationFrame, $timeout, tgLoader, $location, $compile, $template) -> - baseTemplate = $template.get("project/project-navigation-base.html", true) - projectsTemplate = $template.get("project/project-navigation-list.html", true) - - overlay = $(".projects-nav-overlay") - loadingStart = 0 - - hideMenu = () -> - if overlay.is(':visible') - difftime = new Date().getTime() - loadingStart - timeoutValue = 0 - - if (difftime < 1000) - timeoutValue = 1000 - timeoutValue - - timeout timeoutValue, -> - overlay.one 'transitionend', () -> - $(document.body) - .removeClass("loading-project open-projects-nav closed-projects-nav") - .css("overflow-x", "visible") - - overlay.hide() - - $(document.body).addClass("closed-projects-nav") - - tgLoader.disablePreventLoading() - - - link = ($scope, $el, $attrs, $ctrls) -> - $ctrl = $ctrls[0] - $rootscope.$on("project:loaded", hideMenu) - - renderProjects = (projects) -> - html = projectsTemplate({projects: projects}) - $el.find(".projects-list").html(html) - - $scope.$emit("regenerate:project-pagination") - - render = (projects) -> - $el.html($compile(baseTemplate())($scope)) - renderProjects(projects) - - overlay.on 'click', () -> - hideMenu() - - $(document).on 'keydown', (e) => - code = if e.keyCode then e.keyCode else e.which - if code == 27 - hideMenu() - - $scope.$on "nav:projects-list:open", -> - if !$(document.body).hasClass("open-projects-nav") - animationFrame.add () => overlay.show() - - animationFrame.add( - () => $(document.body).css("overflow-x", "hidden") - () => $(document.body).toggleClass("open-projects-nav") - ) - - $el.on "click", ".projects-list > li > a", (event) -> - # HACK: to solve a problem with the loader when the next url - # is equal to the current one - target = angular.element(event.currentTarget) - nextUrl = target.prop("href") - currentUrl = $location.absUrl() - if nextUrl == currentUrl - hideMenu() - return - # END HACK - - $(document.body).addClass('loading-project') - - tgLoader.preventLoading() - - loadingStart = new Date().getTime() - - $el.on "click", ".create-project-button", (event) -> - event.preventDefault() - $ctrl.newProject() - - $el.on "keyup", ".search-project", (event) -> - target = angular.element(event.currentTarget) - $ctrl.filterProjects(target.val()) - - $scope.$on "projects:filtered", -> - renderProjects($scope.filteredProjects) - - $scope.$watch "projects", (projects) -> - render(projects) if projects? - - return { - require: ["tgProjectsNav"] - controller: ProjectsNavigationController - link: link - } - - -module.directive("tgProjectsNav", ["$rootScope", "animationFrame", "$timeout", "tgLoader", "$tgLocation", "$compile", - "$tgTemplate", ProjectsNavigationDirective]) - - ############################################################################# ## Project ############################################################################# @@ -180,32 +35,6 @@ ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $ menuEntriesTemplate = $template.get("project/project-menu.html", true) mainTemplate = _.template(""" - """) @@ -275,10 +104,10 @@ ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location, $ renderMainMenu($el) project = null - $el.on "click", ".logo", (event) -> - event.preventDefault() - target = angular.element(event.currentTarget) - $rootscope.$broadcast("nav:projects-list:open") + # $el.on "click", ".logo", (event) -> + # event.preventDefault() + # target = angular.element(event.currentTarget) + # $rootscope.$broadcast("nav:projects-list:open") $el.on "click", ".user-settings .avatar", (event) -> event.preventDefault() diff --git a/app/coffee/modules/projects/main.coffee b/app/coffee/modules/projects/main.coffee index c041d588..3dc3b6ca 100644 --- a/app/coffee/modules/projects/main.coffee +++ b/app/coffee/modules/projects/main.coffee @@ -116,132 +116,6 @@ class ProjectController extends taiga.Controller module.controller("ProjectController", ProjectController) - -ProjectsPaginationDirective = ($timeout) -> - link = ($scope, $el, $attrs) -> - prevBtn = $el.find(".v-pagination-previous") - nextBtn = $el.find(".v-pagination-next") - container = $el.find("ul") - - pageSize = 0 - containerSize = 0 - - render = -> - pageSize = $el.find(".v-pagination-list").height() - - if container.find("li").length - if hasPagination() - if hasNextPage() - visible(nextBtn) - else - hide(nextBtn) - - if hasPrevPage() - visible(prevBtn) - else - hide(prevBtn) - else - remove() - else - remove() - - hasPagination = -> - containerSize = container.height() - - return containerSize > pageSize - - hasPrevPage = (top) -> - if !top? - top = -parseInt(container.css('top'), 10) || 0 - - return top != 0 - - hasNextPage = (top) -> - containerSize = container.height() - - if !top - top = -parseInt(container.css('top'), 10) || 0 - - return containerSize > pageSize && top + pageSize < containerSize - - nextPage = (callback) -> - top = parseInt(container.css('top'), 10) - newTop = top - pageSize - - lastLi = $el.find(".v-pagination-list li:last-child") - maxTop = -((lastLi.position().top + lastLi.outerHeight()) - pageSize) - - newTop = maxTop if newTop < maxTop - - container.animate({"top": newTop}, callback) - - return newTop - - prevPage = (callback) -> - top = parseInt(container.css('top'), 10) - - newTop = top + pageSize - - newTop = 0 if newTop > 0 - - container.animate({"top": newTop}, callback) - - return newTop - - visible = (element) -> - element.css('visibility', 'visible') - - hide = (element) -> - element.css('visibility', 'hidden') - - checkButtonVisibility = () -> - - remove = () -> - container.css('top', 0) - hide(prevBtn) - hide(nextBtn) - - $el.on "click", ".v-pagination-previous", (event) -> - event.preventDefault() - - if container.is(':animated') - return - - visible(nextBtn) - - newTop = prevPage() - - if !hasPrevPage(newTop) - hide(prevBtn) - - $el.on "click", ".v-pagination-next", (event) -> - event.preventDefault() - - if container.is(':animated') - return - - visible(prevBtn) - - newTop = -nextPage() - - if !hasNextPage(newTop) - hide(nextBtn) - - $scope.$on "regenerate:project-pagination", -> - remove() - render() - - $(window).on "resize.projects-pagination", render - - $scope.$on "$destroy", -> - $(window).off "resize.projects-pagination" - - return { - link: link - } - -module.directive("tgProjectsPagination", ['$timeout', ProjectsPaginationDirective]) - ProjectsListDirective = ($compile, $template) -> template = $template.get('project/project-list.html', true) diff --git a/app/index.jade b/app/index.jade index 6829df65..f994ae36 100644 --- a/app/index.jade +++ b/app/index.jade @@ -15,12 +15,12 @@ html(lang="en") //include partials/includes/modules/projects-nav include partials/includes/components/notification-message + include partials/includes/modules/navigation-bar/navbar + //- the content of nav.menu is in coffe.modules.base TaigaMain directive //nav.menu.hidden(tg-project-menu) - div.master(ng-view) - include partials/includes/modules/navigation-bar/navbar div.lightbox.lightbox-generic-ask include partials/includes/modules/lightbox-generic-ask diff --git a/app/partials/project/project-list.jade b/app/partials/project/project-list.jade index 9e52dbd8..34c72020 100644 --- a/app/partials/project/project-list.jade +++ b/app/partials/project/project-list.jade @@ -1,11 +1,7 @@ div(tg-projects-pagination) - .projects-pagination - a.v-pagination-previous.icon.icon-arrow-up(href='') - .v-pagination-list - ul.projects-list - <% _.each(projects, function(project) { %> - li - a(href!='<%- project.url %>') - <%- project.name %> - <% }) %> - a.v-pagination-next.icon.icon-arrow-bottom(href='') \ No newline at end of file + ul.projects-list + <% _.each(projects, function(project) { %> + li + a.button(href!='<%- project.url %>') + <%- project.name %> + <% }) %> From e958137f56d4146c2aab24a709ed15ecbad59def Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 15 Apr 2015 13:44:43 +0200 Subject: [PATCH 039/366] Vertical Navigation Bar Styles --- app/partials/project/project-menu.jade | 35 ++----- app/styles/modules/common/nav.scss | 128 ++++++++++--------------- app/styles/modules/common/navbar.scss | 4 +- 3 files changed, 62 insertions(+), 105 deletions(-) diff --git a/app/partials/project/project-menu.jade b/app/partials/project/project-menu.jade index 4eeaede7..4b053ee1 100644 --- a/app/partials/project/project-menu.jade +++ b/app/partials/project/project-menu.jade @@ -3,63 +3,44 @@ div(class="menu-container") li(id="nav-search") a(href="" title="{{'PROJECT.SECTION.SEARCH' | translate}}") span(class="icon icon-search") - span(class="item", translate="PROJECT.SECTION.SEARCH") + span.helper(translate="PROJECT.SECTION.SEARCH") <% if (project.is_backlog_activated && project.my_permissions.indexOf("view_us") != -1) { %> li(id="nav-backlog") a(href="" title="{{'PROJECT.SECTION.BACKLOG' | translate}}" tg-nav="project-backlog:project=project.slug") span(class="icon icon-backlog") - span(class="item", translate="PROJECT.SECTION.BACKLOG") + span.helper(translate="PROJECT.SECTION.BACKLOG") <% } %> <% if (project.is_kanban_activated && project.my_permissions.indexOf("view_us") != -1) { %> li(id="nav-kanban") a(href="" title="{{'PROJECT.SECTION.KANBAN' | translate}}" tg-nav="project-kanban:project=project.slug") span(class="icon icon-kanban") - span(class="item", translate="PROJECT.SECTION.KANBAN") + span.helper(translate="PROJECT.SECTION.KANBAN") <% } %> <% if (project.is_issues_activated && project.my_permissions.indexOf("view_issues") != -1) { %> li(id="nav-issues") a(href="" title="{{'PROJECT.SECTION.ISSUES' | translate}}" tg-nav="project-issues:project=project.slug") span(class="icon icon-issues") - span(class="item", translate="PROJECT.SECTION.ISSUES") + span.helper(translate="PROJECT.SECTION.ISSUES") <% } %> <% if (project.is_wiki_activated && project.my_permissions.indexOf("view_wiki_pages") != -1) { %> li(id="nav-wiki") a(href="" title="{{'PROJECT.SECTION.WIKI' | translate}}" tg-nav="project-wiki:project=project.slug") span(class="icon icon-wiki") - span(class="item", translate="PROJECT.SECTION.WIKI") + span.helper(translate="PROJECT.SECTION.WIKI") <% } %> li(id="nav-team") a(href="" title="{{'PROJECT.SECTION.TEAM' | translate}}" tg-nav="project-team:project=project.slug") span(class="icon icon-team") - span(class="item", translate="PROJECT.SECTION.TEAM") + span.helper(translate="PROJECT.SECTION.TEAM") <% if (project.videoconferences) { %> li(id="nav-video") a(href!="<%- project.videoconferenceUrl %>" target="_blank" title="{{'PROJECT.SECTION.MEETUP' | translate}}") span(class="icon icon-video") - span(class="item", translate="PROJECT.SECTION.MEETUP") + span(translate="PROJECT.SECTION.MEETUP") <% } %> <% if (project.i_am_owner) { %> li(id="nav-admin") a(href="" tg-nav="project-admin-home:project=project.slug" title="{{'PROJECT.SECTION.ADMIN' | translate}}") span(class="icon icon-settings") - span(class="item", translate="PROJECT.SECTION.ADMIN") + span.helper(translate="PROJECT.SECTION.ADMIN") <% } %> - <% if (user) { %> - div(class="user") - div(class="user-settings") - ul(class="popover") - li - a(href="" title="{{'USER_SETTINGS.POPOVER.USER_PROFILE' | translate}}", tg-nav="user-settings-user-profile:project=project.slug", translate="USER_SETTINGS.POPOVER.USER_PROFILE") - li - a(href="" title="{{'USER_SETTINGS.POPOVER.CHANGE_PASSWORD' | translate}}", tg-nav="user-settings-user-change-password:project=project.slug", translate="USER_SETTINGS.POPOVER.CHANGE_PASSWORD") - li - a(href="" title="{{'USER_SETTINGS.POPOVER.NOTIFICATIONS' | translate}}", tg-nav="user-settings-mail-notifications:project=project.slug", translate="USER_SETTINGS.POPOVER.NOTIFICATIONS") - <% if (feedbackEnabled) { %> - li - a(href="" class="feedback" title="{{'USER_SETTINGS.POPOVER.FEEDBACK' | translate}}", translate="USER_SETTINGS.POPOVER.FEEDBACK") - <% } %> - li - a(href="" title="{{'COMMON.LOGOUT' | translate}}" class="logout", translate="COMMON.LOGOUT") - a(href="" title="{{'USER_SETTINGS.POPOVER.TITLE_AVATAR' | translate}}" class="avatar" id="nav-user-settings") - img(src="{{ user.photo }}" alt="{{ user.full_name_display }}") - <% } %> diff --git a/app/styles/modules/common/nav.scss b/app/styles/modules/common/nav.scss index cf87eec0..b935e8e9 100644 --- a/app/styles/modules/common/nav.scss +++ b/app/styles/modules/common/nav.scss @@ -1,33 +1,13 @@ +$label-arrow-wh: 12px; + .menu { - @extend %title; background-image: url('../images/menu.png'); - background-position: left bottom; + background-position: center center; height: 100%; min-height: 100vh; - padding: 0 .3rem; + padding: 1rem .3rem; text-transform: uppercase; - width: 90px; -} -.logo-container { - cursor: pointer; - padding: 15px 15px 10px; - object, - img, - svg { - height: 50px; - width: 50px; - } - span { - @extend %large; - color: $white; - display: block; - margin-top: -5px; - text-align: center; - } - sup { - display: block; - line-height: 1rem; - } + width: 50px; } .main-nav { @@ -36,26 +16,55 @@ position: relative; text-align: center; li { - margin-bottom: 1rem; + margin-bottom: .75rem; } a { color: $white; display: block; + position: relative; text-align: center; - &:hover { - color: $fresh-taiga; - transition: color .3s linear; - } - span { + } + a:hover { + color: $fresh-taiga; + transition: color .3s linear; + .helper { + @extend %small; + animation: slideLeft 200ms ease-in-out both; + background: linear-gradient(to right, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, .8) 100%); + color: $white; display: block; + left: 45px; + opacity: 1; + padding: .4rem 1rem; + position: absolute; + top: 0; + transition: all .2s; + z-index: 99; + &:after { + background: rgba($blackish, 1); + content: ''; + height: $label-arrow-wh; + left: calc(-#{$label-arrow-wh}/2); + position: absolute; + top: calc(50% - #{$label-arrow-wh}/2); + transform: rotate(45deg); + width: $label-arrow-wh; + z-index: 98; + } } - .icon { - @extend %xlarge; - line-height: 2.2rem; - } - .item { - @extend %large; - } + } + span { + display: block; + } + .helper { + display: none; + } + .icon { + font-size: 1.5rem; + line-height: 2.2rem; + } + .item { + @extend %large; } .active { color: $fresh-taiga; @@ -63,44 +72,11 @@ } } -.user { - bottom: 1rem; - padding: 0 10px; - position: absolute; - width: 80px; - .popover { - @include popover(150px, '', 60px, 0, '', 15px, '', -6px, 25px); - a { - @extend %small; - color: $white; - text-align: left; - text-transform: none; - &:hover { - color: $fresh-taiga; - transition: color .2s linear; - } - } +@keyframes slideLeft { + 0% { + opacity: 0; } - img { - margin: 0 5px 10px; - width: 80%; - &:hover { - border-color: $fresh-taiga; - transition: border-color .3s linear; - } - } - .user-settings { - position: relative; - } - .settings { - text-align: center; - a { - color: $whitish; - margin-right: .5rem; - &:hover { - color: $fresh-taiga; - transition: color .3s linear; - } - } + 100% { + opacity: 1; } } diff --git a/app/styles/modules/common/navbar.scss b/app/styles/modules/common/navbar.scss index add48ee0..3e9bffd4 100644 --- a/app/styles/modules/common/navbar.scss +++ b/app/styles/modules/common/navbar.scss @@ -26,7 +26,7 @@ a { color: $gray-light; display: inline-block; - padding: .5rem .75rem; + padding: .5rem 2rem; &:hover { background: rgba($gray, .3); color: $fresh-taiga; @@ -36,7 +36,7 @@ } &.user-avatar { padding: 0; - padding-left: .75rem; + padding-left: 2rem; } } From 49b994b531fd7ea1ccf84cb6d7182202072fc6fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 15 Apr 2015 16:16:06 +0200 Subject: [PATCH 040/366] Top Navigation Bar Advanced styles --- app/images/menu-vert.png | Bin 6729 -> 10391 bytes app/styles/modules/common/nav.scss | 14 +++---- app/styles/modules/common/navbar.scss | 52 +++++++++++++++++--------- 3 files changed, 40 insertions(+), 26 deletions(-) diff --git a/app/images/menu-vert.png b/app/images/menu-vert.png index e7d8425dcacdd0d439c720171cc2fda15350b982..d1a8e7c2cb66e64310e5976d75730784266519b7 100644 GIT binary patch literal 10391 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 Bt^5E0 literal 6729 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 diff --git a/app/styles/modules/common/nav.scss b/app/styles/modules/common/nav.scss index b935e8e9..65b7fba5 100644 --- a/app/styles/modules/common/nav.scss +++ b/app/styles/modules/common/nav.scss @@ -5,9 +5,8 @@ $label-arrow-wh: 12px; background-position: center center; height: 100%; min-height: 100vh; - padding: 1rem .3rem; + padding: 1rem 0; text-transform: uppercase; - width: 50px; } .main-nav { @@ -15,17 +14,15 @@ $label-arrow-wh: 12px; padding: 0; position: relative; text-align: center; - li { - margin-bottom: .75rem; - } a { color: $white; display: block; + padding: .75rem .8rem; position: relative; - text-align: center; } a:hover { - color: $fresh-taiga; + //color: $fresh-taiga; + background: rgba($black, .2); transition: color .3s linear; .helper { @extend %small; @@ -33,7 +30,7 @@ $label-arrow-wh: 12px; background: linear-gradient(to right, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, .8) 100%); color: $white; display: block; - left: 45px; + left: 50px; opacity: 1; padding: .4rem 1rem; position: absolute; @@ -67,6 +64,7 @@ $label-arrow-wh: 12px; @extend %large; } .active { + background: rgba($black, .2); color: $fresh-taiga; transition: color .3s linear; } diff --git a/app/styles/modules/common/navbar.scss b/app/styles/modules/common/navbar.scss index 3e9bffd4..c893cf2e 100644 --- a/app/styles/modules/common/navbar.scss +++ b/app/styles/modules/common/navbar.scss @@ -1,12 +1,14 @@ +$top-icon-color: #11241f; + .navbar { - background: rgba($black, .2); + background: rgba($black, .5); display: flex; justify-content: space-between; overflow: hidden; position: relative; &:after { - background: url('../images/menu-vert.png') center center repeat; - background-size: cover; + background: url('../images/menu-vert.png') repeat top left; + background-size: 100%; bottom: 0; content: ''; height: 100%; @@ -23,33 +25,47 @@ align-items: center; display: flex; } + .nav-left { + a { + padding: .5rem 1.5rem; + &.logo { + background: rgba($black, .2); + padding: .4rem .75rem; + } + svg { + height: 1.6rem; + max-width: 2rem; + } + path { + fill: $white; + } + } + } + .nav-right { + a { + padding: .5rem 2rem; + } + } a { - color: $gray-light; + color: $white; display: inline-block; - padding: .5rem 2rem; + transition: all .2s linear; &:hover { - background: rgba($gray, .3); + background: rgba($black, .2); color: $fresh-taiga; svg path { - fill: $gray-light; + fill: $white; } } &.user-avatar { padding: 0; padding-left: 2rem; + span { + padding-right: 1rem; + } } } - .logo { - padding: 0 2rem; - svg { - height: 2rem; - max-width: 2rem; - } - path { - fill: $white; - } - } img { height: 2.5rem; padding-left: .3rem; @@ -59,7 +75,7 @@ height: 1.2rem; max-width: 1.2rem; path { - fill: $gray; + fill: $dark-taiga; transition: all .2s; } } From c521974f6c58377791d6f31f747a7d47e4b70ad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 15 Apr 2015 16:17:04 +0200 Subject: [PATCH 041/366] Remove unneded image --- app/images/github-help.png | Bin 16267 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 app/images/github-help.png diff --git a/app/images/github-help.png b/app/images/github-help.png deleted file mode 100644 index 795fd9f502759f5fc23381ac180d729d8d245de0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16267 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 wVuBPp(yQv2!uE Date: Wed, 15 Apr 2015 16:38:32 +0200 Subject: [PATCH 042/366] First dropdown draft --- .../modules/navigation-bar/navbar.jade | 33 +++++++++++-------- app/styles/modules/common/navbar.scss | 21 +++++++++++- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/app/partials/includes/modules/navigation-bar/navbar.jade b/app/partials/includes/modules/navigation-bar/navbar.jade index fa10a591..d80605c2 100644 --- a/app/partials/includes/modules/navigation-bar/navbar.jade +++ b/app/partials/includes/modules/navigation-bar/navbar.jade @@ -1,16 +1,21 @@ nav.navbar - div.nav-left - a.logo(href="#", title="Dashboard") - include ../../../../svg/logo.svg - a(href="#", title="Discover trending projects") Discover - a(href="#", title="Taiga Support Page") Help - div.nav-right - a(href="", title="Dashboard") - include ../../../../svg/dashboard.svg + div.nav-left + a.logo(href="#", title="Dashboard") + include ../../../../svg/logo.svg + a(href="#", title="Discover trending projects") Discover + a(href="#", title="Taiga Support Page") Help + div.nav-right + a(href="", title="Dashboard") + include ../../../../svg/dashboard.svg + div.top-nav-dropdown a(href="", title="Projects") - include ../../../../svg/projects.svg - a(href="#", title="Organizations") - include ../../../../svg/organizations.svg - a.user-avatar(href="#", title="{{ user.fullname }} profile") - span Sebastián Sanchís - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/brad_frost/128.jpg", alt="{{ user.fullname }} picture") + include ../../../../svg/projects.svg + ul.project-list + - for (var x = 0; x < 5; x++) + li + a(href="#", title="{{ project.title }}") Project 1 + a(href="#", title="Organizations") + include ../../../../svg/organizations.svg + a.user-avatar(href="#", title="{{ user.fullname }} profile") + span Sebastián Sanchís + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/brad_frost/128.jpg", alt="{{ user.fullname }} picture") diff --git a/app/styles/modules/common/navbar.scss b/app/styles/modules/common/navbar.scss index c893cf2e..f572f39c 100644 --- a/app/styles/modules/common/navbar.scss +++ b/app/styles/modules/common/navbar.scss @@ -4,7 +4,6 @@ $top-icon-color: #11241f; background: rgba($black, .5); display: flex; justify-content: space-between; - overflow: hidden; position: relative; &:after { background: url('../images/menu-vert.png') repeat top left; @@ -56,6 +55,9 @@ $top-icon-color: #11241f; svg path { fill: $white; } + +.dropdown { + display: block; + } } &.user-avatar { padding: 0; @@ -79,4 +81,21 @@ $top-icon-color: #11241f; transition: all .2s; } } + .top-nav-dropdown { + position: relative; + } + %dropdown { + background: rgba($black, .85); + border: 1px solid $grayer; + border-radius: 2px; + display: none; + left: 0; + position: absolute; + top: 2rem; + width: 20vh; + z-index: 99; + } + .project-list { + @extend %dropdown; + } } From f4998a3c22a035997f7560732c159e89f540e825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 16 Apr 2015 12:15:05 +0200 Subject: [PATCH 043/366] Dropdown styles --- .../modules/navigation-bar/navbar.jade | 9 ++++--- app/styles/dependencies/mixins.scss | 26 +++++++++++++++++++ app/styles/modules/common/navbar.scss | 9 ++++--- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/app/partials/includes/modules/navigation-bar/navbar.jade b/app/partials/includes/modules/navigation-bar/navbar.jade index d80605c2..f26800cb 100644 --- a/app/partials/includes/modules/navigation-bar/navbar.jade +++ b/app/partials/includes/modules/navigation-bar/navbar.jade @@ -10,10 +10,11 @@ nav.navbar div.top-nav-dropdown a(href="", title="Projects") include ../../../../svg/projects.svg - ul.project-list - - for (var x = 0; x < 5; x++) - li - a(href="#", title="{{ project.title }}") Project 1 + div.project-list + ul + - for (var x = 0; x < 5; x++) + li + a(href="#", title="{{ project.title }}") Project 1 a(href="#", title="Organizations") include ../../../../svg/organizations.svg a.user-avatar(href="#", title="{{ user.fullname }} profile") diff --git a/app/styles/dependencies/mixins.scss b/app/styles/dependencies/mixins.scss index 19730da8..3243be31 100644 --- a/app/styles/dependencies/mixins.scss +++ b/app/styles/dependencies/mixins.scss @@ -75,3 +75,29 @@ width: #{$arrow-width}; } } + +@mixin arrow($arrow-direction, $background-color, $border-color, $border-width, $border-size) { + &:after, + &:before { + #{$arrow-direction}: 100%; + left: 50%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + } + &:after { + border-color: rgba(136, 183, 213, 0); + border-#{$arrow-direction}-color: $background-color; + border-width: $border-size; + margin-left: -$border-size; + } + &:before { + border-color: rgba(0, 0, 0, 0); + border-#{$arrow-direction}-color: $border-color; + border-width: calc($border-size + $border-width); + margin-left: calc(-$border-size + $border-width); + } +} diff --git a/app/styles/modules/common/navbar.scss b/app/styles/modules/common/navbar.scss index f572f39c..e6427e5f 100644 --- a/app/styles/modules/common/navbar.scss +++ b/app/styles/modules/common/navbar.scss @@ -55,7 +55,7 @@ $top-icon-color: #11241f; svg path { fill: $white; } - +.dropdown { + +.project-list { display: block; } } @@ -85,8 +85,6 @@ $top-icon-color: #11241f; position: relative; } %dropdown { - background: rgba($black, .85); - border: 1px solid $grayer; border-radius: 2px; display: none; left: 0; @@ -97,5 +95,10 @@ $top-icon-color: #11241f; } .project-list { @extend %dropdown; + background: rgba($blackish, .9); + border: 1px solid $black; + ul { + @include arrow('bottom', rgba($blackish, .9), $black, 2px, 15px); + } } } From 8442cb1d0b659e2867ec175c7d7f5b15a9f09e73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 16 Apr 2015 15:32:34 +0200 Subject: [PATCH 044/366] Dropdown menus --- .../modules/navigation-bar/navbar.jade | 6 +- app/styles/dependencies/colors.scss | 5 ++ app/styles/dependencies/mixins.scss | 14 ++--- app/styles/modules/common/navbar.scss | 61 ++++++++++++++----- 4 files changed, 62 insertions(+), 24 deletions(-) diff --git a/app/partials/includes/modules/navigation-bar/navbar.jade b/app/partials/includes/modules/navigation-bar/navbar.jade index f26800cb..f22a2230 100644 --- a/app/partials/includes/modules/navigation-bar/navbar.jade +++ b/app/partials/includes/modules/navigation-bar/navbar.jade @@ -10,11 +10,13 @@ nav.navbar div.top-nav-dropdown a(href="", title="Projects") include ../../../../svg/projects.svg - div.project-list + div.dropdown-project-list ul - - for (var x = 0; x < 5; x++) + - for (var x = 0; x < 9; x++) li a(href="#", title="{{ project.title }}") Project 1 + a.see-more-projects-btn.button-gray(href="#", title="See more projects") See more + a.create-project-btn.button-green(href="#", title="Create project") Create project a(href="#", title="Organizations") include ../../../../svg/organizations.svg a.user-avatar(href="#", title="{{ user.fullname }} profile") diff --git a/app/styles/dependencies/colors.scss b/app/styles/dependencies/colors.scss index 3b73fad4..ed5d3ce0 100755 --- a/app/styles/dependencies/colors.scss +++ b/app/styles/dependencies/colors.scss @@ -30,3 +30,8 @@ $yellow-pear: #bbe831; $menu: #232323; $star-fill: #edd400; + +// Top menu values +$top-icon-color: #11241f; +$dropdown-width: 500px; +$dropdown-color: rgba(darken($grayer, 20%), 1); diff --git a/app/styles/dependencies/mixins.scss b/app/styles/dependencies/mixins.scss index 3243be31..c7524d4d 100644 --- a/app/styles/dependencies/mixins.scss +++ b/app/styles/dependencies/mixins.scss @@ -76,7 +76,7 @@ } } -@mixin arrow($arrow-direction, $background-color, $border-color, $border-width, $border-size) { +@mixin arrow($arrow-direction, $border-color, $background-color, $border-width, $border-size) { &:after, &:before { #{$arrow-direction}: 100%; @@ -89,15 +89,15 @@ pointer-events: none; } &:after { - border-color: rgba(136, 183, 213, 0); + border-color: rgba($background-color, 0); border-#{$arrow-direction}-color: $background-color; - border-width: $border-size; - margin-left: -$border-size; + border-width: #{$border-size}px; + margin-left: -#{$border-size}px; } &:before { - border-color: rgba(0, 0, 0, 0); + border-color: rgba($border-color, 0); border-#{$arrow-direction}-color: $border-color; - border-width: calc($border-size + $border-width); - margin-left: calc(-$border-size + $border-width); + border-width: calc(#{$border-size}px + #{$border-width}px); + margin-left: calc(-#{$border-size}px + #{$border-width}px); } } diff --git a/app/styles/modules/common/navbar.scss b/app/styles/modules/common/navbar.scss index e6427e5f..4dd0ef99 100644 --- a/app/styles/modules/common/navbar.scss +++ b/app/styles/modules/common/navbar.scss @@ -1,5 +1,3 @@ -$top-icon-color: #11241f; - .navbar { background: rgba($black, .5); display: flex; @@ -25,7 +23,7 @@ $top-icon-color: #11241f; display: flex; } .nav-left { - a { + >a { padding: .5rem 1.5rem; &.logo { background: rgba($black, .2); @@ -44,6 +42,11 @@ $top-icon-color: #11241f; a { padding: .5rem 2rem; } + .dropdown-project-list { + a { + padding: .8rem .5rem; + } + } } a { color: $white; @@ -55,9 +58,7 @@ $top-icon-color: #11241f; svg path { fill: $white; } - +.project-list { - display: block; - } + } &.user-avatar { padding: 0; @@ -83,22 +84,52 @@ $top-icon-color: #11241f; } .top-nav-dropdown { position: relative; + &:hover { + .dropdown-project-list { + display: block; + } + } } %dropdown { border-radius: 2px; display: none; - left: 0; + left: calc(50% - #{$dropdown-width}/2); + min-width: $dropdown-width; position: absolute; - top: 2rem; - width: 20vh; + top: 2.5rem; z-index: 99; } - .project-list { - @extend %dropdown; - background: rgba($blackish, .9); - border: 1px solid $black; - ul { - @include arrow('bottom', rgba($blackish, .9), $black, 2px, 15px); +} + +.dropdown-project-list { + @extend %dropdown; + background: $blackish; + border: 1px solid $black; + padding: .3rem; + ul { + @include arrow('bottom', $blackish, $blackish, 1, 12); + margin: 0; + margin-bottom: .5rem; + padding: 0; + } + a { + color: $gray-light; + display: block; + &:hover { + background: rgba($white, .1); + color: $fresh-taiga; + } + &.see-more-projects-btn, + &.create-project-btn { + color: $white; + padding: .5rem; + text-align: center; + &:hover { + color: $white; + } + } + &.see-more-projects-btn { + margin-bottom: .3rem; } } } From 10739b4764984fd36c5d32f102932983e81a11b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 17 Apr 2015 08:43:07 +0200 Subject: [PATCH 045/366] Dropdown Lists for organizations and users --- .../dropdown-organization-list.jade | 6 +++++ .../navigation-bar/dropdown-project-list.jade | 7 +++++ .../modules/navigation-bar/dropdown-user.jade | 19 ++++++++++++++ .../modules/navigation-bar/navbar.jade | 26 +++++++++---------- app/styles/dependencies/colors.scss | 2 +- app/styles/modules/common/navbar.scss | 23 +++++++++++++--- 6 files changed, 65 insertions(+), 18 deletions(-) create mode 100644 app/partials/includes/modules/navigation-bar/dropdown-organization-list.jade create mode 100644 app/partials/includes/modules/navigation-bar/dropdown-project-list.jade create mode 100644 app/partials/includes/modules/navigation-bar/dropdown-user.jade diff --git a/app/partials/includes/modules/navigation-bar/dropdown-organization-list.jade b/app/partials/includes/modules/navigation-bar/dropdown-organization-list.jade new file mode 100644 index 00000000..49b37952 --- /dev/null +++ b/app/partials/includes/modules/navigation-bar/dropdown-organization-list.jade @@ -0,0 +1,6 @@ +div.navbar-dropdown.dropdown-organization-list + ul + - for (var x = 0; x < 4; x++) + li + a(href="#", title="{{ project.title }}") Organization 1 + a.create-organization-btn.button-green(href="#", title="Create Organization") Create Organization diff --git a/app/partials/includes/modules/navigation-bar/dropdown-project-list.jade b/app/partials/includes/modules/navigation-bar/dropdown-project-list.jade new file mode 100644 index 00000000..e805759a --- /dev/null +++ b/app/partials/includes/modules/navigation-bar/dropdown-project-list.jade @@ -0,0 +1,7 @@ +div.navbar-dropdown.dropdown-project-list + ul + - for (var x = 0; x < 9; x++) + li + a(href="#", title="{{ project.title }}") Project 1 + a.see-more-projects-btn.button-gray(href="#", title="See more projects") See more + a.create-project-btn.button-green(href="#", title="Create project") Create project diff --git a/app/partials/includes/modules/navigation-bar/dropdown-user.jade b/app/partials/includes/modules/navigation-bar/dropdown-user.jade new file mode 100644 index 00000000..a7f2a638 --- /dev/null +++ b/app/partials/includes/modules/navigation-bar/dropdown-user.jade @@ -0,0 +1,19 @@ +div.navbar-dropdown.dropdown-user + ul + li + a(href="#", title="{{ user.fullname }} profile") View Profile + li + a(href="#", title="Edit your profile") Edit Profile + li + a(href="#", title="Edit your organizations") Edit Organizations + li + a(href="#", title="Edit your settings") Settings + li + a(href="#", title="Edit your notifications") Notifications + li + a(href="#", title="Send feedback") Feedback + li + a(href="#", title="Go to support") Help + li + a(href="#", title="Logout") Logout + diff --git a/app/partials/includes/modules/navigation-bar/navbar.jade b/app/partials/includes/modules/navigation-bar/navbar.jade index f22a2230..d89699d2 100644 --- a/app/partials/includes/modules/navigation-bar/navbar.jade +++ b/app/partials/includes/modules/navigation-bar/navbar.jade @@ -7,18 +7,18 @@ nav.navbar div.nav-right a(href="", title="Dashboard") include ../../../../svg/dashboard.svg - div.top-nav-dropdown + div.topnav-dropdown-wrapper a(href="", title="Projects") include ../../../../svg/projects.svg - div.dropdown-project-list - ul - - for (var x = 0; x < 9; x++) - li - a(href="#", title="{{ project.title }}") Project 1 - a.see-more-projects-btn.button-gray(href="#", title="See more projects") See more - a.create-project-btn.button-green(href="#", title="Create project") Create project - a(href="#", title="Organizations") - include ../../../../svg/organizations.svg - a.user-avatar(href="#", title="{{ user.fullname }} profile") - span Sebastián Sanchís - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/brad_frost/128.jpg", alt="{{ user.fullname }} picture") + include dropdown-project-list + + div.topnav-dropdown-wrapper + a(href="#", title="Organizations") + include ../../../../svg/organizations.svg + include dropdown-organization-list + + div.topnav-dropdown-wrapper + a.user-avatar(href="#", title="{{ user.fullname }} profile") + span Sebastián Sanchís + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/brad_frost/128.jpg", alt="{{ user.fullname }} picture") + include dropdown-user diff --git a/app/styles/dependencies/colors.scss b/app/styles/dependencies/colors.scss index ed5d3ce0..e5b28c0d 100755 --- a/app/styles/dependencies/colors.scss +++ b/app/styles/dependencies/colors.scss @@ -33,5 +33,5 @@ $star-fill: #edd400; // Top menu values $top-icon-color: #11241f; -$dropdown-width: 500px; +$dropdown-width: 350px; $dropdown-color: rgba(darken($grayer, 20%), 1); diff --git a/app/styles/modules/common/navbar.scss b/app/styles/modules/common/navbar.scss index 4dd0ef99..02d32008 100644 --- a/app/styles/modules/common/navbar.scss +++ b/app/styles/modules/common/navbar.scss @@ -42,7 +42,7 @@ a { padding: .5rem 2rem; } - .dropdown-project-list { + .navbar-dropdown { a { padding: .8rem .5rem; } @@ -82,10 +82,10 @@ transition: all .2s; } } - .top-nav-dropdown { + .topnav-dropdown-wrapper { position: relative; &:hover { - .dropdown-project-list { + .navbar-dropdown { display: block; } } @@ -101,11 +101,18 @@ } } -.dropdown-project-list { +.navbar-dropdown { @extend %dropdown; background: $blackish; border: 1px solid $black; padding: .3rem; + &.dropdown-user { + left: calc(50% - 230px/2); + min-width: 230px; + ul { + margin-bottom: 0; + } + } ul { @include arrow('bottom', $blackish, $blackish, 1, 12); margin: 0; @@ -120,6 +127,7 @@ color: $fresh-taiga; } &.see-more-projects-btn, + &.create-organization-btn, &.create-project-btn { color: $white; padding: .5rem; @@ -133,3 +141,10 @@ } } } + +// Generated with Bounce.js. Edit at http://goo.gl/yZlDYR + +@keyframes dropdownFade { + 0% { transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); } + 100% { opacity: 0; transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -400, 0, 0, 1); } +} From 1b993e1b9536675713137752fbc2de9c74902484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 17 Apr 2015 08:58:15 +0200 Subject: [PATCH 046/366] Add nav dropdown animation --- app/styles/modules/common/navbar.scss | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/app/styles/modules/common/navbar.scss b/app/styles/modules/common/navbar.scss index 02d32008..f2c8e80b 100644 --- a/app/styles/modules/common/navbar.scss +++ b/app/styles/modules/common/navbar.scss @@ -86,6 +86,7 @@ position: relative; &:hover { .navbar-dropdown { + animation: dropdownFade .2s cubic-bezier(.09, 0, .99, .01) both; display: block; } } @@ -114,7 +115,7 @@ } } ul { - @include arrow('bottom', $blackish, $blackish, 1, 12); + @include arrow('bottom', $blackish, $blackish, 1, 8); margin: 0; margin-bottom: .5rem; padding: 0; @@ -145,6 +146,14 @@ // Generated with Bounce.js. Edit at http://goo.gl/yZlDYR @keyframes dropdownFade { - 0% { transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); } - 100% { opacity: 0; transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -400, 0, 0, 1); } + 0% { + opacity: 0; + transform: translateY(-.25rem); + } + 60% { + opacity: 1; + } + 100% { + transform: translateY(0); + } } From 1a75748763f8a0541917b4afaca348f493783fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 17 Apr 2015 09:00:45 +0200 Subject: [PATCH 047/366] Refactor animation code --- app/styles/core/animation.scss | 14 ++++++++++++++ app/styles/modules/common/navbar.scss | 15 --------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/app/styles/core/animation.scss b/app/styles/core/animation.scss index 978ec66b..1a2fc285 100644 --- a/app/styles/core/animation.scss +++ b/app/styles/core/animation.scss @@ -44,3 +44,17 @@ flex: 1; } } + +// Drop Down element animations +@keyframes dropdownFade { + 0% { + opacity: 0; + transform: translateY(-.25rem); + } + 60% { + opacity: 1; + } + 100% { + transform: translateY(0); + } +} diff --git a/app/styles/modules/common/navbar.scss b/app/styles/modules/common/navbar.scss index f2c8e80b..e892fa40 100644 --- a/app/styles/modules/common/navbar.scss +++ b/app/styles/modules/common/navbar.scss @@ -142,18 +142,3 @@ } } } - -// Generated with Bounce.js. Edit at http://goo.gl/yZlDYR - -@keyframes dropdownFade { - 0% { - opacity: 0; - transform: translateY(-.25rem); - } - 60% { - opacity: 1; - } - 100% { - transform: translateY(0); - } -} From 61d5f4715752202af6ad45af0c902b18d9980c8b Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 17 Apr 2015 09:09:18 +0200 Subject: [PATCH 048/366] Updating user settings urls --- app/coffee/app.coffee | 6 +++--- app/coffee/modules/base.coffee | 8 ++++---- .../user-settings/change-password.coffee | 15 --------------- app/coffee/modules/user-settings/main.coffee | 14 +------------- .../modules/user-settings/notifications.coffee | 17 +---------------- .../modules/navigation-bar/dropdown-user.jade | 5 ++--- .../includes/modules/user-settings-menu.jade | 6 +++--- 7 files changed, 14 insertions(+), 57 deletions(-) diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 66065784..66ecd527 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -125,11 +125,11 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven {templateUrl: "contrib/main.html"}) # User settings - $routeProvider.when("/project/:pslug/user-settings/user-profile", + $routeProvider.when("/user-settings/user-profile", {templateUrl: "user/user-profile.html"}) - $routeProvider.when("/project/:pslug/user-settings/user-change-password", + $routeProvider.when("/user-settings/user-change-password", {templateUrl: "user/user-change-password.html"}) - $routeProvider.when("/project/:pslug/user-settings/mail-notifications", + $routeProvider.when("/user-settings/mail-notifications", {templateUrl: "user/mail-notifications.html"}) $routeProvider.when("/change-email/:email_token", {templateUrl: "user/change-email.html"}) diff --git a/app/coffee/modules/base.coffee b/app/coffee/modules/base.coffee index d91446ac..eb4cb998 100644 --- a/app/coffee/modules/base.coffee +++ b/app/coffee/modules/base.coffee @@ -103,10 +103,10 @@ urls = { "project-admin-contrib": "/project/:project/admin/contrib/:plugin" # User settings - "user-settings-user-profile": "/project/:project/user-settings/user-profile" - "user-settings-user-change-password": "/project/:project/user-settings/user-change-password" - "user-settings-user-avatar": "/project/:project/user-settings/user-avatar" - "user-settings-mail-notifications": "/project/:project/user-settings/mail-notifications" + "user-settings-user-profile": "/user-settings/user-profile" + "user-settings-user-change-password": "/user-settings/user-change-password" + "user-settings-user-avatar": "/user-settings/user-avatar" + "user-settings-mail-notifications": "/user-settings/mail-notifications" } diff --git a/app/coffee/modules/user-settings/change-password.coffee b/app/coffee/modules/user-settings/change-password.coffee index 9957000d..4ea8c65c 100644 --- a/app/coffee/modules/user-settings/change-password.coffee +++ b/app/coffee/modules/user-settings/change-password.coffee @@ -49,23 +49,8 @@ class UserChangePasswordController extends mixOf(taiga.Controller, taiga.PageMix constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @auth, @translate) -> @scope.sectionName = @translate.instant("CHANGE_PASSWORD.SECTION_NAME") - @scope.project = {} @scope.user = @auth.getUser() - promise = @.loadInitialData() - - promise.then null, @.onInitialDataError.bind(@) - - loadProject: -> - return @rs.projects.getBySlug(@params.pslug).then (project) => - @scope.projectId = project.id - @scope.project = project - @scope.$emit('project:loaded', project) - return project - - loadInitialData: -> - return @.loadProject() - module.controller("UserChangePasswordController", UserChangePasswordController) diff --git a/app/coffee/modules/user-settings/main.coffee b/app/coffee/modules/user-settings/main.coffee index 39257d78..d2807c46 100644 --- a/app/coffee/modules/user-settings/main.coffee +++ b/app/coffee/modules/user-settings/main.coffee @@ -62,23 +62,11 @@ class UserSettingsController extends mixOf(taiga.Controller, taiga.PageMixin) promise.then null, @.onInitialDataError.bind(@) - loadProject: -> - return @rs.projects.getBySlug(@params.pslug).then (project) => - @scope.projectId = project.id - @scope.project = project - @scope.$emit('project:loaded', project) - return project - - loadLocales: -> + loadInitialData: -> return @rs.locales.list().then (locales) => @scope.locales = locales return locales - loadInitialData: -> - promise = @.loadProject() - promise.then => @.loadLocales() - return promise - openDeleteLightbox: -> @rootscope.$broadcast("deletelightbox:new", @scope.user) diff --git a/app/coffee/modules/user-settings/notifications.coffee b/app/coffee/modules/user-settings/notifications.coffee index 5f77599f..bef96385 100644 --- a/app/coffee/modules/user-settings/notifications.coffee +++ b/app/coffee/modules/user-settings/notifications.coffee @@ -46,30 +46,15 @@ class UserNotificationsController extends mixOf(taiga.Controller, taiga.PageMixi constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @auth) -> @scope.sectionName = "USER_SETTINGS.NOTIFICATIONS.SECTION_NAME" - @scope.project = {} @scope.user = @auth.getUser() - promise = @.loadInitialData() - promise.then null, @.onInitialDataError.bind(@) - loadProject: -> - return @rs.projects.getBySlug(@params.pslug).then (project) => - @scope.projectId = project.id - @scope.project = project - @scope.$emit('project:loaded', project) - return project - - loadNotifyPolicies: -> + loadInitialData: -> return @rs.notifyPolicies.list().then (notifyPolicies) => @scope.notifyPolicies = notifyPolicies return notifyPolicies - loadInitialData: -> - promise = @.loadProject() - promise.then(=> @.loadNotifyPolicies()) - return promise - module.controller("UserNotificationsController", UserNotificationsController) diff --git a/app/partials/includes/modules/navigation-bar/dropdown-user.jade b/app/partials/includes/modules/navigation-bar/dropdown-user.jade index a7f2a638..49d132f6 100644 --- a/app/partials/includes/modules/navigation-bar/dropdown-user.jade +++ b/app/partials/includes/modules/navigation-bar/dropdown-user.jade @@ -3,17 +3,16 @@ div.navbar-dropdown.dropdown-user li a(href="#", title="{{ user.fullname }} profile") View Profile li - a(href="#", title="Edit your profile") Edit Profile + a(href="#", tg-nav="user-settings-user-profile", title="Edit your profile") Edit Profile li a(href="#", title="Edit your organizations") Edit Organizations li a(href="#", title="Edit your settings") Settings li - a(href="#", title="Edit your notifications") Notifications + a(href="#", tg-nav="user-settings-mail-notifications", title="Edit your notifications") Notifications li a(href="#", title="Send feedback") Feedback li a(href="#", title="Go to support") Help li a(href="#", title="Logout") Logout - diff --git a/app/partials/includes/modules/user-settings-menu.jade b/app/partials/includes/modules/user-settings-menu.jade index e93963a4..c3e0fe5d 100644 --- a/app/partials/includes/modules/user-settings-menu.jade +++ b/app/partials/includes/modules/user-settings-menu.jade @@ -5,14 +5,14 @@ section.admin-menu nav ul li#usersettingsmenu-user-profile - a(href="", tg-nav="user-settings-user-profile:project=project.slug") + a(href="", tg-nav="user-settings-user-profile") span.title(translate="USER_SETTINGS.MENU.USER_PROFILE") span.icon.icon-arrow-right li#usersettingsmenu-change-password - a(href="" tg-nav="user-settings-user-change-password:project=project.slug") + a(href="" tg-nav="user-settings-user-change-password") span.title(translate="USER_SETTINGS.MENU.CHANGE_PASSWORD") span.icon.icon-arrow-right li#usersettingsmenu-mail-notifications - a(href="", tg-nav="user-settings-mail-notifications:project=project.slug") + a(href="", tg-nav="user-settings-mail-notifications") span.title(translate="USER_SETTINGS.MENU.EMAIL_NOTIFICATIONS") span.icon.icon-arrow-right From 032aeb22807a857a987efd3d31360091e840b1e2 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 17 Apr 2015 09:53:31 +0200 Subject: [PATCH 049/366] Refactoring navbar --- app/coffee/app.coffee | 7 +++- app/coffee/modules/base.coffee | 1 + app/coffee/modules/feedback.coffee | 4 +- app/coffee/modules/projects/lightboxes.coffee | 10 ++++- app/coffee/modules/projects/main.coffee | 9 ++++- app/coffee/modules/resources.coffee | 1 + app/coffee/modules/resources/projects.coffee | 6 ++- app/index.jade | 5 +-- .../dropdown-organization-list.jade | 3 ++ .../dropdown-project-list.directive.coffee | 14 +++++++ .../dropdown-project-list.jade | 12 ++++++ .../dropdown-user.directive.coffee | 13 ++++++ .../dropdown-user/dropdown-user.jade | 24 +++++++++++ .../navigation-bar.directive.coffee | 10 +++++ .../navigation-bar/navigation-bar.jade | 17 ++++++++ .../navigation-bar.module.coffee | 1 + .../navigation-bar/navigation-bar.scss} | 0 .../projects/listing/listing.directive.coffee | 40 +++++++++++++++++++ app/modules/projects/listing/listing.jade | 11 +++++ app/modules/projects/projects.jade | 1 + app/modules/projects/projects.module.coffee | 1 + .../{project/projects.jade => home/home.jade} | 0 .../navigation-bar/dropdown-project-list.jade | 7 ---- .../modules/navigation-bar/dropdown-user.jade | 18 --------- .../modules/navigation-bar/navbar.jade | 24 ----------- .../includes/modules/projects-nav.jade | 8 ---- .../wizard-create-project.jade | 0 27 files changed, 179 insertions(+), 68 deletions(-) rename app/{partials/includes/modules/navigation-bar => modules/navigation-bar/dropdown-organization-list}/dropdown-organization-list.jade (78%) create mode 100644 app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee create mode 100644 app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade create mode 100644 app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee create mode 100644 app/modules/navigation-bar/dropdown-user/dropdown-user.jade create mode 100644 app/modules/navigation-bar/navigation-bar.directive.coffee create mode 100644 app/modules/navigation-bar/navigation-bar.jade create mode 100644 app/modules/navigation-bar/navigation-bar.module.coffee rename app/{styles/modules/common/navbar.scss => modules/navigation-bar/navigation-bar.scss} (100%) create mode 100644 app/modules/projects/listing/listing.directive.coffee create mode 100644 app/modules/projects/listing/listing.jade create mode 100644 app/modules/projects/projects.jade create mode 100644 app/modules/projects/projects.module.coffee rename app/partials/{project/projects.jade => home/home.jade} (100%) delete mode 100644 app/partials/includes/modules/navigation-bar/dropdown-project-list.jade delete mode 100644 app/partials/includes/modules/navigation-bar/dropdown-user.jade delete mode 100644 app/partials/includes/modules/navigation-bar/navbar.jade delete mode 100644 app/partials/includes/modules/projects-nav.jade rename app/partials/{includes/modules => project}/wizard-create-project.jade (100%) diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 66ecd527..373010d9 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -39,7 +39,10 @@ taiga.sessionId = taiga.generateUniqueSessionIdentifier() configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEventsProvider, tgLoaderProvider, $compileProvider, $translateProvider) -> $routeProvider.when("/", - {templateUrl: "project/projects.html", resolve: {loader: tgLoaderProvider.add()}}) + {templateUrl: "home/home.html", resolve: {loader: tgLoaderProvider.add()}}) + + $routeProvider.when("/projects/", + {templateUrl: "projects/projects.html", resolve: {loader: tgLoaderProvider.add()}}) $routeProvider.when("/project/:pslug/", {templateUrl: "project/project.html"}) @@ -317,6 +320,8 @@ modules = [ "taigaEvents", # Specific Modules + "taigaNavigationBar", + "taigaProjects", "taigaRelatedTasks", "taigaBacklog", "taigaTaskboard", diff --git a/app/coffee/modules/base.coffee b/app/coffee/modules/base.coffee index eb4cb998..5aef4043 100644 --- a/app/coffee/modules/base.coffee +++ b/app/coffee/modules/base.coffee @@ -44,6 +44,7 @@ module.directive("tgMain", ["$rootScope", "$window", TaigaMainDirective]) urls = { "home": "/" + "projects": "/projects" "error": "/error" "not-found": "/not-found" "permission-denied": "/permission-denied" diff --git a/app/coffee/modules/feedback.coffee b/app/coffee/modules/feedback.coffee index 403b46da..79ba422f 100644 --- a/app/coffee/modules/feedback.coffee +++ b/app/coffee/modules/feedback.coffee @@ -57,9 +57,7 @@ FeedbackDirective = ($lightboxService, $repo, $confirm, $loading)-> $el.on "submit", "form", submit $scope.$on "feedback:show", -> - $scope.$apply -> - $scope.feedback = {} - + $scope.feedback = {} $lightboxService.open($el) $el.find("textarea").focus() diff --git a/app/coffee/modules/projects/lightboxes.coffee b/app/coffee/modules/projects/lightboxes.coffee index e83d328b..bb595d72 100644 --- a/app/coffee/modules/projects/lightboxes.coffee +++ b/app/coffee/modules/projects/lightboxes.coffee @@ -125,7 +125,15 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project event.preventDefault() lightboxService.close($el) - return {link:link} + + directive = { + link: link, + templateUrl: "project/wizard-create-project.html" + scope: {} + } + + return directive + module.directive("tgLbCreateProject", ["$rootScope", "$tgRepo", "$tgConfirm", "$location", "$tgNavUrls", "$tgResources", "$projectUrl", "$tgLoading", "lightboxService", "$cacheFactory", "$translate", CreateProject]) diff --git a/app/coffee/modules/projects/main.coffee b/app/coffee/modules/projects/main.coffee index 3dc3b6ca..006c4ca2 100644 --- a/app/coffee/modules/projects/main.coffee +++ b/app/coffee/modules/projects/main.coffee @@ -34,10 +34,11 @@ class ProjectsController extends taiga.Controller "$tgLocation", "$appTitle", "$projectUrl", + "$tgConfig", "tgLoader" ] - constructor: (@scope, @q, @rs, @rootscope, @navUrls, @auth, @location, @appTitle, @projectUrl, + constructor: (@scope, @q, @rs, @rootscope, @navUrls, @auth, @location, @appTitle, @projectUrl, @config tgLoader) -> @appTitle.set("Projects") @@ -72,6 +73,12 @@ class ProjectsController extends taiga.Controller @auth.logout() @location.path(@navUrls.resolve("login")) + isFeedbackEnabled: -> + return @config.get("feedbackEnabled") + + sendFeedback: -> + @rootscope.$broadcast("feedback:show") + module.controller("ProjectsController", ProjectsController) diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index a3f05dbb..820d90a7 100644 --- a/app/coffee/modules/resources.coffee +++ b/app/coffee/modules/resources.coffee @@ -40,6 +40,7 @@ urls = { "stats": "/users/%s/stats" # User - Notification + "permissions": "/permissions" "notify-policies": "/notify-policies" # User - Storage diff --git a/app/coffee/modules/resources/projects.coffee b/app/coffee/modules/resources/projects.coffee index ad712d42..4381f10f 100644 --- a/app/coffee/modules/resources/projects.coffee +++ b/app/coffee/modules/resources/projects.coffee @@ -37,7 +37,7 @@ resourceProvider = ($config, $repo, $http, $urls, $auth, $q, $translate) -> return $repo.queryMany("projects") service.listByMember = (memberId) -> - params = {"member": memberId} + params = {"member": memberId, "user_order": 1} return $repo.queryMany("projects", params) service.templates = -> @@ -54,6 +54,10 @@ resourceProvider = ($config, $repo, $http, $urls, $auth, $q, $translate) -> service.stats = (projectId) -> return $repo.queryOneRaw("projects", "#{projectId}/stats") + service.bulkUpdateOrder = (bulkData) -> + url = $urls.resolve("bulk-update-projects-order") + return $http.post(url, bulkData) + service.regenerate_userstories_csv_uuid = (projectId) -> url = "#{$urls.resolve("projects")}/#{projectId}/regenerate_userstories_csv_uuid" return $http.post(url) diff --git a/app/index.jade b/app/index.jade index f994ae36..eae8020d 100644 --- a/app/index.jade +++ b/app/index.jade @@ -15,10 +15,7 @@ html(lang="en") //include partials/includes/modules/projects-nav include partials/includes/components/notification-message - include partials/includes/modules/navigation-bar/navbar - - //- the content of nav.menu is in coffe.modules.base TaigaMain directive - //nav.menu.hidden(tg-project-menu) + div(tg-navigation-bar) div.master(ng-view) diff --git a/app/partials/includes/modules/navigation-bar/dropdown-organization-list.jade b/app/modules/navigation-bar/dropdown-organization-list/dropdown-organization-list.jade similarity index 78% rename from app/partials/includes/modules/navigation-bar/dropdown-organization-list.jade rename to app/modules/navigation-bar/dropdown-organization-list/dropdown-organization-list.jade index 49b37952..91c5e2c2 100644 --- a/app/partials/includes/modules/navigation-bar/dropdown-organization-list.jade +++ b/app/modules/navigation-bar/dropdown-organization-list/dropdown-organization-list.jade @@ -1,3 +1,6 @@ +a(href="#", title="Organizations") + include ../../../svg/organizations.svg + div.navbar-dropdown.dropdown-organization-list ul - for (var x = 0; x < 4; x++) diff --git a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee new file mode 100644 index 00000000..cba847d0 --- /dev/null +++ b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee @@ -0,0 +1,14 @@ +DropdownProjectListDirective = () -> + directive = { + templateUrl: "navigation-bar/dropdown-project-list/dropdown-project-list.html" + controller: "ProjectsController" + scope: {} + bindToController: true + controllerAs: "vm" + } + + return directive + + +angular.module("taigaNavigationBar").directive("tgDropdownProjectList", + DropdownProjectListDirective) diff --git a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade new file mode 100644 index 00000000..44e81163 --- /dev/null +++ b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade @@ -0,0 +1,12 @@ +a(href="", title="Projects") + include ../../../svg/projects.svg + +div.navbar-dropdown.dropdown-project-list + ul + a(href="#", + ng-repeat="project in vm.projects.recents", + ng-bind="::project.name" + tg-nav="project:project=project.slug") + + a.see-more-projects-btn.button-gray(tg-nav="projects", href="#", title="See more projects") See more + a.create-project-btn.button-green(href="#", ng-click="vm.newProject()", title="Create project") Create project diff --git a/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee b/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee new file mode 100644 index 00000000..7a3d0793 --- /dev/null +++ b/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee @@ -0,0 +1,13 @@ +DropdownUserDirective = () -> + directive = { + templateUrl: "navigation-bar/dropdown-user/dropdown-user.html" + controller: "ProjectsController" + scope: {} + bindToController: true + controllerAs: "vm" + } + + return directive + +angular.module("taigaNavigationBar").directive("tgDropdownUser", + DropdownUserDirective) diff --git a/app/modules/navigation-bar/dropdown-user/dropdown-user.jade b/app/modules/navigation-bar/dropdown-user/dropdown-user.jade new file mode 100644 index 00000000..e3d086fd --- /dev/null +++ b/app/modules/navigation-bar/dropdown-user/dropdown-user.jade @@ -0,0 +1,24 @@ +a.user-avatar(href="#", title="{{ vm.user.full_name_display }}") + {{ vm.user.full_name_display }} + img(tg-bo-src="vm.user.photo", alt="{{ vm.user.full_name_display }}") + +div.navbar-dropdown.dropdown-user + ul + li + a(href="#", title="{{ user.fullname }} profile") View Profile + li + a(href="#", tg-nav="user-settings-user-profile", title="Edit your profile") Edit Profile + li + a(href="#", tg-nav="user-settings-user-change-password", title="Change password") Change password + //li + // a(href="#", title="Edit your organizations") Edit Organizations + //li + // a(href="#", title="Edit your settings") Settings + li + a(href="#", tg-nav="user-settings-mail-notifications", title="Edit your notifications") Notifications + li(ng-show="vm.isFeedbackEnabled()") + a(ng-click="vm.sendFeedback()", href="#", title="Send feedback") Feedback + li + a(href="https://taiga.io/support/", target="_blank", title="Go to support") Help + li + a(href="#", ng-click="vm.logout()", title="Logout") Logout diff --git a/app/modules/navigation-bar/navigation-bar.directive.coffee b/app/modules/navigation-bar/navigation-bar.directive.coffee new file mode 100644 index 00000000..1be03abf --- /dev/null +++ b/app/modules/navigation-bar/navigation-bar.directive.coffee @@ -0,0 +1,10 @@ +NavigationBarDirective = () -> + directive = { + templateUrl: "navigation-bar/navigation-bar.html" + } + + return directive + + +angular.module("taigaNavigationBar").directive("tgNavigationBar", + NavigationBarDirective) diff --git a/app/modules/navigation-bar/navigation-bar.jade b/app/modules/navigation-bar/navigation-bar.jade new file mode 100644 index 00000000..32614dc8 --- /dev/null +++ b/app/modules/navigation-bar/navigation-bar.jade @@ -0,0 +1,17 @@ +nav.navbar + div.nav-left + a.logo(href="#", tg-nav="home", title="Dashboard") + include ../../svg/logo.svg + + //a(href="#", title="Discover trending projects") Discover + a(href="https://taiga.io/support/", target="_blank", title="Taiga Support Page") Help + + div.nav-right + a(href="", tg-nav="home", title="Dashboard") + include ../../svg/dashboard.svg + + div.topnav-dropdown-wrapper(tg-dropdown-project-list) + //div.topnav-dropdown-wrapper(tg-dropdown-organization-list) + div.topnav-dropdown-wrapper(tg-dropdown-user) + +div.wizard-create-project(tg-lb-create-project) diff --git a/app/modules/navigation-bar/navigation-bar.module.coffee b/app/modules/navigation-bar/navigation-bar.module.coffee new file mode 100644 index 00000000..a6270366 --- /dev/null +++ b/app/modules/navigation-bar/navigation-bar.module.coffee @@ -0,0 +1 @@ +angular.module("taigaNavigationBar", []) diff --git a/app/styles/modules/common/navbar.scss b/app/modules/navigation-bar/navigation-bar.scss similarity index 100% rename from app/styles/modules/common/navbar.scss rename to app/modules/navigation-bar/navigation-bar.scss diff --git a/app/modules/projects/listing/listing.directive.coffee b/app/modules/projects/listing/listing.directive.coffee new file mode 100644 index 00000000..f05fded6 --- /dev/null +++ b/app/modules/projects/listing/listing.directive.coffee @@ -0,0 +1,40 @@ +ProjectsListingDirective = ($rs) -> + link = (scope, el, attrs, ctrl) -> + itemEl = null + tdom = el.find(".js-sortable") + + tdom.sortable({ + dropOnEmpty: true + revert: 200 + axis: "y" + }) + + tdom.on "sortstop", (event, ui) -> + itemEl = ui.item + project = itemEl.scope().project + index = itemEl.index() + scope.sorted_project_ids = _.without(scope.sorted_project_ids, project.id) + scope.sorted_project_ids.splice(index, 0, project.id) + sortData = [] + for value, index in scope.sorted_project_ids + sortData.push({"project_id": value, "order":index}) + + $rs.projects.bulkUpdateOrder(sortData) + + scope.$watch "vm.projects", (projects) => + if projects? + scope.sorted_project_ids = _.map(projects.all, (p) -> p.id) + + directive = { + templateUrl: "projects/listing/listing.html" + controller: "ProjectsController" + scope: {} + bindToController: true + controllerAs: "vm" + link: link + } + + return directive + +angular.module("taigaProjects").directive("tgProjectsListing", + ["$tgResources", ProjectsListingDirective]) diff --git a/app/modules/projects/listing/listing.jade b/app/modules/projects/listing/listing.jade new file mode 100644 index 00000000..0d59a79b --- /dev/null +++ b/app/modules/projects/listing/listing.jade @@ -0,0 +1,11 @@ +a.create-project-btn.button-green(href="#", ng-click="vm.newProject()", title="Create project") Create project + +h2 Esto es sortable y persiste en el servidor! ;) + +ul.js-sortable + li(tg-bind-scope, ng-repeat="project in vm.projects.all") + span Private: {{project.is_private}}, tags: {{project.tags}}, link: + + a(href="#", + ng-bind="::project.name" + tg-nav="project:project=project.slug") diff --git a/app/modules/projects/projects.jade b/app/modules/projects/projects.jade new file mode 100644 index 00000000..21f045f0 --- /dev/null +++ b/app/modules/projects/projects.jade @@ -0,0 +1 @@ +div(tg-projects-listing) diff --git a/app/modules/projects/projects.module.coffee b/app/modules/projects/projects.module.coffee new file mode 100644 index 00000000..4597eb7d --- /dev/null +++ b/app/modules/projects/projects.module.coffee @@ -0,0 +1 @@ +angular.module("taigaProjects", []) diff --git a/app/partials/project/projects.jade b/app/partials/home/home.jade similarity index 100% rename from app/partials/project/projects.jade rename to app/partials/home/home.jade diff --git a/app/partials/includes/modules/navigation-bar/dropdown-project-list.jade b/app/partials/includes/modules/navigation-bar/dropdown-project-list.jade deleted file mode 100644 index e805759a..00000000 --- a/app/partials/includes/modules/navigation-bar/dropdown-project-list.jade +++ /dev/null @@ -1,7 +0,0 @@ -div.navbar-dropdown.dropdown-project-list - ul - - for (var x = 0; x < 9; x++) - li - a(href="#", title="{{ project.title }}") Project 1 - a.see-more-projects-btn.button-gray(href="#", title="See more projects") See more - a.create-project-btn.button-green(href="#", title="Create project") Create project diff --git a/app/partials/includes/modules/navigation-bar/dropdown-user.jade b/app/partials/includes/modules/navigation-bar/dropdown-user.jade deleted file mode 100644 index 49d132f6..00000000 --- a/app/partials/includes/modules/navigation-bar/dropdown-user.jade +++ /dev/null @@ -1,18 +0,0 @@ -div.navbar-dropdown.dropdown-user - ul - li - a(href="#", title="{{ user.fullname }} profile") View Profile - li - a(href="#", tg-nav="user-settings-user-profile", title="Edit your profile") Edit Profile - li - a(href="#", title="Edit your organizations") Edit Organizations - li - a(href="#", title="Edit your settings") Settings - li - a(href="#", tg-nav="user-settings-mail-notifications", title="Edit your notifications") Notifications - li - a(href="#", title="Send feedback") Feedback - li - a(href="#", title="Go to support") Help - li - a(href="#", title="Logout") Logout diff --git a/app/partials/includes/modules/navigation-bar/navbar.jade b/app/partials/includes/modules/navigation-bar/navbar.jade deleted file mode 100644 index d89699d2..00000000 --- a/app/partials/includes/modules/navigation-bar/navbar.jade +++ /dev/null @@ -1,24 +0,0 @@ -nav.navbar - div.nav-left - a.logo(href="#", title="Dashboard") - include ../../../../svg/logo.svg - a(href="#", title="Discover trending projects") Discover - a(href="#", title="Taiga Support Page") Help - div.nav-right - a(href="", title="Dashboard") - include ../../../../svg/dashboard.svg - div.topnav-dropdown-wrapper - a(href="", title="Projects") - include ../../../../svg/projects.svg - include dropdown-project-list - - div.topnav-dropdown-wrapper - a(href="#", title="Organizations") - include ../../../../svg/organizations.svg - include dropdown-organization-list - - div.topnav-dropdown-wrapper - a.user-avatar(href="#", title="{{ user.fullname }} profile") - span Sebastián Sanchís - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/brad_frost/128.jpg", alt="{{ user.fullname }} picture") - include dropdown-user diff --git a/app/partials/includes/modules/projects-nav.jade b/app/partials/includes/modules/projects-nav.jade deleted file mode 100644 index 2a26f099..00000000 --- a/app/partials/includes/modules/projects-nav.jade +++ /dev/null @@ -1,8 +0,0 @@ -.projects-nav-overlay - div.container - p(translate="COMMON.LOADING_PROJECT") - -div.wizard-create-project(tg-lb-create-project) - include wizard-create-project - -nav.projects-nav(tg-projects-nav) diff --git a/app/partials/includes/modules/wizard-create-project.jade b/app/partials/project/wizard-create-project.jade similarity index 100% rename from app/partials/includes/modules/wizard-create-project.jade rename to app/partials/project/wizard-create-project.jade From d46a8ac2e0a02dfeb8fef31fbe69a4762bf6185b Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 22 Apr 2015 07:33:51 +0200 Subject: [PATCH 050/366] Adding project description --- app/modules/projects/listing/listing.jade | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/modules/projects/listing/listing.jade b/app/modules/projects/listing/listing.jade index 0d59a79b..daa21d31 100644 --- a/app/modules/projects/listing/listing.jade +++ b/app/modules/projects/listing/listing.jade @@ -9,3 +9,5 @@ ul.js-sortable a(href="#", ng-bind="::project.name" tg-nav="project:project=project.slug") + + div {{ ::project.description }} From 4b00f204430cb43eedfac3e1d018b803403f468b Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 22 Apr 2015 14:34:12 +0200 Subject: [PATCH 051/366] Adding translations --- app/locales/locale-en.json | 23 +++++++- .../dropdown-organization-list.jade | 1 + .../dropdown-project-list.directive.coffee | 16 ++++-- .../dropdown-project-list.jade | 13 ++++- .../dropdown-user/dropdown-user.jade | 54 +++++++++++++++---- .../navigation-bar/navigation-bar.jade | 24 +++++++-- .../projects/listing/listing.directive.coffee | 28 ++++++---- app/modules/projects/listing/listing.jade | 6 ++- .../projects/projects-page.controller.coffee | 34 ++++++++++++ app/modules/projects/projects.jade | 2 +- app/modules/projects/projects.service.coffee | 45 ++++++++++++++++ 11 files changed, 214 insertions(+), 32 deletions(-) create mode 100644 app/modules/projects/projects-page.controller.coffee create mode 100644 app/modules/projects/projects.service.coffee diff --git a/app/locales/locale-en.json b/app/locales/locale-en.json index 1f2634d5..f62a9b0e 100644 --- a/app/locales/locale-en.json +++ b/app/locales/locale-en.json @@ -543,9 +543,30 @@ "SECTION_TITLE": "Your projects", "PLACEHOLDER_SEARCH": "Search in...", "ACTION_CREATE_PROJECT": "Create project", + "SEE_MORE_PROJECTS": "See more projects", "TITLE_ACTION_IMPORT": "Import project", "TITLE_PRVIOUS_PROJECT": "Show previous projects", - "TITLE_NEXT_PROJECT": "Show next projects" + "TITLE_NEXT_PROJECT": "Show next projects", + "HELP_TITLE": "Taiga Support Page", + "HELP": "Help", + "FEEDBACK_TITLE": "Send feedback", + "FEEDBACK": "Feedback", + "NOTIFICATIONS_TITLE": "Edit your notification settings", + "NOTIFICATIONS": "Notifications", + "ORGANIZATIONS_TITLE": "Edit your organizations", + "ORGANIZATIONS": "Edit organizations", + "SETTINGS_TITLE": "Edit your settings", + "SETTINGS": "Settings", + "VIEW_PROFILE_TITLE": "View Profile", + "VIEW_PROFILE": "View Profile", + "EDIT_PROFILE_TITLE": "Edit your profile", + "EDIT_PROFILE": "Edit Profile", + "CHANGE_PASSWORD_TITLE": "Change password", + "CHANGE_PASSWORD": "Change password", + "DASHBOARD_TITLE": "Dashboard", + "DISCOVER_TITLE": "Discover trending projects", + "DISCOVER": "Discover" + }, "IMPORT": { "TITLE": "Importing Project", diff --git a/app/modules/navigation-bar/dropdown-organization-list/dropdown-organization-list.jade b/app/modules/navigation-bar/dropdown-organization-list/dropdown-organization-list.jade index 91c5e2c2..e0b23904 100644 --- a/app/modules/navigation-bar/dropdown-organization-list/dropdown-organization-list.jade +++ b/app/modules/navigation-bar/dropdown-organization-list/dropdown-organization-list.jade @@ -1,3 +1,4 @@ +#TODO: fill correctly when implemented a(href="#", title="Organizations") include ../../../svg/organizations.svg diff --git a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee index cba847d0..b6208369 100644 --- a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee +++ b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee @@ -1,14 +1,20 @@ -DropdownProjectListDirective = () -> +DropdownProjectListDirective = (projectsService) -> + link = (scope, el, attrs, ctrl) -> + scope.vm = {} + + projectsService.projectsSuscription (projects) -> + scope.vm.projects = projects + + projectsService.getProjects() + directive = { templateUrl: "navigation-bar/dropdown-project-list/dropdown-project-list.html" - controller: "ProjectsController" scope: {} - bindToController: true - controllerAs: "vm" + link: link } return directive angular.module("taigaNavigationBar").directive("tgDropdownProjectList", - DropdownProjectListDirective) + ["tgProjects", DropdownProjectListDirective]) diff --git a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade index 44e81163..9a541a00 100644 --- a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade +++ b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade @@ -8,5 +8,14 @@ div.navbar-dropdown.dropdown-project-list ng-bind="::project.name" tg-nav="project:project=project.slug") - a.see-more-projects-btn.button-gray(tg-nav="projects", href="#", title="See more projects") See more - a.create-project-btn.button-green(href="#", ng-click="vm.newProject()", title="Create project") Create project + a.see-more-projects-btn.button-gray( + href="#", + tg-nav="projects", + title="{{'PROJECT.NAVIGATION.SEE_MORE_PROJECTS' | translate}}", + translate="PROJECT.NAVIGATION.SEE_MORE_PROJECTS") + + a.create-project-btn.button-green( + href="#", + ng-click="vm.newProject()", + title="{{'PROJECT.NAVIGATION.ACTION_CREATE_PROJECT' | translate}}", + translate="PROJECT.NAVIGATION.ACTION_CREATE_PROJECT") diff --git a/app/modules/navigation-bar/dropdown-user/dropdown-user.jade b/app/modules/navigation-bar/dropdown-user/dropdown-user.jade index e3d086fd..bb7a0045 100644 --- a/app/modules/navigation-bar/dropdown-user/dropdown-user.jade +++ b/app/modules/navigation-bar/dropdown-user/dropdown-user.jade @@ -5,20 +5,56 @@ a.user-avatar(href="#", title="{{ vm.user.full_name_display }}") div.navbar-dropdown.dropdown-user ul li - a(href="#", title="{{ user.fullname }} profile") View Profile + a( + href="#", + title="{{'PROJECT.NAVIGATION.VIEW_PROFILE_TITLE' | translate}}", + translate="PROJECT.NAVIGATION.VIEW_PROFILE") li - a(href="#", tg-nav="user-settings-user-profile", title="Edit your profile") Edit Profile + a( + href="#", + tg-nav="user-settings-user-profile", + title="{{'PROJECT.NAVIGATION.EDIT_PROFILE_TITLE' | translate}}", + translate="PROJECT.NAVIGATION.EDIT_PROFILE") li - a(href="#", tg-nav="user-settings-user-change-password", title="Change password") Change password + a( + href="#", + tg-nav="user-settings-user-change-password", + title="{{'PROJECT.NAVIGATION.CHANGE_PASSWORD_TITLE' | translate}}", + translate="PROJECT.NAVIGATION.CHANGE_PASSWORD") + //li - // a(href="#", title="Edit your organizations") Edit Organizations + // a( + // href="#", + // title="{{'PROJECT.NAVIGATION.ORGANIZATIONS_TITLE' | translate}}", + // translate="PROJECT.NAVIGATION.ORGANIZATIONS") //li - // a(href="#", title="Edit your settings") Settings + // a( + // href="#", + // title="{{'PROJECT.NAVIGATION.SETTINGS_TITLE' | translate}}", + // translate="PROJECT.NAVIGATION.SETTINGS") + li - a(href="#", tg-nav="user-settings-mail-notifications", title="Edit your notifications") Notifications + a( + href="#", + tg-nav="user-settings-mail-notifications", + title="{{'PROJECT.NAVIGATION.NOTIFICATIONS_TITLE' | translate}}", + translate="PROJECT.NAVIGATION.NOTIFICATIONS") + li(ng-show="vm.isFeedbackEnabled()") - a(ng-click="vm.sendFeedback()", href="#", title="Send feedback") Feedback + a( + href="#", + ng-click="vm.sendFeedback()", + title="{{'PROJECT.NAVIGATION.FEEDBACK_TITLE' | translate}}", + translate="PROJECT.NAVIGATION.FEEDBACK") li - a(href="https://taiga.io/support/", target="_blank", title="Go to support") Help + a( + href="https://taiga.io/support/", + target="_blank", + title="{{'PROJECT.NAVIGATION.HELP_TITLE' | translate}}", + translate="PROJECT.NAVIGATION.HELP") li - a(href="#", ng-click="vm.logout()", title="Logout") Logout + a( + href="#", + ng-click="vm.logout()", + title="{{'COMMON.LOGOUT' | translate}}", + translate="COMMON.LOGOUT") diff --git a/app/modules/navigation-bar/navigation-bar.jade b/app/modules/navigation-bar/navigation-bar.jade index 32614dc8..8048587a 100644 --- a/app/modules/navigation-bar/navigation-bar.jade +++ b/app/modules/navigation-bar/navigation-bar.jade @@ -1,13 +1,29 @@ nav.navbar div.nav-left - a.logo(href="#", tg-nav="home", title="Dashboard") + a.logo( + href="#", + tg-nav="home", + title="{{'PROJECT.NAVIGATION.DASHBOARD_TITLE' | translate}}") + include ../../svg/logo.svg - //a(href="#", title="Discover trending projects") Discover - a(href="https://taiga.io/support/", target="_blank", title="Taiga Support Page") Help + //a( + // href="#", + // title="{{'PROJECT.NAVIGATION.DISCOVER_TITLE' | translate}}", + // translate="PROJECT.NAVIGATION.DISCOVER") + + a( + href="https://taiga.io/support/", + target="_blank", + title="{{'PROJECT.NAVIGATION.HELP_TITLE' | translate}}", + translate="PROJECT.NAVIGATION.HELP") div.nav-right - a(href="", tg-nav="home", title="Dashboard") + a( + href="#", + tg-nav="home", + title="{{'PROJECT.NAVIGATION.DASHBOARD_TITLE' | translate}}", + include ../../svg/dashboard.svg div.topnav-dropdown-wrapper(tg-dropdown-project-list) diff --git a/app/modules/projects/listing/listing.directive.coffee b/app/modules/projects/listing/listing.directive.coffee index f05fded6..b7e7cced 100644 --- a/app/modules/projects/listing/listing.directive.coffee +++ b/app/modules/projects/listing/listing.directive.coffee @@ -1,5 +1,6 @@ -ProjectsListingDirective = ($rs) -> +ProjectsListingDirective = (projectsService) -> link = (scope, el, attrs, ctrl) -> + scope.vm = {} itemEl = null tdom = el.find(".js-sortable") @@ -19,22 +20,31 @@ ProjectsListingDirective = ($rs) -> for value, index in scope.sorted_project_ids sortData.push({"project_id": value, "order":index}) - $rs.projects.bulkUpdateOrder(sortData) + projectsService.bulkUpdateProjectsOrder(sortData) - scope.$watch "vm.projects", (projects) => - if projects? - scope.sorted_project_ids = _.map(projects.all, (p) -> p.id) + projectsService.projectsSuscription (projects) -> + scope.vm.projects = projects + scope.sorted_project_ids = _.map(projects.all, (p) -> p.id) + projectsService.getProjects(true) + + """ + projectsService.fetchProjects().then (projects) -> + Object.defineProperty scope.vm, "projects", { + get: () -> + projects = projectsService.getProjects() + if projects + scope.sorted_project_ids = _.map(projects.all, (p) -> p.id) + return projects + } + """ directive = { templateUrl: "projects/listing/listing.html" - controller: "ProjectsController" scope: {} - bindToController: true - controllerAs: "vm" link: link } return directive angular.module("taigaProjects").directive("tgProjectsListing", - ["$tgResources", ProjectsListingDirective]) + ["tgProjects", ProjectsListingDirective]) diff --git a/app/modules/projects/listing/listing.jade b/app/modules/projects/listing/listing.jade index daa21d31..873615f9 100644 --- a/app/modules/projects/listing/listing.jade +++ b/app/modules/projects/listing/listing.jade @@ -1,4 +1,8 @@ -a.create-project-btn.button-green(href="#", ng-click="vm.newProject()", title="Create project") Create project +a.create-project-btn.button-green( + href="#", + ng-click="vm.newProject()", + title="{{'PROJECT.NAVIGATION.ACTION_CREATE_PROJECT' | translate}}", + translate="PROJECT.NAVIGATION.ACTION_CREATE_PROJECT") h2 Esto es sortable y persiste en el servidor! ;) diff --git a/app/modules/projects/projects-page.controller.coffee b/app/modules/projects/projects-page.controller.coffee new file mode 100644 index 00000000..c45217bc --- /dev/null +++ b/app/modules/projects/projects-page.controller.coffee @@ -0,0 +1,34 @@ +class ProjectsPageController extends taiga.Controller + @.$inject = [ + "$scope", + "$q", + "$tgResources", + "$rootScope", + "$tgNavUrls", + "$tgAuth", + "$tgLocation", + "$appTitle", + "$projectUrl", + "$tgConfig", + "tgLoader", + "tgProjects" + ] + + constructor: (@scope, @q, @rs, @rootscope, @navUrls, @auth, @location, + @appTitle, @projectUrl, @config, tgLoader, @projects) -> + @appTitle.set("Projects") + + if !@auth.isAuthenticated() + @location.path(@navUrls.resolve("login")) + + #TODO: + @.user = @auth.getUser() + + #Projects + promise = @projects.fetchProjects() + + # Finally + promise.finally tgLoader.pageLoaded + + +angular.module("taigaProjects").controller("ProjectsPage", ProjectsPageController) diff --git a/app/modules/projects/projects.jade b/app/modules/projects/projects.jade index 21f045f0..656c15cf 100644 --- a/app/modules/projects/projects.jade +++ b/app/modules/projects/projects.jade @@ -1 +1 @@ -div(tg-projects-listing) +div(ng-controller="ProjectsPage", tg-projects-listing) diff --git a/app/modules/projects/projects.service.coffee b/app/modules/projects/projects.service.coffee new file mode 100644 index 00000000..53270b2c --- /dev/null +++ b/app/modules/projects/projects.service.coffee @@ -0,0 +1,45 @@ +class ProjectsService extends taiga.Service + @.$inject = ["$q", "$tgResources", "$rootScope", "$projectUrl"] + + constructor: (@q, @rs, @rootscope, @projectUrl) -> + @.projectsPromise = null + @.projects = null + @.callbacks = [] + + projectsSuscription: (callback) -> + @.callbacks.push(callback) + + notifySuscriptors: -> + for callback in @.callbacks + callback(@.projects) + + fetchProjects: (updateSuscriptors = true) -> + @.projectsPromise = @rs.projects.listByMember(@rootscope.user?.id).then (projects) => + for project in projects + project.url = @projectUrl.get(project) + + @.projects = {'recents': projects.slice(0, 8), 'all': projects} + if updateSuscriptors + @.notifySuscriptors() + + return @.projects + + return @.projectsPromise + + getProjects: (updateSuscriptors = false) -> + if not @.projectsPromise? + promise = @.fetchProjects(not updateSuscriptors) + else + promise = @.projectsPromise + + if updateSuscriptors + promise.then => + @.notifySuscriptors() + + return promise + + bulkUpdateProjectsOrder: (sortData) -> + @rs.projects.bulkUpdateOrder(sortData).then => + @.fetchProjects() + +angular.module("taigaProjects").service("tgProjects", ProjectsService) From d766652d3657f50f63d31ac00faae03aa3164eb7 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 22 Apr 2015 14:39:41 +0200 Subject: [PATCH 052/366] Removing unnecesary files --- app/index.jade | 1 - 1 file changed, 1 deletion(-) diff --git a/app/index.jade b/app/index.jade index eae8020d..a6acad80 100644 --- a/app/index.jade +++ b/app/index.jade @@ -12,7 +12,6 @@ html(lang="en") body(tg-main) - //include partials/includes/modules/projects-nav include partials/includes/components/notification-message div(tg-navigation-bar) From 69bc474b25eb77cedfe467aa17cea6f6365fcfa0 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 22 Apr 2015 14:49:11 +0200 Subject: [PATCH 053/366] Fixing translation typo --- app/modules/navigation-bar/navigation-bar.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/navigation-bar/navigation-bar.jade b/app/modules/navigation-bar/navigation-bar.jade index 8048587a..e642d7b5 100644 --- a/app/modules/navigation-bar/navigation-bar.jade +++ b/app/modules/navigation-bar/navigation-bar.jade @@ -22,7 +22,7 @@ nav.navbar a( href="#", tg-nav="home", - title="{{'PROJECT.NAVIGATION.DASHBOARD_TITLE' | translate}}", + title="{{'PROJECT.NAVIGATION.DASHBOARD_TITLE' | translate}}") include ../../svg/dashboard.svg From 37f3ba68a0bba77d30275fd9eb212c2721ceb13d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 22 Apr 2015 16:29:37 +0200 Subject: [PATCH 054/366] Fix multiple css errors upon refactor --- .../dropdown-user/dropdown-user.jade | 3 +-- app/modules/navigation-bar/navigation-bar.scss | 1 + app/partials/project/project-menu.jade | 16 ++++++++-------- app/styles/components/summary.scss | 7 +------ app/styles/core/base.scss | 1 + app/styles/dependencies/helpers.scss | 6 +++--- app/styles/modules/backlog/backlog-table.scss | 1 + app/styles/modules/common/nav.scss | 3 +-- gulpfile.js | 2 +- 9 files changed, 18 insertions(+), 22 deletions(-) diff --git a/app/modules/navigation-bar/dropdown-user/dropdown-user.jade b/app/modules/navigation-bar/dropdown-user/dropdown-user.jade index bb7a0045..b8d11dfe 100644 --- a/app/modules/navigation-bar/dropdown-user/dropdown-user.jade +++ b/app/modules/navigation-bar/dropdown-user/dropdown-user.jade @@ -1,5 +1,4 @@ -a.user-avatar(href="#", title="{{ vm.user.full_name_display }}") - {{ vm.user.full_name_display }} +a.user-avatar(href="#", title="{{ vm.user.full_name_display }}") {{ vm.user.full_name_display }} img(tg-bo-src="vm.user.photo", alt="{{ vm.user.full_name_display }}") div.navbar-dropdown.dropdown-user diff --git a/app/modules/navigation-bar/navigation-bar.scss b/app/modules/navigation-bar/navigation-bar.scss index e892fa40..667f6a82 100644 --- a/app/modules/navigation-bar/navigation-bar.scss +++ b/app/modules/navigation-bar/navigation-bar.scss @@ -39,6 +39,7 @@ } } .nav-right { + margin-left: auto; a { padding: .5rem 2rem; } diff --git a/app/partials/project/project-menu.jade b/app/partials/project/project-menu.jade index 4b053ee1..0c2d2663 100644 --- a/app/partials/project/project-menu.jade +++ b/app/partials/project/project-menu.jade @@ -1,46 +1,46 @@ div(class="menu-container") ul(class="main-nav") li(id="nav-search") - a(href="" title="{{'PROJECT.SECTION.SEARCH' | translate}}") + a(href="", title="{{'PROJECT.SECTION.SEARCH' | translate}}", tabindex="1") span(class="icon icon-search") span.helper(translate="PROJECT.SECTION.SEARCH") <% if (project.is_backlog_activated && project.my_permissions.indexOf("view_us") != -1) { %> li(id="nav-backlog") - a(href="" title="{{'PROJECT.SECTION.BACKLOG' | translate}}" tg-nav="project-backlog:project=project.slug") + a(href="" title="{{'PROJECT.SECTION.BACKLOG' | translate}}" tg-nav="project-backlog:project=project.slug", tabindex="2") span(class="icon icon-backlog") span.helper(translate="PROJECT.SECTION.BACKLOG") <% } %> <% if (project.is_kanban_activated && project.my_permissions.indexOf("view_us") != -1) { %> li(id="nav-kanban") - a(href="" title="{{'PROJECT.SECTION.KANBAN' | translate}}" tg-nav="project-kanban:project=project.slug") + a(href="" title="{{'PROJECT.SECTION.KANBAN' | translate}}" tg-nav="project-kanban:project=project.slug", tabindex="3") span(class="icon icon-kanban") span.helper(translate="PROJECT.SECTION.KANBAN") <% } %> <% if (project.is_issues_activated && project.my_permissions.indexOf("view_issues") != -1) { %> li(id="nav-issues") - a(href="" title="{{'PROJECT.SECTION.ISSUES' | translate}}" tg-nav="project-issues:project=project.slug") + a(href="" title="{{'PROJECT.SECTION.ISSUES' | translate}}" tg-nav="project-issues:project=project.slug", tabindex="4") span(class="icon icon-issues") span.helper(translate="PROJECT.SECTION.ISSUES") <% } %> <% if (project.is_wiki_activated && project.my_permissions.indexOf("view_wiki_pages") != -1) { %> li(id="nav-wiki") - a(href="" title="{{'PROJECT.SECTION.WIKI' | translate}}" tg-nav="project-wiki:project=project.slug") + a(href="" title="{{'PROJECT.SECTION.WIKI' | translate}}" tg-nav="project-wiki:project=project.slug", tabindex="5") span(class="icon icon-wiki") span.helper(translate="PROJECT.SECTION.WIKI") <% } %> li(id="nav-team") - a(href="" title="{{'PROJECT.SECTION.TEAM' | translate}}" tg-nav="project-team:project=project.slug") + a(href="" title="{{'PROJECT.SECTION.TEAM' | translate}}" tg-nav="project-team:project=project.slug", tabindex="6") span(class="icon icon-team") span.helper(translate="PROJECT.SECTION.TEAM") <% if (project.videoconferences) { %> li(id="nav-video") - a(href!="<%- project.videoconferenceUrl %>" target="_blank" title="{{'PROJECT.SECTION.MEETUP' | translate}}") + a(href!="<%- project.videoconferenceUrl %>" target="_blank" title="{{'PROJECT.SECTION.MEETUP' | translate}}", tabindex="7") span(class="icon icon-video") span(translate="PROJECT.SECTION.MEETUP") <% } %> <% if (project.i_am_owner) { %> li(id="nav-admin") - a(href="" tg-nav="project-admin-home:project=project.slug" title="{{'PROJECT.SECTION.ADMIN' | translate}}") + a(href="" tg-nav="project-admin-home:project=project.slug" title="{{'PROJECT.SECTION.ADMIN' | translate}}", tabindex="8") span(class="icon icon-settings") span.helper(translate="PROJECT.SECTION.ADMIN") <% } %> diff --git a/app/styles/components/summary.scss b/app/styles/components/summary.scss index 33ee7665..13a1e88f 100644 --- a/app/styles/components/summary.scss +++ b/app/styles/components/summary.scss @@ -12,12 +12,9 @@ margin: 0 .5rem; } .data { - margin-top: 4px; .number { color: $fresh-taiga; - top: 0; } - } .icon { @extend %large; @@ -25,15 +22,13 @@ .number { @extend %xlarge; @extend %bold; + line-height: .9; margin-right: .3rem; - position: relative; - top: 5px; } .description { @extend %small; @extend %text; - line-height: .9rem; } } diff --git a/app/styles/core/base.scss b/app/styles/core/base.scss index 79504e0c..a3cab813 100644 --- a/app/styles/core/base.scss +++ b/app/styles/core/base.scss @@ -7,6 +7,7 @@ body { @extend %text; background: $white; // fallback color: $grayer; + line-height: 1.2; min-height: 100%; width: 100%; .master { diff --git a/app/styles/dependencies/helpers.scss b/app/styles/dependencies/helpers.scss index d78b24d9..24ed564b 100644 --- a/app/styles/dependencies/helpers.scss +++ b/app/styles/dependencies/helpers.scss @@ -8,9 +8,9 @@ %xxlarge {font-size: 3rem;} // __Font Types__ // -%title {font-family: 'OpenSans-CondLight', Arial, Helvetica, sans-serif; line-height: 1.5;} -%text {font-family: 'opensans-regular', Arial, Helvetica, sans-serif; line-height: 1.5;} -%bold {font-family: 'opensans-semibold', Arial, Helvetica, sans-serif; line-height: 1.5;} +%title {font-family: 'OpenSans-CondLight', Arial, Helvetica, sans-serif; } +%text {font-family: 'opensans-regular', Arial, Helvetica, sans-serif; } +%bold {font-family: 'opensans-semibold', Arial, Helvetica, sans-serif; } %taiga {font-family: 'taiga';} %mono {font-family: 'courier new', 'monospace';} diff --git a/app/styles/modules/backlog/backlog-table.scss b/app/styles/modules/backlog/backlog-table.scss index 1dc5c5d3..add30736 100644 --- a/app/styles/modules/backlog/backlog-table.scss +++ b/app/styles/modules/backlog/backlog-table.scss @@ -151,6 +151,7 @@ transition: background .2s ease-in; } .user-story-name { + align-items: center; display: flex; flex-wrap: nowrap; input { diff --git a/app/styles/modules/common/nav.scss b/app/styles/modules/common/nav.scss index 65b7fba5..775c3fdc 100644 --- a/app/styles/modules/common/nav.scss +++ b/app/styles/modules/common/nav.scss @@ -3,7 +3,6 @@ $label-arrow-wh: 12px; .menu { background-image: url('../images/menu.png'); background-position: center center; - height: 100%; min-height: 100vh; padding: 1rem 0; text-transform: uppercase; @@ -34,7 +33,7 @@ $label-arrow-wh: 12px; opacity: 1; padding: .4rem 1rem; position: absolute; - top: 0; + top: calc(50% - 1rem); transition: all .2s; z-index: 99; &:after { diff --git a/gulpfile.js b/gulpfile.js index 9a315e9b..ddad3495 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -70,8 +70,8 @@ paths.css_order = [ paths.tmp + "styles/vendor/*", paths.tmp + "styles/core/reset.css", paths.tmp + "styles/core/base.css", - paths.tmp + "styles/core/animation.css", paths.tmp + "styles/core/typography.css", + paths.tmp + "styles/core/animation.css", paths.tmp + "styles/core/elements.css", paths.tmp + "styles/core/forms.css", paths.tmp + "styles/layout/*", From 71f8ab18e0f1633e8cd0ca012ac7c655825b5c91 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 23 Apr 2015 09:30:22 +0200 Subject: [PATCH 055/366] Refactoring projects service --- .../dropdown-project-list.directive.coffee | 6 +-- .../projects/listing/listing.directive.coffee | 23 +++------- app/modules/projects/projects.service.coffee | 45 +++++++------------ 3 files changed, 22 insertions(+), 52 deletions(-) diff --git a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee index b6208369..f04de7a3 100644 --- a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee +++ b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee @@ -1,11 +1,7 @@ DropdownProjectListDirective = (projectsService) -> link = (scope, el, attrs, ctrl) -> scope.vm = {} - - projectsService.projectsSuscription (projects) -> - scope.vm.projects = projects - - projectsService.getProjects() + scope.vm.projects = projectsService.projects directive = { templateUrl: "navigation-bar/dropdown-project-list/dropdown-project-list.html" diff --git a/app/modules/projects/listing/listing.directive.coffee b/app/modules/projects/listing/listing.directive.coffee index b7e7cced..6e73c57e 100644 --- a/app/modules/projects/listing/listing.directive.coffee +++ b/app/modules/projects/listing/listing.directive.coffee @@ -14,30 +14,17 @@ ProjectsListingDirective = (projectsService) -> itemEl = ui.item project = itemEl.scope().project index = itemEl.index() - scope.sorted_project_ids = _.without(scope.sorted_project_ids, project.id) - scope.sorted_project_ids.splice(index, 0, project.id) + sorted_project_ids = _.map(scope.vm.projects.all, (p) -> p.id) + sorted_project_ids = _.without(sorted_project_ids, project.id) + sorted_project_ids.splice(index, 0, project.id) sortData = [] - for value, index in scope.sorted_project_ids + for value, index in sorted_project_ids sortData.push({"project_id": value, "order":index}) projectsService.bulkUpdateProjectsOrder(sortData) - projectsService.projectsSuscription (projects) -> - scope.vm.projects = projects - scope.sorted_project_ids = _.map(projects.all, (p) -> p.id) + scope.vm.projects = projectsService.projects - projectsService.getProjects(true) - - """ - projectsService.fetchProjects().then (projects) -> - Object.defineProperty scope.vm, "projects", { - get: () -> - projects = projectsService.getProjects() - if projects - scope.sorted_project_ids = _.map(projects.all, (p) -> p.id) - return projects - } - """ directive = { templateUrl: "projects/listing/listing.html" scope: {} diff --git a/app/modules/projects/projects.service.coffee b/app/modules/projects/projects.service.coffee index 53270b2c..f9b81314 100644 --- a/app/modules/projects/projects.service.coffee +++ b/app/modules/projects/projects.service.coffee @@ -1,43 +1,30 @@ class ProjectsService extends taiga.Service @.$inject = ["$q", "$tgResources", "$rootScope", "$projectUrl"] - constructor: (@q, @rs, @rootscope, @projectUrl) -> + constructor: (@q, @rs, @rootScope, @projectUrl) -> + @.projects = {all: [], recent: []} + @.inProgress = false @.projectsPromise = null - @.projects = null - @.callbacks = [] + @.fetchProjects() - projectsSuscription: (callback) -> - @.callbacks.push(callback) + fetchProjects: -> + console.log "fetchProjects", @.inProgress + if not @.inProgress + @.inProgress = true + @.projectsPromise = @rs.projects.listByMember(@rootScope.user?.id).then (projects) => + for project in projects + project.url = @projectUrl.get(project) - notifySuscriptors: -> - for callback in @.callbacks - callback(@.projects) + @.projects.recents = projects.slice(0, 8) + @.projects.all = projects - fetchProjects: (updateSuscriptors = true) -> - @.projectsPromise = @rs.projects.listByMember(@rootscope.user?.id).then (projects) => - for project in projects - project.url = @projectUrl.get(project) + return @.projects - @.projects = {'recents': projects.slice(0, 8), 'all': projects} - if updateSuscriptors - @.notifySuscriptors() - - return @.projects + @.projectsPromise.then () => + @.inProgress = false return @.projectsPromise - getProjects: (updateSuscriptors = false) -> - if not @.projectsPromise? - promise = @.fetchProjects(not updateSuscriptors) - else - promise = @.projectsPromise - - if updateSuscriptors - promise.then => - @.notifySuscriptors() - - return promise - bulkUpdateProjectsOrder: (sortData) -> @rs.projects.bulkUpdateOrder(sortData).then => @.fetchProjects() From abff53c5c5ea5f8ebf093333b850dfb1dad55c69 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 23 Apr 2015 12:13:26 +0200 Subject: [PATCH 056/366] Refactoring create project broadcast --- app/coffee/modules/projects/lightboxes.coffee | 15 +++++++++++---- app/coffee/modules/projects/main.coffee | 3 --- .../dropdown-project-list.directive.coffee | 4 +++- .../projects/listing/listing.directive.coffee | 3 +++ app/modules/projects/projects.service.coffee | 5 ++++- bower.json | 3 ++- gulpfile.js | 1 + 7 files changed, 24 insertions(+), 10 deletions(-) diff --git a/app/coffee/modules/projects/lightboxes.coffee b/app/coffee/modules/projects/lightboxes.coffee index bb595d72..03f8273a 100644 --- a/app/coffee/modules/projects/lightboxes.coffee +++ b/app/coffee/modules/projects/lightboxes.coffee @@ -26,7 +26,7 @@ debounce = @.taiga.debounce module = angular.module("taigaProject") -CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $projectUrl, $loading, lightboxService, $cacheFactory, $translate) -> +CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $projectUrl, $loading, lightboxService, $cacheFactory, $translate, projects) -> link = ($scope, $el, attrs) -> $scope.data = {} $scope.templates = [] @@ -46,6 +46,7 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project $location.url($projectUrl.get(response)) lightboxService.close($el) + projects.fetchProjects() onErrorSubmit = (response) -> $loading.finish(submitButton) @@ -69,7 +70,7 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project promise = $repo.create("projects", $scope.data) promise.then(onSuccessSubmit, onErrorSubmit) - $scope.$on "projects:create", -> + createProjectCallback = -> $scope.data = { total_story_points: 100 total_milestones: 5 @@ -89,6 +90,8 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project timeout 600, -> $el.find(".progress-bar").addClass('step1') + projects.emiter.on 'create', createProjectCallback + $el.on "click", ".button-next", (event) -> event.preventDefault() @@ -125,6 +128,9 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project event.preventDefault() lightboxService.close($el) + $scope.$on "$destroy", -> + emitter.off(projects.emiter, createProjectCallback) + $el.off() directive = { link: link, @@ -135,8 +141,9 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project return directive -module.directive("tgLbCreateProject", ["$rootScope", "$tgRepo", "$tgConfirm", "$location", "$tgNavUrls", - "$tgResources", "$projectUrl", "$tgLoading", "lightboxService", "$cacheFactory", "$translate", CreateProject]) +module.directive("tgLbCreateProject", ["$rootScope", "$tgRepo", "$tgConfirm", + "$location", "$tgNavUrls", "$tgResources", "$projectUrl", "$tgLoading", + "lightboxService", "$cacheFactory", "$translate", "tgProjects", CreateProject]) ############################################################################# diff --git a/app/coffee/modules/projects/main.coffee b/app/coffee/modules/projects/main.coffee index 006c4ca2..69532457 100644 --- a/app/coffee/modules/projects/main.coffee +++ b/app/coffee/modules/projects/main.coffee @@ -66,9 +66,6 @@ class ProjectsController extends taiga.Controller return projects - newProject: -> - @rootscope.$broadcast("projects:create") - logout: -> @auth.logout() @location.path(@navUrls.resolve("login")) diff --git a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee index f04de7a3..b63656ee 100644 --- a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee +++ b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee @@ -2,7 +2,9 @@ DropdownProjectListDirective = (projectsService) -> link = (scope, el, attrs, ctrl) -> scope.vm = {} scope.vm.projects = projectsService.projects - + scope.vm.newProject = -> + projectsService.newProject() + directive = { templateUrl: "navigation-bar/dropdown-project-list/dropdown-project-list.html" scope: {} diff --git a/app/modules/projects/listing/listing.directive.coffee b/app/modules/projects/listing/listing.directive.coffee index 6e73c57e..64b3b385 100644 --- a/app/modules/projects/listing/listing.directive.coffee +++ b/app/modules/projects/listing/listing.directive.coffee @@ -25,6 +25,9 @@ ProjectsListingDirective = (projectsService) -> scope.vm.projects = projectsService.projects + scope.vm.newProject = -> + projectsService.newProject() + directive = { templateUrl: "projects/listing/listing.html" scope: {} diff --git a/app/modules/projects/projects.service.coffee b/app/modules/projects/projects.service.coffee index f9b81314..676d1ec6 100644 --- a/app/modules/projects/projects.service.coffee +++ b/app/modules/projects/projects.service.coffee @@ -6,9 +6,9 @@ class ProjectsService extends taiga.Service @.inProgress = false @.projectsPromise = null @.fetchProjects() + @.emiter = new EventEmitter2() fetchProjects: -> - console.log "fetchProjects", @.inProgress if not @.inProgress @.inProgress = true @.projectsPromise = @rs.projects.listByMember(@rootScope.user?.id).then (projects) => @@ -25,6 +25,9 @@ class ProjectsService extends taiga.Service return @.projectsPromise + newProject: -> + @.emiter.emit("create") + bulkUpdateProjectsOrder: (sortData) -> @rs.projects.bulkUpdateOrder(sortData).then => @.fetchProjects() diff --git a/bower.json b/bower.json index 6ea6daad..b34f6910 100644 --- a/bower.json +++ b/bower.json @@ -77,7 +77,8 @@ "angular-translate": "~2.6.1", "angular-translate-loader-static-files": "~2.6.1", "angular-translate-interpolation-messageformat": "~2.6.1", - "ngInfiniteScroll": "1.0.0" + "ngInfiniteScroll": "1.0.0", + "eventemitter2": "~0.4.14" }, "resolutions": { "lodash": "~2.4.1", diff --git a/gulpfile.js b/gulpfile.js index ddad3495..dc280afd 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -143,6 +143,7 @@ paths.libs = [ paths.vendor + "l.js/l.js", paths.vendor + "messageformat/locale/*.js", paths.vendor + "ngInfiniteScroll/build/ng-infinite-scroll.js", + paths.vendor + "eventemitter2/lib/eventemitter2.js", paths.app + "js/jquery.ui.git-custom.js", paths.app + "js/jquery-ui.drag-multiple-custom.js", paths.app + "js/jquery.ui.touch-punch.min.js", From 003499ce44eed058d26c44803697a58fdbe1ab18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 23 Apr 2015 12:17:28 +0200 Subject: [PATCH 057/366] Project list styles and layout --- app/locales/locale-en.json | 7 +-- .../profile/includes/profile-projects.jade | 42 ++++++++-------- .../projects/listing/listing.directive.coffee | 2 + app/modules/projects/listing/listing.jade | 33 +++++++----- .../listing/styles/listing.scss} | 44 ++++++++-------- .../listing/styles/profile-projects.scss | 8 +++ .../projects/listing/styles/project-list.scss | 50 +++++++++++++++++++ csslintrc.json | 2 +- 8 files changed, 127 insertions(+), 61 deletions(-) rename app/modules/{profile/styles/profile-projects.scss => projects/listing/styles/listing.scss} (63%) create mode 100644 app/modules/projects/listing/styles/profile-projects.scss create mode 100644 app/modules/projects/listing/styles/project-list.scss diff --git a/app/locales/locale-en.json b/app/locales/locale-en.json index f62a9b0e..7209d745 100644 --- a/app/locales/locale-en.json +++ b/app/locales/locale-en.json @@ -384,7 +384,7 @@ "TITLE": "Types", "SUBTITLE": "Specify the types your issues could be", "ISSUE_TITLE": "Issues types", - "ACTION_ADD": "Add new type" + "ACTION_ADD": "Add new {{objName}}" }, "ROLES": { "SECTION_NAME": "Roles - {{projectName}}", @@ -523,6 +523,7 @@ "PROJECT": { "WELCOME": "Welcome", "SECTION_PROJECTS": "Projects", + "HELP": "TODO. You can reorder your projects in your favorite way by drag&drop, Taiga will remember your order for every project list.\n Remember that the first ten projects will be shown in your rapid access menu in the top bar dropdown menu", "STATS": { "PROJECT": "project
points", "DEFINED": "defined
points", @@ -565,8 +566,8 @@ "CHANGE_PASSWORD": "Change password", "DASHBOARD_TITLE": "Dashboard", "DISCOVER_TITLE": "Discover trending projects", - "DISCOVER": "Discover" - + "DISCOVER": "Discover", + "ACTION_REORDER": "Drag & drop to reorder" }, "IMPORT": { "TITLE": "Importing Project", diff --git a/app/modules/profile/includes/profile-projects.jade b/app/modules/profile/includes/profile-projects.jade index ab7f3e37..2963a7ee 100644 --- a/app/modules/profile/includes/profile-projects.jade +++ b/app/modules/profile/includes/profile-projects.jade @@ -1,14 +1,14 @@ section.profile-projects - for (var x = 0; x < 3; x++) - div.profile-project-single - div.profile-projects-left + div.project-list-single + div.project-list-single-left - div.profile-project-title + div.project-list-single-title h1 a(href="", title="View {{ project.title }}") My Side Project p We plan to build a hundred of so called "telehubs" so people from all over the world can immediately relocate their physical self at any other telehub in microseconds. The technology and science behind this project is sound and we are now ready to build the first telehub and then massproduce them. - div.profile-project-tags.tags-container + div.project-list-single-tags.tags-container // Tag border style has to be set by JS span.tag(style='border-left: 5px solid #73d216;') span.tag-name python @@ -17,9 +17,9 @@ section.profile-projects span.tag(style='border-left: 5px solid #25f45c;') span.tag-name opensource - div.profile-projects-right + div.project-list-single-right - div.profile-project-stats + div.project-list-single-stats div.stat-comments(title="2 comments") span.icon.icon-comment span.stat-num 2 @@ -30,7 +30,7 @@ section.profile-projects span.icon.icon-open-eye span.stat-num 4 - div.profile-project-members + div.project-list-single-members a(href="", title="View {{ user.nickname }} profile") img(src="https://s3.amazonaws.com/uifaces/faces/twitter/mantia/128.jpg") a(href="", title="View {{ user.nickname }} profile") @@ -48,13 +48,13 @@ section.profile-projects a(href="", title="View {{ user.nickname }} profile") img(src="https://s3.amazonaws.com/uifaces/faces/twitter/syswarren/128.jpg", alt="{{ user.nickname }}") - div.profile-project-single - div.profile-projects-left - div.profile-project-title + div.project-list-single + div.project-list-single-left + div.project-list-single-title h1 a(href="", title="View {{ project.title }}") Teletransportation hubs - div.profile-project-tags.tags-container + div.project-list-single-tags.tags-container // Tag border style has to be set by JS span.tag(style='border-left: 5px solid #43d56f;') span.tag-name javascript @@ -63,8 +63,8 @@ section.profile-projects span.tag(style='border-left: 5px solid #cc43fd;') span.tag-name design - div.profile-projects-right - div.profile-project-stats + div.project-list-single-right + div.project-list-single-stats div.stat-comments(title="2 comments") span.icon.icon-comment span.stat-num 2 @@ -74,21 +74,21 @@ section.profile-projects div.stat-viewer(title="2 followers") span.icon.icon-open-eye span.stat-num 4 - div.profile-project-members + div.project-list-single-members a(href="", title="View {{ user.nickname }} profile") img(src="https://s3.amazonaws.com/uifaces/faces/twitter/mantia/128.jpg") a(href="", title="View {{ user.nickname }} profile") img(src="https://s3.amazonaws.com/uifaces/faces/twitter/annapickard/128.jpg") - div.profile-project-single - div.profile-projects-left + div.project-list-single + div.project-list-single-left - div.profile-project-title + div.project-list-single-title h1 a(href="", title="View {{ project.title }}") Taiga p Una plataforma social para crear comunidad entorno a la tienda. Esta comunidad está pensada para quedar a hacer deporte, compartir iniciativas. - div.profile-project-tags.tags-container + div.project-list-single-tags.tags-container // Tag border style has to be set by JS span.tag(style='border-left: 5px solid #11cd00;') span.tag-name PHP @@ -96,9 +96,9 @@ section.profile-projects span.tag-name marketing span.tag(style='border-left: 5px solid #cdcd54;') span.tag-name wordpress - div.profile-projects-right + div.project-list-single-right - div.profile-project-stats + div.project-list-single-stats div.stat-comments span.icon.icon-comment span.stat-num 2 @@ -109,7 +109,7 @@ section.profile-projects span.icon.icon-open-eye span.stat-num 4 - div.profile-project-members + div.project-list-single-members - for (var x = 0; x < 2; x++) a(href="", title="View {{ user.nickname }} profile") img(src="https://s3.amazonaws.com/uifaces/faces/twitter/mantia/128.jpg") diff --git a/app/modules/projects/listing/listing.directive.coffee b/app/modules/projects/listing/listing.directive.coffee index 64b3b385..0339c929 100644 --- a/app/modules/projects/listing/listing.directive.coffee +++ b/app/modules/projects/listing/listing.directive.coffee @@ -8,6 +8,8 @@ ProjectsListingDirective = (projectsService) -> dropOnEmpty: true revert: 200 axis: "y" + opacity: .95 + placeholder: 'placeholder' }) tdom.on "sortstop", (event, ui) -> diff --git a/app/modules/projects/listing/listing.jade b/app/modules/projects/listing/listing.jade index 873615f9..d1b5d677 100644 --- a/app/modules/projects/listing/listing.jade +++ b/app/modules/projects/listing/listing.jade @@ -1,17 +1,24 @@ -a.create-project-btn.button-green( - href="#", - ng-click="vm.newProject()", - title="{{'PROJECT.NAVIGATION.ACTION_CREATE_PROJECT' | translate}}", - translate="PROJECT.NAVIGATION.ACTION_CREATE_PROJECT") +div.project-list-wrapper.centered + div.project-list-title + h1 My projects + a.create-project-btn.button-green(href="#", ng-click="vm.newProject()", title="{{'PROJECT.NAVIGATION.ACTION_CREATE_PROJECT' | translate}}", translate="PROJECT.NAVIGATION.ACTION_CREATE_PROJECT") + section.project-list-section + div.project-list + ul.js-sortable + li.project-list-single(tg-bind-scope, ng-repeat="project in vm.projects.all") + div.project-list-single-left + div.project-list-single-title + h1 + a(href="#", ng-bind="::project.name", tg-nav="project:project=project.slug") + span {{project.is_private}} + p {{ ::project.description }} -h2 Esto es sortable y persiste en el servidor! ;) + div.project-list-single-tags.tags-container(ng-if="project.tags") + div.tags-block(tg-colorize-tags="project.tags", tg-colorize-tags-type="backlog") -ul.js-sortable - li(tg-bind-scope, ng-repeat="project in vm.projects.all") - span Private: {{project.is_private}}, tags: {{project.tags}}, link: + div.project-list-single-right + span.drag.icon.icon-drag-v - a(href="#", - ng-bind="::project.name" - tg-nav="project:project=project.slug") + aside.help-area + p(translate="PROJECT.HELP") - div {{ ::project.description }} diff --git a/app/modules/profile/styles/profile-projects.scss b/app/modules/projects/listing/styles/listing.scss similarity index 63% rename from app/modules/profile/styles/profile-projects.scss rename to app/modules/projects/listing/styles/listing.scss index b2fae443..b165b095 100644 --- a/app/modules/profile/styles/profile-projects.scss +++ b/app/modules/projects/listing/styles/listing.scss @@ -1,26 +1,23 @@ -.profile-projects { - border-top: 1px solid $whitish; - .profile-project-single { - border-bottom: 1px solid $whitish; - display: flex; - justify-content: center; - min-height: 10rem; - padding: .8rem 1rem; - position: relative; - } - .profile-projects-left, - .profile-projects-right { - display: flex; - flex-direction: column; - } +.project-list-single { + border-bottom: 1px solid $whitish; + display: flex; + justify-content: center; + padding: .8rem 1rem; + position: relative; } -.profile-projects-left { +.project-list-single-left, +.project-list-single-right { + display: flex; + flex-direction: column; +} + +.project-list-single-left { align-content: space-between; flex: 4; h1 { - @extend %bold; - @extend %xlarge; + @extend %text; + @extend %larger; margin-bottom: 0; text-transform: none; } @@ -28,7 +25,7 @@ color: $gray-light; max-width: 70%; } - .profile-project-tags { + .project-list-single-tags { align-content: flex-end; display: flex; flex: 3; @@ -37,13 +34,14 @@ } .tag { align-self: flex-end; - margin-bottom: .3rem; + margin-right: .5rem; + padding: .5rem; } } -.profile-projects-right { +.project-list-single-right { justify-content: space-between; - .profile-project-stats { + .project-list-single-stats { align-self: flex-end; display: flex; div { @@ -60,7 +58,7 @@ } } } - .profile-project-members { + .project-list-single-members { align-self: flex-end; display: flex; flex-grow: 0; diff --git a/app/modules/projects/listing/styles/profile-projects.scss b/app/modules/projects/listing/styles/profile-projects.scss new file mode 100644 index 00000000..32aae76d --- /dev/null +++ b/app/modules/projects/listing/styles/profile-projects.scss @@ -0,0 +1,8 @@ +.profile-projects { + border-top: 1px solid $whitish; + .project-list-single { + min-height: 10rem; + } +} + + diff --git a/app/modules/projects/listing/styles/project-list.scss b/app/modules/projects/listing/styles/project-list.scss new file mode 100644 index 00000000..4e031e8b --- /dev/null +++ b/app/modules/projects/listing/styles/project-list.scss @@ -0,0 +1,50 @@ +.project-list-wrapper { + .project-list-title { + align-items: center; + background: $whitish; + display: flex; + justify-content: space-between; + margin: 2rem 0 1rem; + padding: .9rem 1rem; + h1 { + @extend %larger; + margin: 0; + } + } + .project-list-section { + display: flex; + } + .project-list { + flex: 1; + margin-right: 2rem; + } + .help-area { + @extend %small; + color: $gray-light; + width: 200px; + } + .placeholder { + background-color: lighten($whitish, 3%); + height: 7rem; + width: 100%; + } + .project-list-single { + background: $white; + &:hover { + background: lighten($green-taiga, 60%); + cursor: move; + transition: background .3s; + .drag { + opacity: 1; + } + } + } + .drag { + @extend %large; + align-self: center; + color: $gray-light; + opacity: 0; + transition: opacity .2s; + } +} + diff --git a/csslintrc.json b/csslintrc.json index 6071bc41..3f615cc3 100644 --- a/csslintrc.json +++ b/csslintrc.json @@ -6,7 +6,7 @@ "font-sizes": false, "ids": true, "known-properties": false, - "overqualified-elements": true, + "overqualified-elements": false, "shorthand": true, "text-indent": true, "unique-headings": false, From b2b823d47fe16f1e1d36a6ec81d812e16f6533d9 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 23 Apr 2015 12:53:26 +0200 Subject: [PATCH 058/366] Refactoring feedback --- app/coffee/modules/feedback.coffee | 10 +++++--- app/modules/feedback/feedback-service.coffee | 8 +++++++ .../dropdown-user.directive.coffee | 23 +++++++++++++++---- .../dropdown-user/dropdown-user.jade | 2 +- .../navigation-bar.directive.coffee | 10 ++++++-- .../navigation-bar/navigation-bar.jade | 2 +- .../projects/projects-page.controller.coffee | 3 --- 7 files changed, 43 insertions(+), 15 deletions(-) create mode 100644 app/modules/feedback/feedback-service.coffee diff --git a/app/coffee/modules/feedback.coffee b/app/coffee/modules/feedback.coffee index 79ba422f..bfc4032f 100644 --- a/app/coffee/modules/feedback.coffee +++ b/app/coffee/modules/feedback.coffee @@ -29,7 +29,7 @@ trim = @.taiga.trim module = angular.module("taigaFeedback", []) -FeedbackDirective = ($lightboxService, $repo, $confirm, $loading)-> +FeedbackDirective = ($lightboxService, $repo, $confirm, $loading, feedbackService)-> link = ($scope, $el, $attrs) -> form = $el.find("form").checksley() @@ -56,14 +56,18 @@ FeedbackDirective = ($lightboxService, $repo, $confirm, $loading)-> $el.on "submit", "form", submit - $scope.$on "feedback:show", -> + sendFeedbackCallback = -> $scope.feedback = {} $lightboxService.open($el) $el.find("textarea").focus() + feedbackService.emiter.on "send", sendFeedbackCallback + $scope.$on "$destroy", -> + emitter.off(feedbackService.emiter, sendFeedbackCallback) $el.off() return {link:link} -module.directive("tgLbFeedback", ["lightboxService", "$tgRepo", "$tgConfirm", "$tgLoading", FeedbackDirective]) +module.directive("tgLbFeedback", ["lightboxService", "$tgRepo", "$tgConfirm", + "$tgLoading", "tgFeedback", FeedbackDirective]) diff --git a/app/modules/feedback/feedback-service.coffee b/app/modules/feedback/feedback-service.coffee new file mode 100644 index 00000000..c063f215 --- /dev/null +++ b/app/modules/feedback/feedback-service.coffee @@ -0,0 +1,8 @@ +class FeedbackService extends taiga.Service + constructor: -> + @.emiter = new EventEmitter2() + + sendFeedback: -> + @.emiter.emit("send") + +angular.module("taigaFeedback").service("tgFeedback", FeedbackService) diff --git a/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee b/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee index 7a3d0793..a417292d 100644 --- a/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee +++ b/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee @@ -1,13 +1,26 @@ -DropdownUserDirective = () -> +DropdownUserDirective = (authService, configService, locationService, + navUrlsService, feedbackService) -> + + link = (scope, el, attrs, ctrl) -> + scope.vm = {} + scope.vm.user = authService.getUser() + scope.vm.isFeedbackEnabled = configService.get("feedbackEnabled") + + scope.vm.logout = -> + authService.logout() + locationService.path(navUrlsService.resolve("login")) + + scope.vm.sendFeedback = -> + feedbackService.sendFeedback() + directive = { templateUrl: "navigation-bar/dropdown-user/dropdown-user.html" - controller: "ProjectsController" scope: {} - bindToController: true - controllerAs: "vm" + link: link } return directive angular.module("taigaNavigationBar").directive("tgDropdownUser", - DropdownUserDirective) + ["$tgAuth", "$tgConfig", "$tgLocation", "$tgNavUrls", "tgFeedback", + DropdownUserDirective]) diff --git a/app/modules/navigation-bar/dropdown-user/dropdown-user.jade b/app/modules/navigation-bar/dropdown-user/dropdown-user.jade index b8d11dfe..e7ddf281 100644 --- a/app/modules/navigation-bar/dropdown-user/dropdown-user.jade +++ b/app/modules/navigation-bar/dropdown-user/dropdown-user.jade @@ -39,7 +39,7 @@ div.navbar-dropdown.dropdown-user title="{{'PROJECT.NAVIGATION.NOTIFICATIONS_TITLE' | translate}}", translate="PROJECT.NAVIGATION.NOTIFICATIONS") - li(ng-show="vm.isFeedbackEnabled()") + li(ng-show="vm.isFeedbackEnabled") a( href="#", ng-click="vm.sendFeedback()", diff --git a/app/modules/navigation-bar/navigation-bar.directive.coffee b/app/modules/navigation-bar/navigation-bar.directive.coffee index 1be03abf..44607883 100644 --- a/app/modules/navigation-bar/navigation-bar.directive.coffee +++ b/app/modules/navigation-bar/navigation-bar.directive.coffee @@ -1,10 +1,16 @@ -NavigationBarDirective = () -> +NavigationBarDirective = (projectsService) -> + link = (scope, el, attrs, ctrl) -> + scope.vm = {} + scope.vm.projects = projectsService.projects + directive = { templateUrl: "navigation-bar/navigation-bar.html" + scope: {} + link: link } return directive angular.module("taigaNavigationBar").directive("tgNavigationBar", - NavigationBarDirective) + ["tgProjects", NavigationBarDirective]) diff --git a/app/modules/navigation-bar/navigation-bar.jade b/app/modules/navigation-bar/navigation-bar.jade index e642d7b5..c003c61a 100644 --- a/app/modules/navigation-bar/navigation-bar.jade +++ b/app/modules/navigation-bar/navigation-bar.jade @@ -26,7 +26,7 @@ nav.navbar include ../../svg/dashboard.svg - div.topnav-dropdown-wrapper(tg-dropdown-project-list) + div.topnav-dropdown-wrapper(ng-show="vm.projects.recents", tg-dropdown-project-list) //div.topnav-dropdown-wrapper(tg-dropdown-organization-list) div.topnav-dropdown-wrapper(tg-dropdown-user) diff --git a/app/modules/projects/projects-page.controller.coffee b/app/modules/projects/projects-page.controller.coffee index c45217bc..1496e80a 100644 --- a/app/modules/projects/projects-page.controller.coffee +++ b/app/modules/projects/projects-page.controller.coffee @@ -21,9 +21,6 @@ class ProjectsPageController extends taiga.Controller if !@auth.isAuthenticated() @location.path(@navUrls.resolve("login")) - #TODO: - @.user = @auth.getUser() - #Projects promise = @projects.fetchProjects() From 84868db1cab16d764b7efb22519cc02fdf5d7d77 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 23 Apr 2015 14:57:15 +0200 Subject: [PATCH 059/366] Updating projects link --- .../dropdown-project-list/dropdown-project-list.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade index 9a541a00..42215ebc 100644 --- a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade +++ b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade @@ -1,4 +1,4 @@ -a(href="", title="Projects") +a(href="", title="Projects", tg-nav="projects") include ../../../svg/projects.svg div.navbar-dropdown.dropdown-project-list From 9bcc5414b2c5cd32670fdee7d4000c1429b215f8 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 23 Apr 2015 15:14:42 +0200 Subject: [PATCH 060/366] Updating recent number of projects --- app/modules/projects/projects.service.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/projects/projects.service.coffee b/app/modules/projects/projects.service.coffee index 676d1ec6..1bd6e980 100644 --- a/app/modules/projects/projects.service.coffee +++ b/app/modules/projects/projects.service.coffee @@ -15,7 +15,7 @@ class ProjectsService extends taiga.Service for project in projects project.url = @projectUrl.get(project) - @.projects.recents = projects.slice(0, 8) + @.projects.recents = projects.slice(0, 10) @.projects.all = projects return @.projects From 4b215c7283ee93f504fd6c23136103a72337490c Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 24 Apr 2015 09:09:37 +0200 Subject: [PATCH 061/366] Adding import project buttons --- .../dropdown-project-list/dropdown-project-list.jade | 5 +++++ app/modules/projects/listing/listing.jade | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade index 42215ebc..d9548202 100644 --- a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade +++ b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade @@ -19,3 +19,8 @@ div.navbar-dropdown.dropdown-project-list ng-click="vm.newProject()", title="{{'PROJECT.NAVIGATION.ACTION_CREATE_PROJECT' | translate}}", translate="PROJECT.NAVIGATION.ACTION_CREATE_PROJECT") + + div(tg-import-project-button) + a.button-blackish.import-project-button(href="", title="{{'PROJECT.NAVIGATION.TITLE_ACTION_IMPORT' | translate}}") + span.icon.icon-upload + input.import-file.hidden(type="file") diff --git a/app/modules/projects/listing/listing.jade b/app/modules/projects/listing/listing.jade index d1b5d677..7cd45e87 100644 --- a/app/modules/projects/listing/listing.jade +++ b/app/modules/projects/listing/listing.jade @@ -2,6 +2,11 @@ div.project-list-wrapper.centered div.project-list-title h1 My projects a.create-project-btn.button-green(href="#", ng-click="vm.newProject()", title="{{'PROJECT.NAVIGATION.ACTION_CREATE_PROJECT' | translate}}", translate="PROJECT.NAVIGATION.ACTION_CREATE_PROJECT") + div(tg-import-project-button) + a.button-blackish.import-project-button(href="", title="{{'PROJECT.NAVIGATION.TITLE_ACTION_IMPORT' | translate}}") + span.icon.icon-upload + input.import-file.hidden(type="file") + section.project-list-section div.project-list ul.js-sortable @@ -21,4 +26,3 @@ div.project-list-wrapper.centered aside.help-area p(translate="PROJECT.HELP") - From e46d384d928d42e0120227fe296fca72dd261ebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 24 Apr 2015 13:28:57 +0200 Subject: [PATCH 062/366] User Home Main area --- app/coffee/app.coffee | 2 +- app/modules/home/styles/home-project-list | 0 app/modules/home/styles/home-watching.scss | 15 ++ app/modules/home/styles/home-working.scss | 61 +++++++ app/modules/home/styles/home.scss | 18 ++ app/partials/home/home.jade | 90 +++++++--- app/styles/modules/home-projects-list.scss | 191 --------------------- app/svg/hide.svg | 6 + 8 files changed, 163 insertions(+), 220 deletions(-) create mode 100644 app/modules/home/styles/home-project-list create mode 100644 app/modules/home/styles/home-watching.scss create mode 100644 app/modules/home/styles/home-working.scss create mode 100644 app/modules/home/styles/home.scss delete mode 100644 app/styles/modules/home-projects-list.scss create mode 100644 app/svg/hide.svg diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 373010d9..c7ed28cb 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -39,7 +39,7 @@ taiga.sessionId = taiga.generateUniqueSessionIdentifier() configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEventsProvider, tgLoaderProvider, $compileProvider, $translateProvider) -> $routeProvider.when("/", - {templateUrl: "home/home.html", resolve: {loader: tgLoaderProvider.add()}}) + {templateUrl: "home/home.html"}) $routeProvider.when("/projects/", {templateUrl: "projects/projects.html", resolve: {loader: tgLoaderProvider.add()}}) diff --git a/app/modules/home/styles/home-project-list b/app/modules/home/styles/home-project-list new file mode 100644 index 00000000..e69de29b diff --git a/app/modules/home/styles/home-watching.scss b/app/modules/home/styles/home-watching.scss new file mode 100644 index 00000000..e4f41429 --- /dev/null +++ b/app/modules/home/styles/home-watching.scss @@ -0,0 +1,15 @@ +.watching-empty { + padding: 5vh; + text-align: center; + svg { + margin: 2rem auto; + max-width: 160px; + text-align: center; + path { + fill: $whitish; + } + } + p { + @extend %small; + } +} diff --git a/app/modules/home/styles/home-working.scss b/app/modules/home/styles/home-working.scss new file mode 100644 index 00000000..808bc378 --- /dev/null +++ b/app/modules/home/styles/home-working.scss @@ -0,0 +1,61 @@ +.working-on, +.watching { + .duty-single { + align-items: center; + border-bottom: 1px solid $whitish; + cursor: pointer; + display: flex; + flex-direction: row; + padding: .5rem; + &:last-child { + border: 0; + } + &.blocked { + background: rgba($red-light, .2); + .duty-type, + .duty-status { + color: $red; + } + } + } + .avatar { + flex-basis: 47px; + height: 47px; + margin-right: .5rem; + width: 47px; + } + .duty-data { + flex: 1; + margin-right: .5rem; + } + .duty-type, + .duty-status { + @extend %small; + color: $gray; + margin-right: .3rem; + } + .duty-title { + display: block; + margin-top: .25rem; + } + .duty-id { + color: $gray-light; + margin-right: .3rem; + } + .duty-project { + @extend %small; + align-self: flex-start; + color: $gray-light; + margin-left: auto; + text-align: right; + width: 120px; + } + .see-more { + display: block; + margin: 2rem 30%; + } +} + +.watching { + margin-bottom: 2rem; +} diff --git a/app/modules/home/styles/home.scss b/app/modules/home/styles/home.scss new file mode 100644 index 00000000..0166400b --- /dev/null +++ b/app/modules/home/styles/home.scss @@ -0,0 +1,18 @@ +.home-wrapper { + display: flex; + .main { + flex: 1; + } + .project-list { + width: 250px; + } + .title-bar { + @extend %title; + @extend %larger; + align-content: center; + background: $whitish; + display: flex; + margin: 2rem 0 .5rem; + padding: .9rem 1rem; + } +} diff --git a/app/partials/home/home.jade b/app/partials/home/home.jade index 2eb47170..5cecaa68 100644 --- a/app/partials/home/home.jade +++ b/app/partials/home/home.jade @@ -1,34 +1,68 @@ doctype html include ../includes/components/beta -div.home-projects-list(ng-controller="ProjectsController as ctrl") - .home-projects-wrapper - div.welcome-user - div.info - p(translate="PROJECT.WELCOME") - span(tg-bo-bind="ctrl.user.full_name_display") - a.logout(ng-click="ctrl.logout()" href="", title="{{'COMMON.LOGOUT' | translate}}", translate="COMMON.LOGOUT") +div.home-wrapper.centered + div.main + // TODO Hide if ASSIGNED TO ==== false + div.title-bar.working-on-title Working on + // TODO Hide if ASSIGNED TO ==== false + section.working-on + // TODO Remove and replace for an angular repeat + - for (var x = 0; x < 2; x++) + div.duty-single + img.avatar(src="https://s3.amazonaws.com/uifaces/faces/twitter/rem/128.jpg", title="{{ user.fullname }}") + div.duty-data + div + // TODO, ADD THEIR COLOR TO THE DUTY TYPE AND STATUS + span.duty-type User Story + span.duty-status New + a.duty-title(href="") + span.duty-id #1504 + span.duty-name Documentación III: (notificaciones asíncronas + plugins front/back) + div.duty-project Taiga + div.duty-single.blocked + img.avatar(src="https://s3.amazonaws.com/uifaces/faces/twitter/rem/128.jpg", title="{{ user.fullname }}") + div.duty-data + div + // TODO, ADD THEIR COLOR TO THE DUTY TYPE AND STATUS + span.duty-type User Story + span.duty-status Blocked + a.duty-title(href="") + span.duty-id #1504 + span.duty-name Documentación III: (notificaciones asíncronas + plugins front/back) Documentación III: (notificaciones asíncronas + plugins front/back) + div.duty-project Whatever - .avatar - img(tg-bo-src="ctrl.user.photo", tg-bo-alt="ctrl.user.full_name_display") + div.title-bar.watching-title Watching - .home-projects-list-inner - div.recent-projects - ul - li(ng-repeat="project in ctrl.projects.recents") - .project-content - a(href="{{ project.url }}") - h2(tg-bo-bind="project.name") - p(tg-bo-bind="project.description") + // TODO Show if WATCHERS ==== false + section.watching-empty.hidden + include ../../svg/hide.svg + p Follow the projects, User Stories, Tasks, Issues... that you want to know about :) - div.all-projects - h1(translate="PROJECT.SECTION_PROJECTS") - div(tg-projects-list) - - .create-project-button-wrapper - a.button-green.create-project-button(href="", ng-click="ctrl.newProject()", title="{{'PROJECT.NAVIGATION.ACTION_CREATE_PROJECT' | translate}}") - span(translate="PROJECT.NAVIGATION.ACTION_CREATE_PROJECT") - div(tg-import-project-button) - a.button-blackish.import-project-button(href="", title="{{'PROJECT.NAVIGATION.TITLE_ACTION_IMPORT' | translate}}") - span.icon.icon-upload - input.import-file.hidden(type="file") + // TODO Show if WATCHERS ==== true + section.watching + - for (var x = 0; x < 20; x++) + div.duty-single + img.avatar(src="https://s3.amazonaws.com/uifaces/faces/twitter/annapickard/128.jpg", title="{{ user.fullname }}") + div.duty-data + div + // TODO, ADD THEIR COLOR TO THE DUTY TYPE AND STATUS + span.duty-type User Story + span.duty-status New + a.duty-title(href="") + span.duty-id #1504 + span.duty-name Documentación III: (notificaciones asíncronas + plugins front/back) + div.duty-project Taiga + div.duty-single + img.avatar(src="https://s3.amazonaws.com/uifaces/faces/twitter/sircookieface/128.jpg", title="{{ user.fullname }}") + div.duty-data + div + // TODO, ADD THEIR COLOR TO THE DUTY TYPE AND STATUS + span.duty-type Bug + span.duty-status Ready for test + a.duty-title(href="") + span.duty-id #28 + span.duty-name It is not possible to re-order stories in the sprint view . + div.duty-project Teletransportation hubs + a.button-gray.see-more(href="#", title="See more Watching US") See more + aside.project-list diff --git a/app/styles/modules/home-projects-list.scss b/app/styles/modules/home-projects-list.scss deleted file mode 100644 index bd805c70..00000000 --- a/app/styles/modules/home-projects-list.scss +++ /dev/null @@ -1,191 +0,0 @@ -.home-projects-list, -.home-project { - @extend %background-taiga; - align-content: center; - align-items: center; - background-color: $black; - background-position: center center; - background-size: cover; - display: flex; - height: 100%; - justify-content: center; - left: 0; - padding: 0; - position: fixed; - top: 0; - width: 100%; - .welcome-user { - display: flex; - position: absolute; - right: 1rem; - top: 1rem; - p { - color: $whitish; - margin-bottom: 0; - span:before { - content: ' '; - } - } - .logout { - @extend %small; - float: right; - &:hover { - color: $red-light; - } - } - .info { - padding-right: 1rem; - } - img { - width: 40px; - } - } -} -.home-projects-wrapper { - width: 1200px; -} - -.home-projects-list-inner { - display: flex; -} - -.recent-projects { - flex-grow: 8; - max-width: 800px; - ul { - display: flex; - flex-wrap: wrap; - margin: 0; - padding: 0; - } - a { - height: 100%; - left: 0; - padding: 1rem; - position: absolute; - top: 0; - width: 100%; - } - li { - background-color: rgba($white, .5); - color: $whitish; - flex-basis: 230px; - flex-grow: 1; - flex-shrink: 0; - height: 130px; - margin-bottom: 1rem; - margin-right: 1rem; - overflow: hidden; - position: relative; - transition: background-color .3s linear; - width: 23.5%; - &:hover { - background-color: rgba($fresh-taiga, .5); - cursor: pointer; - transition: background-color .3s linear; - p { - color: $gray-light; - transition: color .3s linear; - } - } - } - h2 { - color: $whitish; - line-height: 2rem; - } - p { - color: $grayer; - transition: color .3s linear; - } -} - -.project-content { - h2 { - margin-bottom: .5rem; - } - p { - @extend %small; - line-height: 1rem; - } -} - -.all-projects { - background-color: rgba(0, 0, 0, .5); - display: flex; - flex-direction: column; - flex-grow: 1; - margin-left: 1rem; - max-height: 422px; - padding: 1rem; - width: 285px; - h1 { - color: $whitish; - flex-shrink: 0; - line-height: 1; - text-align: center; - } - .v-pagination-list { - max-height: 221px; - } - ul { - left: 0; - margin-bottom: 0; - position: relative; - top: 0; - width: 100%; - } - li { - border-bottom: 2px solid $gray; - a { - @extend %large; - @extend %title; - color: $whitish; - display: block; - line-height: 1.2rem; - padding: 1rem; - text-transform: uppercase; - width: 100%; - &:hover { - background-color: $gray; - transition: background-color .3s linear; - } - } - .active { - background-color: $gray; - transition: background-color .3s linear; - } - } - .projects-pagination { - width: 100%; - } - .create-project-button-wrapper { - display: flex; - flex-shrink: 0; - .create-project-button { - flex-grow: 8; - margin-right: .2rem; - text-align: center; - } - .import-project-button { - flex-grow: 1; - padding-left: .5rem; - padding-right: .5rem; - text-align: center; - .icon { - color: $grayer; - margin: 0; - } - } - } - .button-green { - color: $whitish; - text-align: center; - width: 100%; - &:hover { - color: $whitish; - } - } - .v-pagination-next { - margin-bottom: 1rem; - } -} diff --git a/app/svg/hide.svg b/app/svg/hide.svg new file mode 100644 index 00000000..0abadf8e --- /dev/null +++ b/app/svg/hide.svg @@ -0,0 +1,6 @@ + + + + + + From dab4bc9a95dbf69e868eaa315fcb0b195f8da7ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 24 Apr 2015 14:12:16 +0200 Subject: [PATCH 063/366] Home project list WIP --- app/coffee/app.coffee | 1 + .../home/home-project-list.directive.coffee | 18 ++++++++++++++++++ app/modules/home/home-project-list.jade | 10 ++++++++++ app/modules/home/home.module.coffee | 1 + app/modules/home/styles/home-project-list | 0 app/modules/home/styles/home-project-list.scss | 3 +++ app/modules/home/styles/home-working.scss | 5 +---- app/modules/home/styles/home.scss | 6 ++++-- .../projects/listing/listing.directive.coffee | 3 +-- app/partials/home/home.jade | 5 +++-- 10 files changed, 42 insertions(+), 10 deletions(-) create mode 100644 app/modules/home/home-project-list.directive.coffee create mode 100644 app/modules/home/home-project-list.jade create mode 100644 app/modules/home/home.module.coffee delete mode 100644 app/modules/home/styles/home-project-list create mode 100644 app/modules/home/styles/home-project-list.scss diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index c7ed28cb..753ae33a 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -341,6 +341,7 @@ modules = [ "taigaIntegrations", "taigaComponents", "taigaProfile", + "taigaHome", # template cache "templates", diff --git a/app/modules/home/home-project-list.directive.coffee b/app/modules/home/home-project-list.directive.coffee new file mode 100644 index 00000000..ae0048b6 --- /dev/null +++ b/app/modules/home/home-project-list.directive.coffee @@ -0,0 +1,18 @@ +HomeProjectListDirective = (projectsService) -> + link = (scope, el, attrs, ctrl) -> + scope.vm = {} + + scope.vm.projects = projectsService.projects + + scope.vm.newProject = -> + projectsService.newProject() + + directive = { + templateUrl: "home/home-project-list.html" + scope: {} + link: link + } + + return directive + +angular.module("taigaHome").directive("tgHomeProjectList", ["tgProjects", HomeProjectListDirective]) diff --git a/app/modules/home/home-project-list.jade b/app/modules/home/home-project-list.jade new file mode 100644 index 00000000..7617033d --- /dev/null +++ b/app/modules/home/home-project-list.jade @@ -0,0 +1,10 @@ +ul.home-project-list + li.home-project-list-single(tg-bind-scope, ng-repeat="project in vm.projects.all") + div.home-project-list-single-left + div.home-project-list-single-title + h2 + a(href="#", ng-bind="::project.name", tg-nav="project:project=project.slug") + span {{project.is_private}} + p {{ ::project.description }} + + div.home-project-list-single-right diff --git a/app/modules/home/home.module.coffee b/app/modules/home/home.module.coffee new file mode 100644 index 00000000..9cdb1f39 --- /dev/null +++ b/app/modules/home/home.module.coffee @@ -0,0 +1 @@ +module = angular.module("taigaHome", []) diff --git a/app/modules/home/styles/home-project-list b/app/modules/home/styles/home-project-list deleted file mode 100644 index e69de29b..00000000 diff --git a/app/modules/home/styles/home-project-list.scss b/app/modules/home/styles/home-project-list.scss new file mode 100644 index 00000000..ab01f613 --- /dev/null +++ b/app/modules/home/styles/home-project-list.scss @@ -0,0 +1,3 @@ +.home-project-list { + content: 'WIP'; +} diff --git a/app/modules/home/styles/home-working.scss b/app/modules/home/styles/home-working.scss index 808bc378..add730f1 100644 --- a/app/modules/home/styles/home-working.scss +++ b/app/modules/home/styles/home-working.scss @@ -1,5 +1,6 @@ .working-on, .watching { + margin-bottom: 2rem; .duty-single { align-items: center; border-bottom: 1px solid $whitish; @@ -55,7 +56,3 @@ margin: 2rem 30%; } } - -.watching { - margin-bottom: 2rem; -} diff --git a/app/modules/home/styles/home.scss b/app/modules/home/styles/home.scss index 0166400b..72e932c9 100644 --- a/app/modules/home/styles/home.scss +++ b/app/modules/home/styles/home.scss @@ -1,7 +1,9 @@ .home-wrapper { display: flex; - .main { + padding-top: 2rem; + .duty-summary { flex: 1; + margin-right: .5rem; } .project-list { width: 250px; @@ -12,7 +14,7 @@ align-content: center; background: $whitish; display: flex; - margin: 2rem 0 .5rem; + margin: 0 0 .5rem; padding: .9rem 1rem; } } diff --git a/app/modules/projects/listing/listing.directive.coffee b/app/modules/projects/listing/listing.directive.coffee index 0339c929..506af5cd 100644 --- a/app/modules/projects/listing/listing.directive.coffee +++ b/app/modules/projects/listing/listing.directive.coffee @@ -38,5 +38,4 @@ ProjectsListingDirective = (projectsService) -> return directive -angular.module("taigaProjects").directive("tgProjectsListing", - ["tgProjects", ProjectsListingDirective]) +angular.module("taigaProjects").directive("tgProjectsListing", ["tgProjects", ProjectsListingDirective]) diff --git a/app/partials/home/home.jade b/app/partials/home/home.jade index 5cecaa68..74c9ab2c 100644 --- a/app/partials/home/home.jade +++ b/app/partials/home/home.jade @@ -2,7 +2,7 @@ doctype html include ../includes/components/beta div.home-wrapper.centered - div.main + div.duty-summary // TODO Hide if ASSIGNED TO ==== false div.title-bar.working-on-title Working on // TODO Hide if ASSIGNED TO ==== false @@ -65,4 +65,5 @@ div.home-wrapper.centered span.duty-name It is not possible to re-order stories in the sprint view . div.duty-project Teletransportation hubs a.button-gray.see-more(href="#", title="See more Watching US") See more - aside.project-list + aside.project-list(tg-home-project-list) + From 3b95376686c259402f1a76231c41fdb91981a566 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 27 Apr 2015 08:26:39 +0200 Subject: [PATCH 064/366] Fixing project renaming and deleting --- app/coffee/modules/admin/project-profile.coffee | 6 +++--- app/coffee/modules/projects/lightboxes.coffee | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/coffee/modules/admin/project-profile.coffee b/app/coffee/modules/admin/project-profile.coffee index 8d2bd6dd..c3dae556 100644 --- a/app/coffee/modules/admin/project-profile.coffee +++ b/app/coffee/modules/admin/project-profile.coffee @@ -106,7 +106,7 @@ module.controller("ProjectProfileController", ProjectProfileController) ## Project Profile Directive ############################################################################# -ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location) -> +ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location, projectsService) -> link = ($scope, $el, $attrs) -> form = $el.find("form").checksley({"onlyOneErrorElement": true}) submit = debounce 2000, (event) => @@ -123,6 +123,7 @@ ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location) -> newUrl = $navurls.resolve("project-admin-project-profile-details", {project: $scope.project.slug}) $location.path(newUrl) $scope.$emit("project:loaded", $scope.project) + projectsService.fetchProjects() promise.then null, (data) -> $loading.finish(submitButton) @@ -136,8 +137,7 @@ ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location) -> return {link:link} -module.directive("tgProjectProfile", ["$tgRepo", "$tgConfirm", "$tgLoading", "$tgNavUrls", "$tgLocation", - ProjectProfileDirective]) +module.directive("tgProjectProfile", ["$tgRepo", "$tgConfirm", "$tgLoading", "$tgNavUrls", "$tgLocation", "tgProjects", ProjectProfileDirective]) ############################################################################# ## Project Default Values Directive diff --git a/app/coffee/modules/projects/lightboxes.coffee b/app/coffee/modules/projects/lightboxes.coffee index 03f8273a..655228b6 100644 --- a/app/coffee/modules/projects/lightboxes.coffee +++ b/app/coffee/modules/projects/lightboxes.coffee @@ -150,7 +150,7 @@ module.directive("tgLbCreateProject", ["$rootScope", "$tgRepo", "$tgConfirm", ## Delete Project Lightbox Directive ############################################################################# -DeleteProjectDirective = ($repo, $rootscope, $auth, $location, $navUrls, $confirm, lightboxService, tgLoader) -> +DeleteProjectDirective = ($repo, $rootscope, $auth, $location, $navUrls, $confirm, lightboxService, tgLoader, projectsService) -> link = ($scope, $el, $attrs) -> projectToDelete = null $scope.$on "deletelightbox:new", (ctx, project)-> @@ -171,6 +171,7 @@ DeleteProjectDirective = ($repo, $rootscope, $auth, $location, $navUrls, $confir $rootscope.$broadcast("projects:reload") $location.path($navUrls.resolve("home")) $confirm.notify("success") + projectsService.fetchProjects() # FIXME: error handling? promise.then null, -> @@ -188,4 +189,4 @@ DeleteProjectDirective = ($repo, $rootscope, $auth, $location, $navUrls, $confir return {link:link} module.directive("tgLbDeleteProject", ["$tgRepo", "$rootScope", "$tgAuth", "$tgLocation", "$tgNavUrls", - "$tgConfirm", "lightboxService", "tgLoader", DeleteProjectDirective]) + "$tgConfirm", "lightboxService", "tgLoader", "tgProjects", DeleteProjectDirective]) From ae7cf6b663901daf442fc228ba7a1918dff57bb7 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 27 Apr 2015 08:31:25 +0200 Subject: [PATCH 065/366] Fixing task status button --- app/coffee/modules/tasks/detail.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/app/coffee/modules/tasks/detail.coffee b/app/coffee/modules/tasks/detail.coffee index bd25d2f2..f769f852 100644 --- a/app/coffee/modules/tasks/detail.coffee +++ b/app/coffee/modules/tasks/detail.coffee @@ -169,7 +169,6 @@ TaskStatusDisplayDirective = ($template, $compile) -> }) html = $compile(html)($scope) - $el.html(html) $scope.$watch $attrs.ngModel, (task) -> From eb4300660237e35ff6884796c87a23f1ff2c3d2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 27 Apr 2015 08:47:28 +0200 Subject: [PATCH 066/366] Home projects list styles --- app/modules/home/home-project-list.jade | 13 +++---- .../home/styles/home-project-list.scss | 37 ++++++++++++++++++- app/modules/home/styles/home.scss | 2 +- app/modules/projects/listing/listing.jade | 5 ++- .../projects/listing/styles/listing.scss | 11 ++++-- app/styles/dependencies/helpers.scss | 2 +- 6 files changed, 54 insertions(+), 16 deletions(-) diff --git a/app/modules/home/home-project-list.jade b/app/modules/home/home-project-list.jade index 7617033d..79500efb 100644 --- a/app/modules/home/home-project-list.jade +++ b/app/modules/home/home-project-list.jade @@ -1,10 +1,7 @@ ul.home-project-list li.home-project-list-single(tg-bind-scope, ng-repeat="project in vm.projects.all") - div.home-project-list-single-left - div.home-project-list-single-title - h2 - a(href="#", ng-bind="::project.name", tg-nav="project:project=project.slug") - span {{project.is_private}} - p {{ ::project.description }} - - div.home-project-list-single-right + h2.home-project-list-single-title + a(href="#", ng-bind="::project.name", tg-nav="project:project=project.slug") + span {{project.is_private}} + p {{ ::project.description | limitTo:150 }} + span(ng-if="::project.description.length > 150") ... diff --git a/app/modules/home/styles/home-project-list.scss b/app/modules/home/styles/home-project-list.scss index ab01f613..6b8fc7c5 100644 --- a/app/modules/home/styles/home-project-list.scss +++ b/app/modules/home/styles/home-project-list.scss @@ -1,3 +1,38 @@ .home-project-list { - content: 'WIP'; + li { + border-right: 5px solid $whitish; + cursor: pointer; + margin-bottom: .5rem; + padding: .5rem; + padding-right: 1rem; + text-overflow: ellipsis; + transition: border-color .3s linear; + &:hover { + border-color: $fresh-taiga; + p { + color: $gray; + } + } + } + h2 { + @extend %text; + color: $gray; + font-size: 1.5rem; + line-height: 1.3; + margin-bottom: 0; + text-transform: none; + a { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + p { + @extend %text; + @extend %small; + color: $gray-light; + margin: 0; + transition: color .3s linear; + } } diff --git a/app/modules/home/styles/home.scss b/app/modules/home/styles/home.scss index 72e932c9..0a77715e 100644 --- a/app/modules/home/styles/home.scss +++ b/app/modules/home/styles/home.scss @@ -3,7 +3,7 @@ padding-top: 2rem; .duty-summary { flex: 1; - margin-right: .5rem; + margin-right: 2rem; } .project-list { width: 250px; diff --git a/app/modules/projects/listing/listing.jade b/app/modules/projects/listing/listing.jade index 7cd45e87..46c1f9f3 100644 --- a/app/modules/projects/listing/listing.jade +++ b/app/modules/projects/listing/listing.jade @@ -6,7 +6,7 @@ div.project-list-wrapper.centered a.button-blackish.import-project-button(href="", title="{{'PROJECT.NAVIGATION.TITLE_ACTION_IMPORT' | translate}}") span.icon.icon-upload input.import-file.hidden(type="file") - + section.project-list-section div.project-list ul.js-sortable @@ -16,7 +16,8 @@ div.project-list-wrapper.centered h1 a(href="#", ng-bind="::project.name", tg-nav="project:project=project.slug") span {{project.is_private}} - p {{ ::project.description }} + p {{ ::project.description | limitTo:300 }} + span(ng-if="::project.description.length > 300") ... div.project-list-single-tags.tags-container(ng-if="project.tags") div.tags-block(tg-colorize-tags="project.tags", tg-colorize-tags-type="backlog") diff --git a/app/modules/projects/listing/styles/listing.scss b/app/modules/projects/listing/styles/listing.scss index b165b095..0670f52d 100644 --- a/app/modules/projects/listing/styles/listing.scss +++ b/app/modules/projects/listing/styles/listing.scss @@ -2,7 +2,7 @@ border-bottom: 1px solid $whitish; display: flex; justify-content: center; - padding: .8rem 1rem; + padding: .5rem; position: relative; } @@ -18,12 +18,16 @@ h1 { @extend %text; @extend %larger; + color: $gray; margin-bottom: 0; text-transform: none; } p { - color: $gray-light; - max-width: 70%; + @extend %text; + @extend %small; + color: $gray; + margin-bottom: 0; + max-width: 95%; } .project-list-single-tags { align-content: flex-end; @@ -31,6 +35,7 @@ flex: 3; flex-wrap: wrap; justify-content: flex-start; + margin-top: .5rem; } .tag { align-self: flex-end; diff --git a/app/styles/dependencies/helpers.scss b/app/styles/dependencies/helpers.scss index 24ed564b..6c71c3c9 100644 --- a/app/styles/dependencies/helpers.scss +++ b/app/styles/dependencies/helpers.scss @@ -1,6 +1,6 @@ // __Font Sizes__ // %xsmall {font-size: .5rem;} -%small {font-size: .8rem;} +%small {font-size: .9rem;} %medium {font-size: 1rem;} %large {font-size: 1.2rem;} %larger {font-size: 1.6rem;} From 88c1c584ade4d784fd49803d9572bba44c0af322 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 27 Apr 2015 09:11:20 +0200 Subject: [PATCH 067/366] Refactoring home page --- app/coffee/app.coffee | 4 +-- app/modules/home/home-directive.coffee | 9 +++++ app/modules/home/home-page.controller.coffee | 33 +++++++++++++++++++ app/modules/home/home-page.jade | 1 + app/{partials => modules}/home/home.jade | 5 ++- .../list.directive.coffee} | 2 +- .../list.jade} | 0 .../styles/list.scss} | 0 .../{projects.jade => projects-page.jade} | 0 9 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 app/modules/home/home-directive.coffee create mode 100644 app/modules/home/home-page.controller.coffee create mode 100644 app/modules/home/home-page.jade rename app/{partials => modules}/home/home.jade (97%) rename app/modules/home/{home-project-list.directive.coffee => projects/list.directive.coffee} (89%) rename app/modules/home/{home-project-list.jade => projects/list.jade} (100%) rename app/modules/home/{styles/home-project-list.scss => projects/styles/list.scss} (100%) rename app/modules/projects/{projects.jade => projects-page.jade} (100%) diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 753ae33a..3c047ae7 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -39,10 +39,10 @@ taiga.sessionId = taiga.generateUniqueSessionIdentifier() configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEventsProvider, tgLoaderProvider, $compileProvider, $translateProvider) -> $routeProvider.when("/", - {templateUrl: "home/home.html"}) + {templateUrl: "home/home-page.html"}) $routeProvider.when("/projects/", - {templateUrl: "projects/projects.html", resolve: {loader: tgLoaderProvider.add()}}) + {templateUrl: "projects/projects-page.html", resolve: {loader: tgLoaderProvider.add()}}) $routeProvider.when("/project/:pslug/", {templateUrl: "project/project.html"}) diff --git a/app/modules/home/home-directive.coffee b/app/modules/home/home-directive.coffee new file mode 100644 index 00000000..c5218db5 --- /dev/null +++ b/app/modules/home/home-directive.coffee @@ -0,0 +1,9 @@ +HomeDirective = -> + directive = { + templateUrl: "home/home.html" + scope: {} + } + + return directive + +angular.module("taigaProjects").directive("tgHome", HomeDirective) diff --git a/app/modules/home/home-page.controller.coffee b/app/modules/home/home-page.controller.coffee new file mode 100644 index 00000000..eddc1b65 --- /dev/null +++ b/app/modules/home/home-page.controller.coffee @@ -0,0 +1,33 @@ +class ProjectsPageController extends taiga.Controller + @.$inject = [ + "$scope", + "$q", + "$tgResources", + "$rootScope", + "$tgNavUrls", + "$tgAuth", + "$tgLocation", + "$appTitle", + "$projectUrl", + "$tgConfig", + "tgLoader", + "tgProjects", + "$translate" + + ] + + constructor: (@scope, @q, @rs, @rootscope, @navUrls, @auth, @location, + @appTitle, @projectUrl, @config, tgLoader, @projects, @translate) -> + @appTitle.set(@translate.instant("PROJECT.WELCOME")) + + if !@auth.isAuthenticated() + @location.path(@navUrls.resolve("login")) + + #Projects + promise = @projects.fetchProjects() + + # Finally + promise.finally tgLoader.pageLoaded + + +angular.module("taigaHome").controller("HomePage", ProjectsPageController) diff --git a/app/modules/home/home-page.jade b/app/modules/home/home-page.jade new file mode 100644 index 00000000..7bfe3e17 --- /dev/null +++ b/app/modules/home/home-page.jade @@ -0,0 +1 @@ +div(ng-controller="HomePage", tg-home) diff --git a/app/partials/home/home.jade b/app/modules/home/home.jade similarity index 97% rename from app/partials/home/home.jade rename to app/modules/home/home.jade index 74c9ab2c..de24aac1 100644 --- a/app/partials/home/home.jade +++ b/app/modules/home/home.jade @@ -1,7 +1,7 @@ doctype html -include ../includes/components/beta -div.home-wrapper.centered +include ../../partials/includes/components/beta +div.home-wrapper.centered(ng-controller="HomePage") div.duty-summary // TODO Hide if ASSIGNED TO ==== false div.title-bar.working-on-title Working on @@ -66,4 +66,3 @@ div.home-wrapper.centered div.duty-project Teletransportation hubs a.button-gray.see-more(href="#", title="See more Watching US") See more aside.project-list(tg-home-project-list) - diff --git a/app/modules/home/home-project-list.directive.coffee b/app/modules/home/projects/list.directive.coffee similarity index 89% rename from app/modules/home/home-project-list.directive.coffee rename to app/modules/home/projects/list.directive.coffee index ae0048b6..c5a81d84 100644 --- a/app/modules/home/home-project-list.directive.coffee +++ b/app/modules/home/projects/list.directive.coffee @@ -8,7 +8,7 @@ HomeProjectListDirective = (projectsService) -> projectsService.newProject() directive = { - templateUrl: "home/home-project-list.html" + templateUrl: "home/projects/list.html" scope: {} link: link } diff --git a/app/modules/home/home-project-list.jade b/app/modules/home/projects/list.jade similarity index 100% rename from app/modules/home/home-project-list.jade rename to app/modules/home/projects/list.jade diff --git a/app/modules/home/styles/home-project-list.scss b/app/modules/home/projects/styles/list.scss similarity index 100% rename from app/modules/home/styles/home-project-list.scss rename to app/modules/home/projects/styles/list.scss diff --git a/app/modules/projects/projects.jade b/app/modules/projects/projects-page.jade similarity index 100% rename from app/modules/projects/projects.jade rename to app/modules/projects/projects-page.jade From 37a0e59fd683b435be6dcd90f0455923112ebd7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 27 Apr 2015 09:55:51 +0200 Subject: [PATCH 068/366] Project list behaviour --- app/locales/locale-en.json | 1 + app/modules/home/projects/list.jade | 13 ++++++++----- app/modules/home/projects/styles/list.scss | 18 ++++++++++++++++-- app/modules/home/styles/home.scss | 4 ++++ app/modules/projects/listing/listing.jade | 2 +- app/svg/lock.svg | 7 +++++++ 6 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 app/svg/lock.svg diff --git a/app/locales/locale-en.json b/app/locales/locale-en.json index 7209d745..19f7268e 100644 --- a/app/locales/locale-en.json +++ b/app/locales/locale-en.json @@ -524,6 +524,7 @@ "WELCOME": "Welcome", "SECTION_PROJECTS": "Projects", "HELP": "TODO. You can reorder your projects in your favorite way by drag&drop, Taiga will remember your order for every project list.\n Remember that the first ten projects will be shown in your rapid access menu in the top bar dropdown menu", + "PRIVATE": "Private project", "STATS": { "PROJECT": "project
points", "DEFINED": "defined
points", diff --git a/app/modules/home/projects/list.jade b/app/modules/home/projects/list.jade index 79500efb..a2d99f10 100644 --- a/app/modules/home/projects/list.jade +++ b/app/modules/home/projects/list.jade @@ -1,7 +1,10 @@ ul.home-project-list li.home-project-list-single(tg-bind-scope, ng-repeat="project in vm.projects.all") - h2.home-project-list-single-title - a(href="#", ng-bind="::project.name", tg-nav="project:project=project.slug") - span {{project.is_private}} - p {{ ::project.description | limitTo:150 }} - span(ng-if="::project.description.length > 150") ... + a(href="#", tg-nav="project:project=project.slug") + h2.home-project-list-single-title + span.project-name(ng-bind="::project.name", title="{{ ::project.name }}") + span.private(ng-if="project.is_private", title="{{'PROJECT.PRIVATE' | translate}}") + include ../../../svg/lock.svg + p {{ ::project.description | limitTo:150 }} + span(ng-if="::project.description.length > 150") ... +a.see-more-projects-btn.button-gray(href="#", tg-nav="projects", title="{{'PROJECT.NAVIGATION.SEE_MORE_PROJECTS' | translate}}", translate="PROJECT.NAVIGATION.SEE_MORE_PROJECTS") diff --git a/app/modules/home/projects/styles/list.scss b/app/modules/home/projects/styles/list.scss index 6b8fc7c5..64d0c401 100644 --- a/app/modules/home/projects/styles/list.scss +++ b/app/modules/home/projects/styles/list.scss @@ -12,6 +12,9 @@ p { color: $gray; } + .private path { + fill: $gray; + } } } h2 { @@ -21,12 +24,23 @@ line-height: 1.3; margin-bottom: 0; text-transform: none; - a { - display: block; + .project-name { + display: inline-block; + max-width: 90%; overflow: hidden; text-overflow: ellipsis; + vertical-align: middle; white-space: nowrap; } + .private { + display: inline-block; + margin-left: .3rem; + width: .5rem; + path { + fill: $gray-light; + transition: fill .3s linear; + } + } } p { @extend %text; diff --git a/app/modules/home/styles/home.scss b/app/modules/home/styles/home.scss index 0a77715e..a6aad27b 100644 --- a/app/modules/home/styles/home.scss +++ b/app/modules/home/styles/home.scss @@ -8,6 +8,10 @@ .project-list { width: 250px; } + .see-more-projects-btn { + display: block; + margin-top: 2rem; + } .title-bar { @extend %title; @extend %larger; diff --git a/app/modules/projects/listing/listing.jade b/app/modules/projects/listing/listing.jade index 46c1f9f3..513fd18d 100644 --- a/app/modules/projects/listing/listing.jade +++ b/app/modules/projects/listing/listing.jade @@ -14,7 +14,7 @@ div.project-list-wrapper.centered div.project-list-single-left div.project-list-single-title h1 - a(href="#", ng-bind="::project.name", tg-nav="project:project=project.slug") + a(href="#", ng-bind="::project.name", tg-nav="project:project=project.slug", title="{{ ::project.name }}") span {{project.is_private}} p {{ ::project.description | limitTo:300 }} span(ng-if="::project.description.length > 300") ... diff --git a/app/svg/lock.svg b/app/svg/lock.svg new file mode 100644 index 00000000..b2ee1e5a --- /dev/null +++ b/app/svg/lock.svg @@ -0,0 +1,7 @@ + + + + + + + From 3f34ed14121ea43f1dd482966df205fe0a280849 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Fri, 24 Apr 2015 10:59:09 +0200 Subject: [PATCH 069/366] defineImmutableProperty --- app/coffee/utils.coffee | 10 ++++++++++ .../dropdown-project-list.directive.coffee | 4 ++-- app/modules/projects/listing/listing.directive.coffee | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/coffee/utils.coffee b/app/coffee/utils.coffee index 7a0ed706..4205b612 100644 --- a/app/coffee/utils.coffee +++ b/app/coffee/utils.coffee @@ -158,6 +158,15 @@ replaceTags = (str, tags, replace) -> return str +defineImmutableProperty = (obj, name, variable) => + Object.defineProperty obj, name, { + get: () => + if _.isFunction(variable) + return variable.call(obj) + else + return variable + } + taiga = @.taiga taiga.nl2br = nl2br taiga.bindMethods = bindMethods @@ -179,3 +188,4 @@ taiga.startswith = startswith taiga.sizeFormat = sizeFormat taiga.stripTags = stripTags taiga.replaceTags = replaceTags +taiga.defineImmutableProperty = defineImmutableProperty diff --git a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee index b63656ee..1d596710 100644 --- a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee +++ b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee @@ -1,10 +1,10 @@ DropdownProjectListDirective = (projectsService) -> link = (scope, el, attrs, ctrl) -> scope.vm = {} - scope.vm.projects = projectsService.projects + taiga.defineImmutableProperty(scope.vm, "projects", projectsService.projects) scope.vm.newProject = -> projectsService.newProject() - + directive = { templateUrl: "navigation-bar/dropdown-project-list/dropdown-project-list.html" scope: {} diff --git a/app/modules/projects/listing/listing.directive.coffee b/app/modules/projects/listing/listing.directive.coffee index 506af5cd..4879088e 100644 --- a/app/modules/projects/listing/listing.directive.coffee +++ b/app/modules/projects/listing/listing.directive.coffee @@ -25,7 +25,7 @@ ProjectsListingDirective = (projectsService) -> projectsService.bulkUpdateProjectsOrder(sortData) - scope.vm.projects = projectsService.projects + taiga.defineImmutableProperty(scope.vm, "projects", projectsService.projects) scope.vm.newProject = -> projectsService.newProject() From c9ee2dbf84328a61371c5110ffd7d232378df800 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Mon, 27 Apr 2015 10:09:27 +0200 Subject: [PATCH 070/366] tg-repeat --- app/coffee/app.coffee | 3 +- app/coffee/utils.coffee | 15 +- app/js/tg-repeat.js | 310 ++++++++++++++++++ .../dropdown-project-list.directive.coffee | 4 +- .../dropdown-project-list.jade | 2 +- .../navigation-bar.directive.coffee | 3 +- .../navigation-bar/navigation-bar.jade | 2 +- .../projects/listing/listing.directive.coffee | 5 +- app/modules/projects/listing/listing.jade | 6 +- app/modules/projects/projects.service.coffee | 8 +- bower.json | 3 +- gulpfile.js | 2 + 12 files changed, 344 insertions(+), 19 deletions(-) create mode 100644 app/js/tg-repeat.js diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 3c047ae7..2c5275f9 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -350,7 +350,8 @@ modules = [ "ngRoute", "ngAnimate", "pascalprecht.translate", - "infinite-scroll" + "infinite-scroll", + "tgRepeat" ].concat(_.map(@.taigaContribPlugins, (plugin) -> plugin.module)) # Main module definition diff --git a/app/coffee/utils.coffee b/app/coffee/utils.coffee index 4205b612..58874d4f 100644 --- a/app/coffee/utils.coffee +++ b/app/coffee/utils.coffee @@ -158,13 +158,18 @@ replaceTags = (str, tags, replace) -> return str -defineImmutableProperty = (obj, name, variable) => +defineImmutableProperty = (obj, name, fn) => Object.defineProperty obj, name, { get: () => - if _.isFunction(variable) - return variable.call(obj) - else - return variable + if !_.isFunction(fn) + throw "defineImmutableProperty third param must be a function" + + fn_result = fn() + if fn_result && _.isObject(fn_result) + if fn_result.size == undefined + throw "defineImmutableProperty must return immutable data" + + return fn_result } taiga = @.taiga diff --git a/app/js/tg-repeat.js b/app/js/tg-repeat.js new file mode 100644 index 00000000..8b6528a8 --- /dev/null +++ b/app/js/tg-repeat.js @@ -0,0 +1,310 @@ +/* + --replace + minError -> angular.$$minErr + isString -> angular.isString + isArray -> angular.isArray + var expression = $attr.ngRepeat; -> var expression = $attr.tgRepeat; + forEach(nextBlockOrder, function(block) { -> nextBlockOrder.forEach(function(block) { + $scope.$watchCollection(rhs, function ngRepeatAction(collection) { + -> + $scope.$watch(rhs, function ngRepeatAction(immutable_collection) { + var collection = [] + + if (immutable_collection.toJS) { + collection = immutable_collection.toJS(); + } + --copy from angular + copy angular hashKey + copy angular createMap + copy angular isArrayLike + copy angular isWindow + copy angular NODE_TYPE_ELEMENT + copy angular nextUid + copy angular getBlockNodes + --add + jqLite = $ + var uid = 0; +*/ + +(function() { + var NODE_TYPE_ELEMENT = 1; + var uid = 0; + + function nextUid() { + return ++uid; + } + + function hashKey(obj, nextUidFn) { + var key = obj && obj.$$hashKey; + + if (key) { + if (typeof key === 'function') { + key = obj.$$hashKey(); + } + return key; + } + + var objType = typeof obj; + if (objType == 'function' || (objType == 'object' && obj !== null)) { + key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)(); + } else { + key = objType + ':' + obj; + } + + return key; + } + + function createMap() { + return Object.create(null); + } + + function isArrayLike(obj) { + if (obj == null || isWindow(obj)) { + return false; + } + + var length = obj.length; + + if (obj.nodeType === NODE_TYPE_ELEMENT && length) { + return true; + } + + return angular.isString(obj) || angular.isArray(obj) || length === 0 || + typeof length === 'number' && length > 0 && (length - 1) in obj; + } + + function isWindow(obj) { + return obj && obj.window === obj; + } + + function isString(value) {return typeof value === 'string';} + + function getBlockNodes(nodes) { + // TODO(perf): just check if all items in `nodes` are siblings and if they are return the original + // collection, otherwise update the original collection. + var node = nodes[0]; + var endNode = nodes[nodes.length - 1]; + var blockNodes = [node]; + + do { + node = node.nextSibling; + if (!node) break; + blockNodes.push(node); + } while (node !== endNode); + + return jqLite(blockNodes); + } + + var isArray = Array.isArray; + + var jqLite = $; + + var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { + var NG_REMOVED = '$$NG_REMOVED'; + var ngRepeatMinErr = angular.$$minErr('ngRepeat'); + var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) { + // TODO(perf): generate setters to shave off ~40ms or 1-1.5% + scope[valueIdentifier] = value; + if (keyIdentifier) scope[keyIdentifier] = key; + scope.$index = index; + scope.$first = (index === 0); + scope.$last = (index === (arrayLength - 1)); + scope.$middle = !(scope.$first || scope.$last); + // jshint bitwise: false + scope.$odd = !(scope.$even = (index&1) === 0); + // jshint bitwise: true + }; + var getBlockStart = function(block) { + return block.clone[0]; + }; + var getBlockEnd = function(block) { + return block.clone[block.clone.length - 1]; + }; + return { + restrict: 'A', + multiElement: true, + transclude: 'element', + priority: 1000, + terminal: true, + $$tlb: true, + compile: function ngRepeatCompile($element, $attr) { + var expression = $attr.tgRepeat; + var ngRepeatEndComment = document.createComment(' end ngRepeat: ' + expression + ' '); + var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); + if (!match) { + throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", + expression); + } + var lhs = match[1]; + var rhs = match[2]; + var aliasAs = match[3]; + var trackByExp = match[4]; + match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/); + if (!match) { + throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", + lhs); + } + var valueIdentifier = match[3] || match[1]; + var keyIdentifier = match[2]; + if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) || + /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) { + throw ngRepeatMinErr('badident', "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.", + aliasAs); + } + var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn; + var hashFnLocals = {$id: hashKey}; + if (trackByExp) { + trackByExpGetter = $parse(trackByExp); + } else { + trackByIdArrayFn = function(key, value) { + return hashKey(value); + }; + trackByIdObjFn = function(key) { + return key; + }; + } + return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) { + if (trackByExpGetter) { + trackByIdExpFn = function(key, value, index) { + // assign key, value, and $index to the locals so that they can be used in hash functions + if (keyIdentifier) hashFnLocals[keyIdentifier] = key; + hashFnLocals[valueIdentifier] = value; + hashFnLocals.$index = index; + return trackByExpGetter($scope, hashFnLocals); + }; + } + // Store a list of elements from previous run. This is a hash where key is the item from the + // iterator, and the value is objects with following properties. + // - scope: bound scope + // - element: previous element. + // - index: position + // + // We are using no-proto object so that we don't need to guard against inherited props via + // hasOwnProperty. + var lastBlockMap = createMap(); + $scope.$watch(rhs, function ngRepeatAction(immutable_collection) { + var collection = [] + + if (immutable_collection && immutable_collection.toJS) { + collection = immutable_collection.toJS(); + } + + var index, length, + previousNode = $element[0], // node that cloned nodes should be inserted after + // initialized to the comment node anchor + nextNode, + // Same as lastBlockMap but it has the current state. It will become the + // lastBlockMap on the next iteration. + nextBlockMap = createMap(), + collectionLength, + key, value, // key/value of iteration + trackById, + trackByIdFn, + collectionKeys, + block, // last object information {scope, element, id} + nextBlockOrder, + elementsToRemove; + if (aliasAs) { + $scope[aliasAs] = collection; + } + if (isArrayLike(collection)) { + collectionKeys = collection; + trackByIdFn = trackByIdExpFn || trackByIdArrayFn; + } else { + trackByIdFn = trackByIdExpFn || trackByIdObjFn; + // if object, extract keys, in enumeration order, unsorted + collectionKeys = []; + for (var itemKey in collection) { + if (collection.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') { + collectionKeys.push(itemKey); + } + } + } + collectionLength = collectionKeys.length; + nextBlockOrder = new Array(collectionLength); + // locate existing items + for (index = 0; index < collectionLength; index++) { + key = (collection === collectionKeys) ? index : collectionKeys[index]; + value = collection[key]; + trackById = trackByIdFn(key, value, index); + if (lastBlockMap[trackById]) { + // found previously seen block + block = lastBlockMap[trackById]; + delete lastBlockMap[trackById]; + nextBlockMap[trackById] = block; + nextBlockOrder[index] = block; + } else if (nextBlockMap[trackById]) { + // if collision detected. restore lastBlockMap and throw an error + nextBlockOrder.forEach(function(block) { + if (block && block.scope) lastBlockMap[block.id] = block; + }); + throw ngRepeatMinErr('dupes', + "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}", + expression, trackById, value); + } else { + // new never before seen block + nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined}; + nextBlockMap[trackById] = true; + } + } + // remove leftover items + for (var blockKey in lastBlockMap) { + block = lastBlockMap[blockKey]; + elementsToRemove = getBlockNodes(block.clone); + $animate.leave(elementsToRemove); + if (elementsToRemove[0].parentNode) { + // if the element was not removed yet because of pending animation, mark it as deleted + // so that we can ignore it later + for (index = 0, length = elementsToRemove.length; index < length; index++) { + elementsToRemove[index][NG_REMOVED] = true; + } + } + block.scope.$destroy(); + } + // we are not using forEach for perf reasons (trying to avoid #call) + for (index = 0; index < collectionLength; index++) { + key = (collection === collectionKeys) ? index : collectionKeys[index]; + value = collection[key]; + block = nextBlockOrder[index]; + if (block.scope) { + // if we have already seen this object, then we need to reuse the + // associated scope/element + nextNode = previousNode; + // skip nodes that are already pending removal via leave animation + do { + nextNode = nextNode.nextSibling; + } while (nextNode && nextNode[NG_REMOVED]); + if (getBlockStart(block) != nextNode) { + // existing item which got moved + $animate.move(getBlockNodes(block.clone), null, jqLite(previousNode)); + } + previousNode = getBlockEnd(block); + updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); + } else { + // new item which we don't know about + $transclude(function ngRepeatTransclude(clone, scope) { + block.scope = scope; + // http://jsperf.com/clone-vs-createcomment + var endNode = ngRepeatEndComment.cloneNode(false); + clone[clone.length++] = endNode; + // TODO(perf): support naked previousNode in `enter` to avoid creation of jqLite wrapper? + $animate.enter(clone, null, jqLite(previousNode)); + previousNode = endNode; + // Note: We only need the first/last node of the cloned nodes. + // However, we need to keep the reference to the jqlite wrapper as it might be changed later + // by a directive with templateUrl when its template arrives. + block.clone = clone; + nextBlockMap[block.id] = block; + updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); + }); + } + } + lastBlockMap = nextBlockMap; + }); + }; + } + }; + }]; + + angular.module("tgRepeat", []).directive("tgRepeat", ngRepeatDirective); +})(); diff --git a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee index 1d596710..cb5cceb9 100644 --- a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee +++ b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee @@ -1,7 +1,9 @@ DropdownProjectListDirective = (projectsService) -> link = (scope, el, attrs, ctrl) -> scope.vm = {} - taiga.defineImmutableProperty(scope.vm, "projects", projectsService.projects) + + taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.projects.get("recents")) + scope.vm.newProject = -> projectsService.newProject() diff --git a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade index d9548202..08f376b3 100644 --- a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade +++ b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade @@ -4,7 +4,7 @@ a(href="", title="Projects", tg-nav="projects") div.navbar-dropdown.dropdown-project-list ul a(href="#", - ng-repeat="project in vm.projects.recents", + tg-repeat="project in vm.projects", ng-bind="::project.name" tg-nav="project:project=project.slug") diff --git a/app/modules/navigation-bar/navigation-bar.directive.coffee b/app/modules/navigation-bar/navigation-bar.directive.coffee index 44607883..a372521d 100644 --- a/app/modules/navigation-bar/navigation-bar.directive.coffee +++ b/app/modules/navigation-bar/navigation-bar.directive.coffee @@ -1,7 +1,8 @@ NavigationBarDirective = (projectsService) -> link = (scope, el, attrs, ctrl) -> scope.vm = {} - scope.vm.projects = projectsService.projects + + taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.projects.get("recents")) directive = { templateUrl: "navigation-bar/navigation-bar.html" diff --git a/app/modules/navigation-bar/navigation-bar.jade b/app/modules/navigation-bar/navigation-bar.jade index c003c61a..18060a7d 100644 --- a/app/modules/navigation-bar/navigation-bar.jade +++ b/app/modules/navigation-bar/navigation-bar.jade @@ -26,7 +26,7 @@ nav.navbar include ../../svg/dashboard.svg - div.topnav-dropdown-wrapper(ng-show="vm.projects.recents", tg-dropdown-project-list) + div.topnav-dropdown-wrapper(ng-show="vm.projects.size", tg-dropdown-project-list) //div.topnav-dropdown-wrapper(tg-dropdown-organization-list) div.topnav-dropdown-wrapper(tg-dropdown-user) diff --git a/app/modules/projects/listing/listing.directive.coffee b/app/modules/projects/listing/listing.directive.coffee index 4879088e..71a9ff8f 100644 --- a/app/modules/projects/listing/listing.directive.coffee +++ b/app/modules/projects/listing/listing.directive.coffee @@ -16,7 +16,8 @@ ProjectsListingDirective = (projectsService) -> itemEl = ui.item project = itemEl.scope().project index = itemEl.index() - sorted_project_ids = _.map(scope.vm.projects.all, (p) -> p.id) + + sorted_project_ids = _.map(scope.vm.projects.toArray(), (p) -> p.id) sorted_project_ids = _.without(sorted_project_ids, project.id) sorted_project_ids.splice(index, 0, project.id) sortData = [] @@ -25,7 +26,7 @@ ProjectsListingDirective = (projectsService) -> projectsService.bulkUpdateProjectsOrder(sortData) - taiga.defineImmutableProperty(scope.vm, "projects", projectsService.projects) + taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.projects.get("all")) scope.vm.newProject = -> projectsService.newProject() diff --git a/app/modules/projects/listing/listing.jade b/app/modules/projects/listing/listing.jade index 513fd18d..643da715 100644 --- a/app/modules/projects/listing/listing.jade +++ b/app/modules/projects/listing/listing.jade @@ -10,16 +10,16 @@ div.project-list-wrapper.centered section.project-list-section div.project-list ul.js-sortable - li.project-list-single(tg-bind-scope, ng-repeat="project in vm.projects.all") + li.project-list-single(tg-bind-scope, tg-repeat="project in vm.projects") div.project-list-single-left div.project-list-single-title h1 a(href="#", ng-bind="::project.name", tg-nav="project:project=project.slug", title="{{ ::project.name }}") - span {{project.is_private}} + span {{::project.is_private}} p {{ ::project.description | limitTo:300 }} span(ng-if="::project.description.length > 300") ... - div.project-list-single-tags.tags-container(ng-if="project.tags") + div.project-list-single-tags.tags-container(ng-if="::project.tags") div.tags-block(tg-colorize-tags="project.tags", tg-colorize-tags-type="backlog") div.project-list-single-right diff --git a/app/modules/projects/projects.service.coffee b/app/modules/projects/projects.service.coffee index 1bd6e980..3d3ae9fa 100644 --- a/app/modules/projects/projects.service.coffee +++ b/app/modules/projects/projects.service.coffee @@ -2,7 +2,7 @@ class ProjectsService extends taiga.Service @.$inject = ["$q", "$tgResources", "$rootScope", "$projectUrl"] constructor: (@q, @rs, @rootScope, @projectUrl) -> - @.projects = {all: [], recent: []} + @.projects = Immutable.Map() @.inProgress = false @.projectsPromise = null @.fetchProjects() @@ -15,8 +15,10 @@ class ProjectsService extends taiga.Service for project in projects project.url = @projectUrl.get(project) - @.projects.recents = projects.slice(0, 10) - @.projects.all = projects + @.projects = Immutable.fromJS({ + all: projects, + recents: projects.slice(0, 10) + }) return @.projects diff --git a/bower.json b/bower.json index b34f6910..7989c254 100644 --- a/bower.json +++ b/bower.json @@ -78,7 +78,8 @@ "angular-translate-loader-static-files": "~2.6.1", "angular-translate-interpolation-messageformat": "~2.6.1", "ngInfiniteScroll": "1.0.0", - "eventemitter2": "~0.4.14" + "eventemitter2": "~0.4.14", + "immutable": "~3.7.2" }, "resolutions": { "lodash": "~2.4.1", diff --git a/gulpfile.js b/gulpfile.js index dc280afd..b1017fb8 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -144,9 +144,11 @@ paths.libs = [ paths.vendor + "messageformat/locale/*.js", paths.vendor + "ngInfiniteScroll/build/ng-infinite-scroll.js", paths.vendor + "eventemitter2/lib/eventemitter2.js", + paths.vendor + "immutable/dist/immutable.js", paths.app + "js/jquery.ui.git-custom.js", paths.app + "js/jquery-ui.drag-multiple-custom.js", paths.app + "js/jquery.ui.touch-punch.min.js", + paths.app + "js/tg-repeat.js", paths.app + "js/sha1-custom.js" ]; From 7052e88d84e3873e39a487272a7c5ccff5dc801a Mon Sep 17 00:00:00 2001 From: Juanfran Date: Mon, 27 Apr 2015 10:25:06 +0200 Subject: [PATCH 071/366] fix display projects list --- app/modules/navigation-bar/navigation-bar.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/navigation-bar/navigation-bar.scss b/app/modules/navigation-bar/navigation-bar.scss index 667f6a82..19c95bd6 100644 --- a/app/modules/navigation-bar/navigation-bar.scss +++ b/app/modules/navigation-bar/navigation-bar.scss @@ -98,7 +98,7 @@ left: calc(50% - #{$dropdown-width}/2); min-width: $dropdown-width; position: absolute; - top: 2.5rem; + top: 2.4rem; z-index: 99; } } From a69e230126fad47250e70069bdc609e74d5eab77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 27 Apr 2015 10:48:09 +0200 Subject: [PATCH 072/366] Add private projects to project listing --- app/modules/projects/listing/listing.jade | 6 ++++-- .../projects/listing/styles/listing.scss | 20 +++++++++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/app/modules/projects/listing/listing.jade b/app/modules/projects/listing/listing.jade index 643da715..f83bf0a5 100644 --- a/app/modules/projects/listing/listing.jade +++ b/app/modules/projects/listing/listing.jade @@ -14,8 +14,10 @@ div.project-list-wrapper.centered div.project-list-single-left div.project-list-single-title h1 - a(href="#", ng-bind="::project.name", tg-nav="project:project=project.slug", title="{{ ::project.name }}") - span {{::project.is_private}} + a(href="#", tg-nav="project:project=project.slug") + h1.project-name(ng-bind="::project.name", title="{{ ::project.name }}") + span.private(ng-if="project.is_private", title="{{'PROJECT.PRIVATE' | translate}}") + include ../../../svg/lock.svg p {{ ::project.description | limitTo:300 }} span(ng-if="::project.description.length > 300") ... diff --git a/app/modules/projects/listing/styles/listing.scss b/app/modules/projects/listing/styles/listing.scss index 0670f52d..3a2a5d08 100644 --- a/app/modules/projects/listing/styles/listing.scss +++ b/app/modules/projects/listing/styles/listing.scss @@ -17,11 +17,27 @@ flex: 4; h1 { @extend %text; - @extend %larger; - color: $gray; + @extend %large; + display: inline-block; margin-bottom: 0; text-transform: none; } + .project-name { + @extend %text; + @extend %larger; + color: $gray; + vertical-align: middle; + white-space: nowrap; + } + .private { + display: inline-block; + margin-left: .3rem; + width: .5rem; + path { + fill: $gray-light; + transition: fill .3s linear; + } + } p { @extend %text; @extend %small; From 83ab7e2c6993b4478a5fd4ca25d8bbce7d898092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 27 Apr 2015 10:57:09 +0200 Subject: [PATCH 073/366] Import project button in project list title --- app/modules/projects/listing/listing.jade | 6 +++--- app/modules/projects/listing/styles/project-list.scss | 9 +++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/app/modules/projects/listing/listing.jade b/app/modules/projects/listing/listing.jade index f83bf0a5..a5006029 100644 --- a/app/modules/projects/listing/listing.jade +++ b/app/modules/projects/listing/listing.jade @@ -1,9 +1,9 @@ div.project-list-wrapper.centered div.project-list-title h1 My projects - a.create-project-btn.button-green(href="#", ng-click="vm.newProject()", title="{{'PROJECT.NAVIGATION.ACTION_CREATE_PROJECT' | translate}}", translate="PROJECT.NAVIGATION.ACTION_CREATE_PROJECT") - div(tg-import-project-button) - a.button-blackish.import-project-button(href="", title="{{'PROJECT.NAVIGATION.TITLE_ACTION_IMPORT' | translate}}") + div.create-options + a.create-project-btn.button-green(href="#", ng-click="vm.newProject()", title="{{'PROJECT.NAVIGATION.ACTION_CREATE_PROJECT' | translate}}", translate="PROJECT.NAVIGATION.ACTION_CREATE_PROJECT") + a.button-blackish.import-project-button(href="", title="{{'PROJECT.NAVIGATION.TITLE_ACTION_IMPORT' | translate}}", tg-import-project-button) span.icon.icon-upload input.import-file.hidden(type="file") diff --git a/app/modules/projects/listing/styles/project-list.scss b/app/modules/projects/listing/styles/project-list.scss index 4e031e8b..9287be26 100644 --- a/app/modules/projects/listing/styles/project-list.scss +++ b/app/modules/projects/listing/styles/project-list.scss @@ -11,6 +11,15 @@ margin: 0; } } + .import-project-button { + padding: .33rem .5rem; + &:hover { + background: $grayer; + } + .icon-upload { + margin: 0; + } + } .project-list-section { display: flex; } From c2c7fdf67d0ca7b74343a5efa8b0ccbf8ea7c529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 27 Apr 2015 11:09:07 +0200 Subject: [PATCH 074/366] Dropdown project list import button --- .../dropdown-project-list.jade | 14 +++++++------- app/modules/navigation-bar/navigation-bar.scss | 18 +++++++++++++----- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade index 08f376b3..375a9c1d 100644 --- a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade +++ b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade @@ -14,13 +14,13 @@ div.navbar-dropdown.dropdown-project-list title="{{'PROJECT.NAVIGATION.SEE_MORE_PROJECTS' | translate}}", translate="PROJECT.NAVIGATION.SEE_MORE_PROJECTS") - a.create-project-btn.button-green( - href="#", - ng-click="vm.newProject()", - title="{{'PROJECT.NAVIGATION.ACTION_CREATE_PROJECT' | translate}}", - translate="PROJECT.NAVIGATION.ACTION_CREATE_PROJECT") + div.create-options + a.create-project-btn.button-green( + href="#", + ng-click="vm.newProject()", + title="{{'PROJECT.NAVIGATION.ACTION_CREATE_PROJECT' | translate}}", + translate="PROJECT.NAVIGATION.ACTION_CREATE_PROJECT") - div(tg-import-project-button) - a.button-blackish.import-project-button(href="", title="{{'PROJECT.NAVIGATION.TITLE_ACTION_IMPORT' | translate}}") + a.button-blackish.import-project-button(href="", title="{{'PROJECT.NAVIGATION.TITLE_ACTION_IMPORT' | translate}}", tg-import-project-button) span.icon.icon-upload input.import-file.hidden(type="file") diff --git a/app/modules/navigation-bar/navigation-bar.scss b/app/modules/navigation-bar/navigation-bar.scss index 19c95bd6..0338b765 100644 --- a/app/modules/navigation-bar/navigation-bar.scss +++ b/app/modules/navigation-bar/navigation-bar.scss @@ -43,11 +43,6 @@ a { padding: .5rem 2rem; } - .navbar-dropdown { - a { - padding: .8rem .5rem; - } - } } a { color: $white; @@ -124,6 +119,7 @@ a { color: $gray-light; display: block; + padding: .8rem .5rem; &:hover { background: rgba($white, .1); color: $fresh-taiga; @@ -141,5 +137,17 @@ &.see-more-projects-btn { margin-bottom: .3rem; } + &.create-project-btn { + flex: 1; + } + &.import-project-button { + padding-left: .75rem; + padding-right: .75rem; + } + + } + .create-options { + display: flex; + flex-direction: row; } } From bd8bdd3a1188c7e31423f131b29e046bf41b218b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 27 Apr 2015 11:27:26 +0200 Subject: [PATCH 075/366] Empty project list --- app/modules/home/projects/list.jade | 14 ++++++++++++-- app/modules/home/projects/styles/list.scss | 19 +++++++++++++++++++ app/svg/empty-project.svg | 7 +++++++ 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 app/svg/empty-project.svg diff --git a/app/modules/home/projects/list.jade b/app/modules/home/projects/list.jade index a2d99f10..9c69d948 100644 --- a/app/modules/home/projects/list.jade +++ b/app/modules/home/projects/list.jade @@ -1,4 +1,4 @@ -ul.home-project-list +ul.home-project-list(ng-show="vm.projects.length") li.home-project-list-single(tg-bind-scope, ng-repeat="project in vm.projects.all") a(href="#", tg-nav="project:project=project.slug") h2.home-project-list-single-title @@ -7,4 +7,14 @@ ul.home-project-list include ../../../svg/lock.svg p {{ ::project.description | limitTo:150 }} span(ng-if="::project.description.length > 150") ... -a.see-more-projects-btn.button-gray(href="#", tg-nav="projects", title="{{'PROJECT.NAVIGATION.SEE_MORE_PROJECTS' | translate}}", translate="PROJECT.NAVIGATION.SEE_MORE_PROJECTS") +a.see-more-projects-btn.button-gray(ng-show="vm.projects.length", href="#", tg-nav="projects", title="{{'PROJECT.NAVIGATION.SEE_MORE_PROJECTS' | translate}}", translate="PROJECT.NAVIGATION.SEE_MORE_PROJECTS") +section.projects-empty(ng-hide="vm.projects.length") + include ../../../svg/empty-project.svg + p You don't have any projects yet + a.create-project-btn.button-green( + href="#", + ng-click="vm.newProject()", + title="{{'PROJECT.NAVIGATION.ACTION_CREATE_PROJECT' | translate}}", + translate="PROJECT.NAVIGATION.ACTION_CREATE_PROJECT") + + diff --git a/app/modules/home/projects/styles/list.scss b/app/modules/home/projects/styles/list.scss index 64d0c401..50909a97 100644 --- a/app/modules/home/projects/styles/list.scss +++ b/app/modules/home/projects/styles/list.scss @@ -50,3 +50,22 @@ transition: color .3s linear; } } + +.projects-empty { + text-align: center; + svg { + height: 100px; + margin: 1rem auto; + text-align: center; + width: 100%; + path { + fill: $whitish; + } + } + p { + @extend %small; + } + .create-project-btn { + display: block; + } +} diff --git a/app/svg/empty-project.svg b/app/svg/empty-project.svg new file mode 100644 index 00000000..fdc92148 --- /dev/null +++ b/app/svg/empty-project.svg @@ -0,0 +1,7 @@ + + + + + + + From 8705b8ffc4bf952307d198f2e256c1c2fedd624a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 27 Apr 2015 12:02:56 +0200 Subject: [PATCH 076/366] Fix project listing --- app/modules/home/projects/list.directive.coffee | 2 +- app/modules/home/projects/list.jade | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/modules/home/projects/list.directive.coffee b/app/modules/home/projects/list.directive.coffee index c5a81d84..d22975e2 100644 --- a/app/modules/home/projects/list.directive.coffee +++ b/app/modules/home/projects/list.directive.coffee @@ -2,7 +2,7 @@ HomeProjectListDirective = (projectsService) -> link = (scope, el, attrs, ctrl) -> scope.vm = {} - scope.vm.projects = projectsService.projects + taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.projects.get("recents")) scope.vm.newProject = -> projectsService.newProject() diff --git a/app/modules/home/projects/list.jade b/app/modules/home/projects/list.jade index 9c69d948..e76b6ed3 100644 --- a/app/modules/home/projects/list.jade +++ b/app/modules/home/projects/list.jade @@ -1,5 +1,5 @@ ul.home-project-list(ng-show="vm.projects.length") - li.home-project-list-single(tg-bind-scope, ng-repeat="project in vm.projects.all") + li.home-project-list-single(tg-bind-scope, tg-repeat="project in vm.projects") a(href="#", tg-nav="project:project=project.slug") h2.home-project-list-single-title span.project-name(ng-bind="::project.name", title="{{ ::project.name }}") From c9d6fce6cd154a7f57c8484e9c9a38bfc4bc77b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 27 Apr 2015 12:13:59 +0200 Subject: [PATCH 077/366] Fix navigation bar styles --- app/modules/navigation-bar/navigation-bar.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/modules/navigation-bar/navigation-bar.scss b/app/modules/navigation-bar/navigation-bar.scss index 0338b765..3cddf18d 100644 --- a/app/modules/navigation-bar/navigation-bar.scss +++ b/app/modules/navigation-bar/navigation-bar.scss @@ -87,6 +87,11 @@ } } } + .navbar-dropdown { + a { + padding: .8rem .5rem; + } + } %dropdown { border-radius: 2px; display: none; @@ -128,7 +133,6 @@ &.create-organization-btn, &.create-project-btn { color: $white; - padding: .5rem; text-align: center; &:hover { color: $white; From 78299585481d2d4261f1ac8094cf77aa52f9b397 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Mon, 27 Apr 2015 12:42:51 +0200 Subject: [PATCH 078/366] compile & create new project lightbox --- app/coffee/modules/common/lightboxes.coffee | 3 +++ app/coffee/modules/projects/lightboxes.coffee | 7 +++---- app/modules/navigation-bar/navigation-bar.jade | 2 -- app/modules/projects/projects.service.coffee | 7 +++---- .../services/lightbox-factory.service.coffee | 18 ++++++++++++++++++ 5 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 app/modules/services/lightbox-factory.service.coffee diff --git a/app/coffee/modules/common/lightboxes.coffee b/app/coffee/modules/common/lightboxes.coffee index f4d089e9..1c45344a 100644 --- a/app/coffee/modules/common/lightboxes.coffee +++ b/app/coffee/modules/common/lightboxes.coffee @@ -67,6 +67,9 @@ class LightboxService extends taiga.Service $el.addClass('close') + if $el.hasClass("remove-on-close") + $el.remove() + closeAll: -> docEl = angular.element(document) for lightboxEl in docEl.find(".lightbox.open") diff --git a/app/coffee/modules/projects/lightboxes.coffee b/app/coffee/modules/projects/lightboxes.coffee index 655228b6..483ae76b 100644 --- a/app/coffee/modules/projects/lightboxes.coffee +++ b/app/coffee/modules/projects/lightboxes.coffee @@ -70,7 +70,7 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project promise = $repo.create("projects", $scope.data) promise.then(onSuccessSubmit, onErrorSubmit) - createProjectCallback = -> + openLightbox = -> $scope.data = { total_story_points: 100 total_milestones: 5 @@ -90,8 +90,6 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project timeout 600, -> $el.find(".progress-bar").addClass('step1') - projects.emiter.on 'create', createProjectCallback - $el.on "click", ".button-next", (event) -> event.preventDefault() @@ -129,9 +127,10 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project lightboxService.close($el) $scope.$on "$destroy", -> - emitter.off(projects.emiter, createProjectCallback) $el.off() + openLightbox() + directive = { link: link, templateUrl: "project/wizard-create-project.html" diff --git a/app/modules/navigation-bar/navigation-bar.jade b/app/modules/navigation-bar/navigation-bar.jade index 18060a7d..a9921145 100644 --- a/app/modules/navigation-bar/navigation-bar.jade +++ b/app/modules/navigation-bar/navigation-bar.jade @@ -29,5 +29,3 @@ nav.navbar div.topnav-dropdown-wrapper(ng-show="vm.projects.size", tg-dropdown-project-list) //div.topnav-dropdown-wrapper(tg-dropdown-organization-list) div.topnav-dropdown-wrapper(tg-dropdown-user) - -div.wizard-create-project(tg-lb-create-project) diff --git a/app/modules/projects/projects.service.coffee b/app/modules/projects/projects.service.coffee index 3d3ae9fa..0ed2e2c4 100644 --- a/app/modules/projects/projects.service.coffee +++ b/app/modules/projects/projects.service.coffee @@ -1,12 +1,11 @@ class ProjectsService extends taiga.Service - @.$inject = ["$q", "$tgResources", "$rootScope", "$projectUrl"] + @.$inject = ["$q", "$tgResources", "$rootScope", "$projectUrl", "tgLightboxFactory"] - constructor: (@q, @rs, @rootScope, @projectUrl) -> + constructor: (@q, @rs, @rootScope, @projectUrl, @lightboxFactory) -> @.projects = Immutable.Map() @.inProgress = false @.projectsPromise = null @.fetchProjects() - @.emiter = new EventEmitter2() fetchProjects: -> if not @.inProgress @@ -28,7 +27,7 @@ class ProjectsService extends taiga.Service return @.projectsPromise newProject: -> - @.emiter.emit("create") + @lightboxFactory.create("tg-lb-create-project") bulkUpdateProjectsOrder: (sortData) -> @rs.projects.bulkUpdateOrder(sortData).then => diff --git a/app/modules/services/lightbox-factory.service.coffee b/app/modules/services/lightbox-factory.service.coffee new file mode 100644 index 00000000..1cb0a10e --- /dev/null +++ b/app/modules/services/lightbox-factory.service.coffee @@ -0,0 +1,18 @@ +class LightboxFactory + @.$inject = ["$rootScope", "$compile"] + constructor: (@rootScope, @compile) -> + + create: (name) -> + elm = $("
") + .attr(name, true) + .addClass("wizard-create-project") + .addClass("remove-on-close") + + scope = @rootScope.$new() + html = @compile(elm)(scope) + + $(document.body).append(html) + + return + +angular.module("taigaCommon").service("tgLightboxFactory", LightboxFactory) From 4ea48add1149ed47ea8ed82cfb54002ed8411f85 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 28 Apr 2015 08:15:02 +0200 Subject: [PATCH 079/366] destroy lightbox scope on close --- app/coffee/modules/common/lightboxes.coffee | 2 ++ app/modules/services/lightbox-factory.service.coffee | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/coffee/modules/common/lightboxes.coffee b/app/coffee/modules/common/lightboxes.coffee index 1c45344a..ff262cb8 100644 --- a/app/coffee/modules/common/lightboxes.coffee +++ b/app/coffee/modules/common/lightboxes.coffee @@ -68,6 +68,8 @@ class LightboxService extends taiga.Service $el.addClass('close') if $el.hasClass("remove-on-close") + scope = $el.data("scope") + scope.$destroy() $el.remove() closeAll: -> diff --git a/app/modules/services/lightbox-factory.service.coffee b/app/modules/services/lightbox-factory.service.coffee index 1cb0a10e..6605496a 100644 --- a/app/modules/services/lightbox-factory.service.coffee +++ b/app/modules/services/lightbox-factory.service.coffee @@ -3,12 +3,14 @@ class LightboxFactory constructor: (@rootScope, @compile) -> create: (name) -> + scope = @rootScope.$new() + elm = $("
") .attr(name, true) + .attr("tg-bind-scope", true) .addClass("wizard-create-project") .addClass("remove-on-close") - scope = @rootScope.$new() html = @compile(elm)(scope) $(document.body).append(html) From 792d2ae22ca2e5e6790e0adf6ae11dbab7a3fd2f Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 28 Apr 2015 08:16:13 +0200 Subject: [PATCH 080/366] scopeEvent service --- .../services/scope-event.service.coffee | 35 ++++++++ .../services/scope-event.service.spec.coffee | 83 +++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 app/modules/services/scope-event.service.coffee create mode 100644 app/modules/services/scope-event.service.spec.coffee diff --git a/app/modules/services/scope-event.service.coffee b/app/modules/services/scope-event.service.coffee new file mode 100644 index 00000000..922ca6f3 --- /dev/null +++ b/app/modules/services/scope-event.service.coffee @@ -0,0 +1,35 @@ +class ScopeEvent + scopes: {}, + _searchDuplicatedScopes: (id) -> + return _.find Object.keys(@scopes), (key) => + return @scopes[key].$id == id + + _create: (name, scope) -> + duplicatedScopeName = @._searchDuplicatedScopes(scope.$id) + + if duplicatedScopeName + throw new Error("scopeEvent: this scope is already + register with the name \"" + duplicatedScopeName + "\"") + + if @scopes[name] + throw new Error("scopeEvent: \"" + name + "\" already in use") + else + scope._tgEmitter = new EventEmitter2() + + scope.$on "$destroy", () => + scope._tgEmitter.removeAllListeners() + delete @scopes[name] + + @scopes[name] = scope + + emitter: (name, scope) -> + if scope + scope = @._create(name, scope) + else if @scopes[name] + scope = @scopes[name] + else + throw new Error("scopeEvent: \"" + name + "\" scope doesn't exist'") + + return scope._tgEmitter + +angular.module("taigaCommon").service("tgScopeEvent", ScopeEvent) diff --git a/app/modules/services/scope-event.service.spec.coffee b/app/modules/services/scope-event.service.spec.coffee new file mode 100644 index 00000000..95eed0e2 --- /dev/null +++ b/app/modules/services/scope-event.service.spec.coffee @@ -0,0 +1,83 @@ +angular.module("taigaCommon").provider("$exceptionHandler", angular.mock.$ExceptionHandlerProvider) + +describe.only "tgScopeEvent", -> + scopeEvent = null + $rootScope = null + + _inject = -> + inject (_tgScopeEvent_, _$rootScope_) -> + scopeEvent = _tgScopeEvent_ + scopeEvent.scopes = {} + + $rootScope = _$rootScope_ + + _setup = -> + _inject() + + beforeEach -> + module "taigaCommon" + _setup() + + it "get non-existent emitter", () -> + fn = () -> scopeEvent.emitter("test") + + expect(fn).to.throw(Error, "scopeEvent: \"test\" scope doesn't exist'") + + it "create emitter", () -> + scope = $rootScope.$new() + + emitter = scopeEvent.emitter("test", scope) + + expect(emitter).to.be.an.instanceof(EventEmitter2) + + it "get emitter", () -> + scope = $rootScope.$new() + + scopeEvent.emitter("test", scope) + + emitter = scopeEvent.emitter("test") + + expect(emitter).to.be.an.instanceof(EventEmitter2) + + it "duplicate emitter name", () -> + scope = $rootScope.$new() + scope2 = $rootScope.$new() + + scopeEvent.emitter("test", scope) + + fn = () -> scopeEvent.emitter("test", scope2) + + expect(fn).to.throw(Error, "scopeEvent: \"test\" already in use") + + it "duplicate scope", () -> + scope = $rootScope.$new() + + scopeEvent.emitter("test", scope) + + fn = () -> scopeEvent.emitter("test2", scope) + + expect(fn).to.throw(Error, "scopeEvent: this scope is already register with the name \"test\"") + + + it "create listener", () -> + scope = $rootScope.$new() + + emitter = scopeEvent.emitter("test", scope) + emitter.on "test_listener", () -> return + + expect(scope._tgEmitter.listeners("test_listener")[0]).to.be.ok + + it "remove emitter and listeners after scope destroy", () -> + scope = $rootScope.$new() + + emitter = scopeEvent.emitter("test", scope) + + emitter.on "test_listener", () -> return + + expect(scope._tgEmitter.listeners("test_listener")).to.have.length(1) + + expect(scopeEvent.scopes['test']).to.be.ok + scope.$destroy() + + expect(scopeEvent.scopes['test']).to.be.undefined + expect(scope._tgEmitter.listeners("test_listener")).to.have.length(0) From 43501892eefb25a4241a85041f0646c247efe65c Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 28 Apr 2015 09:25:40 +0200 Subject: [PATCH 081/366] Refactoring home --- app/coffee/app.coffee | 3 +- app/coffee/modules/base.coffee | 2 - app/coffee/modules/resources/issues.coffee | 3 + app/coffee/modules/resources/tasks.coffee | 3 + .../modules/resources/userstories.coffee | 3 + app/locales/locale-en.json | 6 ++ app/modules/home/duties/duty-directive.coffee | 37 ++++++++++ app/modules/home/duties/duty.jade | 12 ++++ app/modules/home/home-directive.coffee | 21 +++++- app/modules/home/home-page.controller.coffee | 12 +++- app/modules/home/home-service.coffee | 59 ++++++++++++++++ app/modules/home/home.jade | 70 ++++--------------- app/modules/home/projects/list.jade | 8 +-- .../projects/projects-page.controller.coffee | 7 +- app/modules/projects/projects.service.coffee | 6 ++ 15 files changed, 178 insertions(+), 74 deletions(-) create mode 100644 app/modules/home/duties/duty-directive.coffee create mode 100644 app/modules/home/duties/duty.jade create mode 100644 app/modules/home/home-service.coffee diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 2c5275f9..b7a4f1a1 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -39,7 +39,7 @@ taiga.sessionId = taiga.generateUniqueSessionIdentifier() configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEventsProvider, tgLoaderProvider, $compileProvider, $translateProvider) -> $routeProvider.when("/", - {templateUrl: "home/home-page.html"}) + {templateUrl: "home/home-page.html", resolve: {loader: tgLoaderProvider.add()}}) $routeProvider.when("/projects/", {templateUrl: "projects/projects-page.html", resolve: {loader: tgLoaderProvider.add()}}) @@ -320,6 +320,7 @@ modules = [ "taigaEvents", # Specific Modules + "taigaHome", "taigaNavigationBar", "taigaProjects", "taigaRelatedTasks", diff --git a/app/coffee/modules/base.coffee b/app/coffee/modules/base.coffee index 5aef4043..b74f30bf 100644 --- a/app/coffee/modules/base.coffee +++ b/app/coffee/modules/base.coffee @@ -69,9 +69,7 @@ urls = { "project-search": "/project/:project/search" "project-userstories-detail": "/project/:project/us/:ref" - "project-tasks-detail": "/project/:project/task/:ref" - "project-issues-detail": "/project/:project/issue/:ref" "project-wiki": "/project/:project/wiki" diff --git a/app/coffee/modules/resources/issues.coffee b/app/coffee/modules/resources/issues.coffee index 0cbb99de..bf7c27df 100644 --- a/app/coffee/modules/resources/issues.coffee +++ b/app/coffee/modules/resources/issues.coffee @@ -41,6 +41,9 @@ resourceProvider = ($repo, $http, $urls, $storage, $q) -> params.ref = ref return $repo.queryOne("issues", "by_ref", params) + service.listInAllProjects = (filters) -> + return $repo.queryMany("issues", filters) + service.list = (projectId, filters, options) -> params = {project: projectId} params = _.extend({}, params, filters or {}) diff --git a/app/coffee/modules/resources/tasks.coffee b/app/coffee/modules/resources/tasks.coffee index 56a00de3..7069012c 100644 --- a/app/coffee/modules/resources/tasks.coffee +++ b/app/coffee/modules/resources/tasks.coffee @@ -41,6 +41,9 @@ resourceProvider = ($repo, $http, $urls, $storage) -> params.ref = ref return $repo.queryOne("tasks", "by_ref", params) + service.listInAllProjects = (filters) -> + return $repo.queryMany("tasks", filters) + service.list = (projectId, sprintId=null, userStoryId=null) -> params = {project: projectId} params.milestone = sprintId if sprintId diff --git a/app/coffee/modules/resources/userstories.coffee b/app/coffee/modules/resources/userstories.coffee index ac43dfff..de2f8c9b 100644 --- a/app/coffee/modules/resources/userstories.coffee +++ b/app/coffee/modules/resources/userstories.coffee @@ -38,6 +38,9 @@ resourceProvider = ($repo, $http, $urls, $storage) -> params.ref = ref return $repo.queryOne("userstories", "by_ref", params) + service.listInAllProjects = (filters) -> + return $repo.queryMany("userstories", filters) + service.listUnassigned = (projectId, filters) -> params = {"project": projectId, "milestone": "null"} params = _.extend({}, params, filters or {}) diff --git a/app/locales/locale-en.json b/app/locales/locale-en.json index 19f7268e..4dc5faf1 100644 --- a/app/locales/locale-en.json +++ b/app/locales/locale-en.json @@ -101,6 +101,9 @@ } }, "SEE_USER_PROFILE": "See {{username }} profile", + "USER_STORY": "User story", + "TASK": "Task", + "ISSUE": "Issue", "TAGS": { "PLACEHOLDER": "I'm it! Tag me...", "DELETE": "Delete tag", @@ -241,6 +244,9 @@ "REGISTER": "Register", "CREATE_ACCOUNT": "create your free account here" }, + "HOME": { + "EMPTY_WATCHING": "Follow the projects, User Stories, Tasks, Issues... that you want to know about :)" + }, "ATTACHMENT": { "SECTION_NAME": "attachments", "TITLE": "{{ fileName }} uploaded on {{ date }}", diff --git a/app/modules/home/duties/duty-directive.coffee b/app/modules/home/duties/duty-directive.coffee new file mode 100644 index 00000000..72f544a3 --- /dev/null +++ b/app/modules/home/duties/duty-directive.coffee @@ -0,0 +1,37 @@ +DutyDirective = (navurls, projectsService, $translate) -> + link = (scope, el, attrs, ctrl) -> + scope.vm = {} + scope.vm.duty = scope.duty + + scope.vm.getDutyType = () -> + if scope.vm.duty + if scope.vm.duty._name == "userstories" + return $translate.instant("COMMON.USER_STORY") + if scope.vm.duty._name == "tasks" + return $translate.instant("COMMON.TASK") + if scope.vm.duty._name == "issues" + return $translate.instant("COMMON.ISSUE") + + scope.vm.getUrl = () -> + if scope.vm.duty + ctx = { + project: projectsService.projectsById.get(String(scope.vm.duty.project)).slug + ref: scope.vm.duty.ref + } + return navurls.resolve("project-#{scope.vm.duty._name}-detail", ctx) + + scope.vm.getProjectName = () -> + if scope.vm.duty + return projectsService.projectsById.get(String(scope.vm.duty.project)).name + + directive = { + templateUrl: "home/duties/duty.html" + scope: { + "duty": "=tgDuty" + } + link: link + } + + return directive + +angular.module("taigaHome").directive("tgDuty", ["$tgNavUrls", "tgProjects", "$translate", DutyDirective]) diff --git a/app/modules/home/duties/duty.jade b/app/modules/home/duties/duty.jade new file mode 100644 index 00000000..dcb8fa5e --- /dev/null +++ b/app/modules/home/duties/duty.jade @@ -0,0 +1,12 @@ +img.avatar( + src="https://s3.amazonaws.com/uifaces/faces/twitter/rem/128.jpg" + title="{{ user.fullname }}") + +div.duty-data + div + span.duty-type {{ vm.getDutyType() }} + span.duty-status(ng-style="{'color': vm.duty.status_color}") {{ ::vm.duty.status_name }} + a.duty-title(href="{{ vm.getUrl() }}") + span.duty-id(tg-bo-ref="duty.ref") + span.duty-name {{ ::duty.subject }} +div.duty-project {{ ::vm.getProjectName()}} diff --git a/app/modules/home/home-directive.coffee b/app/modules/home/home-directive.coffee index c5218db5..a929d370 100644 --- a/app/modules/home/home-directive.coffee +++ b/app/modules/home/home-directive.coffee @@ -1,9 +1,26 @@ -HomeDirective = -> +HomeDirective = (homeService) -> + link = (scope, el, attrs, ctrl) -> + scope.vm = {} + taiga.defineImmutableProperty(scope.vm, "workInProgress", () -> homeService.workInProgress) + + scope.$watch "vm.workInProgress", (workInProgress) -> + if workInProgress.size > 0 + userStories = workInProgress.get("assignedTo").get("userStories") + tasks = workInProgress.get("assignedTo").get("tasks") + issues = workInProgress.get("assignedTo").get("issues") + scope.vm.assignedTo = userStories.concat(tasks).concat(issues) + + userStories = workInProgress.get("watching").get("userStories") + tasks = workInProgress.get("watching").get("tasks") + issues = workInProgress.get("watching").get("issues") + scope.vm.watching = userStories.concat(tasks).concat(issues) + directive = { templateUrl: "home/home.html" scope: {} + link: link } return directive -angular.module("taigaProjects").directive("tgHome", HomeDirective) +angular.module("taigaHome").directive("tgHome", ["tgHomeService", HomeDirective]) diff --git a/app/modules/home/home-page.controller.coffee b/app/modules/home/home-page.controller.coffee index eddc1b65..36eeba8f 100644 --- a/app/modules/home/home-page.controller.coffee +++ b/app/modules/home/home-page.controller.coffee @@ -12,22 +12,28 @@ class ProjectsPageController extends taiga.Controller "$tgConfig", "tgLoader", "tgProjects", + "tgHomeService", "$translate" ] constructor: (@scope, @q, @rs, @rootscope, @navUrls, @auth, @location, - @appTitle, @projectUrl, @config, tgLoader, @projects, @translate) -> + @appTitle, @projectUrl, @config, tgLoader, @projectsService, @homeService, + @translate) -> @appTitle.set(@translate.instant("PROJECT.WELCOME")) if !@auth.isAuthenticated() @location.path(@navUrls.resolve("login")) #Projects - promise = @projects.fetchProjects() + projectsPromise = @projectsService.fetchProjects() + + #In progress work + user = @auth.getUser() + workInProgressPromise = @homeService.fetchWorkInProgress(user.id) # Finally - promise.finally tgLoader.pageLoaded + @q.all([projectsPromise, workInProgressPromise]).finally tgLoader.pageLoaded angular.module("taigaHome").controller("HomePage", ProjectsPageController) diff --git a/app/modules/home/home-service.coffee b/app/modules/home/home-service.coffee new file mode 100644 index 00000000..a3b97fce --- /dev/null +++ b/app/modules/home/home-service.coffee @@ -0,0 +1,59 @@ +class HomeService extends taiga.Service + @.$inject = ["$q", "$tgResources", "$rootScope", "$projectUrl"] + + constructor: (@q, @rs, @rootScope, @projectUrl) -> + @.workInProgress = Immutable.Map() + @.inProgress = false + + fetchWorkInProgress: (userId) -> + if not @.inProgress + @.inProgress = true + params = { + status__is_closed: false + assigned_to: userId + } + assignedUserStoriesPromise = @rs.userstories.listInAllProjects(params).then (userstories) => + @.assignedToUserStories = userstories + + assignedTasksPromise = @rs.tasks.listInAllProjects(params).then (tasks) => + @.assignedToTasks = tasks + + assignedIssuesPromise = @rs.issues.listInAllProjects(params).then (issues) => + @.assignedToIssues = issues + + params = { + status__is_closed: false + watchers: userId + } + watchingUserStoriesPromise = @rs.userstories.listInAllProjects(params).then (userstories) => + @.watchingUserStories = userstories + + watchingTasksPromise = @rs.tasks.listInAllProjects(params).then (tasks) => + @.watchingTasks = tasks + + watchingIssuesPromise = @rs.issues.listInAllProjects(params).then (issues) => + @.watchingIssues = issues + + workPromise = @q.all([assignedUserStoriesPromise, assignedTasksPromise, + assignedIssuesPromise, watchingUserStoriesPromise, + watchingUserStoriesPromise, watchingIssuesPromise]) + + workPromise.then => + @.workInProgress = Immutable.fromJS({ + assignedTo: { + userStories: @.assignedToUserStories + tasks: @.assignedToTasks + issues: @.assignedToIssues + } + watching: { + userStories: @.watchingUserStories + tasks: @.watchingTasks + issues: @.watchingIssues + } + }) + + @.inProgress = false + + return workPromise + +angular.module("taigaHome").service("tgHomeService", HomeService) diff --git a/app/modules/home/home.jade b/app/modules/home/home.jade index de24aac1..ccb73a9d 100644 --- a/app/modules/home/home.jade +++ b/app/modules/home/home.jade @@ -1,68 +1,22 @@ doctype html include ../../partials/includes/components/beta -div.home-wrapper.centered(ng-controller="HomePage") +div.home-wrapper.centered div.duty-summary - // TODO Hide if ASSIGNED TO ==== false - div.title-bar.working-on-title Working on - // TODO Hide if ASSIGNED TO ==== false - section.working-on - // TODO Remove and replace for an angular repeat - - for (var x = 0; x < 2; x++) - div.duty-single - img.avatar(src="https://s3.amazonaws.com/uifaces/faces/twitter/rem/128.jpg", title="{{ user.fullname }}") - div.duty-data - div - // TODO, ADD THEIR COLOR TO THE DUTY TYPE AND STATUS - span.duty-type User Story - span.duty-status New - a.duty-title(href="") - span.duty-id #1504 - span.duty-name Documentación III: (notificaciones asíncronas + plugins front/back) - div.duty-project Taiga - div.duty-single.blocked - img.avatar(src="https://s3.amazonaws.com/uifaces/faces/twitter/rem/128.jpg", title="{{ user.fullname }}") - div.duty-data - div - // TODO, ADD THEIR COLOR TO THE DUTY TYPE AND STATUS - span.duty-type User Story - span.duty-status Blocked - a.duty-title(href="") - span.duty-id #1504 - span.duty-name Documentación III: (notificaciones asíncronas + plugins front/back) Documentación III: (notificaciones asíncronas + plugins front/back) - div.duty-project Whatever + // TODO: if not assigned to items? + // TODO: i18n + div.title-bar.working-on-title(ng-show="vm.assignedTo") Working on + section.working-on(ng-show="vm.assignedTo") + div.duty-single(tg-duty="duty", tg-repeat="duty in vm.assignedTo", ng-class="{blocked: duty.is_blocked}") div.title-bar.watching-title Watching - // TODO Show if WATCHERS ==== false - section.watching-empty.hidden + section.watching-empty(ng-show="!vm.watching.size") include ../../svg/hide.svg - p Follow the projects, User Stories, Tasks, Issues... that you want to know about :) + // TODO: i18n + p(translate="HOME.EMPTY_WATCHING") + + section.watching(ng-show="vm.watching") + div.duty-single(tg-duty="duty", tg-repeat="duty in vm.watching", ng-class="{blocked: duty.is_blocked}") - // TODO Show if WATCHERS ==== true - section.watching - - for (var x = 0; x < 20; x++) - div.duty-single - img.avatar(src="https://s3.amazonaws.com/uifaces/faces/twitter/annapickard/128.jpg", title="{{ user.fullname }}") - div.duty-data - div - // TODO, ADD THEIR COLOR TO THE DUTY TYPE AND STATUS - span.duty-type User Story - span.duty-status New - a.duty-title(href="") - span.duty-id #1504 - span.duty-name Documentación III: (notificaciones asíncronas + plugins front/back) - div.duty-project Taiga - div.duty-single - img.avatar(src="https://s3.amazonaws.com/uifaces/faces/twitter/sircookieface/128.jpg", title="{{ user.fullname }}") - div.duty-data - div - // TODO, ADD THEIR COLOR TO THE DUTY TYPE AND STATUS - span.duty-type Bug - span.duty-status Ready for test - a.duty-title(href="") - span.duty-id #28 - span.duty-name It is not possible to re-order stories in the sprint view . - div.duty-project Teletransportation hubs - a.button-gray.see-more(href="#", title="See more Watching US") See more aside.project-list(tg-home-project-list) diff --git a/app/modules/home/projects/list.jade b/app/modules/home/projects/list.jade index e76b6ed3..5fadc4d1 100644 --- a/app/modules/home/projects/list.jade +++ b/app/modules/home/projects/list.jade @@ -1,4 +1,4 @@ -ul.home-project-list(ng-show="vm.projects.length") +ul.home-project-list(ng-show="vm.projects.size") li.home-project-list-single(tg-bind-scope, tg-repeat="project in vm.projects") a(href="#", tg-nav="project:project=project.slug") h2.home-project-list-single-title @@ -7,8 +7,8 @@ ul.home-project-list(ng-show="vm.projects.length") include ../../../svg/lock.svg p {{ ::project.description | limitTo:150 }} span(ng-if="::project.description.length > 150") ... -a.see-more-projects-btn.button-gray(ng-show="vm.projects.length", href="#", tg-nav="projects", title="{{'PROJECT.NAVIGATION.SEE_MORE_PROJECTS' | translate}}", translate="PROJECT.NAVIGATION.SEE_MORE_PROJECTS") -section.projects-empty(ng-hide="vm.projects.length") +a.see-more-projects-btn.button-gray(ng-show="vm.projects.size", href="#", tg-nav="projects", title="{{'PROJECT.NAVIGATION.SEE_MORE_PROJECTS' | translate}}", translate="PROJECT.NAVIGATION.SEE_MORE_PROJECTS") +section.projects-empty(ng-hide="vm.projects.size") include ../../../svg/empty-project.svg p You don't have any projects yet a.create-project-btn.button-green( @@ -16,5 +16,3 @@ section.projects-empty(ng-hide="vm.projects.length") ng-click="vm.newProject()", title="{{'PROJECT.NAVIGATION.ACTION_CREATE_PROJECT' | translate}}", translate="PROJECT.NAVIGATION.ACTION_CREATE_PROJECT") - - diff --git a/app/modules/projects/projects-page.controller.coffee b/app/modules/projects/projects-page.controller.coffee index 1496e80a..97cafb50 100644 --- a/app/modules/projects/projects-page.controller.coffee +++ b/app/modules/projects/projects-page.controller.coffee @@ -11,12 +11,13 @@ class ProjectsPageController extends taiga.Controller "$projectUrl", "$tgConfig", "tgLoader", - "tgProjects" + "tgProjects", + "$translate" ] constructor: (@scope, @q, @rs, @rootscope, @navUrls, @auth, @location, - @appTitle, @projectUrl, @config, tgLoader, @projects) -> - @appTitle.set("Projects") + @appTitle, @projectUrl, @config, tgLoader, @projects, @translate) -> + @appTitle.set(@translate.instant("PROJECT.SECTION_PROJECTS")) if !@auth.isAuthenticated() @location.path(@navUrls.resolve("login")) diff --git a/app/modules/projects/projects.service.coffee b/app/modules/projects/projects.service.coffee index 0ed2e2c4..65edc942 100644 --- a/app/modules/projects/projects.service.coffee +++ b/app/modules/projects/projects.service.coffee @@ -1,8 +1,12 @@ +taiga = @.taiga +groupBy = @.taiga.groupBy + class ProjectsService extends taiga.Service @.$inject = ["$q", "$tgResources", "$rootScope", "$projectUrl", "tgLightboxFactory"] constructor: (@q, @rs, @rootScope, @projectUrl, @lightboxFactory) -> @.projects = Immutable.Map() + @.projectsById = Immutable.Map() @.inProgress = false @.projectsPromise = null @.fetchProjects() @@ -19,6 +23,8 @@ class ProjectsService extends taiga.Service recents: projects.slice(0, 10) }) + @.projectsById = Immutable.fromJS(groupBy(projects, (p) -> p.id)) + return @.projects @.projectsPromise.then () => From d6a4f790c92160a101db2e66675356418e06e698 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 28 Apr 2015 10:03:56 +0200 Subject: [PATCH 082/366] open lightbox feedback with the lightboxFactory service --- app/coffee/modules/feedback.coffee | 15 ++++++++++----- app/index.jade | 2 -- app/modules/feedback/feedback-service.coffee | 10 +++++++--- app/modules/projects/projects.service.coffee | 4 +++- .../services/lightbox-factory.service.coffee | 6 ++++-- .../modules => common}/lightbox-feedback.jade | 0 6 files changed, 24 insertions(+), 13 deletions(-) rename app/partials/{includes/modules => common}/lightbox-feedback.jade (100%) diff --git a/app/coffee/modules/feedback.coffee b/app/coffee/modules/feedback.coffee index bfc4032f..5609a5c1 100644 --- a/app/coffee/modules/feedback.coffee +++ b/app/coffee/modules/feedback.coffee @@ -56,18 +56,23 @@ FeedbackDirective = ($lightboxService, $repo, $confirm, $loading, feedbackServic $el.on "submit", "form", submit - sendFeedbackCallback = -> + openLightbox = -> $scope.feedback = {} $lightboxService.open($el) $el.find("textarea").focus() - feedbackService.emiter.on "send", sendFeedbackCallback - $scope.$on "$destroy", -> - emitter.off(feedbackService.emiter, sendFeedbackCallback) $el.off() - return {link:link} + openLightbox() + + directive = { + link: link, + templateUrl: "common/lightbox-feedback.html" + scope: {} + } + + return directive module.directive("tgLbFeedback", ["lightboxService", "$tgRepo", "$tgConfirm", "$tgLoading", "tgFeedback", FeedbackDirective]) diff --git a/app/index.jade b/app/index.jade index a6acad80..b795b201 100644 --- a/app/index.jade +++ b/app/index.jade @@ -30,8 +30,6 @@ html(lang="en") include partials/includes/modules/lightbox-generic-loading div.lightbox.lightbox-search(tg-search-box) include partials/includes/modules/lightbox-search - div.lightbox.lightbox-feedback.lightbox-generic-form(tg-lb-feedback) - include partials/includes/modules/lightbox-feedback include partials/includes/modules/loader diff --git a/app/modules/feedback/feedback-service.coffee b/app/modules/feedback/feedback-service.coffee index c063f215..281b9925 100644 --- a/app/modules/feedback/feedback-service.coffee +++ b/app/modules/feedback/feedback-service.coffee @@ -1,8 +1,12 @@ class FeedbackService extends taiga.Service - constructor: -> - @.emiter = new EventEmitter2() + @.$inject = ["tgLightboxFactory"] + + constructor: (@lightboxFactory) -> + sendFeedback: -> - @.emiter.emit("send") + @lightboxFactory.create("tg-lb-feedback", { + "class": "lightbox lightbox-feedback lightbox-generic-form" + }) angular.module("taigaFeedback").service("tgFeedback", FeedbackService) diff --git a/app/modules/projects/projects.service.coffee b/app/modules/projects/projects.service.coffee index 65edc942..3a902431 100644 --- a/app/modules/projects/projects.service.coffee +++ b/app/modules/projects/projects.service.coffee @@ -33,7 +33,9 @@ class ProjectsService extends taiga.Service return @.projectsPromise newProject: -> - @lightboxFactory.create("tg-lb-create-project") + @lightboxFactory.create("tg-lb-create-project", { + "class": "wizard-create-project" + }) bulkUpdateProjectsOrder: (sortData) -> @rs.projects.bulkUpdateOrder(sortData).then => diff --git a/app/modules/services/lightbox-factory.service.coffee b/app/modules/services/lightbox-factory.service.coffee index 6605496a..cd5d885c 100644 --- a/app/modules/services/lightbox-factory.service.coffee +++ b/app/modules/services/lightbox-factory.service.coffee @@ -2,15 +2,17 @@ class LightboxFactory @.$inject = ["$rootScope", "$compile"] constructor: (@rootScope, @compile) -> - create: (name) -> + create: (name, attrs) -> scope = @rootScope.$new() elm = $("
") .attr(name, true) .attr("tg-bind-scope", true) - .addClass("wizard-create-project") .addClass("remove-on-close") + if attrs + elm.attr(attrs) + html = @compile(elm)(scope) $(document.body).append(html) diff --git a/app/partials/includes/modules/lightbox-feedback.jade b/app/partials/common/lightbox-feedback.jade similarity index 100% rename from app/partials/includes/modules/lightbox-feedback.jade rename to app/partials/common/lightbox-feedback.jade From 1107355414eed98a8aa6b35ca1464b8ce19cdf7d Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 28 Apr 2015 10:13:32 +0200 Subject: [PATCH 083/366] remove describe.only on tgScopeEvent --- app/modules/services/scope-event.service.spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/services/scope-event.service.spec.coffee b/app/modules/services/scope-event.service.spec.coffee index 95eed0e2..0f898b4c 100644 --- a/app/modules/services/scope-event.service.spec.coffee +++ b/app/modules/services/scope-event.service.spec.coffee @@ -1,6 +1,6 @@ angular.module("taigaCommon").provider("$exceptionHandler", angular.mock.$ExceptionHandlerProvider) -describe.only "tgScopeEvent", -> +describe "tgScopeEvent", -> scopeEvent = null $rootScope = null From 7ac6e64c8b83a3ce7b1c68865bfc480b14a6d0c1 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 28 Apr 2015 11:30:00 +0200 Subject: [PATCH 084/366] Renaming services --- app/coffee/modules/admin/project-profile.coffee | 2 +- app/coffee/modules/feedback.coffee | 2 +- app/coffee/modules/projects/lightboxes.coffee | 8 ++++---- app/locales/locale-en.json | 4 +++- app/modules/feedback/feedback-service.coffee | 2 +- app/modules/home/duties/duty-directive.coffee | 2 +- app/modules/home/duties/duty.jade | 8 ++++---- app/modules/home/home-page.controller.coffee | 2 +- app/modules/home/home.jade | 7 ++----- app/modules/home/projects/list.directive.coffee | 2 +- .../dropdown-project-list.directive.coffee | 2 +- .../dropdown-project-list/dropdown-project-list.jade | 7 ++++--- .../dropdown-user/dropdown-user.directive.coffee | 2 +- .../navigation-bar/navigation-bar.directive.coffee | 2 +- app/modules/projects/listing/listing.directive.coffee | 2 +- app/modules/projects/listing/listing.jade | 7 ++++--- app/modules/projects/projects-page.controller.coffee | 6 +++--- app/modules/projects/projects.service.coffee | 2 +- 18 files changed, 35 insertions(+), 34 deletions(-) diff --git a/app/coffee/modules/admin/project-profile.coffee b/app/coffee/modules/admin/project-profile.coffee index c3dae556..4c52c643 100644 --- a/app/coffee/modules/admin/project-profile.coffee +++ b/app/coffee/modules/admin/project-profile.coffee @@ -137,7 +137,7 @@ ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location, proje return {link:link} -module.directive("tgProjectProfile", ["$tgRepo", "$tgConfirm", "$tgLoading", "$tgNavUrls", "$tgLocation", "tgProjects", ProjectProfileDirective]) +module.directive("tgProjectProfile", ["$tgRepo", "$tgConfirm", "$tgLoading", "$tgNavUrls", "$tgLocation", "tgProjectsService", ProjectProfileDirective]) ############################################################################# ## Project Default Values Directive diff --git a/app/coffee/modules/feedback.coffee b/app/coffee/modules/feedback.coffee index 5609a5c1..247db488 100644 --- a/app/coffee/modules/feedback.coffee +++ b/app/coffee/modules/feedback.coffee @@ -75,4 +75,4 @@ FeedbackDirective = ($lightboxService, $repo, $confirm, $loading, feedbackServic return directive module.directive("tgLbFeedback", ["lightboxService", "$tgRepo", "$tgConfirm", - "$tgLoading", "tgFeedback", FeedbackDirective]) + "$tgLoading", "tgFeedbackService", FeedbackDirective]) diff --git a/app/coffee/modules/projects/lightboxes.coffee b/app/coffee/modules/projects/lightboxes.coffee index 483ae76b..f4f36574 100644 --- a/app/coffee/modules/projects/lightboxes.coffee +++ b/app/coffee/modules/projects/lightboxes.coffee @@ -26,7 +26,7 @@ debounce = @.taiga.debounce module = angular.module("taigaProject") -CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $projectUrl, $loading, lightboxService, $cacheFactory, $translate, projects) -> +CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $projectUrl, $loading, lightboxService, $cacheFactory, $translate, projectsService) -> link = ($scope, $el, attrs) -> $scope.data = {} $scope.templates = [] @@ -46,7 +46,7 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project $location.url($projectUrl.get(response)) lightboxService.close($el) - projects.fetchProjects() + projectsService.fetchProjects() onErrorSubmit = (response) -> $loading.finish(submitButton) @@ -142,7 +142,7 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project module.directive("tgLbCreateProject", ["$rootScope", "$tgRepo", "$tgConfirm", "$location", "$tgNavUrls", "$tgResources", "$projectUrl", "$tgLoading", - "lightboxService", "$cacheFactory", "$translate", "tgProjects", CreateProject]) + "lightboxService", "$cacheFactory", "$translate", "tgProjectsService", CreateProject]) ############################################################################# @@ -188,4 +188,4 @@ DeleteProjectDirective = ($repo, $rootscope, $auth, $location, $navUrls, $confir return {link:link} module.directive("tgLbDeleteProject", ["$tgRepo", "$rootScope", "$tgAuth", "$tgLocation", "$tgNavUrls", - "$tgConfirm", "lightboxService", "tgLoader", "tgProjects", DeleteProjectDirective]) + "$tgConfirm", "lightboxService", "tgLoader", "tgProjectsService", DeleteProjectDirective]) diff --git a/app/locales/locale-en.json b/app/locales/locale-en.json index 4dc5faf1..ff56f20e 100644 --- a/app/locales/locale-en.json +++ b/app/locales/locale-en.json @@ -245,7 +245,9 @@ "CREATE_ACCOUNT": "create your free account here" }, "HOME": { - "EMPTY_WATCHING": "Follow the projects, User Stories, Tasks, Issues... that you want to know about :)" + "EMPTY_WATCHING": "Follow the projects, User Stories, Tasks, Issues... that you want to know about :)", + "WORKING_ON_SECTION": "Working on", + "WATCHING_SECTION": "Watching" }, "ATTACHMENT": { "SECTION_NAME": "attachments", diff --git a/app/modules/feedback/feedback-service.coffee b/app/modules/feedback/feedback-service.coffee index 281b9925..86b8d6f9 100644 --- a/app/modules/feedback/feedback-service.coffee +++ b/app/modules/feedback/feedback-service.coffee @@ -9,4 +9,4 @@ class FeedbackService extends taiga.Service "class": "lightbox lightbox-feedback lightbox-generic-form" }) -angular.module("taigaFeedback").service("tgFeedback", FeedbackService) +angular.module("taigaFeedback").service("tgFeedbackService", FeedbackService) diff --git a/app/modules/home/duties/duty-directive.coffee b/app/modules/home/duties/duty-directive.coffee index 72f544a3..539fafd4 100644 --- a/app/modules/home/duties/duty-directive.coffee +++ b/app/modules/home/duties/duty-directive.coffee @@ -34,4 +34,4 @@ DutyDirective = (navurls, projectsService, $translate) -> return directive -angular.module("taigaHome").directive("tgDuty", ["$tgNavUrls", "tgProjects", "$translate", DutyDirective]) +angular.module("taigaHome").directive("tgDuty", ["$tgNavUrls", "tgProjectsService", "$translate", DutyDirective]) diff --git a/app/modules/home/duties/duty.jade b/app/modules/home/duties/duty.jade index dcb8fa5e..6304f47a 100644 --- a/app/modules/home/duties/duty.jade +++ b/app/modules/home/duties/duty.jade @@ -1,12 +1,12 @@ img.avatar( - src="https://s3.amazonaws.com/uifaces/faces/twitter/rem/128.jpg" - title="{{ user.fullname }}") + src="{{ ::vm.duty.assigned_to_extra_info.photo }}" + title="{{ ::vm.duty.assigned_to_extra_info.full_name_display }}") div.duty-data div - span.duty-type {{ vm.getDutyType() }} + span.duty-type {{ ::vm.getDutyType() }} span.duty-status(ng-style="{'color': vm.duty.status_color}") {{ ::vm.duty.status_name }} - a.duty-title(href="{{ vm.getUrl() }}") + a.duty-title(href="{{ ::vm.getUrl() }}") span.duty-id(tg-bo-ref="duty.ref") span.duty-name {{ ::duty.subject }} div.duty-project {{ ::vm.getProjectName()}} diff --git a/app/modules/home/home-page.controller.coffee b/app/modules/home/home-page.controller.coffee index 36eeba8f..ae45ba75 100644 --- a/app/modules/home/home-page.controller.coffee +++ b/app/modules/home/home-page.controller.coffee @@ -11,7 +11,7 @@ class ProjectsPageController extends taiga.Controller "$projectUrl", "$tgConfig", "tgLoader", - "tgProjects", + "tgProjectsService", "tgHomeService", "$translate" diff --git a/app/modules/home/home.jade b/app/modules/home/home.jade index ccb73a9d..b05ea7ab 100644 --- a/app/modules/home/home.jade +++ b/app/modules/home/home.jade @@ -3,17 +3,14 @@ doctype html include ../../partials/includes/components/beta div.home-wrapper.centered div.duty-summary - // TODO: if not assigned to items? - // TODO: i18n - div.title-bar.working-on-title(ng-show="vm.assignedTo") Working on + div.title-bar.working-on-title(ng-show="vm.assignedTo", translate="HOME.WORKING_ON_SECTION") section.working-on(ng-show="vm.assignedTo") div.duty-single(tg-duty="duty", tg-repeat="duty in vm.assignedTo", ng-class="{blocked: duty.is_blocked}") - div.title-bar.watching-title Watching + div.title-bar.watching-title(translate="HOME.WATCHING_SECTION") section.watching-empty(ng-show="!vm.watching.size") include ../../svg/hide.svg - // TODO: i18n p(translate="HOME.EMPTY_WATCHING") section.watching(ng-show="vm.watching") diff --git a/app/modules/home/projects/list.directive.coffee b/app/modules/home/projects/list.directive.coffee index d22975e2..2e6d250a 100644 --- a/app/modules/home/projects/list.directive.coffee +++ b/app/modules/home/projects/list.directive.coffee @@ -15,4 +15,4 @@ HomeProjectListDirective = (projectsService) -> return directive -angular.module("taigaHome").directive("tgHomeProjectList", ["tgProjects", HomeProjectListDirective]) +angular.module("taigaHome").directive("tgHomeProjectList", ["tgProjectsService", HomeProjectListDirective]) diff --git a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee index cb5cceb9..8ffe96c8 100644 --- a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee +++ b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee @@ -17,4 +17,4 @@ DropdownProjectListDirective = (projectsService) -> angular.module("taigaNavigationBar").directive("tgDropdownProjectList", - ["tgProjects", DropdownProjectListDirective]) + ["tgProjectsService", DropdownProjectListDirective]) diff --git a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade index 375a9c1d..12cdf753 100644 --- a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade +++ b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade @@ -21,6 +21,7 @@ div.navbar-dropdown.dropdown-project-list title="{{'PROJECT.NAVIGATION.ACTION_CREATE_PROJECT' | translate}}", translate="PROJECT.NAVIGATION.ACTION_CREATE_PROJECT") - a.button-blackish.import-project-button(href="", title="{{'PROJECT.NAVIGATION.TITLE_ACTION_IMPORT' | translate}}", tg-import-project-button) - span.icon.icon-upload - input.import-file.hidden(type="file") + span(tg-import-project-button) + a.button-blackish.import-project-button(href="", title="{{'PROJECT.NAVIGATION.TITLE_ACTION_IMPORT' | translate}}") + span.icon.icon-upload + input.import-file.hidden(type="file") diff --git a/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee b/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee index a417292d..018bd433 100644 --- a/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee +++ b/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee @@ -22,5 +22,5 @@ DropdownUserDirective = (authService, configService, locationService, return directive angular.module("taigaNavigationBar").directive("tgDropdownUser", - ["$tgAuth", "$tgConfig", "$tgLocation", "$tgNavUrls", "tgFeedback", + ["$tgAuth", "$tgConfig", "$tgLocation", "$tgNavUrls", "tgFeedbackService", DropdownUserDirective]) diff --git a/app/modules/navigation-bar/navigation-bar.directive.coffee b/app/modules/navigation-bar/navigation-bar.directive.coffee index a372521d..9c23259c 100644 --- a/app/modules/navigation-bar/navigation-bar.directive.coffee +++ b/app/modules/navigation-bar/navigation-bar.directive.coffee @@ -14,4 +14,4 @@ NavigationBarDirective = (projectsService) -> angular.module("taigaNavigationBar").directive("tgNavigationBar", - ["tgProjects", NavigationBarDirective]) + ["tgProjectsService", NavigationBarDirective]) diff --git a/app/modules/projects/listing/listing.directive.coffee b/app/modules/projects/listing/listing.directive.coffee index 71a9ff8f..7d50458e 100644 --- a/app/modules/projects/listing/listing.directive.coffee +++ b/app/modules/projects/listing/listing.directive.coffee @@ -39,4 +39,4 @@ ProjectsListingDirective = (projectsService) -> return directive -angular.module("taigaProjects").directive("tgProjectsListing", ["tgProjects", ProjectsListingDirective]) +angular.module("taigaProjects").directive("tgProjectsListing", ["tgProjectsService", ProjectsListingDirective]) diff --git a/app/modules/projects/listing/listing.jade b/app/modules/projects/listing/listing.jade index a5006029..f643a87a 100644 --- a/app/modules/projects/listing/listing.jade +++ b/app/modules/projects/listing/listing.jade @@ -3,9 +3,10 @@ div.project-list-wrapper.centered h1 My projects div.create-options a.create-project-btn.button-green(href="#", ng-click="vm.newProject()", title="{{'PROJECT.NAVIGATION.ACTION_CREATE_PROJECT' | translate}}", translate="PROJECT.NAVIGATION.ACTION_CREATE_PROJECT") - a.button-blackish.import-project-button(href="", title="{{'PROJECT.NAVIGATION.TITLE_ACTION_IMPORT' | translate}}", tg-import-project-button) - span.icon.icon-upload - input.import-file.hidden(type="file") + span(tg-import-project-button) + a.button-blackish.import-project-button(href="", title="{{'PROJECT.NAVIGATION.TITLE_ACTION_IMPORT' | translate}}") + span.icon.icon-upload + input.import-file.hidden(type="file") section.project-list-section div.project-list diff --git a/app/modules/projects/projects-page.controller.coffee b/app/modules/projects/projects-page.controller.coffee index 97cafb50..67f60490 100644 --- a/app/modules/projects/projects-page.controller.coffee +++ b/app/modules/projects/projects-page.controller.coffee @@ -11,19 +11,19 @@ class ProjectsPageController extends taiga.Controller "$projectUrl", "$tgConfig", "tgLoader", - "tgProjects", + "tgProjectsService", "$translate" ] constructor: (@scope, @q, @rs, @rootscope, @navUrls, @auth, @location, - @appTitle, @projectUrl, @config, tgLoader, @projects, @translate) -> + @appTitle, @projectUrl, @config, tgLoader, @projectsService, @translate) -> @appTitle.set(@translate.instant("PROJECT.SECTION_PROJECTS")) if !@auth.isAuthenticated() @location.path(@navUrls.resolve("login")) #Projects - promise = @projects.fetchProjects() + promise = @projectsService.fetchProjects() # Finally promise.finally tgLoader.pageLoaded diff --git a/app/modules/projects/projects.service.coffee b/app/modules/projects/projects.service.coffee index 3a902431..0248b8c6 100644 --- a/app/modules/projects/projects.service.coffee +++ b/app/modules/projects/projects.service.coffee @@ -41,4 +41,4 @@ class ProjectsService extends taiga.Service @rs.projects.bulkUpdateOrder(sortData).then => @.fetchProjects() -angular.module("taigaProjects").service("tgProjects", ProjectsService) +angular.module("taigaProjects").service("tgProjectsService", ProjectsService) From e63415efd98c2704f284fd3e30406e94f1ad8d8c Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 28 Apr 2015 11:37:26 +0200 Subject: [PATCH 085/366] Fixing translation --- app/locales/locale-en.json | 3 +++ app/modules/projects/listing/listing.jade | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/locales/locale-en.json b/app/locales/locale-en.json index ff56f20e..9ce33468 100644 --- a/app/locales/locale-en.json +++ b/app/locales/locale-en.json @@ -249,6 +249,9 @@ "WORKING_ON_SECTION": "Working on", "WATCHING_SECTION": "Watching" }, + "PROJECTS": { + "MY_PROJECTS": "My projects" + }, "ATTACHMENT": { "SECTION_NAME": "attachments", "TITLE": "{{ fileName }} uploaded on {{ date }}", diff --git a/app/modules/projects/listing/listing.jade b/app/modules/projects/listing/listing.jade index f643a87a..452a7009 100644 --- a/app/modules/projects/listing/listing.jade +++ b/app/modules/projects/listing/listing.jade @@ -1,6 +1,6 @@ div.project-list-wrapper.centered div.project-list-title - h1 My projects + h1(translate="PROJECTS.MY_PROJECTS") div.create-options a.create-project-btn.button-green(href="#", ng-click="vm.newProject()", title="{{'PROJECT.NAVIGATION.ACTION_CREATE_PROJECT' | translate}}", translate="PROJECT.NAVIGATION.ACTION_CREATE_PROJECT") span(tg-import-project-button) From e080bf519631ef2724ce9fd07dfb47d811e252c5 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 28 Apr 2015 11:51:49 +0200 Subject: [PATCH 086/366] Updating names --- app/modules/feedback/feedback-service.coffee | 12 --- app/modules/home/duties/duty-directive.coffee | 37 ------- app/modules/home/home-directive.coffee | 26 ----- app/modules/home/home-service.coffee | 59 ----------- .../home/projects/list.directive.coffee | 18 ---- app/modules/home/projects/list.jade | 18 ---- app/modules/home/projects/styles/list.scss | 71 -------------- app/modules/home/styles/home-watching.scss | 15 --- app/modules/home/styles/home-working.scss | 58 ----------- app/modules/home/styles/home.scss | 24 ----- .../projects/listing/listing.directive.coffee | 42 -------- app/modules/projects/listing/listing.jade | 32 ------ .../projects/listing/styles/listing.scss | 97 ------------------- 13 files changed, 509 deletions(-) delete mode 100644 app/modules/feedback/feedback-service.coffee delete mode 100644 app/modules/home/duties/duty-directive.coffee delete mode 100644 app/modules/home/home-directive.coffee delete mode 100644 app/modules/home/home-service.coffee delete mode 100644 app/modules/home/projects/list.directive.coffee delete mode 100644 app/modules/home/projects/list.jade delete mode 100644 app/modules/home/projects/styles/list.scss delete mode 100644 app/modules/home/styles/home-watching.scss delete mode 100644 app/modules/home/styles/home-working.scss delete mode 100644 app/modules/home/styles/home.scss delete mode 100644 app/modules/projects/listing/listing.directive.coffee delete mode 100644 app/modules/projects/listing/listing.jade delete mode 100644 app/modules/projects/listing/styles/listing.scss diff --git a/app/modules/feedback/feedback-service.coffee b/app/modules/feedback/feedback-service.coffee deleted file mode 100644 index 86b8d6f9..00000000 --- a/app/modules/feedback/feedback-service.coffee +++ /dev/null @@ -1,12 +0,0 @@ -class FeedbackService extends taiga.Service - @.$inject = ["tgLightboxFactory"] - - constructor: (@lightboxFactory) -> - - - sendFeedback: -> - @lightboxFactory.create("tg-lb-feedback", { - "class": "lightbox lightbox-feedback lightbox-generic-form" - }) - -angular.module("taigaFeedback").service("tgFeedbackService", FeedbackService) diff --git a/app/modules/home/duties/duty-directive.coffee b/app/modules/home/duties/duty-directive.coffee deleted file mode 100644 index 539fafd4..00000000 --- a/app/modules/home/duties/duty-directive.coffee +++ /dev/null @@ -1,37 +0,0 @@ -DutyDirective = (navurls, projectsService, $translate) -> - link = (scope, el, attrs, ctrl) -> - scope.vm = {} - scope.vm.duty = scope.duty - - scope.vm.getDutyType = () -> - if scope.vm.duty - if scope.vm.duty._name == "userstories" - return $translate.instant("COMMON.USER_STORY") - if scope.vm.duty._name == "tasks" - return $translate.instant("COMMON.TASK") - if scope.vm.duty._name == "issues" - return $translate.instant("COMMON.ISSUE") - - scope.vm.getUrl = () -> - if scope.vm.duty - ctx = { - project: projectsService.projectsById.get(String(scope.vm.duty.project)).slug - ref: scope.vm.duty.ref - } - return navurls.resolve("project-#{scope.vm.duty._name}-detail", ctx) - - scope.vm.getProjectName = () -> - if scope.vm.duty - return projectsService.projectsById.get(String(scope.vm.duty.project)).name - - directive = { - templateUrl: "home/duties/duty.html" - scope: { - "duty": "=tgDuty" - } - link: link - } - - return directive - -angular.module("taigaHome").directive("tgDuty", ["$tgNavUrls", "tgProjectsService", "$translate", DutyDirective]) diff --git a/app/modules/home/home-directive.coffee b/app/modules/home/home-directive.coffee deleted file mode 100644 index a929d370..00000000 --- a/app/modules/home/home-directive.coffee +++ /dev/null @@ -1,26 +0,0 @@ -HomeDirective = (homeService) -> - link = (scope, el, attrs, ctrl) -> - scope.vm = {} - taiga.defineImmutableProperty(scope.vm, "workInProgress", () -> homeService.workInProgress) - - scope.$watch "vm.workInProgress", (workInProgress) -> - if workInProgress.size > 0 - userStories = workInProgress.get("assignedTo").get("userStories") - tasks = workInProgress.get("assignedTo").get("tasks") - issues = workInProgress.get("assignedTo").get("issues") - scope.vm.assignedTo = userStories.concat(tasks).concat(issues) - - userStories = workInProgress.get("watching").get("userStories") - tasks = workInProgress.get("watching").get("tasks") - issues = workInProgress.get("watching").get("issues") - scope.vm.watching = userStories.concat(tasks).concat(issues) - - directive = { - templateUrl: "home/home.html" - scope: {} - link: link - } - - return directive - -angular.module("taigaHome").directive("tgHome", ["tgHomeService", HomeDirective]) diff --git a/app/modules/home/home-service.coffee b/app/modules/home/home-service.coffee deleted file mode 100644 index a3b97fce..00000000 --- a/app/modules/home/home-service.coffee +++ /dev/null @@ -1,59 +0,0 @@ -class HomeService extends taiga.Service - @.$inject = ["$q", "$tgResources", "$rootScope", "$projectUrl"] - - constructor: (@q, @rs, @rootScope, @projectUrl) -> - @.workInProgress = Immutable.Map() - @.inProgress = false - - fetchWorkInProgress: (userId) -> - if not @.inProgress - @.inProgress = true - params = { - status__is_closed: false - assigned_to: userId - } - assignedUserStoriesPromise = @rs.userstories.listInAllProjects(params).then (userstories) => - @.assignedToUserStories = userstories - - assignedTasksPromise = @rs.tasks.listInAllProjects(params).then (tasks) => - @.assignedToTasks = tasks - - assignedIssuesPromise = @rs.issues.listInAllProjects(params).then (issues) => - @.assignedToIssues = issues - - params = { - status__is_closed: false - watchers: userId - } - watchingUserStoriesPromise = @rs.userstories.listInAllProjects(params).then (userstories) => - @.watchingUserStories = userstories - - watchingTasksPromise = @rs.tasks.listInAllProjects(params).then (tasks) => - @.watchingTasks = tasks - - watchingIssuesPromise = @rs.issues.listInAllProjects(params).then (issues) => - @.watchingIssues = issues - - workPromise = @q.all([assignedUserStoriesPromise, assignedTasksPromise, - assignedIssuesPromise, watchingUserStoriesPromise, - watchingUserStoriesPromise, watchingIssuesPromise]) - - workPromise.then => - @.workInProgress = Immutable.fromJS({ - assignedTo: { - userStories: @.assignedToUserStories - tasks: @.assignedToTasks - issues: @.assignedToIssues - } - watching: { - userStories: @.watchingUserStories - tasks: @.watchingTasks - issues: @.watchingIssues - } - }) - - @.inProgress = false - - return workPromise - -angular.module("taigaHome").service("tgHomeService", HomeService) diff --git a/app/modules/home/projects/list.directive.coffee b/app/modules/home/projects/list.directive.coffee deleted file mode 100644 index 2e6d250a..00000000 --- a/app/modules/home/projects/list.directive.coffee +++ /dev/null @@ -1,18 +0,0 @@ -HomeProjectListDirective = (projectsService) -> - link = (scope, el, attrs, ctrl) -> - scope.vm = {} - - taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.projects.get("recents")) - - scope.vm.newProject = -> - projectsService.newProject() - - directive = { - templateUrl: "home/projects/list.html" - scope: {} - link: link - } - - return directive - -angular.module("taigaHome").directive("tgHomeProjectList", ["tgProjectsService", HomeProjectListDirective]) diff --git a/app/modules/home/projects/list.jade b/app/modules/home/projects/list.jade deleted file mode 100644 index 5fadc4d1..00000000 --- a/app/modules/home/projects/list.jade +++ /dev/null @@ -1,18 +0,0 @@ -ul.home-project-list(ng-show="vm.projects.size") - li.home-project-list-single(tg-bind-scope, tg-repeat="project in vm.projects") - a(href="#", tg-nav="project:project=project.slug") - h2.home-project-list-single-title - span.project-name(ng-bind="::project.name", title="{{ ::project.name }}") - span.private(ng-if="project.is_private", title="{{'PROJECT.PRIVATE' | translate}}") - include ../../../svg/lock.svg - p {{ ::project.description | limitTo:150 }} - span(ng-if="::project.description.length > 150") ... -a.see-more-projects-btn.button-gray(ng-show="vm.projects.size", href="#", tg-nav="projects", title="{{'PROJECT.NAVIGATION.SEE_MORE_PROJECTS' | translate}}", translate="PROJECT.NAVIGATION.SEE_MORE_PROJECTS") -section.projects-empty(ng-hide="vm.projects.size") - include ../../../svg/empty-project.svg - p You don't have any projects yet - a.create-project-btn.button-green( - href="#", - ng-click="vm.newProject()", - title="{{'PROJECT.NAVIGATION.ACTION_CREATE_PROJECT' | translate}}", - translate="PROJECT.NAVIGATION.ACTION_CREATE_PROJECT") diff --git a/app/modules/home/projects/styles/list.scss b/app/modules/home/projects/styles/list.scss deleted file mode 100644 index 50909a97..00000000 --- a/app/modules/home/projects/styles/list.scss +++ /dev/null @@ -1,71 +0,0 @@ -.home-project-list { - li { - border-right: 5px solid $whitish; - cursor: pointer; - margin-bottom: .5rem; - padding: .5rem; - padding-right: 1rem; - text-overflow: ellipsis; - transition: border-color .3s linear; - &:hover { - border-color: $fresh-taiga; - p { - color: $gray; - } - .private path { - fill: $gray; - } - } - } - h2 { - @extend %text; - color: $gray; - font-size: 1.5rem; - line-height: 1.3; - margin-bottom: 0; - text-transform: none; - .project-name { - display: inline-block; - max-width: 90%; - overflow: hidden; - text-overflow: ellipsis; - vertical-align: middle; - white-space: nowrap; - } - .private { - display: inline-block; - margin-left: .3rem; - width: .5rem; - path { - fill: $gray-light; - transition: fill .3s linear; - } - } - } - p { - @extend %text; - @extend %small; - color: $gray-light; - margin: 0; - transition: color .3s linear; - } -} - -.projects-empty { - text-align: center; - svg { - height: 100px; - margin: 1rem auto; - text-align: center; - width: 100%; - path { - fill: $whitish; - } - } - p { - @extend %small; - } - .create-project-btn { - display: block; - } -} diff --git a/app/modules/home/styles/home-watching.scss b/app/modules/home/styles/home-watching.scss deleted file mode 100644 index e4f41429..00000000 --- a/app/modules/home/styles/home-watching.scss +++ /dev/null @@ -1,15 +0,0 @@ -.watching-empty { - padding: 5vh; - text-align: center; - svg { - margin: 2rem auto; - max-width: 160px; - text-align: center; - path { - fill: $whitish; - } - } - p { - @extend %small; - } -} diff --git a/app/modules/home/styles/home-working.scss b/app/modules/home/styles/home-working.scss deleted file mode 100644 index add730f1..00000000 --- a/app/modules/home/styles/home-working.scss +++ /dev/null @@ -1,58 +0,0 @@ -.working-on, -.watching { - margin-bottom: 2rem; - .duty-single { - align-items: center; - border-bottom: 1px solid $whitish; - cursor: pointer; - display: flex; - flex-direction: row; - padding: .5rem; - &:last-child { - border: 0; - } - &.blocked { - background: rgba($red-light, .2); - .duty-type, - .duty-status { - color: $red; - } - } - } - .avatar { - flex-basis: 47px; - height: 47px; - margin-right: .5rem; - width: 47px; - } - .duty-data { - flex: 1; - margin-right: .5rem; - } - .duty-type, - .duty-status { - @extend %small; - color: $gray; - margin-right: .3rem; - } - .duty-title { - display: block; - margin-top: .25rem; - } - .duty-id { - color: $gray-light; - margin-right: .3rem; - } - .duty-project { - @extend %small; - align-self: flex-start; - color: $gray-light; - margin-left: auto; - text-align: right; - width: 120px; - } - .see-more { - display: block; - margin: 2rem 30%; - } -} diff --git a/app/modules/home/styles/home.scss b/app/modules/home/styles/home.scss deleted file mode 100644 index a6aad27b..00000000 --- a/app/modules/home/styles/home.scss +++ /dev/null @@ -1,24 +0,0 @@ -.home-wrapper { - display: flex; - padding-top: 2rem; - .duty-summary { - flex: 1; - margin-right: 2rem; - } - .project-list { - width: 250px; - } - .see-more-projects-btn { - display: block; - margin-top: 2rem; - } - .title-bar { - @extend %title; - @extend %larger; - align-content: center; - background: $whitish; - display: flex; - margin: 0 0 .5rem; - padding: .9rem 1rem; - } -} diff --git a/app/modules/projects/listing/listing.directive.coffee b/app/modules/projects/listing/listing.directive.coffee deleted file mode 100644 index 7d50458e..00000000 --- a/app/modules/projects/listing/listing.directive.coffee +++ /dev/null @@ -1,42 +0,0 @@ -ProjectsListingDirective = (projectsService) -> - link = (scope, el, attrs, ctrl) -> - scope.vm = {} - itemEl = null - tdom = el.find(".js-sortable") - - tdom.sortable({ - dropOnEmpty: true - revert: 200 - axis: "y" - opacity: .95 - placeholder: 'placeholder' - }) - - tdom.on "sortstop", (event, ui) -> - itemEl = ui.item - project = itemEl.scope().project - index = itemEl.index() - - sorted_project_ids = _.map(scope.vm.projects.toArray(), (p) -> p.id) - sorted_project_ids = _.without(sorted_project_ids, project.id) - sorted_project_ids.splice(index, 0, project.id) - sortData = [] - for value, index in sorted_project_ids - sortData.push({"project_id": value, "order":index}) - - projectsService.bulkUpdateProjectsOrder(sortData) - - taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.projects.get("all")) - - scope.vm.newProject = -> - projectsService.newProject() - - directive = { - templateUrl: "projects/listing/listing.html" - scope: {} - link: link - } - - return directive - -angular.module("taigaProjects").directive("tgProjectsListing", ["tgProjectsService", ProjectsListingDirective]) diff --git a/app/modules/projects/listing/listing.jade b/app/modules/projects/listing/listing.jade deleted file mode 100644 index 452a7009..00000000 --- a/app/modules/projects/listing/listing.jade +++ /dev/null @@ -1,32 +0,0 @@ -div.project-list-wrapper.centered - div.project-list-title - h1(translate="PROJECTS.MY_PROJECTS") - div.create-options - a.create-project-btn.button-green(href="#", ng-click="vm.newProject()", title="{{'PROJECT.NAVIGATION.ACTION_CREATE_PROJECT' | translate}}", translate="PROJECT.NAVIGATION.ACTION_CREATE_PROJECT") - span(tg-import-project-button) - a.button-blackish.import-project-button(href="", title="{{'PROJECT.NAVIGATION.TITLE_ACTION_IMPORT' | translate}}") - span.icon.icon-upload - input.import-file.hidden(type="file") - - section.project-list-section - div.project-list - ul.js-sortable - li.project-list-single(tg-bind-scope, tg-repeat="project in vm.projects") - div.project-list-single-left - div.project-list-single-title - h1 - a(href="#", tg-nav="project:project=project.slug") - h1.project-name(ng-bind="::project.name", title="{{ ::project.name }}") - span.private(ng-if="project.is_private", title="{{'PROJECT.PRIVATE' | translate}}") - include ../../../svg/lock.svg - p {{ ::project.description | limitTo:300 }} - span(ng-if="::project.description.length > 300") ... - - div.project-list-single-tags.tags-container(ng-if="::project.tags") - div.tags-block(tg-colorize-tags="project.tags", tg-colorize-tags-type="backlog") - - div.project-list-single-right - span.drag.icon.icon-drag-v - - aside.help-area - p(translate="PROJECT.HELP") diff --git a/app/modules/projects/listing/styles/listing.scss b/app/modules/projects/listing/styles/listing.scss deleted file mode 100644 index 3a2a5d08..00000000 --- a/app/modules/projects/listing/styles/listing.scss +++ /dev/null @@ -1,97 +0,0 @@ -.project-list-single { - border-bottom: 1px solid $whitish; - display: flex; - justify-content: center; - padding: .5rem; - position: relative; -} - -.project-list-single-left, -.project-list-single-right { - display: flex; - flex-direction: column; -} - -.project-list-single-left { - align-content: space-between; - flex: 4; - h1 { - @extend %text; - @extend %large; - display: inline-block; - margin-bottom: 0; - text-transform: none; - } - .project-name { - @extend %text; - @extend %larger; - color: $gray; - vertical-align: middle; - white-space: nowrap; - } - .private { - display: inline-block; - margin-left: .3rem; - width: .5rem; - path { - fill: $gray-light; - transition: fill .3s linear; - } - } - p { - @extend %text; - @extend %small; - color: $gray; - margin-bottom: 0; - max-width: 95%; - } - .project-list-single-tags { - align-content: flex-end; - display: flex; - flex: 3; - flex-wrap: wrap; - justify-content: flex-start; - margin-top: .5rem; - } - .tag { - align-self: flex-end; - margin-right: .5rem; - padding: .5rem; - } -} - -.project-list-single-right { - justify-content: space-between; - .project-list-single-stats { - align-self: flex-end; - display: flex; - div { - color: $gray-light; - margin-right: .5rem; - .icon { - margin-right: .2rem; - vertical-align: center; - } - } - .active { - .icon { - color: $star-fill; - } - } - } - .project-list-single-members { - align-self: flex-end; - display: flex; - flex-grow: 0; - flex-wrap: wrap; - margin-top: 1rem; - max-width: 160px; - a { - display: block; - } - img { - margin-right: .3rem; - width: 34px; - } - } -} From acebe5918dc77b1e10a8b01f0c3193b65b1b9437 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 28 Apr 2015 12:49:39 +0200 Subject: [PATCH 087/366] projects.service tests --- app/modules/projects/projects.service.coffee | 15 +- .../projects/projects.service.spec.coffee | 149 ++++++++++++++++++ 2 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 app/modules/projects/projects.service.spec.coffee diff --git a/app/modules/projects/projects.service.coffee b/app/modules/projects/projects.service.coffee index 0248b8c6..f8d9a474 100644 --- a/app/modules/projects/projects.service.coffee +++ b/app/modules/projects/projects.service.coffee @@ -2,18 +2,19 @@ taiga = @.taiga groupBy = @.taiga.groupBy class ProjectsService extends taiga.Service - @.$inject = ["$q", "$tgResources", "$rootScope", "$projectUrl", "tgLightboxFactory"] + @.$inject = ["$tgResources", "$rootScope", "$projectUrl", "tgLightboxFactory"] - constructor: (@q, @rs, @rootScope, @projectUrl, @lightboxFactory) -> + constructor: (@rs, @rootScope, @projectUrl, @lightboxFactory) -> @.projects = Immutable.Map() @.projectsById = Immutable.Map() - @.inProgress = false + @._inProgress = false @.projectsPromise = null @.fetchProjects() fetchProjects: -> - if not @.inProgress - @.inProgress = true + if not @._inProgress + @._inProgress = true + @.projectsPromise = @rs.projects.listByMember(@rootScope.user?.id).then (projects) => for project in projects project.url = @projectUrl.get(project) @@ -25,10 +26,10 @@ class ProjectsService extends taiga.Service @.projectsById = Immutable.fromJS(groupBy(projects, (p) -> p.id)) + @._inProgress = false + return @.projects - @.projectsPromise.then () => - @.inProgress = false return @.projectsPromise diff --git a/app/modules/projects/projects.service.spec.coffee b/app/modules/projects/projects.service.spec.coffee new file mode 100644 index 00000000..5b3f95ac --- /dev/null +++ b/app/modules/projects/projects.service.spec.coffee @@ -0,0 +1,149 @@ +describe "tgProjects", -> + projectsService = provide = null + mocks = {} + + _mockResources = () -> + mocks.resources = {} + + mocks.resources.projects = { + listByMember: sinon.stub() + } + + mocks.thenStub = sinon.stub() + mocks.resources.projects.listByMember.withArgs(10).returns({ + then: mocks.thenStub + }) + + provide.value "$tgResources", mocks.resources + + _mockRootScope = () -> + provide.value "$rootScope", {user: {id: 10}} + + _mockProjectUrl = () -> + mocks.projectUrl = {get: sinon.stub()} + + mocks.projectUrl.get = (project) -> + return "url-" + project.id + + provide.value "$projectUrl", mocks.projectUrl + + _mockLightboxFactory = () -> + mocks.lightboxFactory = { + create: sinon.stub() + } + + provide.value "tgLightboxFactory", mocks.lightboxFactory + + _inject = (callback) -> + inject (_tgProjects_) -> + projectsService = _tgProjects_ + callback() if callback + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockResources() + _mockRootScope() + _mockProjectUrl() + _mockLightboxFactory() + + return null + + _setup = -> + _mocks() + + beforeEach -> + module "taigaProjects" + _setup() + _inject() + + describe "fetch items", -> + projects = [] + + beforeEach -> + projects = [ + {"id": 1}, + {"id": 2}, + {"id": 3}, + {"id": 4}, + {"id": 5}, + {"id": 6}, + {"id": 7}, + {"id": 8}, + {"id": 9}, + {"id": 10}, + {"id": 11}, + {"id": 12}, + ] + + it "all & recents filled", () -> + mocks.thenStub.callArg(0, projects) + + expect(projectsService.projects.get("all").toJS()).to.be.eql(projects) + expect(projectsService.projects.get("recents").toJS()).to.be.eql(projects.slice(0, 10)) + + it "_inProgress change to false when tgResources end", () -> + expect(projectsService._inProgress).to.be.true + + mocks.thenStub.callArg(0, projects) + + expect(projectsService._inProgress).to.be.false + + it "_inProgress prevent new ajax calls", () -> + projectsService.fetchProjects() + projectsService.fetchProjects() + + mocks.thenStub.callArg(0, projects) + + expect(mocks.resources.projects.listByMember).have.been.calledOnce + + it "group projects by id", () -> + mocks.thenStub.callArg(0, projects) + + expect(projectsService.projectsById.size).to.be.equal(12) + expect(projectsService.projectsById.toJS()[1].id).to.be.equal(projects[0].id) + + it "add urls in the project object", () -> + mocks.thenStub.callArg(0, projects) + + expect(projectsService.projectsById.toJS()[1].url).to.be.equal("url-1") + expect(projectsService.projects.get("all").toJS()[0].url).to.be.equal("url-1") + expect(projectsService.projects.get("recents").toJS()[0].url).to.be.equal("url-1") + + it "newProject, create the wizard lightbox", () -> + projectsService.newProject() + + expect(mocks.lightboxFactory.create).to.have.been.calledWith("tg-lb-create-project", { + "class": "wizard-create-project" + }) + + it "bulkUpdateProjectsOrder and then fetch projects again", () -> + projects_order = [ + {"id": 8}, + {"id": 2}, + {"id": 3}, + {"id": 9}, + {"id": 1}, + {"id": 4}, + {"id": 10}, + {"id": 5}, + {"id": 6}, + {"id": 7}, + {"id": 11}, + {"id": 12}, + ] + + thenStub = sinon.stub() + + mocks.resources.projects.bulkUpdateOrder = sinon.stub() + mocks.resources.projects.bulkUpdateOrder.withArgs(projects_order).returns({ + then: thenStub + }) + + projectsService.fetchProjects = sinon.stub() + + projectsService.bulkUpdateProjectsOrder(projects_order) + + thenStub.callArg(0) + + expect(projectsService.fetchProjects).to.have.been.calledOnce From 5bedf76d966eb2e3e88b41a3e5f287c47accb374 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 28 Apr 2015 12:51:06 +0200 Subject: [PATCH 088/366] rename projects service spec --- app/modules/projects/projects.service.spec.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/modules/projects/projects.service.spec.coffee b/app/modules/projects/projects.service.spec.coffee index 5b3f95ac..1dbc1f57 100644 --- a/app/modules/projects/projects.service.spec.coffee +++ b/app/modules/projects/projects.service.spec.coffee @@ -35,8 +35,8 @@ describe "tgProjects", -> provide.value "tgLightboxFactory", mocks.lightboxFactory _inject = (callback) -> - inject (_tgProjects_) -> - projectsService = _tgProjects_ + inject (_tgProjectsService_) -> + projectsService = _tgProjectsService_ callback() if callback _mocks = () -> From fdabf6514cb0bad2e81b7181ed2d85a857c173eb Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 28 Apr 2015 14:32:50 +0200 Subject: [PATCH 089/366] lightbox-factory spec --- .../lightbox-factory.service.spec.coffee | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 app/modules/services/lightbox-factory.service.spec.coffee diff --git a/app/modules/services/lightbox-factory.service.spec.coffee b/app/modules/services/lightbox-factory.service.spec.coffee new file mode 100644 index 00000000..c939e66b --- /dev/null +++ b/app/modules/services/lightbox-factory.service.spec.coffee @@ -0,0 +1,71 @@ +describe "tgLightboxFactory", -> + lightboxFactoryService = provide = null + mocks = {} + + _mockRootScope = () -> + mocks.rootScope = sinon.stub() + provide.value "$rootScope", {$new: mocks.rootScope} + + _mockCompile = () -> + mocks.compile = sinon.stub() + fn = () -> "

fake

" + mocks.compile.returns(fn) + provide.value "$compile", mocks.compile + + _inject = (callback) -> + inject (_tgLightboxFactory_) -> + lightboxFactoryService = _tgLightboxFactory_ + callback() if callback + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockRootScope() + _mockCompile() + + return null + + _setup = -> + _mocks() + + beforeEach -> + module "taigaCommon" + _setup() + _inject() + + it "insert directive", () -> + lightboxFactoryService.create("fake-directive") + + expect($(document.body).find("#fake")).to.have.length(1) + + it "directive must have the tg-bind-scope directive", () -> + lightboxFactoryService.create("fake-directive") + + checkDirective = sinon.match ( (value) -> + return value.attr("tg-bind-scope") + ), "checkDirective" + + expect(mocks.compile.withArgs(checkDirective)).to.have.been.calledOnce + + it "custom attributes", () -> + attrs = { + "class": "x1", + "id": "x2" + } + + lightboxFactoryService.create("fake-directive", attrs) + + checkAttributes = sinon.match ( (value) -> + return value.attr("class") == "x1" && value.attr("id") == "x2" + ), "checkAttributes" + + expect(mocks.compile.withArgs(checkAttributes)).to.have.been.calledOnce + + it "directive has class remove-on-close", () -> + lightboxFactoryService.create("fake-directive") + + checkClass = sinon.match ( (value) -> + return value.hasClass("remove-on-close") + ), "checkClass" + + expect(mocks.compile.withArgs(checkClass)).to.have.been.calledOnce From bdf97faf50428f7e216b0504bf24af80b90d9bb9 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 28 Apr 2015 14:55:02 +0200 Subject: [PATCH 090/366] Fixing duy --- app/modules/home/duties/duty.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/home/duties/duty.jade b/app/modules/home/duties/duty.jade index 6304f47a..51804629 100644 --- a/app/modules/home/duties/duty.jade +++ b/app/modules/home/duties/duty.jade @@ -5,7 +5,7 @@ img.avatar( div.duty-data div span.duty-type {{ ::vm.getDutyType() }} - span.duty-status(ng-style="{'color': vm.duty.status_color}") {{ ::vm.duty.status_name }} + span.duty-status(ng-style="{'color': vm.duty.status_extra_info.color}") {{ ::vm.duty.status_extra_info.name }} a.duty-title(href="{{ ::vm.getUrl() }}") span.duty-id(tg-bo-ref="duty.ref") span.duty-name {{ ::duty.subject }} From 6815a9c8d093da30b5df7b7243f8ce3dd41be8b1 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 28 Apr 2015 14:56:28 +0200 Subject: [PATCH 091/366] Adding lost files --- app/modules/feedback/feedback.service.coffee | 12 +++ app/modules/home/duties/duty.directive.coffee | 37 +++++++ app/modules/home/duties/duty.scss | 73 ++++++++++++++ app/modules/home/home.directive.coffee | 26 +++++ app/modules/home/home.scss | 24 +++++ app/modules/home/home.service.coffee | 59 +++++++++++ .../home-project-list.directive.coffee | 18 ++++ .../home/projects/home-project-list.jade | 18 ++++ .../home/projects/home-project-list.scss | 71 ++++++++++++++ .../listing/projects-listing.directive.coffee | 42 ++++++++ .../projects/listing/projects-listing.jade | 32 ++++++ .../projects/listing/projects-listing.scss | 97 +++++++++++++++++++ 12 files changed, 509 insertions(+) create mode 100644 app/modules/feedback/feedback.service.coffee create mode 100644 app/modules/home/duties/duty.directive.coffee create mode 100644 app/modules/home/duties/duty.scss create mode 100644 app/modules/home/home.directive.coffee create mode 100644 app/modules/home/home.scss create mode 100644 app/modules/home/home.service.coffee create mode 100644 app/modules/home/projects/home-project-list.directive.coffee create mode 100644 app/modules/home/projects/home-project-list.jade create mode 100644 app/modules/home/projects/home-project-list.scss create mode 100644 app/modules/projects/listing/projects-listing.directive.coffee create mode 100644 app/modules/projects/listing/projects-listing.jade create mode 100644 app/modules/projects/listing/projects-listing.scss diff --git a/app/modules/feedback/feedback.service.coffee b/app/modules/feedback/feedback.service.coffee new file mode 100644 index 00000000..86b8d6f9 --- /dev/null +++ b/app/modules/feedback/feedback.service.coffee @@ -0,0 +1,12 @@ +class FeedbackService extends taiga.Service + @.$inject = ["tgLightboxFactory"] + + constructor: (@lightboxFactory) -> + + + sendFeedback: -> + @lightboxFactory.create("tg-lb-feedback", { + "class": "lightbox lightbox-feedback lightbox-generic-form" + }) + +angular.module("taigaFeedback").service("tgFeedbackService", FeedbackService) diff --git a/app/modules/home/duties/duty.directive.coffee b/app/modules/home/duties/duty.directive.coffee new file mode 100644 index 00000000..539fafd4 --- /dev/null +++ b/app/modules/home/duties/duty.directive.coffee @@ -0,0 +1,37 @@ +DutyDirective = (navurls, projectsService, $translate) -> + link = (scope, el, attrs, ctrl) -> + scope.vm = {} + scope.vm.duty = scope.duty + + scope.vm.getDutyType = () -> + if scope.vm.duty + if scope.vm.duty._name == "userstories" + return $translate.instant("COMMON.USER_STORY") + if scope.vm.duty._name == "tasks" + return $translate.instant("COMMON.TASK") + if scope.vm.duty._name == "issues" + return $translate.instant("COMMON.ISSUE") + + scope.vm.getUrl = () -> + if scope.vm.duty + ctx = { + project: projectsService.projectsById.get(String(scope.vm.duty.project)).slug + ref: scope.vm.duty.ref + } + return navurls.resolve("project-#{scope.vm.duty._name}-detail", ctx) + + scope.vm.getProjectName = () -> + if scope.vm.duty + return projectsService.projectsById.get(String(scope.vm.duty.project)).name + + directive = { + templateUrl: "home/duties/duty.html" + scope: { + "duty": "=tgDuty" + } + link: link + } + + return directive + +angular.module("taigaHome").directive("tgDuty", ["$tgNavUrls", "tgProjectsService", "$translate", DutyDirective]) diff --git a/app/modules/home/duties/duty.scss b/app/modules/home/duties/duty.scss new file mode 100644 index 00000000..20725659 --- /dev/null +++ b/app/modules/home/duties/duty.scss @@ -0,0 +1,73 @@ +.working-on, +.watching { + margin-bottom: 2rem; + .duty-single { + align-items: center; + border-bottom: 1px solid $whitish; + cursor: pointer; + display: flex; + flex-direction: row; + padding: .5rem; + &:last-child { + border: 0; + } + &.blocked { + background: rgba($red-light, .2); + .duty-type, + .duty-status { + color: $red; + } + } + } + .avatar { + flex-basis: 47px; + height: 47px; + margin-right: .5rem; + width: 47px; + } + .duty-data { + flex: 1; + margin-right: .5rem; + } + .duty-type, + .duty-status { + @extend %small; + color: $gray; + margin-right: .3rem; + } + .duty-title { + display: block; + margin-top: .25rem; + } + .duty-id { + color: $gray-light; + margin-right: .3rem; + } + .duty-project { + @extend %small; + align-self: flex-start; + color: $gray-light; + margin-left: auto; + text-align: right; + width: 120px; + } + .see-more { + display: block; + margin: 2rem 30%; + } +} +.watching-empty { + padding: 5vh; + text-align: center; + svg { + margin: 2rem auto; + max-width: 160px; + text-align: center; + path { + fill: $whitish; + } + } + p { + @extend %small; + } +} diff --git a/app/modules/home/home.directive.coffee b/app/modules/home/home.directive.coffee new file mode 100644 index 00000000..a929d370 --- /dev/null +++ b/app/modules/home/home.directive.coffee @@ -0,0 +1,26 @@ +HomeDirective = (homeService) -> + link = (scope, el, attrs, ctrl) -> + scope.vm = {} + taiga.defineImmutableProperty(scope.vm, "workInProgress", () -> homeService.workInProgress) + + scope.$watch "vm.workInProgress", (workInProgress) -> + if workInProgress.size > 0 + userStories = workInProgress.get("assignedTo").get("userStories") + tasks = workInProgress.get("assignedTo").get("tasks") + issues = workInProgress.get("assignedTo").get("issues") + scope.vm.assignedTo = userStories.concat(tasks).concat(issues) + + userStories = workInProgress.get("watching").get("userStories") + tasks = workInProgress.get("watching").get("tasks") + issues = workInProgress.get("watching").get("issues") + scope.vm.watching = userStories.concat(tasks).concat(issues) + + directive = { + templateUrl: "home/home.html" + scope: {} + link: link + } + + return directive + +angular.module("taigaHome").directive("tgHome", ["tgHomeService", HomeDirective]) diff --git a/app/modules/home/home.scss b/app/modules/home/home.scss new file mode 100644 index 00000000..a6aad27b --- /dev/null +++ b/app/modules/home/home.scss @@ -0,0 +1,24 @@ +.home-wrapper { + display: flex; + padding-top: 2rem; + .duty-summary { + flex: 1; + margin-right: 2rem; + } + .project-list { + width: 250px; + } + .see-more-projects-btn { + display: block; + margin-top: 2rem; + } + .title-bar { + @extend %title; + @extend %larger; + align-content: center; + background: $whitish; + display: flex; + margin: 0 0 .5rem; + padding: .9rem 1rem; + } +} diff --git a/app/modules/home/home.service.coffee b/app/modules/home/home.service.coffee new file mode 100644 index 00000000..a3b97fce --- /dev/null +++ b/app/modules/home/home.service.coffee @@ -0,0 +1,59 @@ +class HomeService extends taiga.Service + @.$inject = ["$q", "$tgResources", "$rootScope", "$projectUrl"] + + constructor: (@q, @rs, @rootScope, @projectUrl) -> + @.workInProgress = Immutable.Map() + @.inProgress = false + + fetchWorkInProgress: (userId) -> + if not @.inProgress + @.inProgress = true + params = { + status__is_closed: false + assigned_to: userId + } + assignedUserStoriesPromise = @rs.userstories.listInAllProjects(params).then (userstories) => + @.assignedToUserStories = userstories + + assignedTasksPromise = @rs.tasks.listInAllProjects(params).then (tasks) => + @.assignedToTasks = tasks + + assignedIssuesPromise = @rs.issues.listInAllProjects(params).then (issues) => + @.assignedToIssues = issues + + params = { + status__is_closed: false + watchers: userId + } + watchingUserStoriesPromise = @rs.userstories.listInAllProjects(params).then (userstories) => + @.watchingUserStories = userstories + + watchingTasksPromise = @rs.tasks.listInAllProjects(params).then (tasks) => + @.watchingTasks = tasks + + watchingIssuesPromise = @rs.issues.listInAllProjects(params).then (issues) => + @.watchingIssues = issues + + workPromise = @q.all([assignedUserStoriesPromise, assignedTasksPromise, + assignedIssuesPromise, watchingUserStoriesPromise, + watchingUserStoriesPromise, watchingIssuesPromise]) + + workPromise.then => + @.workInProgress = Immutable.fromJS({ + assignedTo: { + userStories: @.assignedToUserStories + tasks: @.assignedToTasks + issues: @.assignedToIssues + } + watching: { + userStories: @.watchingUserStories + tasks: @.watchingTasks + issues: @.watchingIssues + } + }) + + @.inProgress = false + + return workPromise + +angular.module("taigaHome").service("tgHomeService", HomeService) diff --git a/app/modules/home/projects/home-project-list.directive.coffee b/app/modules/home/projects/home-project-list.directive.coffee new file mode 100644 index 00000000..3bcbe7d9 --- /dev/null +++ b/app/modules/home/projects/home-project-list.directive.coffee @@ -0,0 +1,18 @@ +HomeProjectListDirective = (projectsService) -> + link = (scope, el, attrs, ctrl) -> + scope.vm = {} + + taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.projects.get("recents")) + + scope.vm.newProject = -> + projectsService.newProject() + + directive = { + templateUrl: "home/projects/home-project-list.html" + scope: {} + link: link + } + + return directive + +angular.module("taigaHome").directive("tgHomeProjectList", ["tgProjectsService", HomeProjectListDirective]) diff --git a/app/modules/home/projects/home-project-list.jade b/app/modules/home/projects/home-project-list.jade new file mode 100644 index 00000000..5fadc4d1 --- /dev/null +++ b/app/modules/home/projects/home-project-list.jade @@ -0,0 +1,18 @@ +ul.home-project-list(ng-show="vm.projects.size") + li.home-project-list-single(tg-bind-scope, tg-repeat="project in vm.projects") + a(href="#", tg-nav="project:project=project.slug") + h2.home-project-list-single-title + span.project-name(ng-bind="::project.name", title="{{ ::project.name }}") + span.private(ng-if="project.is_private", title="{{'PROJECT.PRIVATE' | translate}}") + include ../../../svg/lock.svg + p {{ ::project.description | limitTo:150 }} + span(ng-if="::project.description.length > 150") ... +a.see-more-projects-btn.button-gray(ng-show="vm.projects.size", href="#", tg-nav="projects", title="{{'PROJECT.NAVIGATION.SEE_MORE_PROJECTS' | translate}}", translate="PROJECT.NAVIGATION.SEE_MORE_PROJECTS") +section.projects-empty(ng-hide="vm.projects.size") + include ../../../svg/empty-project.svg + p You don't have any projects yet + a.create-project-btn.button-green( + href="#", + ng-click="vm.newProject()", + title="{{'PROJECT.NAVIGATION.ACTION_CREATE_PROJECT' | translate}}", + translate="PROJECT.NAVIGATION.ACTION_CREATE_PROJECT") diff --git a/app/modules/home/projects/home-project-list.scss b/app/modules/home/projects/home-project-list.scss new file mode 100644 index 00000000..50909a97 --- /dev/null +++ b/app/modules/home/projects/home-project-list.scss @@ -0,0 +1,71 @@ +.home-project-list { + li { + border-right: 5px solid $whitish; + cursor: pointer; + margin-bottom: .5rem; + padding: .5rem; + padding-right: 1rem; + text-overflow: ellipsis; + transition: border-color .3s linear; + &:hover { + border-color: $fresh-taiga; + p { + color: $gray; + } + .private path { + fill: $gray; + } + } + } + h2 { + @extend %text; + color: $gray; + font-size: 1.5rem; + line-height: 1.3; + margin-bottom: 0; + text-transform: none; + .project-name { + display: inline-block; + max-width: 90%; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: middle; + white-space: nowrap; + } + .private { + display: inline-block; + margin-left: .3rem; + width: .5rem; + path { + fill: $gray-light; + transition: fill .3s linear; + } + } + } + p { + @extend %text; + @extend %small; + color: $gray-light; + margin: 0; + transition: color .3s linear; + } +} + +.projects-empty { + text-align: center; + svg { + height: 100px; + margin: 1rem auto; + text-align: center; + width: 100%; + path { + fill: $whitish; + } + } + p { + @extend %small; + } + .create-project-btn { + display: block; + } +} diff --git a/app/modules/projects/listing/projects-listing.directive.coffee b/app/modules/projects/listing/projects-listing.directive.coffee new file mode 100644 index 00000000..b88e72cd --- /dev/null +++ b/app/modules/projects/listing/projects-listing.directive.coffee @@ -0,0 +1,42 @@ +ProjectsListingDirective = (projectsService) -> + link = (scope, el, attrs, ctrl) -> + scope.vm = {} + itemEl = null + tdom = el.find(".js-sortable") + + tdom.sortable({ + dropOnEmpty: true + revert: 200 + axis: "y" + opacity: .95 + placeholder: 'placeholder' + }) + + tdom.on "sortstop", (event, ui) -> + itemEl = ui.item + project = itemEl.scope().project + index = itemEl.index() + + sorted_project_ids = _.map(scope.vm.projects.toArray(), (p) -> p.id) + sorted_project_ids = _.without(sorted_project_ids, project.id) + sorted_project_ids.splice(index, 0, project.id) + sortData = [] + for value, index in sorted_project_ids + sortData.push({"project_id": value, "order":index}) + + projectsService.bulkUpdateProjectsOrder(sortData) + + taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.projects.get("all")) + + scope.vm.newProject = -> + projectsService.newProject() + + directive = { + templateUrl: "projects/listing/projects-listing.html" + scope: {} + link: link + } + + return directive + +angular.module("taigaProjects").directive("tgProjectsListing", ["tgProjectsService", ProjectsListingDirective]) diff --git a/app/modules/projects/listing/projects-listing.jade b/app/modules/projects/listing/projects-listing.jade new file mode 100644 index 00000000..452a7009 --- /dev/null +++ b/app/modules/projects/listing/projects-listing.jade @@ -0,0 +1,32 @@ +div.project-list-wrapper.centered + div.project-list-title + h1(translate="PROJECTS.MY_PROJECTS") + div.create-options + a.create-project-btn.button-green(href="#", ng-click="vm.newProject()", title="{{'PROJECT.NAVIGATION.ACTION_CREATE_PROJECT' | translate}}", translate="PROJECT.NAVIGATION.ACTION_CREATE_PROJECT") + span(tg-import-project-button) + a.button-blackish.import-project-button(href="", title="{{'PROJECT.NAVIGATION.TITLE_ACTION_IMPORT' | translate}}") + span.icon.icon-upload + input.import-file.hidden(type="file") + + section.project-list-section + div.project-list + ul.js-sortable + li.project-list-single(tg-bind-scope, tg-repeat="project in vm.projects") + div.project-list-single-left + div.project-list-single-title + h1 + a(href="#", tg-nav="project:project=project.slug") + h1.project-name(ng-bind="::project.name", title="{{ ::project.name }}") + span.private(ng-if="project.is_private", title="{{'PROJECT.PRIVATE' | translate}}") + include ../../../svg/lock.svg + p {{ ::project.description | limitTo:300 }} + span(ng-if="::project.description.length > 300") ... + + div.project-list-single-tags.tags-container(ng-if="::project.tags") + div.tags-block(tg-colorize-tags="project.tags", tg-colorize-tags-type="backlog") + + div.project-list-single-right + span.drag.icon.icon-drag-v + + aside.help-area + p(translate="PROJECT.HELP") diff --git a/app/modules/projects/listing/projects-listing.scss b/app/modules/projects/listing/projects-listing.scss new file mode 100644 index 00000000..3a2a5d08 --- /dev/null +++ b/app/modules/projects/listing/projects-listing.scss @@ -0,0 +1,97 @@ +.project-list-single { + border-bottom: 1px solid $whitish; + display: flex; + justify-content: center; + padding: .5rem; + position: relative; +} + +.project-list-single-left, +.project-list-single-right { + display: flex; + flex-direction: column; +} + +.project-list-single-left { + align-content: space-between; + flex: 4; + h1 { + @extend %text; + @extend %large; + display: inline-block; + margin-bottom: 0; + text-transform: none; + } + .project-name { + @extend %text; + @extend %larger; + color: $gray; + vertical-align: middle; + white-space: nowrap; + } + .private { + display: inline-block; + margin-left: .3rem; + width: .5rem; + path { + fill: $gray-light; + transition: fill .3s linear; + } + } + p { + @extend %text; + @extend %small; + color: $gray; + margin-bottom: 0; + max-width: 95%; + } + .project-list-single-tags { + align-content: flex-end; + display: flex; + flex: 3; + flex-wrap: wrap; + justify-content: flex-start; + margin-top: .5rem; + } + .tag { + align-self: flex-end; + margin-right: .5rem; + padding: .5rem; + } +} + +.project-list-single-right { + justify-content: space-between; + .project-list-single-stats { + align-self: flex-end; + display: flex; + div { + color: $gray-light; + margin-right: .5rem; + .icon { + margin-right: .2rem; + vertical-align: center; + } + } + .active { + .icon { + color: $star-fill; + } + } + } + .project-list-single-members { + align-self: flex-end; + display: flex; + flex-grow: 0; + flex-wrap: wrap; + margin-top: 1rem; + max-width: 160px; + a { + display: block; + } + img { + margin-right: .3rem; + width: 34px; + } + } +} From c9bc100cc376f5814e984d375f699a102f64b572 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 29 Apr 2015 08:35:35 +0200 Subject: [PATCH 092/366] Fixing titles --- app/modules/home/home-page.controller.coffee | 19 ++++++++++++------- app/modules/home/home.jade | 4 ++-- .../projects/projects-page.controller.coffee | 5 ++++- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/app/modules/home/home-page.controller.coffee b/app/modules/home/home-page.controller.coffee index ae45ba75..7e813b05 100644 --- a/app/modules/home/home-page.controller.coffee +++ b/app/modules/home/home-page.controller.coffee @@ -20,20 +20,25 @@ class ProjectsPageController extends taiga.Controller constructor: (@scope, @q, @rs, @rootscope, @navUrls, @auth, @location, @appTitle, @projectUrl, @config, tgLoader, @projectsService, @homeService, @translate) -> - @appTitle.set(@translate.instant("PROJECT.WELCOME")) if !@auth.isAuthenticated() @location.path(@navUrls.resolve("login")) - #Projects - projectsPromise = @projectsService.fetchProjects() + promise = @.loadInitialData() - #In progress work - user = @auth.getUser() - workInProgressPromise = @homeService.fetchWorkInProgress(user.id) + # On Success + promise.then => + @appTitle.set(@translate.instant("PROJECT.WELCOME")) # Finally - @q.all([projectsPromise, workInProgressPromise]).finally tgLoader.pageLoaded + promise.finally tgLoader.pageLoaded + loadInitialData: -> + user = @auth.getUser() + #Projects + promise = @projectsService.fetchProjects() + return promise.then () => + #In progress work + return @homeService.fetchWorkInProgress(user.id) angular.module("taigaHome").controller("HomePage", ProjectsPageController) diff --git a/app/modules/home/home.jade b/app/modules/home/home.jade index b05ea7ab..40f04c9d 100644 --- a/app/modules/home/home.jade +++ b/app/modules/home/home.jade @@ -3,8 +3,8 @@ doctype html include ../../partials/includes/components/beta div.home-wrapper.centered div.duty-summary - div.title-bar.working-on-title(ng-show="vm.assignedTo", translate="HOME.WORKING_ON_SECTION") - section.working-on(ng-show="vm.assignedTo") + div.title-bar.working-on-title(ng-show="vm.assignedTo.size", translate="HOME.WORKING_ON_SECTION") + section.working-on(ng-show="vm.assignedTo.size") div.duty-single(tg-duty="duty", tg-repeat="duty in vm.assignedTo", ng-class="{blocked: duty.is_blocked}") div.title-bar.watching-title(translate="HOME.WATCHING_SECTION") diff --git a/app/modules/projects/projects-page.controller.coffee b/app/modules/projects/projects-page.controller.coffee index 67f60490..c91a5880 100644 --- a/app/modules/projects/projects-page.controller.coffee +++ b/app/modules/projects/projects-page.controller.coffee @@ -17,7 +17,6 @@ class ProjectsPageController extends taiga.Controller constructor: (@scope, @q, @rs, @rootscope, @navUrls, @auth, @location, @appTitle, @projectUrl, @config, tgLoader, @projectsService, @translate) -> - @appTitle.set(@translate.instant("PROJECT.SECTION_PROJECTS")) if !@auth.isAuthenticated() @location.path(@navUrls.resolve("login")) @@ -25,6 +24,10 @@ class ProjectsPageController extends taiga.Controller #Projects promise = @projectsService.fetchProjects() + # On Success + promise.then => + @appTitle.set(@translate.instant("PROJECT.SECTION_PROJECTS")) + # Finally promise.finally tgLoader.pageLoaded From 939326d30b5fcefeb933825353b30b0f370ed81c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 29 Apr 2015 09:35:44 +0200 Subject: [PATCH 093/366] Remove 200% width and horizontal scroll --- app/modules/navigation-bar/navigation-bar.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/modules/navigation-bar/navigation-bar.scss b/app/modules/navigation-bar/navigation-bar.scss index 3cddf18d..7521ba8c 100644 --- a/app/modules/navigation-bar/navigation-bar.scss +++ b/app/modules/navigation-bar/navigation-bar.scss @@ -5,7 +5,7 @@ position: relative; &:after { background: url('../images/menu-vert.png') repeat top left; - background-size: 100%; + background-size: 200%; bottom: 0; content: ''; height: 100%; @@ -13,7 +13,6 @@ position: absolute; right: 0; top: 0; - width: 200%; z-index: -1; } .nav-left, From be88edd1353eacb80a1b23f8cf71d16862f9960e Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 29 Apr 2015 09:53:22 +0200 Subject: [PATCH 094/366] Updating projects sorting --- app/coffee/modules/resources/projects.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/coffee/modules/resources/projects.coffee b/app/coffee/modules/resources/projects.coffee index 4381f10f..b7243a33 100644 --- a/app/coffee/modules/resources/projects.coffee +++ b/app/coffee/modules/resources/projects.coffee @@ -37,7 +37,7 @@ resourceProvider = ($config, $repo, $http, $urls, $auth, $q, $translate) -> return $repo.queryMany("projects") service.listByMember = (memberId) -> - params = {"member": memberId, "user_order": 1} + params = {"member": memberId, "order_by": "memberships__user_order"} return $repo.queryMany("projects", params) service.templates = -> From a08357b8cac56184ee6a045ebd4ed6c93474aa7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 29 Apr 2015 09:56:20 +0200 Subject: [PATCH 095/366] Project list animations --- app/modules/home/projects/home-project-list.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/modules/home/projects/home-project-list.scss b/app/modules/home/projects/home-project-list.scss index 50909a97..6f942ed9 100644 --- a/app/modules/home/projects/home-project-list.scss +++ b/app/modules/home/projects/home-project-list.scss @@ -6,14 +6,16 @@ padding: .5rem; padding-right: 1rem; text-overflow: ellipsis; - transition: border-color .3s linear; &:hover { border-color: $fresh-taiga; + transition: border-color .3s linear; p { color: $gray; + transition: color .3s linear; } .private path { fill: $gray; + transition: fill .3s linear; } } } @@ -38,7 +40,6 @@ width: .5rem; path { fill: $gray-light; - transition: fill .3s linear; } } } @@ -47,7 +48,6 @@ @extend %small; color: $gray-light; margin: 0; - transition: color .3s linear; } } From 548d3d314b7ee72cdd438202eb472987b4d5aedd Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 29 Apr 2015 13:56:12 +0200 Subject: [PATCH 096/366] Fixing error when connection fails --- app/modules/projects/projects.service.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/modules/projects/projects.service.coffee b/app/modules/projects/projects.service.coffee index f8d9a474..1696f860 100644 --- a/app/modules/projects/projects.service.coffee +++ b/app/modules/projects/projects.service.coffee @@ -26,10 +26,10 @@ class ProjectsService extends taiga.Service @.projectsById = Immutable.fromJS(groupBy(projects, (p) -> p.id)) - @._inProgress = false - return @.projects + @.projectsPromise.finally => + @._inProgress = false return @.projectsPromise From bb3496f3d8486088851e60c6c8cbec502705201c Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 30 Apr 2015 09:07:16 +0200 Subject: [PATCH 097/366] Fixing tests --- app/modules/projects/projects.service.coffee | 3 ++- app/modules/projects/projects.service.spec.coffee | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/modules/projects/projects.service.coffee b/app/modules/projects/projects.service.coffee index 1696f860..9699faf8 100644 --- a/app/modules/projects/projects.service.coffee +++ b/app/modules/projects/projects.service.coffee @@ -15,7 +15,8 @@ class ProjectsService extends taiga.Service if not @._inProgress @._inProgress = true - @.projectsPromise = @rs.projects.listByMember(@rootScope.user?.id).then (projects) => + @.projectsPromise = @rs.projects.listByMember(@rootScope.user?.id) + @.projectsPromise.then (projects) => for project in projects project.url = @projectUrl.get(project) diff --git a/app/modules/projects/projects.service.spec.coffee b/app/modules/projects/projects.service.spec.coffee index 1dbc1f57..a34d4648 100644 --- a/app/modules/projects/projects.service.spec.coffee +++ b/app/modules/projects/projects.service.spec.coffee @@ -10,8 +10,10 @@ describe "tgProjects", -> } mocks.thenStub = sinon.stub() + mocks.finallyStub = sinon.stub() mocks.resources.projects.listByMember.withArgs(10).returns({ then: mocks.thenStub + finally: mocks.finallyStub }) provide.value "$tgResources", mocks.resources @@ -86,6 +88,7 @@ describe "tgProjects", -> expect(projectsService._inProgress).to.be.true mocks.thenStub.callArg(0, projects) + mocks.finallyStub.callArg(0) expect(projectsService._inProgress).to.be.false From 7e9813032328d3d4384116e77e4cd575f66175e4 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 30 Apr 2015 11:38:36 +0200 Subject: [PATCH 098/366] Adding tests for home service --- app/modules/home/home.service.coffee | 8 +- app/modules/home/home.service.spec.coffee | 119 ++++++++++++++++++++++ 2 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 app/modules/home/home.service.spec.coffee diff --git a/app/modules/home/home.service.coffee b/app/modules/home/home.service.coffee index a3b97fce..42f245ba 100644 --- a/app/modules/home/home.service.coffee +++ b/app/modules/home/home.service.coffee @@ -3,11 +3,11 @@ class HomeService extends taiga.Service constructor: (@q, @rs, @rootScope, @projectUrl) -> @.workInProgress = Immutable.Map() - @.inProgress = false + @._inProgress = false fetchWorkInProgress: (userId) -> - if not @.inProgress - @.inProgress = true + if not @._inProgress + @._inProgress = true params = { status__is_closed: false assigned_to: userId @@ -52,7 +52,7 @@ class HomeService extends taiga.Service } }) - @.inProgress = false + @._inProgress = false return workPromise diff --git a/app/modules/home/home.service.spec.coffee b/app/modules/home/home.service.spec.coffee new file mode 100644 index 00000000..22a0f3d6 --- /dev/null +++ b/app/modules/home/home.service.spec.coffee @@ -0,0 +1,119 @@ +describe "tgHome", -> + homeService = provide = timeout = null + mocks = {} + + _mockResources = () -> + mocks.resources = {} + + mocks.resources.userstories = { + listInAllProjects: sinon.stub() + } + + mocks.resources.tasks = { + listInAllProjects: sinon.stub() + } + + mocks.resources.issues = { + listInAllProjects: sinon.stub() + } + + paramsAssignedTo = { + status__is_closed: false + assigned_to: 1 + } + + paramsWatching = { + status__is_closed: false + watchers: 1 + } + + mocks.thenStubAssignedToUserstories = sinon.stub() + mocks.resources.userstories.listInAllProjects.withArgs(paramsAssignedTo).returns({ + then: mocks.thenStubAssignedToUserstories + }) + + mocks.thenStubAssignedToTasks = sinon.stub() + mocks.resources.tasks.listInAllProjects.withArgs(paramsAssignedTo).returns({ + then: mocks.thenStubAssignedToTasks + }) + + mocks.thenStubAssignedToIssues = sinon.stub() + mocks.resources.issues.listInAllProjects.withArgs(paramsAssignedTo).returns({ + then: mocks.thenStubAssignedToIssues + }) + + + mocks.thenStubWatchingUserstories = sinon.stub() + mocks.resources.userstories.listInAllProjects.withArgs(paramsWatching).returns({ + then: mocks.thenStubWatchingUserstories + }) + + mocks.thenStubWatchingTasks = sinon.stub() + mocks.resources.tasks.listInAllProjects.withArgs(paramsWatching).returns({ + then: mocks.thenStubWatchingTasks + }) + + mocks.thenStubWatchingIssues = sinon.stub() + mocks.resources.issues.listInAllProjects.withArgs(paramsWatching).returns({ + then: mocks.thenStubWatchingIssues + }) + + provide.value "$tgResources", mocks.resources + + _mockProjectUrl = () -> + mocks.projectUrl = {get: sinon.stub()} + mocks.projectUrl.get = (project) -> + return "url-" + project.id + + provide.value "$projectUrl", mocks.projectUrl + + _inject = (callback) -> + inject (_$q_, _$tgResources_, _$rootScope_, _$projectUrl_, _$timeout_, _tgHomeService_) -> + timeout = _$timeout_ + homeService = _tgHomeService_ + callback() if callback + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockResources() + _mockProjectUrl() + return null + + _setup = -> + _mocks() + + beforeEach -> + module "taigaHome" + _setup() + _inject() + + describe "fetch items", -> + it "work in progress filled", () -> + homeService.fetchWorkInProgress(1) + mocks.thenStubAssignedToUserstories.callArg(0, [{"id": 1}]) + mocks.thenStubAssignedToTasks.callArg(0, [{"id": 2}]) + mocks.thenStubAssignedToIssues.callArg(0, [{"id": 3}]) + mocks.thenStubWatchingUserstories.callArg(0, [{"id": 4}]) + mocks.thenStubWatchingTasks.callArg(0, [{"id": 5}]) + mocks.thenStubWatchingIssues.callArg(0, [{"id": 6}]) + + timeout.flush() + expect(homeService.workInProgress.toJS()).to.be.eql({ + assignedTo: { + userStories: [{"id": 1}] + tasks: [{"id": 2}] + issues: [{"id": 3}] + } + watching: { + userStories: [{"id": 4}] + tasks: [{"id": 5}] + issues: [{"id": 6}] + } + }) + + it "_inProgress change to false when tgResources end", () -> + homeService.fetchWorkInProgress(1) + expect(homeService._inProgress).to.be.true + timeout.flush() + expect(homeService._inProgress).to.be.false From ad2d1d766f30099aa22bafda1603954ad79a302e Mon Sep 17 00:00:00 2001 From: Juanfran Date: Mon, 4 May 2015 08:13:28 +0200 Subject: [PATCH 099/366] automatic loader close --- app/coffee/app.coffee | 21 +++++++++++- app/coffee/modules/common/loader.coffee | 33 +++++++++++++++++-- .../projects/projects-page.controller.coffee | 11 +------ 3 files changed, 52 insertions(+), 13 deletions(-) diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index b7a4f1a1..d9316682 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -42,7 +42,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven {templateUrl: "home/home-page.html", resolve: {loader: tgLoaderProvider.add()}}) $routeProvider.when("/projects/", - {templateUrl: "projects/projects-page.html", resolve: {loader: tgLoaderProvider.add()}}) + {templateUrl: "projects/projects-page.html", resolve: {loader: tgLoaderProvider.add(true)}}) $routeProvider.when("/project/:pslug/", {templateUrl: "project/project.html"}) @@ -206,6 +206,25 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven $httpProvider.interceptors.push("authHttpIntercept") + + loaderIntercept = (loaderService) -> + return { + request: (config) -> + loaderService.logRequest() + + return config + + response: (response) -> + loaderService.logResponse() + + return response + } + + + $provide.factory("loaderIntercept", ["tgLoader", loaderIntercept]) + + $httpProvider.interceptors.push("loaderIntercept") + # If there is an error in the version throw a notify error. # IMPROVEiMENT: Move this version error handler to USs, issues and tasks repository versionCheckHttpIntercept = ($q) -> diff --git a/app/coffee/modules/common/loader.coffee b/app/coffee/modules/common/loader.coffee index df14802d..01e48791 100644 --- a/app/coffee/modules/common/loader.coffee +++ b/app/coffee/modules/common/loader.coffee @@ -57,18 +57,22 @@ Loader = () -> defaultConfig = { enabled: false, - minTime: 300 + minTime: 300, + auto: false } config = _.merge({}, defaultConfig) - @.add = () -> + @.add = (auto = false) -> return () -> if !forceDisabled config.enabled = true + config.auto = auto @.$get = ["$rootScope", ($rootscope) -> startLoadTime = 0 + requestCount = 0 + lastResponseDate = 0 reset = () -> config = _.merge({}, defaultConfig) @@ -86,6 +90,23 @@ Loader = () -> timeout(timeoutValue, -> $rootscope.$broadcast("loader:end")) + + autoClose = () -> + maxAuto = 5000 + timeoutAuto = setTimeout (() -> + pageLoaded() + + clearInterval(intervalAuto) + ), maxAuto + + intervalAuto = setInterval (() -> + if lastResponseDate && requestCount == 0 + pageLoaded() + + clearInterval(intervalAuto) + clearTimeout(timeoutAuto) + ), 200 + start = () -> startLoadTime = new Date().getTime() $rootscope.$broadcast("loader:start") @@ -98,6 +119,8 @@ Loader = () -> if config.enabled start() + autoClose() if config.auto + onStart: (fn) -> $rootscope.$on("loader:start", fn) @@ -109,6 +132,12 @@ Loader = () -> disablePreventLoading: () -> forceDisabled = false + + logRequest: () -> + requestCount++ + logResponse: () -> + requestCount-- + lastResponseDate = new Date().getTime() } ] diff --git a/app/modules/projects/projects-page.controller.coffee b/app/modules/projects/projects-page.controller.coffee index c91a5880..5e09e837 100644 --- a/app/modules/projects/projects-page.controller.coffee +++ b/app/modules/projects/projects-page.controller.coffee @@ -21,15 +21,6 @@ class ProjectsPageController extends taiga.Controller if !@auth.isAuthenticated() @location.path(@navUrls.resolve("login")) - #Projects - promise = @projectsService.fetchProjects() - - # On Success - promise.then => - @appTitle.set(@translate.instant("PROJECT.SECTION_PROJECTS")) - - # Finally - promise.finally tgLoader.pageLoaded - + @appTitle.set(@translate.instant("PROJECT.SECTION_PROJECTS")) angular.module("taigaProjects").controller("ProjectsPage", ProjectsPageController) From 62ee94f21423ffa704d8c21e82eb6b171482d081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 4 May 2015 09:40:37 +0200 Subject: [PATCH 100/366] Minor style fixes in create project options bar --- app/locales/locale-en.json | 2 +- .../projects/listing/styles/project-list.scss | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/app/locales/locale-en.json b/app/locales/locale-en.json index 9ce33468..c56a4a61 100644 --- a/app/locales/locale-en.json +++ b/app/locales/locale-en.json @@ -534,7 +534,7 @@ "PROJECT": { "WELCOME": "Welcome", "SECTION_PROJECTS": "Projects", - "HELP": "TODO. You can reorder your projects in your favorite way by drag&drop, Taiga will remember your order for every project list.\n Remember that the first ten projects will be shown in your rapid access menu in the top bar dropdown menu", + "HELP": "Reorder your projects to set in the top the most used ones.
The top 10 projects will appear in the top navigation bar project list", "PRIVATE": "Private project", "STATS": { "PROJECT": "project
points", diff --git a/app/modules/projects/listing/styles/project-list.scss b/app/modules/projects/listing/styles/project-list.scss index 9287be26..006e9f1a 100644 --- a/app/modules/projects/listing/styles/project-list.scss +++ b/app/modules/projects/listing/styles/project-list.scss @@ -11,13 +11,19 @@ margin: 0; } } - .import-project-button { - padding: .33rem .5rem; - &:hover { - background: $grayer; + .create-options a { + &.create-project-btn { + margin-right: .25rem; + padding: .6rem 2.5rem; } - .icon-upload { - margin: 0; + &.import-project-button { + padding: .53rem .8rem; + &:hover { + background: $grayer; + } + .icon-upload { + margin: 0; + } } } .project-list-section { From b70dbda2ab445ae6e75bb98d8479af51d56db7e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 4 May 2015 13:29:48 +0200 Subject: [PATCH 101/366] Edit Styles in user profile to fit in languages --- app/partials/includes/modules/admin-menu.jade | 3 --- app/partials/includes/modules/user-settings-menu.jade | 6 ------ app/partials/user/mail-notifications.jade | 2 +- app/partials/user/user-change-password.jade | 2 +- app/partials/user/user-profile.jade | 2 +- app/styles/modules/admin/admin-menu.scss | 2 +- app/styles/modules/user-settings/user-profile.scss | 8 ++++++++ 7 files changed, 12 insertions(+), 13 deletions(-) diff --git a/app/partials/includes/modules/admin-menu.jade b/app/partials/includes/modules/admin-menu.jade index 56eab9f8..7c1524b2 100644 --- a/app/partials/includes/modules/admin-menu.jade +++ b/app/partials/includes/modules/admin-menu.jade @@ -1,7 +1,4 @@ section.admin-menu - header - h1(translate="ADMIN.MENU.TITLE") - nav ul li#adminmenu-project-profile diff --git a/app/partials/includes/modules/user-settings-menu.jade b/app/partials/includes/modules/user-settings-menu.jade index c3e0fe5d..8376f2b2 100644 --- a/app/partials/includes/modules/user-settings-menu.jade +++ b/app/partials/includes/modules/user-settings-menu.jade @@ -1,18 +1,12 @@ section.admin-menu - header - h1(translate="USER_SETTINGS.MENU.SECTION_TITLE") - nav ul li#usersettingsmenu-user-profile a(href="", tg-nav="user-settings-user-profile") span.title(translate="USER_SETTINGS.MENU.USER_PROFILE") - span.icon.icon-arrow-right li#usersettingsmenu-change-password a(href="" tg-nav="user-settings-user-change-password") span.title(translate="USER_SETTINGS.MENU.CHANGE_PASSWORD") - span.icon.icon-arrow-right li#usersettingsmenu-mail-notifications a(href="", tg-nav="user-settings-mail-notifications") span.title(translate="USER_SETTINGS.MENU.EMAIL_NOTIFICATIONS") - span.icon.icon-arrow-right diff --git a/app/partials/user/mail-notifications.jade b/app/partials/user/mail-notifications.jade index c4aa3f9f..f7d6fc83 100644 --- a/app/partials/user/mail-notifications.jade +++ b/app/partials/user/mail-notifications.jade @@ -4,7 +4,7 @@ div.wrapper(tg-user-notifications, ng-controller="UserNotificationsController as ng-init="section='mail-notifications'") nav.menu.hidden(tg-project-menu) - sidebar.menu-secondary.sidebar(tg-user-settings-navigation="mail-notifications") + sidebar.menu-secondary.sidebar.user-profile-nav(tg-user-settings-navigation="mail-notifications") include ../includes/modules/user-settings-menu section.main.admin-common diff --git a/app/partials/user/user-change-password.jade b/app/partials/user/user-change-password.jade index 655b592b..7cb4828c 100644 --- a/app/partials/user/user-change-password.jade +++ b/app/partials/user/user-change-password.jade @@ -3,7 +3,7 @@ doctype html div.wrapper(tg-user-change-password, ng-controller="UserChangePasswordController as ctrl", ng-init="section='user-settings'") nav.menu.hidden(tg-project-menu) - sidebar.menu-secondary.sidebar(tg-user-settings-navigation="change-password") + sidebar.menu-secondary.sidebar.user-profile-nav(tg-user-settings-navigation="change-password") include ../includes/modules/user-settings-menu section.main.user-change-password diff --git a/app/partials/user/user-profile.jade b/app/partials/user/user-profile.jade index 6a4cc03e..4edf1049 100644 --- a/app/partials/user/user-profile.jade +++ b/app/partials/user/user-profile.jade @@ -3,7 +3,7 @@ doctype html div.wrapper(tg-user-profile, ng-controller="UserSettingsController as ctrl", ng-init="section='user-settings'") nav.menu.hidden(tg-project-menu) - sidebar.menu-secondary.sidebar(tg-user-settings-navigation="user-profile") + sidebar.menu-secondary.sidebar.user-profile-nav(tg-user-settings-navigation="user-profile") include ../includes/modules/user-settings-menu section.main.user-profile diff --git a/app/styles/modules/admin/admin-menu.scss b/app/styles/modules/admin/admin-menu.scss index 99b82728..ff60c1c5 100644 --- a/app/styles/modules/admin/admin-menu.scss +++ b/app/styles/modules/admin/admin-menu.scss @@ -2,7 +2,7 @@ li { @extend %larger; @extend %title; - border-bottom: 1px solid $gray-light; + border-bottom: 1px solid darken($whitish, 10%); text-transform: uppercase; &:last-child { border-bottom: 0; diff --git a/app/styles/modules/user-settings/user-profile.scss b/app/styles/modules/user-settings/user-profile.scss index 2deea46a..92334207 100644 --- a/app/styles/modules/user-settings/user-profile.scss +++ b/app/styles/modules/user-settings/user-profile.scss @@ -1,3 +1,11 @@ +.user-profile-nav { + padding: 0; + .active { + background: $white; + color: $fresh-taiga; + } +} + .user-profile { form { max-width: 700px; From 4cebbac1692d25272b15980709e1f03f08533a03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 4 May 2015 14:02:17 +0200 Subject: [PATCH 102/366] Fix admin layout in new header bar --- app/partials/admin/admin-memberships.jade | 2 +- .../admin/admin-project-default-values.jade | 2 +- app/partials/admin/admin-project-export.jade | 2 +- app/partials/admin/admin-project-modules.jade | 2 +- app/partials/admin/admin-project-profile.jade | 2 +- app/partials/admin/admin-project-reports.jade | 2 +- .../admin-project-values-custom-fields.jade | 2 +- .../admin/admin-project-values-points.jade | 2 +- .../admin-project-values-priorities.jade | 2 +- .../admin-project-values-severities.jade | 2 +- .../admin/admin-project-values-status.jade | 2 +- .../admin/admin-project-values-types.jade | 2 +- app/partials/admin/admin-roles.jade | 2 +- .../admin/admin-third-parties-bitbucket.jade | 2 +- .../admin/admin-third-parties-github.jade | 2 +- .../admin/admin-third-parties-gitlab.jade | 2 +- .../admin/admin-third-parties-webhooks.jade | 2 +- app/partials/contrib/main.jade | 2 +- app/partials/includes/modules/admin-menu.jade | 6 --- .../admin-submenu-project-profile.jade | 8 ---- .../modules/admin-submenu-project-values.jade | 9 ----- .../includes/modules/admin-submenu-roles.jade | 6 +-- .../modules/admin-submenu-third-parties.jade | 7 ---- .../modules/admin/admin-submenu-contrib.jade | 4 -- app/partials/user/mail-notifications.jade | 2 +- app/partials/user/user-change-password.jade | 2 +- app/partials/user/user-profile.jade | 2 +- app/styles/components/settings-nav.scss | 6 +++ app/styles/core/base.scss | 1 - app/styles/modules/admin/admin-menu.scss | 17 --------- .../modules/admin/admin-submenu-roles.scss | 38 ------------------- app/styles/modules/admin/admin-submenu.scss | 27 ++++++------- app/styles/modules/common/nav.scss | 1 + .../modules/user-settings/user-profile.scss | 8 ---- 34 files changed, 41 insertions(+), 139 deletions(-) create mode 100644 app/styles/components/settings-nav.scss diff --git a/app/partials/admin/admin-memberships.jade b/app/partials/admin/admin-memberships.jade index 035b825e..67c08184 100644 --- a/app/partials/admin/admin-memberships.jade +++ b/app/partials/admin/admin-memberships.jade @@ -4,7 +4,7 @@ div.wrapper.memberships(ng-controller="MembershipsController as ctrl", ng-init="section='admin'; sectionName='ADMIN.MEMBERSHIPS.TITLE'", tg-memberships) nav.menu.hidden(tg-project-menu) - sidebar.menu-secondary.sidebar(tg-admin-navigation="memberships") + sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="memberships") include ../includes/modules/admin-menu section.main.admin-membership diff --git a/app/partials/admin/admin-project-default-values.jade b/app/partials/admin/admin-project-default-values.jade index c53c9e76..5ab040e9 100644 --- a/app/partials/admin/admin-project-default-values.jade +++ b/app/partials/admin/admin-project-default-values.jade @@ -3,7 +3,7 @@ doctype html div.wrapper(tg-project-default-values, ng-controller="ProjectProfileController as ctrl", ng-init="section='admin'; sectionName='ADMIN.PROJECT_DEFAULT_VALUES.TITLE'") nav.menu.hidden(tg-project-menu) - sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile") + sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="project-profile") include ../includes/modules/admin-menu sidebar.menu-tertiary.sidebar(tg-admin-navigation="default-values") diff --git a/app/partials/admin/admin-project-export.jade b/app/partials/admin/admin-project-export.jade index dc605827..7b28020d 100644 --- a/app/partials/admin/admin-project-export.jade +++ b/app/partials/admin/admin-project-export.jade @@ -4,7 +4,7 @@ div.wrapper(ng-controller="ProjectProfileController as ctrl", ng-init="section='admin'; sectionName='ADMIN.PROJECT_EXPORT.TITLE'") nav.menu.hidden(tg-project-menu) - sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile") + sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="project-profile") include ../includes/modules/admin-menu sidebar.menu-tertiary.sidebar(tg-admin-navigation="export") diff --git a/app/partials/admin/admin-project-modules.jade b/app/partials/admin/admin-project-modules.jade index f28c3440..ddf37af1 100644 --- a/app/partials/admin/admin-project-modules.jade +++ b/app/partials/admin/admin-project-modules.jade @@ -3,7 +3,7 @@ doctype html div.wrapper(tg-project-modules, ng-controller="ProjectProfileController as ctrl", ng-init="section='admin'; sectionName='ADMIN.MODULES.TITLE'") nav.menu.hidden(tg-project-menu) - sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile") + sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="project-profile") include ../includes/modules/admin-menu sidebar.menu-tertiary.sidebar(tg-admin-navigation="modules") diff --git a/app/partials/admin/admin-project-profile.jade b/app/partials/admin/admin-project-profile.jade index ebfbcb91..fb382699 100644 --- a/app/partials/admin/admin-project-profile.jade +++ b/app/partials/admin/admin-project-profile.jade @@ -3,7 +3,7 @@ doctype html div.wrapper(tg-project-profile, ng-controller="ProjectProfileController as ctrl", ng-init="section='admin'; sectionName='ADMIN.PROJECT_PROFILE.PROJECT_DETAILS'") nav.menu.hidden(tg-project-menu) - sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile") + sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="project-profile") include ../includes/modules/admin-menu sidebar.menu-tertiary.sidebar(tg-admin-navigation="details") diff --git a/app/partials/admin/admin-project-reports.jade b/app/partials/admin/admin-project-reports.jade index 234200db..7ca8e6c0 100644 --- a/app/partials/admin/admin-project-reports.jade +++ b/app/partials/admin/admin-project-reports.jade @@ -3,7 +3,7 @@ doctype html div.wrapper(ng-controller="ProjectProfileController as ctrl", ng-init="section='admin'; sectionName='ADMIN.REPORTS.TITLE'") nav.menu.hidden(tg-project-menu) - sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile") + sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="project-profile") include ../includes/modules/admin-menu sidebar.menu-tertiary.sidebar(tg-admin-navigation="reports") diff --git a/app/partials/admin/admin-project-values-custom-fields.jade b/app/partials/admin/admin-project-values-custom-fields.jade index 548f2e8d..f93775d4 100644 --- a/app/partials/admin/admin-project-values-custom-fields.jade +++ b/app/partials/admin/admin-project-values-custom-fields.jade @@ -5,7 +5,7 @@ div.wrapper(ng-controller="ProjectValuesSectionController", nav.menu.hidden(tg-project-menu) - sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values") + sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="project-values") include ../includes/modules/admin-menu sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-custom-fields") diff --git a/app/partials/admin/admin-project-values-points.jade b/app/partials/admin/admin-project-values-points.jade index 33dceff8..9fca5183 100644 --- a/app/partials/admin/admin-project-values-points.jade +++ b/app/partials/admin/admin-project-values-points.jade @@ -4,7 +4,7 @@ div.wrapper(ng-controller="ProjectValuesSectionController") nav.menu.hidden(tg-project-menu) - sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values") + sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="project-values") include ../includes/modules/admin-menu sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-points") diff --git a/app/partials/admin/admin-project-values-priorities.jade b/app/partials/admin/admin-project-values-priorities.jade index 213b29c6..cfbb9554 100644 --- a/app/partials/admin/admin-project-values-priorities.jade +++ b/app/partials/admin/admin-project-values-priorities.jade @@ -3,7 +3,7 @@ doctype html div.wrapper(ng-controller="ProjectValuesSectionController") nav.menu.hidden(tg-project-menu) - sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values") + sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="project-values") include ../includes/modules/admin-menu sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-priorities") diff --git a/app/partials/admin/admin-project-values-severities.jade b/app/partials/admin/admin-project-values-severities.jade index eb5b7686..91382a63 100644 --- a/app/partials/admin/admin-project-values-severities.jade +++ b/app/partials/admin/admin-project-values-severities.jade @@ -3,7 +3,7 @@ doctype html div.wrapper(ng-controller="ProjectValuesSectionController") nav.menu.hidden(tg-project-menu) - sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values") + sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="project-values") include ../includes/modules/admin-menu sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-severities") diff --git a/app/partials/admin/admin-project-values-status.jade b/app/partials/admin/admin-project-values-status.jade index 66fb5a9c..009ec54f 100644 --- a/app/partials/admin/admin-project-values-status.jade +++ b/app/partials/admin/admin-project-values-status.jade @@ -3,7 +3,7 @@ doctype html div.wrapper(ng-controller="ProjectValuesSectionController", ng-init="section='admin'; sectionName='ADMIN.PROJECT_VALUES_STATUS.TITLE'") nav.menu.hidden(tg-project-menu) - sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values") + sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="project-values") include ../includes/modules/admin-menu sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-status") diff --git a/app/partials/admin/admin-project-values-types.jade b/app/partials/admin/admin-project-values-types.jade index f4e8cd58..63daf5fe 100644 --- a/app/partials/admin/admin-project-values-types.jade +++ b/app/partials/admin/admin-project-values-types.jade @@ -4,7 +4,7 @@ div.wrapper(ng-controller="ProjectValuesSectionController" ng-init="sectionName='ADMIN.PROJECT_VALUES_TYPES.TITLE'") nav.menu.hidden(tg-project-menu) - sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values") + sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="project-values") include ../includes/modules/admin-menu sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-types") diff --git a/app/partials/admin/admin-roles.jade b/app/partials/admin/admin-roles.jade index 0117516e..8ab7a050 100644 --- a/app/partials/admin/admin-roles.jade +++ b/app/partials/admin/admin-roles.jade @@ -4,7 +4,7 @@ div.wrapper.roles(ng-controller="RolesController as ctrl", ng-init="section='admin'", tg-roles) nav.menu.hidden(tg-project-menu) - sidebar.menu-secondary.sidebar(tg-admin-navigation="roles") + sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="roles") include ../includes/modules/admin-menu sidebar.menu-tertiary.sidebar diff --git a/app/partials/admin/admin-third-parties-bitbucket.jade b/app/partials/admin/admin-third-parties-bitbucket.jade index b65f8d17..95aabb22 100644 --- a/app/partials/admin/admin-third-parties-bitbucket.jade +++ b/app/partials/admin/admin-third-parties-bitbucket.jade @@ -3,7 +3,7 @@ doctype html div.wrapper.roles(tg-bitbucket-webhooks, ng-controller="BitbucketController as ctrl", ng-init="section='admin'") nav.menu.hidden(tg-project-menu) - sidebar.menu-secondary.sidebar(tg-admin-navigation="third-parties") + sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="third-parties") include ../includes/modules/admin-menu sidebar.menu-tertiary.sidebar(tg-admin-navigation="third-parties-bitbucket") include ../includes/modules/admin-submenu-third-parties diff --git a/app/partials/admin/admin-third-parties-github.jade b/app/partials/admin/admin-third-parties-github.jade index 478ab0ac..99785f93 100644 --- a/app/partials/admin/admin-third-parties-github.jade +++ b/app/partials/admin/admin-third-parties-github.jade @@ -3,7 +3,7 @@ doctype html div.wrapper.roles(tg-github-webhooks, ng-controller="GithubController as ctrl", ng-init="section='admin'") nav.menu.hidden(tg-project-menu) - sidebar.menu-secondary.sidebar(tg-admin-navigation="third-parties") + sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="third-parties") include ../includes/modules/admin-menu sidebar.menu-tertiary.sidebar(tg-admin-navigation="third-parties-github") include ../includes/modules/admin-submenu-third-parties diff --git a/app/partials/admin/admin-third-parties-gitlab.jade b/app/partials/admin/admin-third-parties-gitlab.jade index 289d0025..d9d67ad4 100644 --- a/app/partials/admin/admin-third-parties-gitlab.jade +++ b/app/partials/admin/admin-third-parties-gitlab.jade @@ -3,7 +3,7 @@ doctype html div.wrapper.roles(tg-gitlab-webhooks, ng-controller="GitlabController as ctrl", ng-init="section='admin'") nav.menu.hidden(tg-project-menu) - sidebar.menu-secondary.sidebar(tg-admin-navigation="third-parties") + sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="third-parties") include ../includes/modules/admin-menu sidebar.menu-tertiary.sidebar(tg-admin-navigation="third-parties-gitlab") include ../includes/modules/admin-submenu-third-parties diff --git a/app/partials/admin/admin-third-parties-webhooks.jade b/app/partials/admin/admin-third-parties-webhooks.jade index 1a0c6545..3eaad257 100644 --- a/app/partials/admin/admin-third-parties-webhooks.jade +++ b/app/partials/admin/admin-third-parties-webhooks.jade @@ -3,7 +3,7 @@ doctype html div.wrapper.roles(ng-controller="WebhooksController as ctrl", ng-init="section='admin'") nav.menu.hidden(tg-project-menu) - sidebar.menu-secondary.sidebar(tg-admin-navigation="third-parties") + sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="third-parties") include ../includes/modules/admin-menu sidebar.menu-tertiary.sidebar(tg-admin-navigation="third-parties-webhooks") include ../includes/modules/admin-submenu-third-parties diff --git a/app/partials/contrib/main.jade b/app/partials/contrib/main.jade index 26aed247..b3eba848 100644 --- a/app/partials/contrib/main.jade +++ b/app/partials/contrib/main.jade @@ -2,7 +2,7 @@ doctype html div.wrapper.roles(ng-init="section='admin'", ng-controller="ContribController as ctrl") nav.menu.hidden(tg-project-menu) - sidebar.menu-secondary.sidebar(tg-admin-navigation="contrib") + sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="contrib") include ../includes/modules/admin-menu sidebar.menu-tertiary.sidebar diff --git a/app/partials/includes/modules/admin-menu.jade b/app/partials/includes/modules/admin-menu.jade index 7c1524b2..1ceec640 100644 --- a/app/partials/includes/modules/admin-menu.jade +++ b/app/partials/includes/modules/admin-menu.jade @@ -4,24 +4,18 @@ section.admin-menu li#adminmenu-project-profile a(href="", tg-nav="project-admin-project-profile-details:project=project.slug") span.title(translate="ADMIN.MENU.PROJECT") - span.icon.icon-arrow-right li#adminmenu-project-values a(href="", tg-nav="project-admin-project-values-status:project=project.slug") span.title(translate="ADMIN.MENU.ATTRIBUTES") - span.icon.icon-arrow-right li#adminmenu-memberships a(href="" tg-nav="project-admin-memberships:project=project.slug") span.title(translate="ADMIN.MENU.MEMBERS") - span.icon.icon-arrow-right li#adminmenu-roles a(href="" tg-nav="project-admin-roles:project=project.slug") span.title(translate="ADMIN.MENU.PERMISSIONS") - span.icon.icon-arrow-right li#adminmenu-third-parties a(href="" tg-nav="project-admin-third-parties-webhooks:project=project.slug") span.title(translate="ADMIN.MENU.INTEGRATIONS") - span.icon.icon-arrow-right li#adminmenu-contrib(ng-show="adminPlugins.length > 0") a(href="" tg-nav="project-admin-contrib:project=project.slug,plugin=adminPlugins[0].slug") span.title(translate="COMMON.PLUGINS") - span.icon.icon-arrow-right diff --git a/app/partials/includes/modules/admin-submenu-project-profile.jade b/app/partials/includes/modules/admin-submenu-project-profile.jade index 404b6d52..11637613 100644 --- a/app/partials/includes/modules/admin-submenu-project-profile.jade +++ b/app/partials/includes/modules/admin-submenu-project-profile.jade @@ -1,26 +1,18 @@ section.admin-submenu - header - h1(translate="ADMIN.SUBMENU_PROJECT_PROFILE.TITLE") - nav ul li#adminmenu-details a(href="", tg-nav="project-admin-project-profile-details:project=project.slug") span.title(translate="ADMIN.PROJECT_PROFILE.PROJECT_DETAILS") - span.icon.icon-arrow-right li#adminmenu-default-values a(href="", tg-nav="project-admin-project-profile-default-values:project=project.slug") span.title(translate="ADMIN.PROJECT_DEFAULT_VALUES.TITLE") - span.icon.icon-arrow-right li#adminmenu-modules a(href="", tg-nav="project-admin-project-profile-modules:project=project.slug") span.title(translate="ADMIN.MODULES.TITLE") - span.icon.icon-arrow-right li#adminmenu-export a(href="", tg-nav="project-admin-project-profile-export:project=project.slug") span.title(translate="ADMIN.PROJECT_EXPORT.TITLE") - span.icon.icon-arrow-right li#adminmenu-reports a(href="", tg-nav="project-admin-project-profile-reports:project=project.slug") span.title(translate="ADMIN.REPORTS.TITLE") - span.icon.icon-arrow-right diff --git a/app/partials/includes/modules/admin-submenu-project-values.jade b/app/partials/includes/modules/admin-submenu-project-values.jade index 73047d78..00533831 100644 --- a/app/partials/includes/modules/admin-submenu-project-values.jade +++ b/app/partials/includes/modules/admin-submenu-project-values.jade @@ -1,35 +1,26 @@ section.admin-submenu - header - h1(translate="ADMIN.SUBMENU_PROJECT_ATTRIBUTES.TITLE") - nav ul li#adminmenu-values-status a(href="", tg-nav="project-admin-project-values-status:project=project.slug") span.title(translate="ADMIN.SUBMENU_PROJECT_VALUES.STATUS") - span.icon.icon-arrow-right li#adminmenu-values-points a(href="", tg-nav="project-admin-project-values-points:project=project.slug") span.title(translate="ADMIN.SUBMENU_PROJECT_VALUES.POINTS") - span.icon.icon-arrow-right li#adminmenu-values-priorities a(href="", tg-nav="project-admin-project-values-priorities:project=project.slug") span.title(translate="ADMIN.SUBMENU_PROJECT_VALUES.PRIORITIES") - span.icon.icon-arrow-right li#adminmenu-values-severities a(href="", tg-nav="project-admin-project-values-severities:project=project.slug") span.title(translate="ADMIN.SUBMENU_PROJECT_VALUES.SEVERITIES") - span.icon.icon-arrow-right li#adminmenu-values-types a(href="", tg-nav="project-admin-project-values-types:project=project.slug") span.title(translate="ADMIN.SUBMENU_PROJECT_VALUES.TYPES") - span.icon.icon-arrow-right li#adminmenu-values-custom-fields a(href="", tg-nav="project-admin-project-values-custom-fields:project=project.slug") span.title(translate="ADMIN.SUBMENU_PROJECT_VALUES.CUSTOM_FIELDS") - span.icon.icon-arrow-right diff --git a/app/partials/includes/modules/admin-submenu-roles.jade b/app/partials/includes/modules/admin-submenu-roles.jade index 08827eff..8ecce050 100644 --- a/app/partials/includes/modules/admin-submenu-roles.jade +++ b/app/partials/includes/modules/admin-submenu-roles.jade @@ -1,13 +1,9 @@ -section.admin-submenu-roles - header - h1(translate="ADMIN.SUBMENU_ROLES.TITLE") - +section.admin-submenu.admin-submenu-roles nav ul li(ng-repeat="item in roles") a(href="" ng-click="ctrl.setRole(item)", ng-class="{active: role.id == item.id}") span.single-role {{ item.name }} - span.icon.icon-arrow-right div(tg-new-role) a.button-gray.add-button(href="", title="{{'ADMIN.SUBMENU_ROLES.TITLE_ACTION_NEW_ROLE' | translate}}") diff --git a/app/partials/includes/modules/admin-submenu-third-parties.jade b/app/partials/includes/modules/admin-submenu-third-parties.jade index b045c4ee..a0666cb1 100644 --- a/app/partials/includes/modules/admin-submenu-third-parties.jade +++ b/app/partials/includes/modules/admin-submenu-third-parties.jade @@ -1,22 +1,15 @@ section.admin-submenu - header - h1(translate="ADMIN.SUBMENU_THIDPARTIES.TITLE") - nav ul li#adminmenu-third-parties-webhooks.third-parties-webhooks a(href="", tg-nav="project-admin-third-parties-webhooks:project=project.slug") span.title Webhooks - span.icon.icon-arrow-right 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 li#adminmenu-third-parties-gitlab a(href="", tg-nav="project-admin-third-parties-gitlab:project=project.slug") span.title Gitlab - span.icon.icon-arrow-right li#adminmenu-third-parties-bitbucket a(href="", tg-nav="project-admin-third-parties-bitbucket:project=project.slug") span.title Bitbucket - span.icon.icon-arrow-right diff --git a/app/partials/includes/modules/admin/admin-submenu-contrib.jade b/app/partials/includes/modules/admin/admin-submenu-contrib.jade index d3ee28ec..6d072e95 100644 --- a/app/partials/includes/modules/admin/admin-submenu-contrib.jade +++ b/app/partials/includes/modules/admin/admin-submenu-contrib.jade @@ -1,10 +1,6 @@ section.admin-submenu - header - h1(translate="COMMON.PLUGINS") - nav ul li#adminmenu-contrib(ng-repeat="plugin in adminPlugins") a(href="", tg-nav="project-admin-contrib:project=projectSlug,plugin=plugin.slug" ng-class="{active: plugin.slug == currentPlugin.slug}") span.title {{ plugin.name }} - span.icon.icon-arrow-right diff --git a/app/partials/user/mail-notifications.jade b/app/partials/user/mail-notifications.jade index f7d6fc83..d79ec9b2 100644 --- a/app/partials/user/mail-notifications.jade +++ b/app/partials/user/mail-notifications.jade @@ -4,7 +4,7 @@ div.wrapper(tg-user-notifications, ng-controller="UserNotificationsController as ng-init="section='mail-notifications'") nav.menu.hidden(tg-project-menu) - sidebar.menu-secondary.sidebar.user-profile-nav(tg-user-settings-navigation="mail-notifications") + sidebar.menu-secondary.sidebar.settings-nav(tg-user-settings-navigation="mail-notifications") include ../includes/modules/user-settings-menu section.main.admin-common diff --git a/app/partials/user/user-change-password.jade b/app/partials/user/user-change-password.jade index 7cb4828c..0b75a899 100644 --- a/app/partials/user/user-change-password.jade +++ b/app/partials/user/user-change-password.jade @@ -3,7 +3,7 @@ doctype html div.wrapper(tg-user-change-password, ng-controller="UserChangePasswordController as ctrl", ng-init="section='user-settings'") nav.menu.hidden(tg-project-menu) - sidebar.menu-secondary.sidebar.user-profile-nav(tg-user-settings-navigation="change-password") + sidebar.menu-secondary.sidebar.settings-nav(tg-user-settings-navigation="change-password") include ../includes/modules/user-settings-menu section.main.user-change-password diff --git a/app/partials/user/user-profile.jade b/app/partials/user/user-profile.jade index 4edf1049..bc88f297 100644 --- a/app/partials/user/user-profile.jade +++ b/app/partials/user/user-profile.jade @@ -3,7 +3,7 @@ doctype html div.wrapper(tg-user-profile, ng-controller="UserSettingsController as ctrl", ng-init="section='user-settings'") nav.menu.hidden(tg-project-menu) - sidebar.menu-secondary.sidebar.user-profile-nav(tg-user-settings-navigation="user-profile") + sidebar.menu-secondary.sidebar.settings-nav(tg-user-settings-navigation="user-profile") include ../includes/modules/user-settings-menu section.main.user-profile diff --git a/app/styles/components/settings-nav.scss b/app/styles/components/settings-nav.scss new file mode 100644 index 00000000..b7f4767a --- /dev/null +++ b/app/styles/components/settings-nav.scss @@ -0,0 +1,6 @@ +.settings-nav { + padding: 0; + .active { + background: $white; + } +} diff --git a/app/styles/core/base.scss b/app/styles/core/base.scss index a3cab813..5e6758ff 100644 --- a/app/styles/core/base.scss +++ b/app/styles/core/base.scss @@ -83,7 +83,6 @@ body { background-color: $dark-taiga; flex: 0 0 auto; min-height: 100vh; - padding: 2em 1em; width: 255px; } diff --git a/app/styles/modules/admin/admin-menu.scss b/app/styles/modules/admin/admin-menu.scss index ff60c1c5..98e89234 100644 --- a/app/styles/modules/admin/admin-menu.scss +++ b/app/styles/modules/admin/admin-menu.scss @@ -11,22 +11,5 @@ a { display: block; padding: 1rem 0 1rem 1rem; - &:hover { - .icon { - opacity: 1; - transition: opacity .3s linear; - } - } - } - .active { - .icon { - opacity: 1; - transition: opacity .3s linear; - } - } - .icon { - color: $blackish; - float: right; - opacity: 0; } } diff --git a/app/styles/modules/admin/admin-submenu-roles.scss b/app/styles/modules/admin/admin-submenu-roles.scss index e9cb013c..fc99ee1b 100644 --- a/app/styles/modules/admin/admin-submenu-roles.scss +++ b/app/styles/modules/admin/admin-submenu-roles.scss @@ -1,44 +1,6 @@ .admin-submenu-roles { - h1 { - @extend %xlarge; - color: $white; - } - li { - @extend %larger; - @extend %title; - border-bottom: 1px solid $gray-light; - text-transform: uppercase; - &:last-child { - border-bottom: 0; - } - } - a { - color: $white; - display: block; - padding: 1rem 0 1rem 1rem; - &.active, - &:hover { - color: $blackish; - .icon { - opacity: 1; - transition: opacity .3s linear; - } - } - } .single-role { @include ellipsis(175px); display: inline-block; } - .icon { - color: $white; - float: right; - opacity: 0; - } - .button-gray { - padding: .5rem 0; - text-align: center; - &:hover { - background-color: $blackish; - } - } } diff --git a/app/styles/modules/admin/admin-submenu.scss b/app/styles/modules/admin/admin-submenu.scss index 58e32f39..1a1c5347 100644 --- a/app/styles/modules/admin/admin-submenu.scss +++ b/app/styles/modules/admin/admin-submenu.scss @@ -1,8 +1,4 @@ .admin-submenu { - h1 { - @extend %xlarge; - color: $white; - } li { @extend %larger; @extend %title; @@ -17,14 +13,15 @@ display: flex; justify-content: space-between; padding: 1rem 0 1rem 1rem; - &.active, &:hover { - color: $blackish; - .icon { - color: $blackish; - opacity: 1; - transition: opacity .3s linear; - } + background: lighten($dark-taiga, 3%); + color: $white; + transition: all .2s; + } + &.active { + background: lighten($dark-taiga, 10%); + color: $white; + transition: all .2s; } span { display: block; @@ -34,12 +31,12 @@ white-space: nowrap; } } - .icon { - color: $white; - float: right; - opacity: 0; + input { + margin: 0 1rem; + width: 85%; } .button-gray { + margin: 0 1rem; padding: .5rem 0; text-align: center; &:hover { diff --git a/app/styles/modules/common/nav.scss b/app/styles/modules/common/nav.scss index 775c3fdc..f02c3d98 100644 --- a/app/styles/modules/common/nav.scss +++ b/app/styles/modules/common/nav.scss @@ -1,6 +1,7 @@ $label-arrow-wh: 12px; .menu { + background-color: $dark-taiga; background-image: url('../images/menu.png'); background-position: center center; min-height: 100vh; diff --git a/app/styles/modules/user-settings/user-profile.scss b/app/styles/modules/user-settings/user-profile.scss index 92334207..2deea46a 100644 --- a/app/styles/modules/user-settings/user-profile.scss +++ b/app/styles/modules/user-settings/user-profile.scss @@ -1,11 +1,3 @@ -.user-profile-nav { - padding: 0; - .active { - background: $white; - color: $fresh-taiga; - } -} - .user-profile { form { max-width: 700px; From 32b38875c2f3c15bea651af9d4d8e328debaf8bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 5 May 2015 08:36:49 +0200 Subject: [PATCH 103/366] Fix home duty navigation menu --- app/modules/home/duties/duty.jade | 23 ++++++++++++----------- app/modules/home/duties/duty.scss | 8 +++++--- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/app/modules/home/duties/duty.jade b/app/modules/home/duties/duty.jade index 51804629..0565a71e 100644 --- a/app/modules/home/duties/duty.jade +++ b/app/modules/home/duties/duty.jade @@ -1,12 +1,13 @@ -img.avatar( - src="{{ ::vm.duty.assigned_to_extra_info.photo }}" - title="{{ ::vm.duty.assigned_to_extra_info.full_name_display }}") +a(href="{{ ::vm.getUrl() }}", title="{{ ::duty.subject }}") + img.avatar( + src="{{ ::vm.duty.assigned_to_extra_info.photo }}" + title="{{ ::vm.duty.assigned_to_extra_info.full_name_display }}") -div.duty-data - div - span.duty-type {{ ::vm.getDutyType() }} - span.duty-status(ng-style="{'color': vm.duty.status_extra_info.color}") {{ ::vm.duty.status_extra_info.name }} - a.duty-title(href="{{ ::vm.getUrl() }}") - span.duty-id(tg-bo-ref="duty.ref") - span.duty-name {{ ::duty.subject }} -div.duty-project {{ ::vm.getProjectName()}} + div.duty-data + div + span.duty-type {{ ::vm.getDutyType() }} + span.duty-status(ng-style="{'color': vm.duty.status_extra_info.color}") {{ ::vm.duty.status_extra_info.name }} + span.duty-title + span.duty-id(tg-bo-ref="duty.ref") + span.duty-name {{ ::duty.subject }} + div.duty-project {{ ::vm.getProjectName()}} diff --git a/app/modules/home/duties/duty.scss b/app/modules/home/duties/duty.scss index 20725659..c4a2804b 100644 --- a/app/modules/home/duties/duty.scss +++ b/app/modules/home/duties/duty.scss @@ -2,11 +2,8 @@ .watching { margin-bottom: 2rem; .duty-single { - align-items: center; border-bottom: 1px solid $whitish; cursor: pointer; - display: flex; - flex-direction: row; padding: .5rem; &:last-child { border: 0; @@ -18,6 +15,11 @@ color: $red; } } + >a { + align-items: center; + display: flex; + flex-direction: row; + } } .avatar { flex-basis: 47px; From 776c1fe01906d337d3ec132c4ae93a59c5f2b280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 5 May 2015 08:38:01 +0200 Subject: [PATCH 104/366] Fix admin and user profile navigation menu --- .../includes/modules/user-settings-menu.jade | 6 +++--- app/partials/project/project-menu.jade | 14 +++++++------- app/styles/components/settings-nav.scss | 1 + app/styles/core/base.scss | 2 +- app/styles/modules/admin/admin-menu.scss | 2 +- app/styles/modules/admin/admin-submenu.scss | 2 +- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/app/partials/includes/modules/user-settings-menu.jade b/app/partials/includes/modules/user-settings-menu.jade index 8376f2b2..68afaab3 100644 --- a/app/partials/includes/modules/user-settings-menu.jade +++ b/app/partials/includes/modules/user-settings-menu.jade @@ -2,11 +2,11 @@ section.admin-menu nav ul li#usersettingsmenu-user-profile - a(href="", tg-nav="user-settings-user-profile") + a(href="", tg-nav="user-settings-user-profile", title="{{ 'USER_SETTINGS.MENU.USER_PROFILE' | translate }}") span.title(translate="USER_SETTINGS.MENU.USER_PROFILE") li#usersettingsmenu-change-password - a(href="" tg-nav="user-settings-user-change-password") + a(href="" tg-nav="user-settings-user-change-password", title="{{ 'USER_SETTINGS.MENU.CHANGE_PASSWORD' | translate }}") span.title(translate="USER_SETTINGS.MENU.CHANGE_PASSWORD") li#usersettingsmenu-mail-notifications - a(href="", tg-nav="user-settings-mail-notifications") + a(href="", tg-nav="user-settings-mail-notifications", title="{{ 'USER_SETTINGS.MENU.EMAIL_NOTIFICATIONS' | translate }}") span.title(translate="USER_SETTINGS.MENU.EMAIL_NOTIFICATIONS") diff --git a/app/partials/project/project-menu.jade b/app/partials/project/project-menu.jade index 0c2d2663..d9f341da 100644 --- a/app/partials/project/project-menu.jade +++ b/app/partials/project/project-menu.jade @@ -6,41 +6,41 @@ div(class="menu-container") span.helper(translate="PROJECT.SECTION.SEARCH") <% if (project.is_backlog_activated && project.my_permissions.indexOf("view_us") != -1) { %> li(id="nav-backlog") - a(href="" title="{{'PROJECT.SECTION.BACKLOG' | translate}}" tg-nav="project-backlog:project=project.slug", tabindex="2") + a(href="" title="{{'PROJECT.SECTION.BACKLOG' | translate}}" tg-nav="project-backlog:project=project.slug", tabindex="1") span(class="icon icon-backlog") span.helper(translate="PROJECT.SECTION.BACKLOG") <% } %> <% if (project.is_kanban_activated && project.my_permissions.indexOf("view_us") != -1) { %> li(id="nav-kanban") - a(href="" title="{{'PROJECT.SECTION.KANBAN' | translate}}" tg-nav="project-kanban:project=project.slug", tabindex="3") + a(href="" title="{{'PROJECT.SECTION.KANBAN' | translate}}" tg-nav="project-kanban:project=project.slug", tabindex="1") span(class="icon icon-kanban") span.helper(translate="PROJECT.SECTION.KANBAN") <% } %> <% if (project.is_issues_activated && project.my_permissions.indexOf("view_issues") != -1) { %> li(id="nav-issues") - a(href="" title="{{'PROJECT.SECTION.ISSUES' | translate}}" tg-nav="project-issues:project=project.slug", tabindex="4") + a(href="" title="{{'PROJECT.SECTION.ISSUES' | translate}}" tg-nav="project-issues:project=project.slug", tabindex="1") span(class="icon icon-issues") span.helper(translate="PROJECT.SECTION.ISSUES") <% } %> <% if (project.is_wiki_activated && project.my_permissions.indexOf("view_wiki_pages") != -1) { %> li(id="nav-wiki") - a(href="" title="{{'PROJECT.SECTION.WIKI' | translate}}" tg-nav="project-wiki:project=project.slug", tabindex="5") + a(href="" title="{{'PROJECT.SECTION.WIKI' | translate}}" tg-nav="project-wiki:project=project.slug", tabindex="1") span(class="icon icon-wiki") span.helper(translate="PROJECT.SECTION.WIKI") <% } %> li(id="nav-team") - a(href="" title="{{'PROJECT.SECTION.TEAM' | translate}}" tg-nav="project-team:project=project.slug", tabindex="6") + a(href="" title="{{'PROJECT.SECTION.TEAM' | translate}}" tg-nav="project-team:project=project.slug", tabindex="1") span(class="icon icon-team") span.helper(translate="PROJECT.SECTION.TEAM") <% if (project.videoconferences) { %> li(id="nav-video") - a(href!="<%- project.videoconferenceUrl %>" target="_blank" title="{{'PROJECT.SECTION.MEETUP' | translate}}", tabindex="7") + a(href!="<%- project.videoconferenceUrl %>" target="_blank" title="{{'PROJECT.SECTION.MEETUP' | translate}}", tabindex="1") span(class="icon icon-video") span(translate="PROJECT.SECTION.MEETUP") <% } %> <% if (project.i_am_owner) { %> li(id="nav-admin") - a(href="" tg-nav="project-admin-home:project=project.slug" title="{{'PROJECT.SECTION.ADMIN' | translate}}", tabindex="8") + a(href="" tg-nav="project-admin-home:project=project.slug" title="{{'PROJECT.SECTION.ADMIN' | translate}}", tabindex="1") span(class="icon icon-settings") span.helper(translate="PROJECT.SECTION.ADMIN") <% } %> diff --git a/app/styles/components/settings-nav.scss b/app/styles/components/settings-nav.scss index b7f4767a..afd07554 100644 --- a/app/styles/components/settings-nav.scss +++ b/app/styles/components/settings-nav.scss @@ -1,5 +1,6 @@ .settings-nav { padding: 0; + width: 250px; .active { background: $white; } diff --git a/app/styles/core/base.scss b/app/styles/core/base.scss index 5e6758ff..87c80586 100644 --- a/app/styles/core/base.scss +++ b/app/styles/core/base.scss @@ -83,7 +83,7 @@ body { background-color: $dark-taiga; flex: 0 0 auto; min-height: 100vh; - width: 255px; + width: 250px; } .extrabar { diff --git a/app/styles/modules/admin/admin-menu.scss b/app/styles/modules/admin/admin-menu.scss index 98e89234..38e52885 100644 --- a/app/styles/modules/admin/admin-menu.scss +++ b/app/styles/modules/admin/admin-menu.scss @@ -1,6 +1,6 @@ .admin-menu { li { - @extend %larger; + @extend %large; @extend %title; border-bottom: 1px solid darken($whitish, 10%); text-transform: uppercase; diff --git a/app/styles/modules/admin/admin-submenu.scss b/app/styles/modules/admin/admin-submenu.scss index 1a1c5347..d8710a55 100644 --- a/app/styles/modules/admin/admin-submenu.scss +++ b/app/styles/modules/admin/admin-submenu.scss @@ -1,6 +1,6 @@ .admin-submenu { li { - @extend %larger; + @extend %large; @extend %title; border-bottom: 1px solid $gray-light; text-transform: uppercase; From 0c9b2f78b3513cedd140e57f834269012825cf8b Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 5 May 2015 10:24:05 +0200 Subject: [PATCH 105/366] page controller --- app/coffee/app.coffee | 26 +++- app/modules/home/home-page.jade | 2 +- app/modules/page/page.controller.coffee | 18 +++ app/modules/page/page.controller.spec.coffee | 123 ++++++++++++++++++ app/modules/page/page.module.coffee | 1 + .../projects/projects-page.controller.coffee | 26 ---- app/modules/projects/projects-page.jade | 2 +- 7 files changed, 168 insertions(+), 30 deletions(-) create mode 100644 app/modules/page/page.controller.coffee create mode 100644 app/modules/page/page.controller.spec.coffee create mode 100644 app/modules/page/page.module.coffee delete mode 100644 app/modules/projects/projects-page.controller.coffee diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index d9316682..63ea2edf 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -39,10 +39,31 @@ taiga.sessionId = taiga.generateUniqueSessionIdentifier() configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEventsProvider, tgLoaderProvider, $compileProvider, $translateProvider) -> $routeProvider.when("/", - {templateUrl: "home/home-page.html", resolve: {loader: tgLoaderProvider.add()}}) + { + templateUrl: "home/home-page.html", + resolve: { + loader: tgLoaderProvider.add(true), + pageParams: -> { + "title": "PROJECT.WELCOME" + "authRequired": true + } + } + } + ) $routeProvider.when("/projects/", - {templateUrl: "projects/projects-page.html", resolve: {loader: tgLoaderProvider.add(true)}}) + { + templateUrl: "projects/projects-page.html", + resolve: { + loader: tgLoaderProvider.add(true), + pageParams: -> { + "title": "PROJECT.SECTION_PROJECTS" + "authRequired": true + } + }, + controller: "Page" + } + ) $routeProvider.when("/project/:pslug/", {templateUrl: "project/project.html"}) @@ -362,6 +383,7 @@ modules = [ "taigaComponents", "taigaProfile", "taigaHome", + "taigaPage", # template cache "templates", diff --git a/app/modules/home/home-page.jade b/app/modules/home/home-page.jade index 7bfe3e17..9e2a5c4a 100644 --- a/app/modules/home/home-page.jade +++ b/app/modules/home/home-page.jade @@ -1 +1 @@ -div(ng-controller="HomePage", tg-home) +div(tg-home) diff --git a/app/modules/page/page.controller.coffee b/app/modules/page/page.controller.coffee new file mode 100644 index 00000000..5a317157 --- /dev/null +++ b/app/modules/page/page.controller.coffee @@ -0,0 +1,18 @@ +class PageController extends taiga.Controller + @.$inject = [ + "$tgAuth", + "$appTitle", + "$translate", + "$tgLocation", + "$tgNavUrls", + "pageParams" + ] + + constructor: (@auth, @appTitle, @translate, @location, @navUrls, @pageParams) -> + if @pageParams.authRequired && !@auth.isAuthenticated() + @location.path(@navUrls.resolve("login")) + + if @pageParams.title + @translate(@pageParams.title).then (text) => @appTitle.set(text) + +angular.module("taigaPage").controller("Page", PageController) diff --git a/app/modules/page/page.controller.spec.coffee b/app/modules/page/page.controller.spec.coffee new file mode 100644 index 00000000..8163c668 --- /dev/null +++ b/app/modules/page/page.controller.spec.coffee @@ -0,0 +1,123 @@ +describe "PageController", -> + pageCtrl = null + provide = null + controller = null + mocks = {} + + _mockPageParams = () -> + mocks.pageParams = {} + + provide.value "pageParams", mocks.pageParams + + _mockAuth = () -> + mocks.auth = { + isAuthenticated: sinon.stub() + } + + provide.value "$tgAuth", mocks.auth + + _mockAppTitle = () -> + mocks.appTitle = { + set: sinon.spy() + } + + provide.value "$appTitle", mocks.appTitle + + _mockLocation = () -> + mocks.location = { + path: sinon.spy() + } + + provide.value "$tgLocation", mocks.location + + _mockNavUrls = () -> + mocks.navUrls = { + resolve: sinon.stub() + } + + provide.value "$tgNavUrls", mocks.navUrls + + _mockTranslate = () -> + mocks.translate = sinon.stub() + + provide.value "$translate", mocks.translate + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockAppTitle() + _mockPageParams() + _mockAuth() + _mockLocation() + _mockNavUrls() + _mockTranslate() + + return null + + beforeEach -> + module "taigaPage" + + _mocks() + + inject ($controller) -> + controller = $controller + + describe "auth", () -> + it "if auth is required and the user is not logged redirect to login page", () -> + locationPath = "location-path" + + mocks.pageParams.authRequired = true + mocks.auth.isAuthenticated.returns(false) + mocks.navUrls.resolve.withArgs("login").returns(locationPath) + + pageCtrl = controller "Page", + $scope: {} + + expect(mocks.location.path.withArgs(locationPath)).have.been.calledOnce + + it "if auth is not required no redirect to login page", () -> + locationPath = "location-path" + + mocks.pageParams.authRequired = false + mocks.auth.isAuthenticated.returns(false) + mocks.navUrls.resolve.withArgs("login").returns(locationPath) + + pageCtrl = controller "Page", + $scope: {} + + expect(mocks.location.path).have.callCount(0) + + it "if auth is required and the user is logged no redirect", () -> + locationPath = "location-path" + + mocks.pageParams.authRequired = true + mocks.auth.isAuthenticated.returns(true) + mocks.navUrls.resolve.withArgs("login").returns(locationPath) + + pageCtrl = controller "Page", + $scope: {} + + expect(mocks.location.path).have.callCount(0) + + describe "page title", () -> + it "if title is defined set it", () -> + thenStub = sinon.stub() + + mocks.pageParams.title = "TITLE" + mocks.translate.withArgs("TITLE").returns({ + then: thenStub + }) + + pageCtrl = controller "Page", + $scope: {} + + thenStub.callArg(0, "TITLE") + + expect(mocks.appTitle.set.withArgs("TITLE")).have.been.calledOnce + + it "if title is not defined not call appTitle", () -> + pageCtrl = controller "Page", + $scope: {} + + expect(mocks.translate).have.callCount(0) + expect(mocks.appTitle.set.withArgs("TITLE")).have.callCount(0) diff --git a/app/modules/page/page.module.coffee b/app/modules/page/page.module.coffee new file mode 100644 index 00000000..82f679b0 --- /dev/null +++ b/app/modules/page/page.module.coffee @@ -0,0 +1 @@ +module = angular.module("taigaPage", []) diff --git a/app/modules/projects/projects-page.controller.coffee b/app/modules/projects/projects-page.controller.coffee deleted file mode 100644 index 5e09e837..00000000 --- a/app/modules/projects/projects-page.controller.coffee +++ /dev/null @@ -1,26 +0,0 @@ -class ProjectsPageController extends taiga.Controller - @.$inject = [ - "$scope", - "$q", - "$tgResources", - "$rootScope", - "$tgNavUrls", - "$tgAuth", - "$tgLocation", - "$appTitle", - "$projectUrl", - "$tgConfig", - "tgLoader", - "tgProjectsService", - "$translate" - ] - - constructor: (@scope, @q, @rs, @rootscope, @navUrls, @auth, @location, - @appTitle, @projectUrl, @config, tgLoader, @projectsService, @translate) -> - - if !@auth.isAuthenticated() - @location.path(@navUrls.resolve("login")) - - @appTitle.set(@translate.instant("PROJECT.SECTION_PROJECTS")) - -angular.module("taigaProjects").controller("ProjectsPage", ProjectsPageController) diff --git a/app/modules/projects/projects-page.jade b/app/modules/projects/projects-page.jade index 656c15cf..21f045f0 100644 --- a/app/modules/projects/projects-page.jade +++ b/app/modules/projects/projects-page.jade @@ -1 +1 @@ -div(ng-controller="ProjectsPage", tg-projects-listing) +div(tg-projects-listing) From bb6920df8ff101809c14f5db774425735cfb4b11 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 5 May 2015 11:09:25 +0200 Subject: [PATCH 106/366] remove home-page controller --- app/modules/home/home-page.controller.coffee | 44 -------------------- 1 file changed, 44 deletions(-) delete mode 100644 app/modules/home/home-page.controller.coffee diff --git a/app/modules/home/home-page.controller.coffee b/app/modules/home/home-page.controller.coffee deleted file mode 100644 index 7e813b05..00000000 --- a/app/modules/home/home-page.controller.coffee +++ /dev/null @@ -1,44 +0,0 @@ -class ProjectsPageController extends taiga.Controller - @.$inject = [ - "$scope", - "$q", - "$tgResources", - "$rootScope", - "$tgNavUrls", - "$tgAuth", - "$tgLocation", - "$appTitle", - "$projectUrl", - "$tgConfig", - "tgLoader", - "tgProjectsService", - "tgHomeService", - "$translate" - - ] - - constructor: (@scope, @q, @rs, @rootscope, @navUrls, @auth, @location, - @appTitle, @projectUrl, @config, tgLoader, @projectsService, @homeService, - @translate) -> - - if !@auth.isAuthenticated() - @location.path(@navUrls.resolve("login")) - - promise = @.loadInitialData() - - # On Success - promise.then => - @appTitle.set(@translate.instant("PROJECT.WELCOME")) - - # Finally - promise.finally tgLoader.pageLoaded - - loadInitialData: -> - user = @auth.getUser() - #Projects - promise = @projectsService.fetchProjects() - return promise.then () => - #In progress work - return @homeService.fetchWorkInProgress(user.id) - -angular.module("taigaHome").controller("HomePage", ProjectsPageController) From 4e7c1ce90f9cf29d14945ffd2a3e0bdeeb6336d4 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 5 May 2015 12:18:45 +0200 Subject: [PATCH 107/366] immutable service properties --- app/modules/home/home.service.coffee | 24 +++++++++++++------- app/modules/home/home.service.spec.coffee | 12 ++++++++-- app/modules/projects/projects.service.coffee | 22 ++++++++++-------- 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/app/modules/home/home.service.coffee b/app/modules/home/home.service.coffee index 42f245ba..c6123c4c 100644 --- a/app/modules/home/home.service.coffee +++ b/app/modules/home/home.service.coffee @@ -1,11 +1,18 @@ class HomeService extends taiga.Service - @.$inject = ["$q", "$tgResources", "$rootScope", "$projectUrl"] + @.$inject = ["$q", "$tgResources", "$rootScope", "$projectUrl", "$tgAuth"] - constructor: (@q, @rs, @rootScope, @projectUrl) -> - @.workInProgress = Immutable.Map() + constructor: (@q, @rs, @rootScope, @projectUrl, @auth) -> + @._workInProgress = Immutable.Map() + @._projectPromise = null @._inProgress = false - fetchWorkInProgress: (userId) -> + taiga.defineImmutableProperty @, "workInProgress", () => return @._workInProgress + + @.fetchWorkInProgress() + + fetchWorkInProgress: () -> + userId = @auth.getUser().id + if not @._inProgress @._inProgress = true params = { @@ -21,6 +28,7 @@ class HomeService extends taiga.Service assignedIssuesPromise = @rs.issues.listInAllProjects(params).then (issues) => @.assignedToIssues = issues + params = { status__is_closed: false watchers: userId @@ -34,12 +42,12 @@ class HomeService extends taiga.Service watchingIssuesPromise = @rs.issues.listInAllProjects(params).then (issues) => @.watchingIssues = issues - workPromise = @q.all([assignedUserStoriesPromise, assignedTasksPromise, + @._projectPromise = @q.all([assignedUserStoriesPromise, assignedTasksPromise, assignedIssuesPromise, watchingUserStoriesPromise, watchingUserStoriesPromise, watchingIssuesPromise]) - workPromise.then => - @.workInProgress = Immutable.fromJS({ + @._projectPromise.then => + @._workInProgress = Immutable.fromJS({ assignedTo: { userStories: @.assignedToUserStories tasks: @.assignedToTasks @@ -54,6 +62,6 @@ class HomeService extends taiga.Service @._inProgress = false - return workPromise + return @._projectPromise angular.module("taigaHome").service("tgHomeService", HomeService) diff --git a/app/modules/home/home.service.spec.coffee b/app/modules/home/home.service.spec.coffee index 22a0f3d6..f2cb34ce 100644 --- a/app/modules/home/home.service.spec.coffee +++ b/app/modules/home/home.service.spec.coffee @@ -67,6 +67,15 @@ describe "tgHome", -> provide.value "$projectUrl", mocks.projectUrl + _mockAuth = () -> + mocks.auth = { + getUser: sinon.stub() + } + + mocks.auth.getUser.returns(id: 1) + + provide.value "$tgAuth", mocks.auth + _inject = (callback) -> inject (_$q_, _$tgResources_, _$rootScope_, _$projectUrl_, _$timeout_, _tgHomeService_) -> timeout = _$timeout_ @@ -78,6 +87,7 @@ describe "tgHome", -> provide = $provide _mockResources() _mockProjectUrl() + _mockAuth() return null _setup = -> @@ -90,7 +100,6 @@ describe "tgHome", -> describe "fetch items", -> it "work in progress filled", () -> - homeService.fetchWorkInProgress(1) mocks.thenStubAssignedToUserstories.callArg(0, [{"id": 1}]) mocks.thenStubAssignedToTasks.callArg(0, [{"id": 2}]) mocks.thenStubAssignedToIssues.callArg(0, [{"id": 3}]) @@ -113,7 +122,6 @@ describe "tgHome", -> }) it "_inProgress change to false when tgResources end", () -> - homeService.fetchWorkInProgress(1) expect(homeService._inProgress).to.be.true timeout.flush() expect(homeService._inProgress).to.be.false diff --git a/app/modules/projects/projects.service.coffee b/app/modules/projects/projects.service.coffee index 9699faf8..a2ea2ef8 100644 --- a/app/modules/projects/projects.service.coffee +++ b/app/modules/projects/projects.service.coffee @@ -5,34 +5,38 @@ class ProjectsService extends taiga.Service @.$inject = ["$tgResources", "$rootScope", "$projectUrl", "tgLightboxFactory"] constructor: (@rs, @rootScope, @projectUrl, @lightboxFactory) -> - @.projects = Immutable.Map() - @.projectsById = Immutable.Map() + @._projects = Immutable.Map() + @._projectsById = Immutable.Map() @._inProgress = false - @.projectsPromise = null + @._projectsPromise = null + + taiga.defineImmutableProperty @, "projects", () => return @._projects + taiga.defineImmutableProperty @, "projectsById", () => return @._projectsById + @.fetchProjects() fetchProjects: -> if not @._inProgress @._inProgress = true - @.projectsPromise = @rs.projects.listByMember(@rootScope.user?.id) - @.projectsPromise.then (projects) => + @._projectsPromise = @rs.projects.listByMember(@rootScope.user?.id) + @._projectsPromise.then (projects) => for project in projects project.url = @projectUrl.get(project) - @.projects = Immutable.fromJS({ + @._projects = Immutable.fromJS({ all: projects, recents: projects.slice(0, 10) }) - @.projectsById = Immutable.fromJS(groupBy(projects, (p) -> p.id)) + @._projectsById = Immutable.fromJS(groupBy(projects, (p) -> p.id)) return @.projects - @.projectsPromise.finally => + @._projectsPromise.finally => @._inProgress = false - return @.projectsPromise + return @._projectsPromise newProject: -> @lightboxFactory.create("tg-lb-create-project", { From ffa635ad85ce804e06f7158690803628a17406b5 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 5 May 2015 12:34:47 +0200 Subject: [PATCH 108/366] Adding home directive tests --- app/modules/home/home.directive.coffee | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/modules/home/home.directive.coffee b/app/modules/home/home.directive.coffee index a929d370..a19108bc 100644 --- a/app/modules/home/home.directive.coffee +++ b/app/modules/home/home.directive.coffee @@ -1,5 +1,6 @@ HomeDirective = (homeService) -> link = (scope, el, attrs, ctrl) -> + console.log 111111 scope.vm = {} taiga.defineImmutableProperty(scope.vm, "workInProgress", () -> homeService.workInProgress) @@ -15,12 +16,14 @@ HomeDirective = (homeService) -> issues = workInProgress.get("watching").get("issues") scope.vm.watching = userStories.concat(tasks).concat(issues) - directive = { + return { templateUrl: "home/home.html" scope: {} link: link } - return directive +HomeDirective.$inject = [ + "tgHomeService" +] -angular.module("taigaHome").directive("tgHome", ["tgHomeService", HomeDirective]) +angular.module("taigaHome").directive("tgHome", HomeDirective) From 0e4da5b48317a8a25c3a0680eb2ce12b65a1cefe Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 5 May 2015 12:41:59 +0200 Subject: [PATCH 109/366] Adding test --- app/modules/home/home.directive.spec.coffee | 45 +++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 app/modules/home/home.directive.spec.coffee diff --git a/app/modules/home/home.directive.spec.coffee b/app/modules/home/home.directive.spec.coffee new file mode 100644 index 00000000..648733b3 --- /dev/null +++ b/app/modules/home/home.directive.spec.coffee @@ -0,0 +1,45 @@ +describe "homeDirective", () -> + scope = compile = provide = null + mockTgHomeService = null + template = "
" + + createDirective = () -> + elm = compile(template)(scope) + return elm + + _mockTgHomeService = () -> + mockTgHomeService = { + workInProgress: Immutable.fromJS({ + assignedTo: { + userStories: [{"id": 1}] + tasks: [] + issues: [] + } + watching: { + userStories: [] + tasks: [] + issues: [] + } + }) + } + + provide.value "tgHomeService", mockTgHomeService + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgHomeService() + return null + + beforeEach -> + module "taigaHome" + + _mocks() + + inject ($rootScope, $compile) -> + scope = $rootScope.$new() + compile = $compile + + it "home directive content", () -> + elm = createDirective() + console.log 111, elm, elm.find('div') From 5239faab6b7141348ab4f7f52dcbdedab3b63368 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 5 May 2015 14:45:44 +0200 Subject: [PATCH 110/366] Adding duty directive tests --- .../home/duties/duty.directive.spec.coffee | 79 +++++++++++++++++++ app/modules/home/home.directive.coffee | 1 - app/modules/home/home.directive.spec.coffee | 31 ++++++-- app/modules/home/home.jade | 2 +- 4 files changed, 103 insertions(+), 10 deletions(-) create mode 100644 app/modules/home/duties/duty.directive.spec.coffee diff --git a/app/modules/home/duties/duty.directive.spec.coffee b/app/modules/home/duties/duty.directive.spec.coffee new file mode 100644 index 00000000..25c9729f --- /dev/null +++ b/app/modules/home/duties/duty.directive.spec.coffee @@ -0,0 +1,79 @@ +describe "homeDirective", () -> + scope = compile = provide = null + mockTgProjectsService = null + mockTgNavUrls = null + mockTranslate = null + template = "
" + + createDirective = () -> + elm = compile(template)(scope) + return elm + + _mockTgNavUrls = () -> + mockTgNavUrls = { + resolve: sinon.stub() + } + provide.value "$tgNavUrls", mockTgNavUrls + + _mockTgProjectsService = () -> + mockTgProjectsService = { + projectsById: { + get: sinon.stub() + } + } + provide.value "tgProjectsService", mockTgProjectsService + + _mockTranslate = () -> + mockTranslate = { + instant: sinon.stub() + } + provide.value "$translate", mockTranslate + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgNavUrls() + _mockTgProjectsService() + _mockTranslate() + return null + + beforeEach -> + module "templates" + module "taigaHome" + + _mocks() + + inject ($rootScope, $compile) -> + scope = $rootScope.$new() + compile = $compile + + it "duty directive content", () -> + scope.duty = { + project: 1 + ref: 1 + _name: "userstories" + subject: "Testing js subject" + status_extra_info: { + color: "#CCCCCC" + } + assigned_to_extra_info: { + photo: "http://jstesting.taiga.io/photo" + full_name_display: "Taiga testing js" + } + } + + mockTgProjectsService.projectsById.get + .withArgs("1") + .returns({slug: "project-slug", "name": "testing js project"}) + + mockTgNavUrls.resolve + .withArgs("project-userstories-detail", {project: "project-slug", ref: 1}) + .returns("http://jstesting.taiga.io") + + mockTranslate.instant + .withArgs("COMMON.USER_STORY") + .returns("COMMON.USER_STORY") + + elm = createDirective() + scope.$apply() + expect(elm.find('.duty-status').css("color")).to.be.equal('rgb(204, 204, 204)') diff --git a/app/modules/home/home.directive.coffee b/app/modules/home/home.directive.coffee index a19108bc..1b74a29d 100644 --- a/app/modules/home/home.directive.coffee +++ b/app/modules/home/home.directive.coffee @@ -1,6 +1,5 @@ HomeDirective = (homeService) -> link = (scope, el, attrs, ctrl) -> - console.log 111111 scope.vm = {} taiga.defineImmutableProperty(scope.vm, "workInProgress", () -> homeService.workInProgress) diff --git a/app/modules/home/home.directive.spec.coffee b/app/modules/home/home.directive.spec.coffee index 648733b3..cc12cfaf 100644 --- a/app/modules/home/home.directive.spec.coffee +++ b/app/modules/home/home.directive.spec.coffee @@ -1,7 +1,6 @@ describe "homeDirective", () -> scope = compile = provide = null - mockTgHomeService = null - template = "
" + template = "
" createDirective = () -> elm = compile(template)(scope) @@ -12,26 +11,41 @@ describe "homeDirective", () -> workInProgress: Immutable.fromJS({ assignedTo: { userStories: [{"id": 1}] - tasks: [] - issues: [] + tasks: [{"id": 2}] + issues: [{"id": 3}] } watching: { - userStories: [] - tasks: [] - issues: [] + userStories: [{"id": 4}] + tasks: [{"id": 5}] + issues: [{"id": 6}] } }) } provide.value "tgHomeService", mockTgHomeService + _mockTranslateFilter = () -> + mockTranslateFilter = (value) -> + return value + provide.value "translateFilter", mockTranslateFilter + + _mockTgDuty = () -> + provide.factory 'tgDutyDirective', () -> {} + + _mockHomeProjectList = () -> + provide.factory 'tgHomeProjectListDirective', () -> {} + _mocks = () -> module ($provide) -> provide = $provide + _mockTgDuty() + _mockHomeProjectList() _mockTgHomeService() + _mockTranslateFilter() return null beforeEach -> + module "templates" module "taigaHome" _mocks() @@ -42,4 +56,5 @@ describe "homeDirective", () -> it "home directive content", () -> elm = createDirective() - console.log 111, elm, elm.find('div') + scope.$apply() + expect(elm.find('.duty-single')).to.have.length(6) diff --git a/app/modules/home/home.jade b/app/modules/home/home.jade index 40f04c9d..f2d5e217 100644 --- a/app/modules/home/home.jade +++ b/app/modules/home/home.jade @@ -5,7 +5,7 @@ div.home-wrapper.centered div.duty-summary div.title-bar.working-on-title(ng-show="vm.assignedTo.size", translate="HOME.WORKING_ON_SECTION") section.working-on(ng-show="vm.assignedTo.size") - div.duty-single(tg-duty="duty", tg-repeat="duty in vm.assignedTo", ng-class="{blocked: duty.is_blocked}") + div.duty-single(tg-duty="duty", ng-repeat="duty in vm.assignedTo", ng-class="{blocked: duty.is_blocked}") div.title-bar.watching-title(translate="HOME.WATCHING_SECTION") From 710f621602f663033841a81866fdca7e1313ae9a Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 5 May 2015 14:51:26 +0200 Subject: [PATCH 111/366] Fixing template --- app/modules/home/home.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/home/home.jade b/app/modules/home/home.jade index f2d5e217..40f04c9d 100644 --- a/app/modules/home/home.jade +++ b/app/modules/home/home.jade @@ -5,7 +5,7 @@ div.home-wrapper.centered div.duty-summary div.title-bar.working-on-title(ng-show="vm.assignedTo.size", translate="HOME.WORKING_ON_SECTION") section.working-on(ng-show="vm.assignedTo.size") - div.duty-single(tg-duty="duty", ng-repeat="duty in vm.assignedTo", ng-class="{blocked: duty.is_blocked}") + div.duty-single(tg-duty="duty", tg-repeat="duty in vm.assignedTo", ng-class="{blocked: duty.is_blocked}") div.title-bar.watching-title(translate="HOME.WATCHING_SECTION") From 7224a1f688e5edd012b0057c6065d36f2fb843a0 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 5 May 2015 15:14:59 +0200 Subject: [PATCH 112/366] Fixing tests --- app/modules/home/duties/duty.directive.spec.coffee | 10 ++++------ app/modules/home/home.directive.spec.coffee | 3 ++- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/modules/home/duties/duty.directive.spec.coffee b/app/modules/home/duties/duty.directive.spec.coffee index 25c9729f..71909cbc 100644 --- a/app/modules/home/duties/duty.directive.spec.coffee +++ b/app/modules/home/duties/duty.directive.spec.coffee @@ -52,10 +52,6 @@ describe "homeDirective", () -> project: 1 ref: 1 _name: "userstories" - subject: "Testing js subject" - status_extra_info: { - color: "#CCCCCC" - } assigned_to_extra_info: { photo: "http://jstesting.taiga.io/photo" full_name_display: "Taiga testing js" @@ -72,8 +68,10 @@ describe "homeDirective", () -> mockTranslate.instant .withArgs("COMMON.USER_STORY") - .returns("COMMON.USER_STORY") + .returns("User story translated") elm = createDirective() scope.$apply() - expect(elm.find('.duty-status').css("color")).to.be.equal('rgb(204, 204, 204)') + expect(elm.isolateScope().vm.getDutyType()).to.be.equal("User story translated") + expect(elm.isolateScope().vm.getUrl()).to.be.equal("http://jstesting.taiga.io") + expect(elm.isolateScope().vm.getProjectName()).to.be.equal("testing js project") diff --git a/app/modules/home/home.directive.spec.coffee b/app/modules/home/home.directive.spec.coffee index cc12cfaf..9d96a1ee 100644 --- a/app/modules/home/home.directive.spec.coffee +++ b/app/modules/home/home.directive.spec.coffee @@ -57,4 +57,5 @@ describe "homeDirective", () -> it "home directive content", () -> elm = createDirective() scope.$apply() - expect(elm.find('.duty-single')).to.have.length(6) + expect(elm.isolateScope().vm.assignedTo.size).to.be.equal(3) + expect(elm.isolateScope().vm.watching.size).to.be.equal(3) From e399999750849837761e02f7ccce0fc153289b4c Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 5 May 2015 15:21:32 +0200 Subject: [PATCH 113/366] init profile projects --- .../profile/includes/profile-projects.jade | 129 ------------------ .../profile-projects.directive.coffee | 17 +++ .../profile-projects/profile-projects.jade | 43 ++++++ app/modules/profile/profile.jade | 2 +- app/modules/projects/projects.service.coffee | 14 +- .../projects/projects.service.spec.coffee | 16 ++- 6 files changed, 89 insertions(+), 132 deletions(-) delete mode 100644 app/modules/profile/includes/profile-projects.jade create mode 100644 app/modules/profile/profile-projects/profile-projects.directive.coffee create mode 100644 app/modules/profile/profile-projects/profile-projects.jade diff --git a/app/modules/profile/includes/profile-projects.jade b/app/modules/profile/includes/profile-projects.jade deleted file mode 100644 index 2963a7ee..00000000 --- a/app/modules/profile/includes/profile-projects.jade +++ /dev/null @@ -1,129 +0,0 @@ -section.profile-projects - - for (var x = 0; x < 3; x++) - div.project-list-single - div.project-list-single-left - - div.project-list-single-title - h1 - a(href="", title="View {{ project.title }}") My Side Project - p We plan to build a hundred of so called "telehubs" so people from all over the world can immediately relocate their physical self at any other telehub in microseconds. The technology and science behind this project is sound and we are now ready to build the first telehub and then massproduce them. - - div.project-list-single-tags.tags-container - // Tag border style has to be set by JS - span.tag(style='border-left: 5px solid #73d216;') - span.tag-name python - span.tag(style='border-left: 5px solid #cc0000;') - span.tag-name groovy - span.tag(style='border-left: 5px solid #25f45c;') - span.tag-name opensource - - div.project-list-single-right - - div.project-list-single-stats - div.stat-comments(title="2 comments") - span.icon.icon-comment - span.stat-num 2 - div.stat-favorite.active(title="2 favorites") - span.icon.icon-star-fill - span.stat-num 4 - div.stat-viewer(title="2 followers") - span.icon.icon-open-eye - span.stat-num 4 - - div.project-list-single-members - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/mantia/128.jpg") - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/annapickard/128.jpg") - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/nexy_dre/128.jpg", alt="{{ user.nickname }}") - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/pixeliris/128.jpg", alt="{{ user.nickname }}") - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/cbillins/128.jpg", alt="{{ user.nickname }}") - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/allisongrayce/128.jpg", alt="{{ user.nickname }}") - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/uxceo/128.jpg", alt="{{ user.nickname }}") - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/syswarren/128.jpg", alt="{{ user.nickname }}") - - div.project-list-single - div.project-list-single-left - div.project-list-single-title - h1 - a(href="", title="View {{ project.title }}") Teletransportation hubs - - div.project-list-single-tags.tags-container - // Tag border style has to be set by JS - span.tag(style='border-left: 5px solid #43d56f;') - span.tag-name javascript - span.tag(style='border-left: 5px solid #0000cc;') - span.tag-name css - span.tag(style='border-left: 5px solid #cc43fd;') - span.tag-name design - - div.project-list-single-right - div.project-list-single-stats - div.stat-comments(title="2 comments") - span.icon.icon-comment - span.stat-num 2 - div.stat-favorite(title="2 favorites") - span.icon.icon-star-fill - span.stat-num 4 - div.stat-viewer(title="2 followers") - span.icon.icon-open-eye - span.stat-num 4 - div.project-list-single-members - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/mantia/128.jpg") - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/annapickard/128.jpg") - - div.project-list-single - div.project-list-single-left - - div.project-list-single-title - h1 - a(href="", title="View {{ project.title }}") Taiga - p Una plataforma social para crear comunidad entorno a la tienda. Esta comunidad está pensada para quedar a hacer deporte, compartir iniciativas. - - div.project-list-single-tags.tags-container - // Tag border style has to be set by JS - span.tag(style='border-left: 5px solid #11cd00;') - span.tag-name PHP - span.tag(style='border-left: 5px solid #ff00dd;') - span.tag-name marketing - span.tag(style='border-left: 5px solid #cdcd54;') - span.tag-name wordpress - div.project-list-single-right - - div.project-list-single-stats - div.stat-comments - span.icon.icon-comment - span.stat-num 2 - div.stat-favorite.active - span.icon.icon-star-fill - span.stat-num 4 - div.stat-viewer - span.icon.icon-open-eye - span.stat-num 4 - - div.project-list-single-members - - for (var x = 0; x < 2; x++) - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/mantia/128.jpg") - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/annapickard/128.jpg") - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/nexy_dre/128.jpg", alt="{{ user.nickname }}") - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/pixeliris/128.jpg", alt="{{ user.nickname }}") - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/cbillins/128.jpg", alt="{{ user.nickname }}") - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/allisongrayce/128.jpg", alt="{{ user.nickname }}") - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/uxceo/128.jpg", alt="{{ user.nickname }}") - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/syswarren/128.jpg", alt="{{ user.nickname }}") diff --git a/app/modules/profile/profile-projects/profile-projects.directive.coffee b/app/modules/profile/profile-projects/profile-projects.directive.coffee new file mode 100644 index 00000000..d58ea85c --- /dev/null +++ b/app/modules/profile/profile-projects/profile-projects.directive.coffee @@ -0,0 +1,17 @@ +ProfileProjectsDirective = (projectsService) -> + link = (scope, el, attrs, ctrl) -> + scope.vm = {} + taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.projects.get("all")) + + directive = { + templateUrl: "profile/profile-projects/profile-projects.html" + scope: {} + link: link + } + + return directive + + +ProfileProjectsDirective.$inject = ["tgProjectsService"] + +angular.module("taigaProfile").directive("tgProfileProjects", ProfileProjectsDirective) diff --git a/app/modules/profile/profile-projects/profile-projects.jade b/app/modules/profile/profile-projects/profile-projects.jade new file mode 100644 index 00000000..b6c6cd16 --- /dev/null +++ b/app/modules/profile/profile-projects/profile-projects.jade @@ -0,0 +1,43 @@ +section.profile-projects + div.project-list-single(tg-repeat="project in vm.projects") + div.project-list-single-left + + div.project-list-single-title + h1 + a(href="#", tg-nav="project:project=project.slug", title="{{ ::project.name }}") {{::project.name}} + p {{ ::project.description | limitTo:300 }} + + div.project-list-single-tags.tags-container + span.tag(style='border-left: 5px solid {{::tag.color}};', ng-repeat="tag in ::project.colorized_tags") + span.tag-name {{::tag.name}} + + div.project-list-single-right + + div.project-list-single-stats + div.stat-comments(title="2 comments") + span.icon.icon-comment + span.stat-num 2 + div.stat-favorite.active(title="2 favorites") + span.icon.icon-star-fill + span.stat-num 4 + div.stat-viewer(title="2 followers") + span.icon.icon-open-eye + span.stat-num 4 + + div.project-list-single-members + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/mantia/128.jpg") + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/annapickard/128.jpg") + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/nexy_dre/128.jpg", alt="{{ user.nickname }}") + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/pixeliris/128.jpg", alt="{{ user.nickname }}") + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/cbillins/128.jpg", alt="{{ user.nickname }}") + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/allisongrayce/128.jpg", alt="{{ user.nickname }}") + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/uxceo/128.jpg", alt="{{ user.nickname }}") + a(href="", title="View {{ user.nickname }} profile") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/syswarren/128.jpg", alt="{{ user.nickname }}") diff --git a/app/modules/profile/profile.jade b/app/modules/profile/profile.jade index ceede2d0..88d88eb6 100644 --- a/app/modules/profile/profile.jade +++ b/app/modules/profile/profile.jade @@ -10,7 +10,7 @@ div.profile.centered include includes/profile-timeline div(tg-profile-tab="projects", tab-title="Projects Tab", tab-icon="icon-project") - include includes/profile-projects + div(tg-profile-projects) div(tg-profile-tab="contacts", tab-title="Contacts Tab", tab-icon="icon-team") include includes/profile-contacts diff --git a/app/modules/projects/projects.service.coffee b/app/modules/projects/projects.service.coffee index a2ea2ef8..11c41624 100644 --- a/app/modules/projects/projects.service.coffee +++ b/app/modules/projects/projects.service.coffee @@ -21,9 +21,21 @@ class ProjectsService extends taiga.Service @._projectsPromise = @rs.projects.listByMember(@rootScope.user?.id) @._projectsPromise.then (projects) => - for project in projects + _.map projects, (project) => project.url = @projectUrl.get(project) + + console.log project.memberships + + project.colorized_tags = [] + + if project.tags + tags = project.tags.sort() + + project.colorized_tags = _.map tags, (tag) -> + color = project.tags_colors[tag] + return {name: tag, color: color} + @._projects = Immutable.fromJS({ all: projects, recents: projects.slice(0, 10) diff --git a/app/modules/projects/projects.service.spec.coffee b/app/modules/projects/projects.service.spec.coffee index a34d4648..615ac5ce 100644 --- a/app/modules/projects/projects.service.spec.coffee +++ b/app/modules/projects/projects.service.spec.coffee @@ -64,7 +64,7 @@ describe "tgProjects", -> beforeEach -> projects = [ - {"id": 1}, + {"id": 1, tags: ['xx', 'yy', 'aa'], tags_colors: {xx: "red", yy: "blue", aa: "white"}}, {"id": 2}, {"id": 3}, {"id": 4}, @@ -113,6 +113,20 @@ describe "tgProjects", -> expect(projectsService.projects.get("all").toJS()[0].url).to.be.equal("url-1") expect(projectsService.projects.get("recents").toJS()[0].url).to.be.equal("url-1") + it "add sorted colorized_tags project object", () -> + mocks.thenStub.callArg(0, projects) + + tags = [ + {name: "aa", color: "white"}, + {name: "xx", color: "red"}, + {name: "yy", color: "blue"} + ]; + + + colorized_tags = projectsService.projects.get("all").toJS()[0].colorized_tags + + expect(colorized_tags).to.be.eql(tags) + it "newProject, create the wizard lightbox", () -> projectsService.newProject() From 1ee92e99096f23a718df2ed573a32e086a3c2839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 5 May 2015 15:26:23 +0200 Subject: [PATCH 114/366] Projects Page --- .../home/projects/home-project-list.scss | 8 -- .../projects/listing/projects-listing.scss | 9 -- app/partials/project/project.jade | 27 ++++- app/styles/components/private.scss | 9 ++ app/styles/modules/home-project.scss | 104 +++++++++++++----- 5 files changed, 110 insertions(+), 47 deletions(-) create mode 100644 app/styles/components/private.scss diff --git a/app/modules/home/projects/home-project-list.scss b/app/modules/home/projects/home-project-list.scss index 6f942ed9..d8f9071c 100644 --- a/app/modules/home/projects/home-project-list.scss +++ b/app/modules/home/projects/home-project-list.scss @@ -34,14 +34,6 @@ vertical-align: middle; white-space: nowrap; } - .private { - display: inline-block; - margin-left: .3rem; - width: .5rem; - path { - fill: $gray-light; - } - } } p { @extend %text; diff --git a/app/modules/projects/listing/projects-listing.scss b/app/modules/projects/listing/projects-listing.scss index 3a2a5d08..5354288c 100644 --- a/app/modules/projects/listing/projects-listing.scss +++ b/app/modules/projects/listing/projects-listing.scss @@ -29,15 +29,6 @@ vertical-align: middle; white-space: nowrap; } - .private { - display: inline-block; - margin-left: .3rem; - width: .5rem; - path { - fill: $gray-light; - transition: fill .3s linear; - } - } p { @extend %text; @extend %small; diff --git a/app/partials/project/project.jade b/app/partials/project/project.jade index 5cba01e1..d02dce3b 100644 --- a/app/partials/project/project.jade +++ b/app/partials/project/project.jade @@ -2,9 +2,30 @@ doctype html div.wrapper(ng-controller="ProjectController as ctrl") nav.menu.hidden(tg-project-menu) - section.main.single-project - h1 - span.green(tg-bo-bind="project.name", class="project-name") + div.main.centered.single-project + section.single-project-intro + h1 + span.green(tg-bo-bind="project.name", class="project-name") + span.private(ng-if="project.is_private", title="{{'PROJECT.PRIVATE' | translate}}") + include ../../svg/lock.svg + p.description(tg-bo-bind="project.description") + div.project-list-single-tags.tags-container(ng-if="::project.tags") + div.tags-block(tg-colorize-tags="project.tags", tg-colorize-tags-type="backlog") + div.project-data + section.timeline + span TODO. Missing the amazing timeline around!! + section.involved-data + h2.title Team + ul.involved-team + a(href="", title="{{member.full_name}}", ng-repeat="member in project.memberships") + img(tg-bo-src="member.photo", alt="{{member.full_name}}") + + h2.title Organizations + div.involved-organization + a(href="", title="User Name") + img(src="https://s3.amazonaws.com/uifaces/faces/twitter/dan_higham/48.jpg", alt="{{member.full_name}}") + + // div.summary div.summary-stats span.number(ng-bind="stats.total_points") -- diff --git a/app/styles/components/private.scss b/app/styles/components/private.scss new file mode 100644 index 00000000..e77260ff --- /dev/null +++ b/app/styles/components/private.scss @@ -0,0 +1,9 @@ +.private { + display: inline-block; + margin-left: .3rem; + width: .5rem; + path { + fill: $gray-light; + transition: fill .3s linear; + } +} diff --git a/app/styles/modules/home-project.scss b/app/styles/modules/home-project.scss index b093b945..d418719e 100644 --- a/app/styles/modules/home-project.scss +++ b/app/styles/modules/home-project.scss @@ -1,34 +1,84 @@ -.summary-stats { - align-items: flex-start; - display: flex; - .info-num { - @extend %xlarge; - @extend %bold; - float: left; - margin-right: .3rem; - position: relative; - top: 5px; +.single-project { + h1 { + line-height: 1.2; + margin: 0; + vertical-align: middle; } - .info-text { - @extend %small; - float: left; - line-height: .9rem; + .private { + font-size: 1rem; + vertical-align: super; } -} - -.project-data-container { - display: flex; - justify-content: space-between; - ul { - flex-grow: 0; - max-width: 33%; + .project-data { + display: flex; } - li { - display: inline-block; - margin-right: .1rem; - width: 10%; - figure { + .title { + @extend %larger; + @extend %title; + align-content: center; + background: $whitish; + display: flex; + margin-bottom: .5rem; + padding: .9rem 1rem; + } + .timeline { + flex: 1; + margin-right: 1rem; + } + .involved-data { + flex-basis: 200px; + } + .involved-team, + .involved-organization { + display: flex; + flex-wrap: wrap; + margin-bottom: 1rem; + a { + display: block; + margin-right: .14rem; + width: 24%; + &:nth-child(4n) { + margin-right: 0; + } + } + img { width: 100%; } } } + + +//.summary-stats { +// align-items: flex-start; +// display: flex; +// .info-num { +// @extend %xlarge; +// @extend %bold; +// float: left; +// margin-right: .3rem; +// position: relative; +// top: 5px; +// } +// .info-text { +// @extend %small; +// float: left; +// line-height: .9rem; +// } +//} +// +//.project-data-container { +// display: flex; +// justify-content: space-between; +// ul { +// flex-grow: 0; +// max-width: 33%; +// } +// li { +// display: inline-block; +// margin-right: .1rem; +// width: 10%; +// figure { +// width: 100%; +// } +// } +//} +// From 70aa783729c6fcf50e3023461e21498f57d55d2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 6 May 2015 08:14:17 +0200 Subject: [PATCH 115/366] Minor fixes to the project page styles --- app/styles/modules/home-project.scss | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/styles/modules/home-project.scss b/app/styles/modules/home-project.scss index d418719e..d2e068d2 100644 --- a/app/styles/modules/home-project.scss +++ b/app/styles/modules/home-project.scss @@ -12,13 +12,15 @@ display: flex; } .title { - @extend %larger; - @extend %title; + @extend %medium; + @extend %text; + @extend %bold; align-content: center; background: $whitish; display: flex; + justify-content: space-between; margin-bottom: .5rem; - padding: .9rem 1rem; + padding: .5rem 1rem; } .timeline { flex: 1; From 963754301f2ac6de9f27b2a1e790115af1994f8c Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 6 May 2015 09:21:57 +0200 Subject: [PATCH 116/366] Adding tests --- app/modules/feedback/feedback.service.coffee | 1 - .../feedback/feedback.service.spec.coffee | 38 +++++++ .../home/duties/duty.directive.spec.coffee | 4 +- .../home-project-list-directive.spec.coffee | 65 ++++++++++++ ...ropdown-project-list.directive.spec.coffee | 58 +++++++++++ .../dropdown-user.directive.spec.coffee | 99 +++++++++++++++++++ .../navigation-bar.directive.spec.coffee | 66 +++++++++++++ app/modules/projects/projects.service.coffee | 3 - 8 files changed, 328 insertions(+), 6 deletions(-) create mode 100644 app/modules/feedback/feedback.service.spec.coffee create mode 100644 app/modules/home/projects/home-project-list-directive.spec.coffee create mode 100644 app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.spec.coffee create mode 100644 app/modules/navigation-bar/dropdown-user/dropdown-user.directive.spec.coffee create mode 100644 app/modules/navigation-bar/navigation-bar.directive.spec.coffee diff --git a/app/modules/feedback/feedback.service.coffee b/app/modules/feedback/feedback.service.coffee index 86b8d6f9..06f3e5f5 100644 --- a/app/modules/feedback/feedback.service.coffee +++ b/app/modules/feedback/feedback.service.coffee @@ -3,7 +3,6 @@ class FeedbackService extends taiga.Service constructor: (@lightboxFactory) -> - sendFeedback: -> @lightboxFactory.create("tg-lb-feedback", { "class": "lightbox lightbox-feedback lightbox-generic-form" diff --git a/app/modules/feedback/feedback.service.spec.coffee b/app/modules/feedback/feedback.service.spec.coffee new file mode 100644 index 00000000..da067961 --- /dev/null +++ b/app/modules/feedback/feedback.service.spec.coffee @@ -0,0 +1,38 @@ +describe "tgFeedbackService", -> + feedbackService = provide = null + mocks = {} + + _mockTgLightboxFactory = () -> + mocks.tgLightboxFactory = { + create: sinon.stub() + } + + provide.value "tgLightboxFactory", mocks.tgLightboxFactory + + _inject = (callback) -> + inject (_tgFeedbackService_) -> + feedbackService = _tgFeedbackService_ + callback() if callback + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgLightboxFactory() + return null + + _setup = -> + _mocks() + + beforeEach -> + module "taigaFeedback" + _setup() + _inject() + + it "work in progress filled", () -> + expect(mocks.tgLightboxFactory.create.callCount).to.be.equal(0) + feedbackService.sendFeedback() + expect(mocks.tgLightboxFactory.create.callCount).to.be.equal(1) + params = { + "class": "lightbox lightbox-feedback lightbox-generic-form" + } + expect(mocks.tgLightboxFactory.create.calledWith("tg-lb-feedback", params)).to.be.true() diff --git a/app/modules/home/duties/duty.directive.spec.coffee b/app/modules/home/duties/duty.directive.spec.coffee index 71909cbc..88ceb7b7 100644 --- a/app/modules/home/duties/duty.directive.spec.coffee +++ b/app/modules/home/duties/duty.directive.spec.coffee @@ -1,4 +1,4 @@ -describe "homeDirective", () -> +describe "dutyDirective", () -> scope = compile = provide = null mockTgProjectsService = null mockTgNavUrls = null @@ -47,7 +47,7 @@ describe "homeDirective", () -> scope = $rootScope.$new() compile = $compile - it "duty directive content", () -> + it "duty directive scope content", () -> scope.duty = { project: 1 ref: 1 diff --git a/app/modules/home/projects/home-project-list-directive.spec.coffee b/app/modules/home/projects/home-project-list-directive.spec.coffee new file mode 100644 index 00000000..1ee02cbe --- /dev/null +++ b/app/modules/home/projects/home-project-list-directive.spec.coffee @@ -0,0 +1,65 @@ +describe "homeProjectListDirective", () -> + scope = compile = provide = null + mockTgProjectsService = null + template = "
" + recents = [] + + createDirective = () -> + elm = compile(template)(scope) + return elm + + _mockTgProjectsService = () -> + mockTgProjectsService = { + newProject: sinon.stub() + projects: { + get: sinon.stub() + } + } + provide.value "tgProjectsService", mockTgProjectsService + + _mockTranslateFilter = () -> + mockTranslateFilter = (value) -> + return value + provide.value "translateFilter", mockTranslateFilter + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgProjectsService() + _mockTranslateFilter() + return null + + beforeEach -> + module "templates" + module "taigaHome" + + _mocks() + + inject ($rootScope, $compile) -> + scope = $rootScope.$new() + compile = $compile + + recents = Immutable.fromJS([ + { + id:1 + }, + { + id: 2 + } + ]) + + it "home project list directive scope content", () -> + mockTgProjectsService.projects.get + .withArgs("recents") + .returns(recents) + + elm = createDirective() + scope.$apply() + expect(elm.isolateScope().vm.projects.size).to.be.equal(2) + + it "home project list directive newProject", () -> + elm = createDirective() + scope.$apply() + expect(mockTgProjectsService.newProject.callCount).to.be.equal(0) + elm.isolateScope().vm.newProject() + expect(mockTgProjectsService.newProject.callCount).to.be.equal(1) diff --git a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.spec.coffee b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.spec.coffee new file mode 100644 index 00000000..90420887 --- /dev/null +++ b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.spec.coffee @@ -0,0 +1,58 @@ +describe "dropdownProjectListDirective", () -> + scope = compile = provide = null + mockTgProjectsService = null + template = "
" + recents = [] + + createDirective = () -> + elm = compile(template)(scope) + return elm + + _mockTgProjectsService = () -> + mockTgProjectsService = { + newProject: sinon.stub() + projects: { + get: sinon.stub() + } + } + provide.value "tgProjectsService", mockTgProjectsService + + _mockTranslateFilter = () -> + mockTranslateFilter = (value) -> + return value + provide.value "translateFilter", mockTranslateFilter + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgProjectsService() + _mockTranslateFilter() + return null + + beforeEach -> + module "templates" + module "taigaNavigationBar" + + _mocks() + + inject ($rootScope, $compile) -> + scope = $rootScope.$new() + compile = $compile + + recents = Immutable.fromJS([ + { + id:1 + }, + { + id: 2 + } + ]) + + it "dropdown project list directive scope content", () -> + mockTgProjectsService.projects.get + .withArgs("recents") + .returns(recents) + + elm = createDirective() + scope.$apply() + expect(elm.isolateScope().vm.projects.size).to.be.equal(2) diff --git a/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.spec.coffee b/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.spec.coffee new file mode 100644 index 00000000..f4994de2 --- /dev/null +++ b/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.spec.coffee @@ -0,0 +1,99 @@ +describe "dropdownUserDirective", () -> + scope = compile = provide = null + mockTgAuth = null + mockTgConfig = null + mockTgLocation = null + mockTgNavUrls = null + mockTgFeedbackService = null + template = "
" + + createDirective = () -> + elm = compile(template)(scope) + return elm + + _mockTranslateFilter = () -> + mockTranslateFilter = (value) -> + return value + provide.value "translateFilter", mockTranslateFilter + + _mockTgAuth = () -> + mockTgAuth = { + getUser: sinon.stub() + logout: sinon.stub() + } + provide.value "$tgAuth", mockTgAuth + + _mockTgConfig = () -> + mockTgConfig = { + get: sinon.stub() + } + provide.value "$tgConfig", mockTgConfig + + _mockTgLocation = () -> + mockTgLocation = { + path: sinon.stub() + } + provide.value "$tgLocation", mockTgLocation + + _mockTgNavUrls = () -> + mockTgNavUrls = { + resolve: sinon.stub() + } + provide.value "$tgNavUrls", mockTgNavUrls + + _mockTgFeedbackService = () -> + mockTgFeedbackService = { + sendFeedback: sinon.stub() + } + provide.value "tgFeedbackService", mockTgFeedbackService + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTranslateFilter() + _mockTgAuth() + _mockTgConfig() + _mockTgLocation() + _mockTgNavUrls() + _mockTgFeedbackService() + return null + + beforeEach -> + module "templates" + module "taigaNavigationBar" + + _mocks() + + inject ($rootScope, $compile) -> + scope = $rootScope.$new() + compile = $compile + + it "dropdown user directive scope content", () -> + mockTgAuth.getUser.withArgs().returns({id: 66}) + mockTgConfig.get.withArgs("feedbackEnabled").returns(true) + elm = createDirective() + scope.$apply() + + vm = elm.isolateScope().vm + expect(vm.user.id).to.be.equal(66) + expect(vm.isFeedbackEnabled).to.be.equal(true) + + it "dropdown user log out", () -> + mockTgNavUrls.resolve.withArgs("login").returns("/login") + elm = createDirective() + scope.$apply() + vm = elm.isolateScope().vm + expect(mockTgAuth.logout.callCount).to.be.equal(0) + expect(mockTgLocation.path.callCount).to.be.equal(0) + vm.logout() + expect(mockTgAuth.logout.callCount).to.be.equal(1) + expect(mockTgLocation.path.callCount).to.be.equal(1) + expect(mockTgLocation.path.calledWith("/login")).to.be.true() + + it "dropdown user send feedback", () -> + elm = createDirective() + scope.$apply() + vm = elm.isolateScope().vm + expect(mockTgFeedbackService.sendFeedback.callCount).to.be.equal(0) + vm.sendFeedback() + expect(mockTgFeedbackService.sendFeedback.callCount).to.be.equal(1) diff --git a/app/modules/navigation-bar/navigation-bar.directive.spec.coffee b/app/modules/navigation-bar/navigation-bar.directive.spec.coffee new file mode 100644 index 00000000..8e909f54 --- /dev/null +++ b/app/modules/navigation-bar/navigation-bar.directive.spec.coffee @@ -0,0 +1,66 @@ +describe "navigationBarDirective", () -> + scope = compile = provide = null + mockTgProjectsService = null + template = "
" + recents = [] + + createDirective = () -> + elm = compile(template)(scope) + return elm + + _mockTgProjectsService = () -> + mockTgProjectsService = { + newProject: sinon.stub() + projects: { + get: sinon.stub() + } + } + provide.value "tgProjectsService", mockTgProjectsService + + _mockTranslateFilter = () -> + mockTranslateFilter = (value) -> + return value + provide.value "translateFilter", mockTranslateFilter + + _mockTgDropdownProjectListDirective = () -> + provide.factory 'tgDropdownProjectListDirective', () -> {} + + _mockTgDropdownUserDirective = () -> + provide.factory 'tgDropdownUserDirective', () -> {} + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgProjectsService() + _mockTranslateFilter() + _mockTgDropdownProjectListDirective() + _mockTgDropdownUserDirective() + return null + + beforeEach -> + module "templates" + module "taigaNavigationBar" + + _mocks() + + inject ($rootScope, $compile) -> + scope = $rootScope.$new() + compile = $compile + + recents = Immutable.fromJS([ + { + id:1 + }, + { + id: 2 + } + ]) + + it "navigation bar directive scope content", () -> + mockTgProjectsService.projects.get + .withArgs("recents") + .returns(recents) + + elm = createDirective() + scope.$apply() + expect(elm.isolateScope().vm.projects.size).to.be.equal(2) diff --git a/app/modules/projects/projects.service.coffee b/app/modules/projects/projects.service.coffee index 11c41624..ba109f6c 100644 --- a/app/modules/projects/projects.service.coffee +++ b/app/modules/projects/projects.service.coffee @@ -24,9 +24,6 @@ class ProjectsService extends taiga.Service _.map projects, (project) => project.url = @projectUrl.get(project) - - console.log project.memberships - project.colorized_tags = [] if project.tags From c2c40ac7a4eea94a9cf59be4370edc357caa85ee Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 6 May 2015 07:51:07 +0200 Subject: [PATCH 117/366] hide project info --- .../profile-projects/profile-projects.jade | 57 +++++++++---------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/app/modules/profile/profile-projects/profile-projects.jade b/app/modules/profile/profile-projects/profile-projects.jade index b6c6cd16..785d128b 100644 --- a/app/modules/profile/profile-projects/profile-projects.jade +++ b/app/modules/profile/profile-projects/profile-projects.jade @@ -11,33 +11,32 @@ section.profile-projects span.tag(style='border-left: 5px solid {{::tag.color}};', ng-repeat="tag in ::project.colorized_tags") span.tag-name {{::tag.name}} - div.project-list-single-right + // div.project-list-single-right + // div.project-list-single-stats + // div.stat-comments(title="2 comments") + // span.icon.icon-comment + // span.stat-num 2 + // div.stat-favorite.active(title="2 favorites") + // span.icon.icon-star-fill + // span.stat-num 4 + // div.stat-viewer(title="2 followers") + // span.icon.icon-open-eye + // span.stat-num 4 - div.project-list-single-stats - div.stat-comments(title="2 comments") - span.icon.icon-comment - span.stat-num 2 - div.stat-favorite.active(title="2 favorites") - span.icon.icon-star-fill - span.stat-num 4 - div.stat-viewer(title="2 followers") - span.icon.icon-open-eye - span.stat-num 4 - - div.project-list-single-members - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/mantia/128.jpg") - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/annapickard/128.jpg") - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/nexy_dre/128.jpg", alt="{{ user.nickname }}") - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/pixeliris/128.jpg", alt="{{ user.nickname }}") - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/cbillins/128.jpg", alt="{{ user.nickname }}") - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/allisongrayce/128.jpg", alt="{{ user.nickname }}") - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/uxceo/128.jpg", alt="{{ user.nickname }}") - a(href="", title="View {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/syswarren/128.jpg", alt="{{ user.nickname }}") + // div.project-list-single-members + // a(href="", title="View {{ user.nickname }} profile") + // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/mantia/128.jpg") + // a(href="", title="View {{ user.nickname }} profile") + // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/annapickard/128.jpg") + // a(href="", title="View {{ user.nickname }} profile") + // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/nexy_dre/128.jpg", alt="{{ user.nickname }}") + // a(href="", title="View {{ user.nickname }} profile") + // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/pixeliris/128.jpg", alt="{{ user.nickname }}") + // a(href="", title="View {{ user.nickname }} profile") + // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/cbillins/128.jpg", alt="{{ user.nickname }}") + // a(href="", title="View {{ user.nickname }} profile") + // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/allisongrayce/128.jpg", alt="{{ user.nickname }}") + // a(href="", title="View {{ user.nickname }} profile") + // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/uxceo/128.jpg", alt="{{ user.nickname }}") + // a(href="", title="View {{ user.nickname }} profile") + // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/syswarren/128.jpg", alt="{{ user.nickname }}") From 2eef2a35f4e1e43363311d9246271116ba252f66 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 6 May 2015 10:55:32 +0200 Subject: [PATCH 118/366] timeline with immutable objects --- .../profile/includes/profile-timeline.jade | 3 - ...profile-timeline-item-title.service.coffee | 6 +- ...le-timeline-item-title.service.spec.coffee | 6 +- .../profile-timeline-item.controller.coffee | 32 +++-- ...ofile-timeline-item.controller.spec.coffee | 4 +- .../profile-timeline-item.jade | 2 +- .../profile-timeline.controller.coffee | 55 ++------- .../profile-timeline.controller.spec.coffee | 70 ++++------- .../profile-timeline.directive.coffee | 9 ++ .../profile-timeline/profile-timeline.jade | 3 + .../profile-timeline.service.coffee | 51 ++++++++ .../profile-timeline.service.spec.coffee | 113 ++++++++++++++++++ app/modules/profile/profile.jade | 2 +- 13 files changed, 238 insertions(+), 118 deletions(-) delete mode 100644 app/modules/profile/includes/profile-timeline.jade create mode 100644 app/modules/profile/profile-timeline/profile-timeline.directive.coffee create mode 100644 app/modules/profile/profile-timeline/profile-timeline.jade create mode 100644 app/modules/profile/profile-timeline/profile-timeline.service.coffee create mode 100644 app/modules/profile/profile-timeline/profile-timeline.service.spec.coffee diff --git a/app/modules/profile/includes/profile-timeline.jade b/app/modules/profile/includes/profile-timeline.jade deleted file mode 100644 index 7ec2c7ee..00000000 --- a/app/modules/profile/includes/profile-timeline.jade +++ /dev/null @@ -1,3 +0,0 @@ -section.profile-timeline(ng-controller="ProfileTimeline as ctrl") - div(infinite-scroll="ctrl.loadTimeline()", infinite-scroll-distance="3", infinite-scroll-disabled='ctrl.loadingData') - div(ng-repeat="timeline in ctrl.timelineList", tg-profile-timeline-item="timeline") diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.coffee b/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.coffee index a4079994..09da8a2f 100644 --- a/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.coffee +++ b/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.coffee @@ -21,7 +21,7 @@ class ProfileTimelineItemTitle if param == "username" user = timeline.data.user title_attr = @translate.instant('COMMON.SEE_USER_PROFILE', {username: user.username}) - url = 'user-profile:username=activity.user.username' + url = 'user-profile:username=vm.activity.user.username' return @._getLink(url, user.username, title_attr) else if param == 'field_name' @@ -30,12 +30,12 @@ class ProfileTimelineItemTitle return @translate.instant(@._fieldTranslationKey[field_name]) else if param == 'project_name' - url = 'project:project=activity.project.slug' + url = 'project:project=vm.activity.project.slug' return @._getLink(url, timeline.data.project.name) else if param == 'sprint_name' - url = 'project-taskboard:project=activity.project.slug,sprint=activity.sprint.slug' + url = 'project-taskboard:project=vm.activity.project.slug,sprint=vm.activity.sprint.slug' return @._getLink(url, timeline.data.milestone.name) diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.spec.coffee b/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.spec.coffee index 133dde8b..f9def537 100644 --- a/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.spec.coffee +++ b/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.spec.coffee @@ -52,7 +52,7 @@ describe "tgProfileTimelineItemTitle", -> .returns('user-param') usernamelink = sinon.match ((value) -> - return value.username == '
xx' + return value.username == 'xx' ), "usernamelink" mockTranslate.instant @@ -113,7 +113,7 @@ describe "tgProfileTimelineItemTitle", -> } projectparam = sinon.match ((value) -> - return value.project_name == 'project_name' + return value.project_name == 'project_name' ), "projectparam" mockTranslate.instant @@ -141,7 +141,7 @@ describe "tgProfileTimelineItemTitle", -> } milestoneparam = sinon.match ((value) -> - return value.sprint_name == 'milestone_name' + return value.sprint_name == 'milestone_name' ), "milestoneparam" mockTranslate.instant diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.coffee b/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.coffee index e0cf1141..7ab2901c 100644 --- a/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.coffee +++ b/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.coffee @@ -1,31 +1,33 @@ class ProfileTimelineItemController @.$inject = [ - "$scope", "$sce", "tgProfileTimelineItemType", "tgProfileTimelineItemTitle" ] - constructor: (@scope, @sce, @profileTimelineItemType, @profileTimelineItemTitle) -> - event = @parseEventType(@scope.vm.timeline.event_type) - type = @profileTimelineItemType.getType(@scope.vm.timeline, event) + constructor: (@sce, @profileTimelineItemType, @profileTimelineItemTitle) -> + timeline = @.timeline + event = @.parseEventType(timeline.event_type) + type = @profileTimelineItemType.getType(timeline, event) @.activity = {} - @.activity.user = @scope.vm.timeline.data.user - @.activity.project = @scope.vm.timeline.data.project - @.activity.sprint = @scope.vm.timeline.data.milestone - @.activity.title = @profileTimelineItemTitle.getTitle(@scope.vm.timeline, event, type) - @.activity.created_formated = moment(@scope.vm.timeline.created).fromNow() + @.activity.user = timeline.data.user + @.activity.project = timeline.data.project + @.activity.sprint = timeline.data.milestone + @.activity.title = @profileTimelineItemTitle.getTitle(timeline, event, type) + @.activity.created_formated = moment(timeline.created).fromNow() + #test + @.activity.obj = @.getObject(timeline, event) if type.description - @.activity.description = @sce.trustAsHtml(type.description(@scope.vm.timeline)) + @.activity.description = @sce.trustAsHtml(type.description(timeline)) if type.member - @.activity.member = type.member(@scope.vm.timeline) + @.activity.member = type.member(timeline) - if @scope.vm.timeline.data.values_diff?.attachments - @.activity.attachments = @scope.vm.timeline.data.values_diff.attachments.new + if timeline.data.values_diff?.attachments + @.activity.attachments = timeline.data.values_diff.attachments.new parseEventType: (event_type) -> event_type = event_type.split(".") @@ -36,5 +38,9 @@ class ProfileTimelineItemController type: event_type[2] } + getObject: (timeline, event) -> + if timeline.data[event.obj] + return timeline.data[event.obj] + angular.module("taigaProfile") .controller("ProfileTimelineItem", ProfileTimelineItemController) diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.spec.coffee b/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.spec.coffee index 5f59fbd0..32edcf26 100644 --- a/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.spec.coffee +++ b/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.spec.coffee @@ -72,7 +72,7 @@ describe "ProfileTimelineItemController", -> it "basic activity fields filled", () -> timeline = scope.vm.timeline - myCtrl = controller("ProfileTimelineItem", {$scope: scope}) + myCtrl = controller("ProfileTimelineItem", {$scope: scope}, {timeline: timeline}) expect(myCtrl.activity.user).to.be.equal(timeline.data.user) expect(myCtrl.activity.project).to.be.equal(timeline.data.project) @@ -94,7 +94,7 @@ describe "ProfileTimelineItemController", -> mockType.description.withArgs(timeline).returns(description) mockType.member.withArgs(timeline).returns(member) - myCtrl = controller("ProfileTimelineItem", {$scope: scope}) + myCtrl = controller("ProfileTimelineItem", {$scope: scope}, {timeline: timeline}) expect(myCtrl.activity.description).to.be.an('object') # $sce.trustAsHtml expect(myCtrl.activity.member).to.be.equal(member) diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item.jade b/app/modules/profile/profile-timeline-item/profile-timeline-item.jade index 64471945..dbf10787 100644 --- a/app/modules/profile/profile-timeline-item/profile-timeline-item.jade +++ b/app/modules/profile/profile-timeline-item/profile-timeline-item.jade @@ -14,7 +14,7 @@ div.activity-image .profile-member-picture img(ng-src="{{::vm.activity.member.user.photo}}", alt="{{::vm.activity.member.user.name}}") .activity-member-info - a(tg-nav="user-profile:username=activity.member.user.username", title="{{::vm.activity.member.user.name }}") + a(tg-nav="user-profile:username=vm.activity.member.user.username", title="{{::vm.activity.member.user.name }}") span {{::vm.activity.member.user.name}} p {{::vm.activity.member.role.name}} diff --git a/app/modules/profile/profile-timeline/profile-timeline.controller.coffee b/app/modules/profile/profile-timeline/profile-timeline.controller.coffee index 714d89ae..db97b33a 100644 --- a/app/modules/profile/profile-timeline/profile-timeline.controller.coffee +++ b/app/modules/profile/profile-timeline/profile-timeline.controller.coffee @@ -25,59 +25,26 @@ mixOf = @.taiga.mixOf class ProfileTimelineController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.FiltersMixin) @.$inject = [ - "$tgResources", - "$tgAuth" + "$tgAuth", + "tgProfileTimelineService" ] - _valid_fields: [ - 'status', - 'subject', - 'description', - 'assigned_to', - 'points', - 'severity', - 'priority', - 'type', - 'attachments', - 'milestone', - 'is_blocked', - 'is_iocaine', - 'content_diff', - 'name', - 'estimated_finish', - 'estimated_start' - ] - - constructor: (@rs, @auth) -> - @.timelineList = [] - @.pagination = {page: 1} + constructor: (@auth, @profileTimelineService) -> + @.timelineList = Immutable.List() + @.page = 1 @.loadingData = false - _isValidField: (values) => - return _.some values, (value) => @._valid_fields.indexOf(value) != -1 - - _filterValidTimelineItems: (timeline) => - if timeline.data.values_diff - values = Object.keys(timeline.data.values_diff) - - if values && values.length - if !@._isValidField(values) - return false - else if values[0] == 'attachments' && timeline.data.values_diff.attachments.new.length == 0 - return false - - return true - loadTimeline: () -> user = @auth.getUser() @.loadingData = true - return @rs.timeline.profile(user.id, @.pagination).then (result) => - newTimelineList = _.filter result.data, @._filterValidTimelineItems - @.timelineList = @timelineList.concat(newTimelineList) - @.pagination.page++ - @.loadingData = false + @profileTimelineService + .getTimeline(user.id, @.page) + .then (newTimelineList) => + @.timelineList = @.timelineList.concat(newTimelineList) + @.page++ + @.loadingData = false angular.module("taigaProfile") .controller("ProfileTimeline", ProfileTimelineController) diff --git a/app/modules/profile/profile-timeline/profile-timeline.controller.spec.coffee b/app/modules/profile/profile-timeline/profile-timeline.controller.spec.coffee index 18669f00..954560cb 100644 --- a/app/modules/profile/profile-timeline/profile-timeline.controller.spec.coffee +++ b/app/modules/profile/profile-timeline/profile-timeline.controller.spec.coffee @@ -1,13 +1,17 @@ describe "ProfileTimelineController", -> myCtrl = scope = $q = provide = null + mocks = {} + mockUser = {id: 3} - _mockTgResources = () -> - provide.value "$tgResources", { - timeline: {} + _mockProfileTimeline = () -> + mocks.profileTimelineService = { + getTimeline: sinon.stub() } + provide.value "tgProfileTimelineService", mocks.profileTimelineService + _mockTgAuth = () -> provide.value "$tgAuth", { getUser: () -> @@ -17,7 +21,7 @@ describe "ProfileTimelineController", -> _mocks = () -> module ($provide) -> provide = $provide - _mockTgResources() + _mockProfileTimeline() _mockTgAuth() return null @@ -32,60 +36,31 @@ describe "ProfileTimelineController", -> myCtrl = $controller "ProfileTimeline" it "timelineList should be an array", () -> - expect(myCtrl.timelineList).is.an("array") - + expect(myCtrl.timelineList.toJS()).is.an("array") it "pagination starts at 1", () -> - expect(myCtrl.pagination.page).to.be.equal(1) + expect(myCtrl.page).to.be.equal(1) describe "load timeline", () -> thenStub = timelineList = null beforeEach () -> - timelineList = { - data: [ - { # valid item - data: { - values_diff: { - "status": "xx", - "subject": "xx" - } - } - }, - { # invalid item - data: { - values_diff: { - "fake": "xx" - } - } - }, - { # invalid item - data: { - values_diff: { - "fake2": "xx" - } - } - }, - { # valid item - data: { - values_diff: { - "fake2": "xx", - "milestone": "xx" - } - } - } - ] - } + timelineList = Immutable.fromJS([ + { fake: "fake"}, + { fake: "fake"}, + { fake: "fake"}, + { fake: "fake"} + ]) thenStub = sinon.stub() profileStub = sinon.stub() - .withArgs(mockUser.id, myCtrl.pagination) + .withArgs(mockUser.id, myCtrl.page) .returns({ then: thenStub }) - myCtrl.rs.timeline.profile = profileStub + mocks.profileTimelineService.getTimeline = profileStub it "the loadingData variable must be true during the timeline load", () -> expect(myCtrl.loadingData).to.be.false @@ -99,18 +74,17 @@ describe "ProfileTimelineController", -> expect(myCtrl.loadingData).to.be.false it "pagiantion increase one every call to loadTimeline", () -> - expect(myCtrl.pagination.page).to.equal(1) + expect(myCtrl.page).to.equal(1) myCtrl.loadTimeline() thenStub.callArgWith(0, timelineList) - expect(myCtrl.pagination.page).to.equal(2) + expect(myCtrl.page).to.equal(2) - it "filter the invalid timeline items", () -> + it "timeline items", () -> myCtrl.loadTimeline() thenStub.callArgWith(0, timelineList) - expect(myCtrl.timelineList[0]).to.be.equal(timelineList.data[0]) - expect(myCtrl.timelineList[1]).to.be.equal(timelineList.data[3]) + expect(myCtrl.timelineList.size).to.be.eql(4) diff --git a/app/modules/profile/profile-timeline/profile-timeline.directive.coffee b/app/modules/profile/profile-timeline/profile-timeline.directive.coffee new file mode 100644 index 00000000..ae4ba91d --- /dev/null +++ b/app/modules/profile/profile-timeline/profile-timeline.directive.coffee @@ -0,0 +1,9 @@ +ProfileTimelineDirective = -> + return { + templateUrl: "profile/profile-timeline/profile-timeline.html", + controller: "ProfileTimeline", + controllerAs: "vm", + scope: {} + } + +angular.module("taigaProfile").directive("tgProfileTimeline", ProfileTimelineDirective) diff --git a/app/modules/profile/profile-timeline/profile-timeline.jade b/app/modules/profile/profile-timeline/profile-timeline.jade new file mode 100644 index 00000000..20b6fdb5 --- /dev/null +++ b/app/modules/profile/profile-timeline/profile-timeline.jade @@ -0,0 +1,3 @@ +section.profile-timeline + div(infinite-scroll="vm.loadTimeline()", infinite-scroll-distance="3", infinite-scroll-disabled='vm.loadingData') + div(tg-repeat="timeline in vm.timelineList", tg-profile-timeline-item="timeline") diff --git a/app/modules/profile/profile-timeline/profile-timeline.service.coffee b/app/modules/profile/profile-timeline/profile-timeline.service.coffee new file mode 100644 index 00000000..0ac90906 --- /dev/null +++ b/app/modules/profile/profile-timeline/profile-timeline.service.coffee @@ -0,0 +1,51 @@ +taiga = @.taiga + +class ProfileTimelineService extends taiga.Service + @.$inject = ["$tgResources"] + + constructor: (@rs) -> + + _valid_fields: [ + 'status', + 'subject', + 'description', + 'assigned_to', + 'points', + 'severity', + 'priority', + 'type', + 'attachments', + 'milestone', + 'is_blocked', + 'is_iocaine', + 'content_diff', + 'name', + 'estimated_finish', + 'estimated_start' + ] + + _isValidField: (values) => + return _.some values, (value) => @._valid_fields.indexOf(value) != -1 + + _filterValidTimelineItems: (timeline) => + if timeline.data.values_diff + values = Object.keys(timeline.data.values_diff) + + if values && values.length + if !@._isValidField(values) + return false + else if values[0] == 'attachments' && + timeline.data.values_diff.attachments.new.length == 0 + return false + + return true + + getTimeline: (userId, page) -> + return @rs.timeline.profile(userId, page) + .then (result) => + newTimelineList = _.filter result.data, @._filterValidTimelineItems + + return Immutable.fromJS(newTimelineList) + + +angular.module("taigaProjects").service("tgProfileTimelineService", ProfileTimelineService) diff --git a/app/modules/profile/profile-timeline/profile-timeline.service.spec.coffee b/app/modules/profile/profile-timeline/profile-timeline.service.spec.coffee new file mode 100644 index 00000000..77445b3a --- /dev/null +++ b/app/modules/profile/profile-timeline/profile-timeline.service.spec.coffee @@ -0,0 +1,113 @@ +describe "tgProfileTimelineService", -> + provide = null + $q = null + $rootScope = null + profileTimelineService = null + mocks = {} + + _mockResources = () -> + mocks.resources = {} + + mocks.resources.timeline = { + profile: sinon.stub() + } + + provide.value "$tgResources", mocks.resources + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockResources() + + return null + + _setup = -> + _mocks() + + _inject = (callback) -> + inject (_tgProfileTimelineService_, _$q_, _$rootScope_) -> + profileTimelineService = _tgProfileTimelineService_ + $q = _$q_ + $rootScope = _$rootScope_ + callback() if callback + + beforeEach -> + module "taigaProjects" + _setup() + _inject() + + it "filter invalid timeline items", (done) -> + valid_items = { + data: [ + { # valid item + data: { + values_diff: { + "status": "xx", + "subject": "xx" + } + } + }, + { # invalid item + data: { + values_diff: { + "fake": "xx" + } + } + }, + { # invalid item + data: { + values_diff: { + "fake2": "xx" + } + } + }, + { # valid item + data: { + values_diff: { + "fake2": "xx", + "milestone": "xx" + } + } + }, + { # invalid item + data: { + values_diff: { + attachments: { + new: [] + } + } + } + }, + { # valid item + data: { + values_diff: { + attachments: { + new: [1, 2] + } + } + } + } + ] + } + + userId = 3 + page = 2 + + mocks.resources.timeline.profile = (_userId_, _page_) -> + expect(_userId_).to.be.equal(userId) + expect(_page_).to.be.equal(page) + + return $q (resolve, reject) -> + resolve(valid_items) + + profileTimelineService.getTimeline(userId, page) + .then (_items_) -> + items = _items_.toJS() + + expect(items[0]).to.be.eql(valid_items.data[0]) + expect(items[1]).to.be.eql(valid_items.data[3]) + expect(items[2]).to.be.eql(valid_items.data[5]) + + done() + + $rootScope.$apply() diff --git a/app/modules/profile/profile.jade b/app/modules/profile/profile.jade index 88d88eb6..9264c001 100644 --- a/app/modules/profile/profile.jade +++ b/app/modules/profile/profile.jade @@ -7,7 +7,7 @@ div.profile.centered div.content-wrapper div.content div(tg-profile-tab="activity", tab-title="Activity Tab", tab-icon="icon-timeline", tab-active) - include includes/profile-timeline + div(tg-profile-timeline) div(tg-profile-tab="projects", tab-title="Projects Tab", tab-icon="icon-project") div(tg-profile-projects) From e3f4a6344e0a45eee1a3db9ddfd89bdf67f8d166 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 6 May 2015 15:28:36 +0200 Subject: [PATCH 119/366] Fix vertical scroll for nav bar. TG-2641 ready-for-test --- app/modules/navigation-bar/navigation-bar.scss | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/modules/navigation-bar/navigation-bar.scss b/app/modules/navigation-bar/navigation-bar.scss index 7521ba8c..c0810447 100644 --- a/app/modules/navigation-bar/navigation-bar.scss +++ b/app/modules/navigation-bar/navigation-bar.scss @@ -56,8 +56,10 @@ } &.user-avatar { + min-width: 200px; padding: 0; padding-left: 2rem; + text-align: right; span { padding-right: 1rem; } @@ -66,7 +68,7 @@ } img { height: 2.5rem; - padding-left: .3rem; + padding-left: .5rem; vertical-align: middle; } svg { @@ -108,8 +110,8 @@ border: 1px solid $black; padding: .3rem; &.dropdown-user { - left: calc(50% - 230px/2); - min-width: 230px; + left: calc(50% - 200px/2); + min-width: 200px; ul { margin-bottom: 0; } From 498d69032f4186ce635dbe385e320fc0ccc8aec2 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 7 May 2015 08:27:24 +0200 Subject: [PATCH 120/366] Refactoring services --- app/modules/home/duties/duty.directive.coffee | 12 ------- .../home/duties/duty.directive.spec.coffee | 2 -- app/modules/home/duties/duty.jade | 4 +-- app/modules/home/home.directive.coffee | 31 +++++++++++------ app/modules/home/home.directive.spec.coffee | 34 +++++++++++++++++-- app/modules/home/home.service.coffee | 31 +++++++++++++++-- app/modules/home/home.service.spec.coffee | 7 ++++ app/modules/projects/projects.service.coffee | 25 ++++++++------ .../projects/projects.service.spec.coffee | 16 ++++----- 9 files changed, 111 insertions(+), 51 deletions(-) diff --git a/app/modules/home/duties/duty.directive.coffee b/app/modules/home/duties/duty.directive.coffee index 539fafd4..bfda7866 100644 --- a/app/modules/home/duties/duty.directive.coffee +++ b/app/modules/home/duties/duty.directive.coffee @@ -12,18 +12,6 @@ DutyDirective = (navurls, projectsService, $translate) -> if scope.vm.duty._name == "issues" return $translate.instant("COMMON.ISSUE") - scope.vm.getUrl = () -> - if scope.vm.duty - ctx = { - project: projectsService.projectsById.get(String(scope.vm.duty.project)).slug - ref: scope.vm.duty.ref - } - return navurls.resolve("project-#{scope.vm.duty._name}-detail", ctx) - - scope.vm.getProjectName = () -> - if scope.vm.duty - return projectsService.projectsById.get(String(scope.vm.duty.project)).name - directive = { templateUrl: "home/duties/duty.html" scope: { diff --git a/app/modules/home/duties/duty.directive.spec.coffee b/app/modules/home/duties/duty.directive.spec.coffee index 88ceb7b7..c8dbb867 100644 --- a/app/modules/home/duties/duty.directive.spec.coffee +++ b/app/modules/home/duties/duty.directive.spec.coffee @@ -73,5 +73,3 @@ describe "dutyDirective", () -> elm = createDirective() scope.$apply() expect(elm.isolateScope().vm.getDutyType()).to.be.equal("User story translated") - expect(elm.isolateScope().vm.getUrl()).to.be.equal("http://jstesting.taiga.io") - expect(elm.isolateScope().vm.getProjectName()).to.be.equal("testing js project") diff --git a/app/modules/home/duties/duty.jade b/app/modules/home/duties/duty.jade index 0565a71e..b1b94e8a 100644 --- a/app/modules/home/duties/duty.jade +++ b/app/modules/home/duties/duty.jade @@ -1,4 +1,4 @@ -a(href="{{ ::vm.getUrl() }}", title="{{ ::duty.subject }}") +a(href="{{ ::vm.duty.url }}", title="{{ ::duty.subject }}") img.avatar( src="{{ ::vm.duty.assigned_to_extra_info.photo }}" title="{{ ::vm.duty.assigned_to_extra_info.full_name_display }}") @@ -10,4 +10,4 @@ a(href="{{ ::vm.getUrl() }}", title="{{ ::duty.subject }}") span.duty-title span.duty-id(tg-bo-ref="duty.ref") span.duty-name {{ ::duty.subject }} - div.duty-project {{ ::vm.getProjectName()}} + div.duty-project {{ ::vm.duty.projectName}} diff --git a/app/modules/home/home.directive.coffee b/app/modules/home/home.directive.coffee index 1b74a29d..47d8f99c 100644 --- a/app/modules/home/home.directive.coffee +++ b/app/modules/home/home.directive.coffee @@ -1,18 +1,25 @@ -HomeDirective = (homeService) -> +HomeDirective = ($q, homeService, projectsService) -> link = (scope, el, attrs, ctrl) -> scope.vm = {} - taiga.defineImmutableProperty(scope.vm, "workInProgress", () -> homeService.workInProgress) - scope.$watch "vm.workInProgress", (workInProgress) -> - if workInProgress.size > 0 - userStories = workInProgress.get("assignedTo").get("userStories") - tasks = workInProgress.get("assignedTo").get("tasks") - issues = workInProgress.get("assignedTo").get("issues") + projectsPromise = projectsService.getCurrentUserProjects() + workInProgresPromise = homeService.getWorkInProgress() + + $q.all([projectsPromise, workInProgresPromise]).then -> + homeService.attachProjectInfoToWorkInProgress(projectsService.currentUserProjectsById) + + taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.currentUserProjects) + taiga.defineImmutableProperty(scope.vm, "workInProgress", () -> homeService.workInProgress) + + if scope.vm.workInProgress.size > 0 + userStories = scope.vm.workInProgress.get("assignedTo").get("userStories") + tasks = scope.vm.workInProgress.get("assignedTo").get("tasks") + issues = scope.vm.workInProgress.get("assignedTo").get("issues") scope.vm.assignedTo = userStories.concat(tasks).concat(issues) - userStories = workInProgress.get("watching").get("userStories") - tasks = workInProgress.get("watching").get("tasks") - issues = workInProgress.get("watching").get("issues") + userStories = scope.vm.workInProgress.get("watching").get("userStories") + tasks = scope.vm.workInProgress.get("watching").get("tasks") + issues = scope.vm.workInProgress.get("watching").get("issues") scope.vm.watching = userStories.concat(tasks).concat(issues) return { @@ -22,7 +29,9 @@ HomeDirective = (homeService) -> } HomeDirective.$inject = [ - "tgHomeService" + "$q", + "tgHomeService", + "tgProjectsService" ] angular.module("taigaHome").directive("tgHome", HomeDirective) diff --git a/app/modules/home/home.directive.spec.coffee b/app/modules/home/home.directive.spec.coffee index 9d96a1ee..e91aa9cb 100644 --- a/app/modules/home/home.directive.spec.coffee +++ b/app/modules/home/home.directive.spec.coffee @@ -1,5 +1,7 @@ describe "homeDirective", () -> - scope = compile = provide = null + scope = compile = provide = timeout = null + mockTgHomeService = mockTgProjectsService = null + thenStubGetCurrentUserProjectsById = thenStubGetWorkInProgress = null template = "
" createDirective = () -> @@ -7,7 +9,10 @@ describe "homeDirective", () -> return elm _mockTgHomeService = () -> + thenStubGetWorkInProgress = sinon.stub() + mockTgHomeService = { + getWorkInProgress: sinon.stub() workInProgress: Immutable.fromJS({ assignedTo: { userStories: [{"id": 1}] @@ -20,8 +25,12 @@ describe "homeDirective", () -> issues: [{"id": 6}] } }) + attachProjectInfoToWorkInProgress: sinon.stub() } + mockTgHomeService.getWorkInProgress.returns({ + then: thenStubGetWorkInProgress + }) provide.value "tgHomeService", mockTgHomeService _mockTranslateFilter = () -> @@ -35,6 +44,20 @@ describe "homeDirective", () -> _mockHomeProjectList = () -> provide.factory 'tgHomeProjectListDirective', () -> {} + _mockTgProjectsService = () -> + thenStubGetCurrentUserProjectsById = sinon.stub() + mockTgProjectsService = { + getCurrentUserProjects: sinon.stub() + currentUserProjectsById: { + get: sinon.stub() + } + } + + mockTgProjectsService.getCurrentUserProjects.returns({ + then: thenStubGetCurrentUserProjectsById + }) + provide.value "tgProjectsService", mockTgProjectsService + _mocks = () -> module ($provide) -> provide = $provide @@ -42,6 +65,7 @@ describe "homeDirective", () -> _mockHomeProjectList() _mockTgHomeService() _mockTranslateFilter() + _mockTgProjectsService() return null beforeEach -> @@ -50,12 +74,18 @@ describe "homeDirective", () -> _mocks() - inject ($rootScope, $compile) -> + inject ($rootScope, $compile, $timeout) -> scope = $rootScope.$new() compile = $compile + timeout = $timeout it "home directive content", () -> elm = createDirective() scope.$apply() + + thenStubGetCurrentUserProjectsById.callArg(0) + thenStubGetWorkInProgress.callArg(0) + timeout.flush() + expect(elm.isolateScope().vm.assignedTo.size).to.be.equal(3) expect(elm.isolateScope().vm.watching.size).to.be.equal(3) diff --git a/app/modules/home/home.service.coffee b/app/modules/home/home.service.coffee index c6123c4c..f0d6c5a4 100644 --- a/app/modules/home/home.service.coffee +++ b/app/modules/home/home.service.coffee @@ -1,7 +1,7 @@ class HomeService extends taiga.Service - @.$inject = ["$q", "$tgResources", "$rootScope", "$projectUrl", "$tgAuth"] + @.$inject = ["$q", "$tgNavUrls", "$tgResources", "$rootScope", "$projectUrl", "$tgAuth"] - constructor: (@q, @rs, @rootScope, @projectUrl, @auth) -> + constructor: (@q, @navurls, @rs, @rootScope, @projectUrl, @auth) -> @._workInProgress = Immutable.Map() @._projectPromise = null @._inProgress = false @@ -10,6 +10,32 @@ class HomeService extends taiga.Service @.fetchWorkInProgress() + attachProjectInfoToWorkInProgress: (projectsById) -> + _attachProjectInfoToDuty = (duty) => + project = projectsById.get(String(duty.project)) + ctx = { + project: project.slug + ref: duty.ref + } + Object.defineProperty(duty, "url", {get: () => @navurls.resolve("project-#{duty._name}-detail", ctx)}) + Object.defineProperty(duty, "projectName", {get: () => project.name}) + + @._workInProgress = Immutable.fromJS({ + assignedTo: { + userStories: _.map(_.clone(@.assignedToUserStories), _attachProjectInfoToDuty) + tasks: _.map(_.clone(@.assignedToTasks), _attachProjectInfoToDuty) + issues: _.map(_.clone(@.assignedToIssues), _attachProjectInfoToDuty) + } + watching: { + userStories: _.map(_.clone(@.watchingUserStories), _attachProjectInfoToDuty) + tasks: _.map(_.clone(@.watchingTasks), _attachProjectInfoToDuty) + issues: _.map(_.clone(@.watchingIssues), _attachProjectInfoToDuty) + } + }) + + getWorkInProgress: () -> + return @._projectPromise + fetchWorkInProgress: () -> userId = @auth.getUser().id @@ -28,7 +54,6 @@ class HomeService extends taiga.Service assignedIssuesPromise = @rs.issues.listInAllProjects(params).then (issues) => @.assignedToIssues = issues - params = { status__is_closed: false watchers: userId diff --git a/app/modules/home/home.service.spec.coffee b/app/modules/home/home.service.spec.coffee index f2cb34ce..5d7c1cc8 100644 --- a/app/modules/home/home.service.spec.coffee +++ b/app/modules/home/home.service.spec.coffee @@ -76,6 +76,12 @@ describe "tgHome", -> provide.value "$tgAuth", mocks.auth + _mockTgNavUrls = () -> + mocks.tgNavUrls = { + resolve: sinon.stub() + } + provide.value "$tgNavUrls", mocks.tgNavUrls + _inject = (callback) -> inject (_$q_, _$tgResources_, _$rootScope_, _$projectUrl_, _$timeout_, _tgHomeService_) -> timeout = _$timeout_ @@ -88,6 +94,7 @@ describe "tgHome", -> _mockResources() _mockProjectUrl() _mockAuth() + _mockTgNavUrls() return null _setup = -> diff --git a/app/modules/projects/projects.service.coffee b/app/modules/projects/projects.service.coffee index ba109f6c..e02191c0 100644 --- a/app/modules/projects/projects.service.coffee +++ b/app/modules/projects/projects.service.coffee @@ -5,22 +5,25 @@ class ProjectsService extends taiga.Service @.$inject = ["$tgResources", "$rootScope", "$projectUrl", "tgLightboxFactory"] constructor: (@rs, @rootScope, @projectUrl, @lightboxFactory) -> - @._projects = Immutable.Map() - @._projectsById = Immutable.Map() + @._currentUserProjects = Immutable.Map() + @._currentUserProjectsById = Immutable.Map() @._inProgress = false - @._projectsPromise = null + @._currentUserProjectsPromise = null - taiga.defineImmutableProperty @, "projects", () => return @._projects - taiga.defineImmutableProperty @, "projectsById", () => return @._projectsById + taiga.defineImmutableProperty @, "currentUserProjects", () => return @._currentUserProjects + taiga.defineImmutableProperty @, "currentUserProjectsById", () => return @._currentUserProjectsById @.fetchProjects() + getCurrentUserProjects: -> + return @._currentUserProjectsPromise + fetchProjects: -> if not @._inProgress @._inProgress = true - @._projectsPromise = @rs.projects.listByMember(@rootScope.user?.id) - @._projectsPromise.then (projects) => + @._currentUserProjectsPromise = @rs.projects.listByMember(@rootScope.user?.id) + @._currentUserProjectsPromise.then (projects) => _.map projects, (project) => project.url = @projectUrl.get(project) @@ -33,19 +36,19 @@ class ProjectsService extends taiga.Service color = project.tags_colors[tag] return {name: tag, color: color} - @._projects = Immutable.fromJS({ + @._currentUserProjects = Immutable.fromJS({ all: projects, recents: projects.slice(0, 10) }) - @._projectsById = Immutable.fromJS(groupBy(projects, (p) -> p.id)) + @._currentUserProjectsById = Immutable.fromJS(groupBy(projects, (p) -> p.id)) return @.projects - @._projectsPromise.finally => + @._currentUserProjectsPromise.finally => @._inProgress = false - return @._projectsPromise + return @._currentUserProjectsPromise newProject: -> @lightboxFactory.create("tg-lb-create-project", { diff --git a/app/modules/projects/projects.service.spec.coffee b/app/modules/projects/projects.service.spec.coffee index 615ac5ce..0ba28182 100644 --- a/app/modules/projects/projects.service.spec.coffee +++ b/app/modules/projects/projects.service.spec.coffee @@ -81,8 +81,8 @@ describe "tgProjects", -> it "all & recents filled", () -> mocks.thenStub.callArg(0, projects) - expect(projectsService.projects.get("all").toJS()).to.be.eql(projects) - expect(projectsService.projects.get("recents").toJS()).to.be.eql(projects.slice(0, 10)) + expect(projectsService.currentUserProjects.get("all").toJS()).to.be.eql(projects) + expect(projectsService.currentUserProjects.get("recents").toJS()).to.be.eql(projects.slice(0, 10)) it "_inProgress change to false when tgResources end", () -> expect(projectsService._inProgress).to.be.true @@ -103,15 +103,15 @@ describe "tgProjects", -> it "group projects by id", () -> mocks.thenStub.callArg(0, projects) - expect(projectsService.projectsById.size).to.be.equal(12) - expect(projectsService.projectsById.toJS()[1].id).to.be.equal(projects[0].id) + expect(projectsService.currentUserProjectsById.size).to.be.equal(12) + expect(projectsService.currentUserProjectsById.toJS()[1].id).to.be.equal(projects[0].id) it "add urls in the project object", () -> mocks.thenStub.callArg(0, projects) - expect(projectsService.projectsById.toJS()[1].url).to.be.equal("url-1") - expect(projectsService.projects.get("all").toJS()[0].url).to.be.equal("url-1") - expect(projectsService.projects.get("recents").toJS()[0].url).to.be.equal("url-1") + expect(projectsService.currentUserProjectsById.toJS()[1].url).to.be.equal("url-1") + expect(projectsService.currentUserProjects.get("all").toJS()[0].url).to.be.equal("url-1") + expect(projectsService.currentUserProjects.get("recents").toJS()[0].url).to.be.equal("url-1") it "add sorted colorized_tags project object", () -> mocks.thenStub.callArg(0, projects) @@ -123,7 +123,7 @@ describe "tgProjects", -> ]; - colorized_tags = projectsService.projects.get("all").toJS()[0].colorized_tags + colorized_tags = projectsService.currentUserProjects.get("all").toJS()[0].colorized_tags expect(colorized_tags).to.be.eql(tags) From c34bf099eb5018c2d7c8fa65f7086d65a299c1e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 7 May 2015 08:36:00 +0200 Subject: [PATCH 121/366] Timeline layout and styles refactor --- .../profile-timeline-item.jade | 4 ++-- .../profile-timeline/profile-timeline.scss | 23 ++++++++----------- app/modules/profile/profile.jade | 23 ++++++++----------- app/modules/profile/profile.scss | 18 ++++----------- .../profile/styles/profile-content-tabs.scss | 2 +- app/styles/core/elements.scss | 7 +++--- 6 files changed, 30 insertions(+), 47 deletions(-) diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item.jade b/app/modules/profile/profile-timeline-item/profile-timeline-item.jade index dbf10787..68a22c80 100644 --- a/app/modules/profile/profile-timeline-item/profile-timeline-item.jade +++ b/app/modules/profile/profile-timeline-item/profile-timeline-item.jade @@ -7,7 +7,7 @@ div.activity-image p(tg-compile-html="vm.activity.title") - .activity-comment-quote(ng-if="::vm.activity.description") + blockquote.activity-comment-quote(ng-if="::vm.activity.description") p(ng-bind-html="vm.activity.description") .activity-member-view(ng-if="::vm.activity.member") @@ -19,4 +19,4 @@ div.activity-image p {{::vm.activity.member.role.name}} div(ng-repeat="attachment in vm.activity.attachments") - div(tg-profile-timeline-attachment="vm.attachment") \ No newline at end of file + div(tg-profile-timeline-attachment="vm.attachment") diff --git a/app/modules/profile/profile-timeline/profile-timeline.scss b/app/modules/profile/profile-timeline/profile-timeline.scss index fcb51bd6..8b19e65e 100644 --- a/app/modules/profile/profile-timeline/profile-timeline.scss +++ b/app/modules/profile/profile-timeline/profile-timeline.scss @@ -10,16 +10,18 @@ margin-right: 100px; } .activity-date { - @extend %xsmall; color: $gray-light; + font-size: .75rem; position: absolute; right: .5rem; - top: .5rem; + top: 1.2rem; } .profile-contact-picture { - margin-right: .5rem; + flex-basis: 38px; + flex-shrink: 0; + margin-right: 1rem; vertical-align: center; - width: 35px; + width: 38px; img { width: 100%; } @@ -42,15 +44,10 @@ .activity-comment, .activity-project, .activity-image { - .activity-comment-quote, - .activity-comment-attachment { - @extend %small; - border-left: 2px solid $whitish; - color: $gray-light; + blockquote { margin-bottom: .5rem; - margin-left: calc(35px + .5rem); + margin-left: calc(35px + 1rem); margin-top: .5rem; - padding: .2rem 1rem; img { max-height: 640px; max-width: 100%; @@ -61,7 +58,7 @@ .activity-notification-list { border-left: 2px solid $whitish; margin-bottom: .5rem; - margin-left: calc(35px + .5rem); + margin-left: calc(35px + 1rem); margin-top: .5rem; padding: .2rem 1rem; li { @@ -72,7 +69,7 @@ .activity-member-view { display: flex; margin-bottom: .5rem; - margin-left: calc(35px + .5rem); + margin-left: calc(35px + 1rem); margin-top: .5rem; .activity-member-info { flex: 1; diff --git a/app/modules/profile/profile.jade b/app/modules/profile/profile.jade index 9264c001..8393deba 100644 --- a/app/modules/profile/profile.jade +++ b/app/modules/profile/profile.jade @@ -2,20 +2,17 @@ include ../../partials/includes/components/beta div.profile.centered include includes/profile-bar div.main - div.hero - div(tg-profile-tabs) - div.content-wrapper - div.content - div(tg-profile-tab="activity", tab-title="Activity Tab", tab-icon="icon-timeline", tab-active) - div(tg-profile-timeline) + div.timeline-wrapper(tg-profile-tabs) + div(tg-profile-tab="activity", tab-title="Activity Tab", tab-icon="icon-timeline", tab-active) + div(tg-profile-timeline) - div(tg-profile-tab="projects", tab-title="Projects Tab", tab-icon="icon-project") - div(tg-profile-projects) + div(tg-profile-tab="projects", tab-title="Projects Tab", tab-icon="icon-project") + div(tg-profile-projects) - div(tg-profile-tab="contacts", tab-title="Contacts Tab", tab-icon="icon-team") - include includes/profile-contacts + div(tg-profile-tab="contacts", tab-title="Contacts Tab", tab-icon="icon-team") + include includes/profile-contacts - div(tg-profile-tab="favorities", tab-title="Favorites Tab", tab-icon="icon-star-fill") - include includes/profile-favorites + div(tg-profile-tab="favorities", tab-title="Favorites Tab", tab-icon="icon-star-fill") + include includes/profile-favorites - include includes/profile-sidebar + include includes/profile-sidebar diff --git a/app/modules/profile/profile.scss b/app/modules/profile/profile.scss index cb9a6610..9ff15544 100644 --- a/app/modules/profile/profile.scss +++ b/app/modules/profile/profile.scss @@ -8,24 +8,12 @@ } .main { display: flex; - flex: 1; - flex-direction: column; padding: 0; } - .hero { + .timeline-wrapper { background: lighten($whitish, 10%); - margin-bottom: .3rem; - min-height: 200px; - } - .content-wrapper { - display: flex; - min-height: 100vh; - } - .content { - background: lighten($whitish, 10%); - flex: 1; + flex-basis: 768px; margin-right: 1rem; - max-width: 786px; > div { opacity: 1; padding-top: 0; @@ -38,6 +26,8 @@ } } .profile-sidebar { + flex-basis: 150px; + flex-shrink: 0; width: 150px; } } diff --git a/app/modules/profile/styles/profile-content-tabs.scss b/app/modules/profile/styles/profile-content-tabs.scss index 67cc0e79..648d9d7f 100644 --- a/app/modules/profile/styles/profile-content-tabs.scss +++ b/app/modules/profile/styles/profile-content-tabs.scss @@ -4,7 +4,7 @@ .tab { color: $gray-light; display: inline-block; - padding: .4rem 1rem; + padding: 1rem 1.25rem; &:hover, &.active { color: $grayer; diff --git a/app/styles/core/elements.scss b/app/styles/core/elements.scss index 965dedeb..6644404e 100644 --- a/app/styles/core/elements.scss +++ b/app/styles/core/elements.scss @@ -4,13 +4,12 @@ blockquote, blockquote p { color: $gray; - font-style: italic; - line-height: 24px; + line-height: 1.25rem; } blockquote { - border-left: 1px solid $gray-light; + border-left: 5px solid $whitish; margin: 0 0 20px; - padding: 9px 20px 0 19px; + padding: .5rem 1.25rem; cite { @extend %small; color: $gray; From e69a266c9225291ee12f19437b68a0c287765cc0 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 7 May 2015 09:29:41 +0200 Subject: [PATCH 122/366] Refactoring services --- app/modules/home/duties/duty.directive.coffee | 8 ++- app/modules/home/home.service.coffee | 5 +- app/modules/home/home.service.spec.coffee | 49 +++++++++++++++++++ .../home-project-list-directive.spec.coffee | 4 +- .../home-project-list.directive.coffee | 6 ++- .../dropdown-project-list.directive.coffee | 8 +-- ...ropdown-project-list.directive.spec.coffee | 4 +- .../dropdown-user.directive.coffee | 12 +++-- .../navigation-bar.directive.coffee | 8 +-- .../navigation-bar.directive.spec.coffee | 4 +- .../profile-projects.directive.coffee | 2 +- .../listing/projects-listing.directive.coffee | 2 +- 12 files changed, 90 insertions(+), 22 deletions(-) diff --git a/app/modules/home/duties/duty.directive.coffee b/app/modules/home/duties/duty.directive.coffee index bfda7866..0cb45456 100644 --- a/app/modules/home/duties/duty.directive.coffee +++ b/app/modules/home/duties/duty.directive.coffee @@ -22,4 +22,10 @@ DutyDirective = (navurls, projectsService, $translate) -> return directive -angular.module("taigaHome").directive("tgDuty", ["$tgNavUrls", "tgProjectsService", "$translate", DutyDirective]) +DutyDirective.$inject = [ + "$tgNavUrls", + "tgProjectsService", + "$translate" +] + +angular.module("taigaHome").directive("tgDuty", DutyDirective) diff --git a/app/modules/home/home.service.coffee b/app/modules/home/home.service.coffee index f0d6c5a4..0d1615ba 100644 --- a/app/modules/home/home.service.coffee +++ b/app/modules/home/home.service.coffee @@ -17,8 +17,9 @@ class HomeService extends taiga.Service project: project.slug ref: duty.ref } - Object.defineProperty(duty, "url", {get: () => @navurls.resolve("project-#{duty._name}-detail", ctx)}) - Object.defineProperty(duty, "projectName", {get: () => project.name}) + duty.url = @navurls.resolve("project-#{duty._name}-detail", ctx) + duty.projectName = project.name + return duty @._workInProgress = Immutable.fromJS({ assignedTo: { diff --git a/app/modules/home/home.service.spec.coffee b/app/modules/home/home.service.spec.coffee index 5d7c1cc8..3803a6b4 100644 --- a/app/modules/home/home.service.spec.coffee +++ b/app/modules/home/home.service.spec.coffee @@ -132,3 +132,52 @@ describe "tgHome", -> expect(homeService._inProgress).to.be.true timeout.flush() expect(homeService._inProgress).to.be.false + + it "project info filled", () -> + duty = { + id: 66 + _name: "userstories" + ref: 123 + project: 1 + } + mocks.thenStubAssignedToUserstories.callArg(0, [duty]) + mocks.thenStubAssignedToTasks.callArg(0) + mocks.thenStubAssignedToIssues.callArg(0) + mocks.thenStubWatchingUserstories.callArg(0) + mocks.thenStubWatchingTasks.callArg(0) + mocks.thenStubWatchingIssues.callArg(0) + timeout.flush() + + projectsById = { + get: () -> { + name: "Testing project" + slug: "testing-project" + } + } + + mocks.tgNavUrls.resolve + .withArgs("project-userstories-detail", {project: "testing-project", ref: 123}) + .returns("/testing-project/us/123") + + homeService.attachProjectInfoToWorkInProgress(projectsById) + expect(homeService.workInProgress.toJS()).to.be.eql({ + assignedTo: { + userStories: [ + { + id: 66 + _name: "userstories" + ref: 123 + project: 1 + url: "/testing-project/us/123" + projectName: "Testing project" + } + ] + tasks: [] + issues: [] + } + watching: { + userStories: [] + tasks: [] + issues: [] + } + }) diff --git a/app/modules/home/projects/home-project-list-directive.spec.coffee b/app/modules/home/projects/home-project-list-directive.spec.coffee index 1ee02cbe..2ad8b7b6 100644 --- a/app/modules/home/projects/home-project-list-directive.spec.coffee +++ b/app/modules/home/projects/home-project-list-directive.spec.coffee @@ -11,7 +11,7 @@ describe "homeProjectListDirective", () -> _mockTgProjectsService = () -> mockTgProjectsService = { newProject: sinon.stub() - projects: { + currentUserProjects: { get: sinon.stub() } } @@ -49,7 +49,7 @@ describe "homeProjectListDirective", () -> ]) it "home project list directive scope content", () -> - mockTgProjectsService.projects.get + mockTgProjectsService.currentUserProjects.get .withArgs("recents") .returns(recents) diff --git a/app/modules/home/projects/home-project-list.directive.coffee b/app/modules/home/projects/home-project-list.directive.coffee index 3bcbe7d9..17947490 100644 --- a/app/modules/home/projects/home-project-list.directive.coffee +++ b/app/modules/home/projects/home-project-list.directive.coffee @@ -2,7 +2,7 @@ HomeProjectListDirective = (projectsService) -> link = (scope, el, attrs, ctrl) -> scope.vm = {} - taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.projects.get("recents")) + taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.currentUserProjects.get("recents")) scope.vm.newProject = -> projectsService.newProject() @@ -15,4 +15,6 @@ HomeProjectListDirective = (projectsService) -> return directive -angular.module("taigaHome").directive("tgHomeProjectList", ["tgProjectsService", HomeProjectListDirective]) +HomeProjectListDirective.$inject = ["tgProjectsService"] + +angular.module("taigaHome").directive("tgHomeProjectList", HomeProjectListDirective) diff --git a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee index 8ffe96c8..be019c11 100644 --- a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee +++ b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee @@ -2,7 +2,7 @@ DropdownProjectListDirective = (projectsService) -> link = (scope, el, attrs, ctrl) -> scope.vm = {} - taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.projects.get("recents")) + taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.currentUserProjects.get("recents")) scope.vm.newProject = -> projectsService.newProject() @@ -15,6 +15,8 @@ DropdownProjectListDirective = (projectsService) -> return directive +DropdownProjectListDirective.$inject = [ + "tgProjectsService" +] -angular.module("taigaNavigationBar").directive("tgDropdownProjectList", - ["tgProjectsService", DropdownProjectListDirective]) +angular.module("taigaNavigationBar").directive("tgDropdownProjectList", DropdownProjectListDirective) diff --git a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.spec.coffee b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.spec.coffee index 90420887..46fe9b75 100644 --- a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.spec.coffee +++ b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.spec.coffee @@ -11,7 +11,7 @@ describe "dropdownProjectListDirective", () -> _mockTgProjectsService = () -> mockTgProjectsService = { newProject: sinon.stub() - projects: { + currentUserProjects: { get: sinon.stub() } } @@ -49,7 +49,7 @@ describe "dropdownProjectListDirective", () -> ]) it "dropdown project list directive scope content", () -> - mockTgProjectsService.projects.get + mockTgProjectsService.currentUserProjects.get .withArgs("recents") .returns(recents) diff --git a/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee b/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee index 018bd433..18f46138 100644 --- a/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee +++ b/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee @@ -21,6 +21,12 @@ DropdownUserDirective = (authService, configService, locationService, return directive -angular.module("taigaNavigationBar").directive("tgDropdownUser", - ["$tgAuth", "$tgConfig", "$tgLocation", "$tgNavUrls", "tgFeedbackService", - DropdownUserDirective]) +DropdownUserDirective.$inject = [ + "$tgAuth", + "$tgConfig", + "$tgLocation", + "$tgNavUrls", + "tgFeedbackService" +] + +angular.module("taigaNavigationBar").directive("tgDropdownUser", DropdownUserDirective) diff --git a/app/modules/navigation-bar/navigation-bar.directive.coffee b/app/modules/navigation-bar/navigation-bar.directive.coffee index 9c23259c..cdda7d28 100644 --- a/app/modules/navigation-bar/navigation-bar.directive.coffee +++ b/app/modules/navigation-bar/navigation-bar.directive.coffee @@ -2,7 +2,7 @@ NavigationBarDirective = (projectsService) -> link = (scope, el, attrs, ctrl) -> scope.vm = {} - taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.projects.get("recents")) + taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.currentUserProjects.get("recents")) directive = { templateUrl: "navigation-bar/navigation-bar.html" @@ -12,6 +12,8 @@ NavigationBarDirective = (projectsService) -> return directive +NavigationBarDirective.$inject = [ + "tgProjectsService" +] -angular.module("taigaNavigationBar").directive("tgNavigationBar", - ["tgProjectsService", NavigationBarDirective]) +angular.module("taigaNavigationBar").directive("tgNavigationBar", NavigationBarDirective) diff --git a/app/modules/navigation-bar/navigation-bar.directive.spec.coffee b/app/modules/navigation-bar/navigation-bar.directive.spec.coffee index 8e909f54..31f2b13e 100644 --- a/app/modules/navigation-bar/navigation-bar.directive.spec.coffee +++ b/app/modules/navigation-bar/navigation-bar.directive.spec.coffee @@ -11,7 +11,7 @@ describe "navigationBarDirective", () -> _mockTgProjectsService = () -> mockTgProjectsService = { newProject: sinon.stub() - projects: { + currentUserProjects: { get: sinon.stub() } } @@ -57,7 +57,7 @@ describe "navigationBarDirective", () -> ]) it "navigation bar directive scope content", () -> - mockTgProjectsService.projects.get + mockTgProjectsService.currentUserProjects.get .withArgs("recents") .returns(recents) diff --git a/app/modules/profile/profile-projects/profile-projects.directive.coffee b/app/modules/profile/profile-projects/profile-projects.directive.coffee index d58ea85c..bb9c2f71 100644 --- a/app/modules/profile/profile-projects/profile-projects.directive.coffee +++ b/app/modules/profile/profile-projects/profile-projects.directive.coffee @@ -1,7 +1,7 @@ ProfileProjectsDirective = (projectsService) -> link = (scope, el, attrs, ctrl) -> scope.vm = {} - taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.projects.get("all")) + taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.currentUserProjects.get("all")) directive = { templateUrl: "profile/profile-projects/profile-projects.html" diff --git a/app/modules/projects/listing/projects-listing.directive.coffee b/app/modules/projects/listing/projects-listing.directive.coffee index b88e72cd..1ae889f8 100644 --- a/app/modules/projects/listing/projects-listing.directive.coffee +++ b/app/modules/projects/listing/projects-listing.directive.coffee @@ -26,7 +26,7 @@ ProjectsListingDirective = (projectsService) -> projectsService.bulkUpdateProjectsOrder(sortData) - taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.projects.get("all")) + taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.currentUserProjects.get("all")) scope.vm.newProject = -> projectsService.newProject() From b52b1fdcd306e2ac48b3670c9dddb8ad036eefe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 7 May 2015 09:46:23 +0200 Subject: [PATCH 123/366] Edit profile --- app/modules/profile/includes/profile-bar.jade | 11 ++-- .../profile-timeline/profile-timeline.scss | 1 + app/modules/profile/styles/profile-bar.scss | 58 +++++++++++++++++-- .../profile/styles/profile-sidebar.scss | 1 + app/svg/location.svg | 7 +++ 5 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 app/svg/location.svg diff --git a/app/modules/profile/includes/profile-bar.jade b/app/modules/profile/includes/profile-bar.jade index 873fa152..fdae316e 100644 --- a/app/modules/profile/includes/profile-bar.jade +++ b/app/modules/profile/includes/profile-bar.jade @@ -1,8 +1,10 @@ section.profile-bar div.profile-image-wrapper img.profile-img(src="http://i.imgur.com/gPiHzHN.jpg", alt="{{ user.nickname }}") - a.edit-profile(href="", title="Edit profile") - span.icon.icon-edit + div.edit-profile(title="Edit profile") + a.profile-edition(title="Edit Profile", href="") + span.icon.icon-edit + span Edit Profile a.button-green span Follow div.profile-data @@ -10,12 +12,11 @@ section.profile-bar // If user has no defined role in its profile use all its project defined roles followed by an & h2 Backend Developer & Stackeholder div.location - // span.icon.icon-location + include ../../../svg/location.svg span Madrid // Remove Abuse Flag when a user is seeing itself a.flag(href="", title="Report Abuse") - svg(xmlns:svg="http://www.w3.org/2000/svg", xmlns="http://www.w3.org/2000/svg", xml:space="preserve", enable-background="new 0 0 100 100" viewBox="0 0 14.7 20.3", y="0", x="0", version="1.1") - path(d="M2.9 4C2 4.5 1 5.4 0.8 5.6L0.7 5.5 0 5.9 8.3 20.3 9.1 19.9 5.4 13.6c0.2-0.2 1.2-1.1 2.1-1.6 1.8-1.1 2.7-0.8 4.5-1.9 1.8-1 2.6-2.3 2.6-2.3L10.2 0C10.2 0 9.3 1.2 7.5 2.2 5.7 3.3 4.7 2.9 2.9 4Z") + include ../../../svg/flag.svg // These values in profile stats are not defined yet in UX. Please ask div.profile-stats div.stat diff --git a/app/modules/profile/profile-timeline/profile-timeline.scss b/app/modules/profile/profile-timeline/profile-timeline.scss index 8b19e65e..8dc6f266 100644 --- a/app/modules/profile/profile-timeline/profile-timeline.scss +++ b/app/modules/profile/profile-timeline/profile-timeline.scss @@ -6,6 +6,7 @@ position: relative; .activity-info { align-items: center; + color: $gray-light; display: flex; margin-right: 100px; } diff --git a/app/modules/profile/styles/profile-bar.scss b/app/modules/profile/styles/profile-bar.scss index 59aba130..61c5d76d 100644 --- a/app/modules/profile/styles/profile-bar.scss +++ b/app/modules/profile/styles/profile-bar.scss @@ -1,19 +1,57 @@ .profile-bar { .profile-image-wrapper { + height: 200px; + margin-bottom: .25rem; + overflow: hidden; position: relative; + &:hover { + .edit-profile { + opacity: 1; + transition: opacity .2s cubic-bezier(.01, .7, 1, 1); + } + } } .profile-img { max-width: 100%; } .edit-profile { @extend %large; - background: rgba(255, 255, 255, .6); - border-radius: 50%; - color: $grayer; - left: calc(50% - 1rem); - padding: 1rem; + background: rgba($black, .6); + bottom: 0; + left: 0; + opacity: 0; position: absolute; - top: calc(50% - 1rem); + right: 0; + top: 0; + &:hover { + .profile-edition { + opacity: 1; + transition: all .2s cubic-bezier(.01, .7, 1, 1); + transition-delay: .2s; + } + } + + } + .profile-edition { + @extend %small; + background: rgba($white, .1); + color: $white; + height: 30px; + left: calc(50% - 55px); + opacity: 0; + padding: .4rem; + position: absolute; + top: calc(50% - 5px); + width: 110px; + .icon-edit { + color: $white; + margin-right: .3rem; + } + &:hover { + background: rgba($white, .4); + color: $white; + transition: all .2s cubic-bezier(.77, .01, .97, .85); + } } .button-green { display: block; @@ -57,6 +95,14 @@ .location { color: $gray-light; margin-bottom: 1rem; + svg { + position: relative; + top: 2px; + width: .75rem; + } + path { + fill: $gray-light; + } } .profile-stats { background: $whitish; diff --git a/app/modules/profile/styles/profile-sidebar.scss b/app/modules/profile/styles/profile-sidebar.scss index 299821cb..3be11664 100644 --- a/app/modules/profile/styles/profile-sidebar.scss +++ b/app/modules/profile/styles/profile-sidebar.scss @@ -8,6 +8,7 @@ .icon { color: $gray-light; margin-right: .3rem; + vertical-align: text-bottom; } } p { diff --git a/app/svg/location.svg b/app/svg/location.svg new file mode 100644 index 00000000..2e4b6957 --- /dev/null +++ b/app/svg/location.svg @@ -0,0 +1,7 @@ + + + + Location + + + From 88c21fcb6ab415cf7f3c1b59b7d9c394350cf4f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 7 May 2015 09:59:08 +0200 Subject: [PATCH 124/366] Profile bar --- app/modules/profile/includes/profile-bar.jade | 22 +++++++------------ app/modules/profile/styles/profile-bar.scss | 14 +----------- 2 files changed, 9 insertions(+), 27 deletions(-) diff --git a/app/modules/profile/includes/profile-bar.jade b/app/modules/profile/includes/profile-bar.jade index fdae316e..a7a7aab3 100644 --- a/app/modules/profile/includes/profile-bar.jade +++ b/app/modules/profile/includes/profile-bar.jade @@ -28,20 +28,14 @@ section.profile-bar div.stat span.stat-number 34 span.stat-name contacts - // These badges are not defined yet in UX. Please ask - div.profile-badges - div.badge - div.badge - div.badge - div.badge - - div.profile-organizations - h3 Organizations - div.profile-organizations-wrapper - div.organization - div.organization - div.organization - div.organization + // TODO Hide until organizations come + div.profile-organizations + h3 Organizations + div.profile-organizations-wrapper + div.organization + div.organization + div.organization + div.organization div.profile-quote span "Small minds talk about people, average minds discuss events, great minds discuss ideas" diff --git a/app/modules/profile/styles/profile-bar.scss b/app/modules/profile/styles/profile-bar.scss index 61c5d76d..96c74f91 100644 --- a/app/modules/profile/styles/profile-bar.scss +++ b/app/modules/profile/styles/profile-bar.scss @@ -126,18 +126,6 @@ display: block; } } - .profile-badges { - display: flex; - justify-content: space-between; - margin-bottom: 1rem; - .badge { - background: $gray-light; - border-radius: 5px; - height: 45px; - margin-right: .2rem; - width: 45px; - } - } .profile-organizations { border-bottom: 1px solid $whitish; @@ -163,7 +151,7 @@ .profile-quote { @extend %title; - @extend %xlarge; + @extend %larger; background: url('/images/quote.png') no-repeat top left; line-height: 1.2; padding: .5rem; From 9cb6a37f3cab8dba3e0cfca6fdd0ddc332fc81a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 7 May 2015 12:10:27 +0200 Subject: [PATCH 125/366] Translations --- app/locales/locale-en.json | 10 ++++++++++ app/modules/profile/includes/profile-bar.jade | 16 ++++++++-------- app/modules/profile/profile.jade | 2 +- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/app/locales/locale-en.json b/app/locales/locale-en.json index c56a4a61..59f10abc 100644 --- a/app/locales/locale-en.json +++ b/app/locales/locale-en.json @@ -531,6 +531,16 @@ "TITLE": "Services" } }, + "USER": { + "PROFILE": { + "EDIT": "Edit profile", + "FOLLOW": "Follow", + "PROJECTS": "Projects", + "CLOSED_US": "Closed US", + "CONTACTS": "Contacts", + "REPORT": "Report Abuse" + } + }, "PROJECT": { "WELCOME": "Welcome", "SECTION_PROJECTS": "Projects", diff --git a/app/modules/profile/includes/profile-bar.jade b/app/modules/profile/includes/profile-bar.jade index a7a7aab3..fde3251b 100644 --- a/app/modules/profile/includes/profile-bar.jade +++ b/app/modules/profile/includes/profile-bar.jade @@ -1,12 +1,12 @@ section.profile-bar div.profile-image-wrapper img.profile-img(src="http://i.imgur.com/gPiHzHN.jpg", alt="{{ user.nickname }}") - div.edit-profile(title="Edit profile") - a.profile-edition(title="Edit Profile", href="") + div.edit-profile(title="{{ 'USER.PROFILE.EDIT' | translate }}") + a.profile-edition(title="{{ 'USER.PROFILE.EDIT' | translate }}", href="") span.icon.icon-edit - span Edit Profile + span(translate="USER.PROFILE.EDIT") a.button-green - span Follow + span(translate="USER.PROFILE.FOLLOW") div.profile-data h1 Silvia Rodríguez // If user has no defined role in its profile use all its project defined roles followed by an & @@ -15,19 +15,19 @@ section.profile-bar include ../../../svg/location.svg span Madrid // Remove Abuse Flag when a user is seeing itself - a.flag(href="", title="Report Abuse") + a.flag(href="", title="{{ 'USER.PROFILE.REPORT' | translate }}") include ../../../svg/flag.svg // These values in profile stats are not defined yet in UX. Please ask div.profile-stats div.stat span.stat-number 3 - span.stat-name projects + span.stat-name(translate="USER.PROFILE.PROJECTS") div.stat span.stat-number 67 - span.stat-name us done + span.stat-name(translate="USER.PROFILE.CLOSED_US") div.stat span.stat-number 34 - span.stat-name contacts + span.stat-name(translate="USER.PROFILE.CONTACTS") // TODO Hide until organizations come div.profile-organizations h3 Organizations diff --git a/app/modules/profile/profile.jade b/app/modules/profile/profile.jade index 8393deba..8ce0ba20 100644 --- a/app/modules/profile/profile.jade +++ b/app/modules/profile/profile.jade @@ -12,7 +12,7 @@ div.profile.centered div(tg-profile-tab="contacts", tab-title="Contacts Tab", tab-icon="icon-team") include includes/profile-contacts - div(tg-profile-tab="favorities", tab-title="Favorites Tab", tab-icon="icon-star-fill") + div(tg-profile-tab="favorites", tab-title="Favorites Tab", tab-icon="icon-star-fill") include includes/profile-favorites include includes/profile-sidebar From 4ae492996df657bef710582f7c74d1586930c95f Mon Sep 17 00:00:00 2001 From: Juanfran Date: Thu, 7 May 2015 12:11:59 +0200 Subject: [PATCH 126/366] user service to get contacts --- app/coffee/modules/resources.coffee | 4 + app/coffee/modules/resources/timeline.coffee | 6 +- app/coffee/modules/resources/users.coffee | 47 ++++++++++ .../profile-projects.controller.coffee | 20 ++++ .../profile-projects.controller.spec.coffee | 72 ++++++++++++++ .../profile-projects.directive.coffee | 21 ++--- .../profile-tab/profile-tab.directive.coffee | 28 +++--- .../profile/profile-tabs/profile-tabs.jade | 2 +- .../profile-timeline-item.controller.coffee | 1 - .../profile-timeline/profile-timeline.jade | 2 +- app/modules/services/user.service.coffee | 24 +++++ app/modules/services/user.service.spec.coffee | 93 +++++++++++++++++++ 12 files changed, 292 insertions(+), 28 deletions(-) create mode 100644 app/coffee/modules/resources/users.coffee create mode 100644 app/modules/profile/profile-projects/profile-projects.controller.coffee create mode 100644 app/modules/profile/profile-projects/profile-projects.controller.spec.coffee create mode 100644 app/modules/services/user.service.coffee create mode 100644 app/modules/services/user.service.spec.coffee diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index 820d90a7..8d2b5e0f 100644 --- a/app/coffee/modules/resources.coffee +++ b/app/coffee/modules/resources.coffee @@ -145,6 +145,9 @@ urls = { # locales "locales": "/locales" + + # user + "contacts": "/users/%s/contacts" } # Initialize api urls service @@ -192,5 +195,6 @@ module.run([ "$tgWebhookLogsResourcesProvider", "$tgLocalesResourcesProvider", "$tgTimelineResourcesProvider", + "$tgUsersResourcesProvider", initResources ]) diff --git a/app/coffee/modules/resources/timeline.coffee b/app/coffee/modules/resources/timeline.coffee index 61c91805..31529120 100644 --- a/app/coffee/modules/resources/timeline.coffee +++ b/app/coffee/modules/resources/timeline.coffee @@ -24,7 +24,11 @@ taiga = @.taiga resourceProvider = ($repo) -> service = {} - service.profile = (userId, params) -> + service.profile = (userId, page) -> + params = { + page: page + } + return $repo.queryOnePaginatedRaw("timeline-profile", userId, params) return (instance) -> diff --git a/app/coffee/modules/resources/users.coffee b/app/coffee/modules/resources/users.coffee new file mode 100644 index 00000000..3db62b96 --- /dev/null +++ b/app/coffee/modules/resources/users.coffee @@ -0,0 +1,47 @@ +### +# 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/resources/user.coffee +### + + +taiga = @.taiga +sizeFormat = @.taiga.sizeFormat + + +resourceProvider = ($http, $urls) -> + service = {} + + service.contacts = (userId, options={}) -> + url = $urls.resolve("contacts", userId) + httpOptions = {headers: {}} + + if not options.enablePagination + httpOptions.headers["x-disable-pagination"] = "1" + + return $http.get(url, {}, httpOptions) + .then (result) -> + return result.data + + return (instance) -> + instance.users = service + + +module = angular.module("taigaResources") +module.factory("$tgUsersResourcesProvider", ["$tgHttp", "$tgUrls", "$q", + resourceProvider]) diff --git a/app/modules/profile/profile-projects/profile-projects.controller.coffee b/app/modules/profile/profile-projects/profile-projects.controller.coffee new file mode 100644 index 00000000..f18a32af --- /dev/null +++ b/app/modules/profile/profile-projects/profile-projects.controller.coffee @@ -0,0 +1,20 @@ +class ProfileProjectsController + @.$inject = [ + "tgUserService", + "$tgAuth" + ] + + constructor: (@userService, @auth) -> + + loadProjects: () -> + userId = @auth.getUser().id + + @userService.getProjects(userId) + .then (projects) => + return @userService.attachUserContactsToProjects(userId, projects) + .then (projects) => + @.projects = projects + + +angular.module("taigaProfile") + .controller("ProfileProjects", ProfileProjectsController) diff --git a/app/modules/profile/profile-projects/profile-projects.controller.spec.coffee b/app/modules/profile/profile-projects/profile-projects.controller.spec.coffee new file mode 100644 index 00000000..ab8988c4 --- /dev/null +++ b/app/modules/profile/profile-projects/profile-projects.controller.spec.coffee @@ -0,0 +1,72 @@ +describe "ProfileProjects", -> + $controller = null + $q = null + provide = null + $rootScope = null + mocks = {} + + _mockUserService = () -> + mocks.userService = { + getProjects: sinon.stub(), + attachUserContactsToProjects: sinon.stub() + } + + provide.value "tgUserService", mocks.userService + + _mockAuthService = () -> + stub = sinon.stub() + + stub.returns({id: 2}) + + provide.value "$tgAuth", { + getUser: stub + } + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockUserService() + _mockAuthService() + + return null + + _inject = (callback) -> + inject (_$controller_, _$q_, _$rootScope_) -> + $q = _$q_ + $rootScope = _$rootScope_ + $controller = _$controller_ + + beforeEach -> + module "taigaProfile" + _mocks() + _inject() + + it "load projects with contacts attached", (done) -> + userId = 2 + projects = [ + {id: 1}, + {id: 2}, + {id: 3} + ] + + projectsWithContacts = [ + {id: 1, contacts: "fake"}, + {id: 2, contacts: "fake"}, + {id: 3, contacts: "fake"} + ] + + mocks.userService.getProjects = (userId) -> + expect(userId).to.be.equal(userId) + + return $q (resolve, reject) -> + resolve(projects) + + mocks.userService.attachUserContactsToProjects.withArgs(userId, projects).returns(projectsWithContacts) + + ctrl = $controller("ProfileProjects") + + ctrl.loadProjects().then () -> + expect(ctrl.projects).to.be.equal(projectsWithContacts) + done() + + $rootScope.$apply() diff --git a/app/modules/profile/profile-projects/profile-projects.directive.coffee b/app/modules/profile/profile-projects/profile-projects.directive.coffee index bb9c2f71..72cf4472 100644 --- a/app/modules/profile/profile-projects/profile-projects.directive.coffee +++ b/app/modules/profile/profile-projects/profile-projects.directive.coffee @@ -1,17 +1,14 @@ -ProfileProjectsDirective = (projectsService) -> - link = (scope, el, attrs, ctrl) -> - scope.vm = {} - taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.currentUserProjects.get("all")) +ProfileProjectsDirective = () -> + link = (scope, elm, attr, ctrl) -> + ctrl.loadProjects() - directive = { - templateUrl: "profile/profile-projects/profile-projects.html" - scope: {} + return { + templateUrl: "profile/profile-projects/profile-projects.html", + scope: {}, link: link + bindToController: true, + controllerAs: "vm", + controller: "ProfileProjects" } - return directive - - -ProfileProjectsDirective.$inject = ["tgProjectsService"] - angular.module("taigaProfile").directive("tgProfileProjects", ProfileProjectsDirective) diff --git a/app/modules/profile/profile-tab/profile-tab.directive.coffee b/app/modules/profile/profile-tab/profile-tab.directive.coffee index ed6859e1..ca6f3e59 100644 --- a/app/modules/profile/profile-tab/profile-tab.directive.coffee +++ b/app/modules/profile/profile-tab/profile-tab.directive.coffee @@ -1,13 +1,22 @@ ProfileTabDirective = () -> - link = ($scope, $el, $attrs, $ctrl) -> - $scope.tab = {} + link = (scope, element, attrs, ctrl, transclude) -> + scope.tab = {} - $scope.tab.name = $attrs.tgProfileTab - $scope.tab.title = $attrs.tabTitle - $scope.tab.icon = $attrs.tabIcon - $scope.tab.active = !!$attrs.tabActive + scope.tab.name = attrs.tgProfileTab + scope.tab.title = attrs.tabTitle + scope.tab.icon = attrs.tabIcon + scope.tab.active = !!attrs.tabActive - $ctrl.addTab($scope.tab) + ctrl.addTab(scope.tab) + + scope.$watch "tab.active", (active) -> + if active + transclude scope, (clone, scope) -> + element.append(clone) + else + element.children().each (idx, elm) -> + scope.$$childHead.$destroy() + elm.remove() return { scope: {} @@ -15,11 +24,6 @@ ProfileTabDirective = () -> link: link transclude: true replace: true - template: """ -
- -
- """ } angular.module("taigaProfile") diff --git a/app/modules/profile/profile-tabs/profile-tabs.jade b/app/modules/profile/profile-tabs/profile-tabs.jade index 727a4d3e..beb2d776 100644 --- a/app/modules/profile/profile-tabs/profile-tabs.jade +++ b/app/modules/profile/profile-tabs/profile-tabs.jade @@ -1,6 +1,6 @@ div nav.profile-content-tabs - a.tab(ng-repeat="tab in vm.tabs", href="", title="{{::tab.title}}", ng-class="{active: tab.active}" ng-click="vm.toggleTab(tab)") + a.tab(ng-repeat="tab in ::vm.tabs", href="", title="{{::tab.title}}", ng-class="{active: tab.active}" ng-click="vm.toggleTab(tab)") span.icon(ng-class="::tab.icon") span {{::tab.name}} diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.coffee b/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.coffee index 7ab2901c..8145dd74 100644 --- a/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.coffee +++ b/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.coffee @@ -17,7 +17,6 @@ class ProfileTimelineItemController @.activity.sprint = timeline.data.milestone @.activity.title = @profileTimelineItemTitle.getTitle(timeline, event, type) @.activity.created_formated = moment(timeline.created).fromNow() - #test @.activity.obj = @.getObject(timeline, event) if type.description diff --git a/app/modules/profile/profile-timeline/profile-timeline.jade b/app/modules/profile/profile-timeline/profile-timeline.jade index 20b6fdb5..6901f624 100644 --- a/app/modules/profile/profile-timeline/profile-timeline.jade +++ b/app/modules/profile/profile-timeline/profile-timeline.jade @@ -1,3 +1,3 @@ section.profile-timeline - div(infinite-scroll="vm.loadTimeline()", infinite-scroll-distance="3", infinite-scroll-disabled='vm.loadingData') + div(infinite-scroll="vm.loadTimeline()", infinite-scroll-distance="3", infinite-scroll-disabled="vm.loadingData") div(tg-repeat="timeline in vm.timelineList", tg-profile-timeline-item="timeline") diff --git a/app/modules/services/user.service.coffee b/app/modules/services/user.service.coffee new file mode 100644 index 00000000..67b9ae65 --- /dev/null +++ b/app/modules/services/user.service.coffee @@ -0,0 +1,24 @@ +taiga = @.taiga + +class UserService extends taiga.Service + @.$inject = ["$tgResources"] + + constructor: (@rs) -> + + getProjects: (userId) -> + return @rs.projects.listByMember(userId) + .then (projects) -> return Immutable.fromJS(projects) + + attachUserContactsToProjects: (userId, projects) -> + return @rs.users.contacts(userId) + .then (contacts) -> return Immutable.fromJS(contacts) + .then (contacts) -> + projects = projects.map (project) -> + project.contacts = contacts.filter (contact) -> + return project.members.indexOf(contact.id) != -1 + + return project + + return projects + +angular.module("taigaCommon").service("tgUserService", UserService) diff --git a/app/modules/services/user.service.spec.coffee b/app/modules/services/user.service.spec.coffee new file mode 100644 index 00000000..e7c671ae --- /dev/null +++ b/app/modules/services/user.service.spec.coffee @@ -0,0 +1,93 @@ +# pending new resouercees + +describe.skip "UserService", -> + userService = null + $q = null + provide = null + $rootScope = null + mocks = {} + + _mockResources = () -> + mocks.resources = {} + mocks.resources.projects = { + listByMember: sinon.stub() + } + + mocks.resources.users = { + contacts: sinon.stub() + } + + provide.value "$tgResources", mocks.resources + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockResources() + + return null + + _inject = (callback) -> + inject (_tgUserService_, _$q_, _$rootScope_) -> + userService = _tgUserService_ + $q = _$q_ + $rootScope = _$rootScope_ + + beforeEach -> + module "taigaCommon" + _mocks() + _inject() + + it "get user projects", (done) -> + userId = 2 + + projects = [ + {id: 1}, + {id: 2}, + {id: 3} + ] + + mocks.resources.projects.listByMember = (userId) -> + expect(userId).to.be.equal(userId) + + return $q (resolve, reject) -> + resolve(projects) + + userService.getProjects(userId).then (_projects_) -> + expect(_projects_.toJS()).to.be.eql(projects) + done() + + $rootScope.$apply() + + it "attach user contacts to projects", (done) -> + userId = 2 + + class Project + constructor: (@id, @members) -> + + projects = Immutable.fromJS([ + new Project(1, [1, 2, 3]), + new Project(1, [2, 3]), + new Project(1, [1]) + ]) + + contacts = Immutable.fromJS([ + {id: 1, name: "fake1"}, + {id: 2, name: "fake2"}, + {id: 3, name: "fake3"} + ]) + + mocks.resources.users.contacts = (userId) -> + expect(userId).to.be.equal(userId) + + return $q (resolve, reject) -> + resolve(contacts) + + userService.attachUserContactsToProjects(userId, projects).then (_projects_) -> + contacts = _projects_.get(0).contacts + + console.log _projects_.get(0) + + expect(contacts[0]).to.be.equal('fake1') + done() + + $rootScope.$apply() From d799bf727c827c8ea379f59a86bc27f09b6b32c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 7 May 2015 12:36:12 +0200 Subject: [PATCH 127/366] User statistics --- app/modules/profile/styles/profile-bar.scss | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/modules/profile/styles/profile-bar.scss b/app/modules/profile/styles/profile-bar.scss index 96c74f91..80ce4f43 100644 --- a/app/modules/profile/styles/profile-bar.scss +++ b/app/modules/profile/styles/profile-bar.scss @@ -105,24 +105,26 @@ } } .profile-stats { - background: $whitish; - border-radius: 5px; + border-bottom: 1px solid $whitish; + border-top: 1px solid $whitish; + color: $gray; display: flex; justify-content: space-between; margin-bottom: 1rem; - padding: .5rem 1rem; + padding: 1rem .5rem; .stat { padding: 0 .2rem; text-align: center; } .stat-number { @extend %xlarge; - @extend %title; + @extend %bold; display: block; line-height: 1; } .stat-name { @extend %title; + @extend %small; display: block; } } From 1687bde95c3d2ea6c30022eb78e170a28f440c2a Mon Sep 17 00:00:00 2001 From: Juanfran Date: Fri, 8 May 2015 08:27:07 +0200 Subject: [PATCH 128/366] fix profile timeline attachments --- app/locales/locale-en.json | 6 +++--- .../profile-timeline-attachment.directive.coffee | 6 +++--- .../profile-timeline-item/profile-timeline-item.jade | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/locales/locale-en.json b/app/locales/locale-en.json index 59f10abc..623326df 100644 --- a/app/locales/locale-en.json +++ b/app/locales/locale-en.json @@ -1149,9 +1149,9 @@ "WIKI_CREATED": "{{username}} has created a new Wiki page in {{project_name}} {{obj_name}}", "NEW_PROJECT": "{{username}} has a new project {{project_name}}", "MILESTONE_UPDATED": "{{username}} has updated the milestone {{obj_name}}", - "US_UPDATED": "{{username}} has updated the field \"{{field_name}}\" of the US {{obj_name}}", - "ISSUE_UPDATED": "{{username}} has updated the field \"{{field_name}}\" of the Issue {{obj_name}}", - "TASK_UPDATED": "{{username}} has updated the field \"{{field_name}}\" of the Task {{obj_name}}", + "US_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the US {{obj_name}}", + "ISSUE_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the Issue {{obj_name}}", + "TASK_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the Task {{obj_name}}", "WIKI_UPDATED": "{{username}} has update the Wiki page {{obj_name}}", "NEW_COMMENT_US": "{{username}} has commented in the US {{obj_name}}", "NEW_COMMENT_ISSUE": "{{username}} has commented in the Issue {{obj_name}}", diff --git a/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.directive.coffee b/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.directive.coffee index 268bcd73..ae2edded 100644 --- a/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.directive.coffee +++ b/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.directive.coffee @@ -11,11 +11,11 @@ ProfileTimelineAttachmentDirective = (template, $compile) -> is_image = isImage(scope.attachment.url) if is_image - template = template.get("profile/profile-timeline-attachment/profile-timeline-attachment-image.html") + templateHtml = template.get("profile/profile-timeline-attachment/profile-timeline-attachment-image.html") else - template = template.get("profile/profile-timeline-attachment/profile-timeline-attachment.html") + templateHtml = template.get("profile/profile-timeline-attachment/profile-timeline-attachment.html") - el.html(template) + el.html(templateHtml) $compile(el.contents())(scope) return { diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item.jade b/app/modules/profile/profile-timeline-item/profile-timeline-item.jade index 68a22c80..44d06484 100644 --- a/app/modules/profile/profile-timeline-item/profile-timeline-item.jade +++ b/app/modules/profile/profile-timeline-item/profile-timeline-item.jade @@ -19,4 +19,4 @@ div.activity-image p {{::vm.activity.member.role.name}} div(ng-repeat="attachment in vm.activity.attachments") - div(tg-profile-timeline-attachment="vm.attachment") + div(tg-profile-timeline-attachment="attachment") From ee99e20c823fc9cf075ad7091a109dab01131947 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Fri, 8 May 2015 08:49:07 +0200 Subject: [PATCH 129/366] remove image with it doesn't exist --- .../profile-timeline-attachment.directive.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.directive.coffee b/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.directive.coffee index ae2edded..e733a089 100644 --- a/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.directive.coffee +++ b/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.directive.coffee @@ -18,6 +18,8 @@ ProfileTimelineAttachmentDirective = (template, $compile) -> el.html(templateHtml) $compile(el.contents())(scope) + el.find("img").error () -> @.remove() + return { link: link scope: { From 7209c5f70b1cad8e5bfa3747628c5b81a260e745 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Fri, 8 May 2015 09:03:52 +0200 Subject: [PATCH 130/366] ignore deleted items in timeline --- .../profile-timeline.service.coffee | 8 +++++++- .../profile-timeline.service.spec.coffee | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/app/modules/profile/profile-timeline/profile-timeline.service.coffee b/app/modules/profile/profile-timeline/profile-timeline.service.coffee index 0ac90906..b6c99236 100644 --- a/app/modules/profile/profile-timeline/profile-timeline.service.coffee +++ b/app/modules/profile/profile-timeline/profile-timeline.service.coffee @@ -24,9 +24,12 @@ class ProfileTimelineService extends taiga.Service 'estimated_start' ] - _isValidField: (values) => + _isValidField: (values) -> return _.some values, (value) => @._valid_fields.indexOf(value) != -1 + _isValidEvent: (event) -> + return event.split(".").slice(-1)[0] != 'delete' + _filterValidTimelineItems: (timeline) => if timeline.data.values_diff values = Object.keys(timeline.data.values_diff) @@ -38,6 +41,9 @@ class ProfileTimelineService extends taiga.Service timeline.data.values_diff.attachments.new.length == 0 return false + if !@._isValidEvent(timeline.event_type) + return false + return true getTimeline: (userId, page) -> diff --git a/app/modules/profile/profile-timeline/profile-timeline.service.spec.coffee b/app/modules/profile/profile-timeline/profile-timeline.service.spec.coffee index 77445b3a..03766f40 100644 --- a/app/modules/profile/profile-timeline/profile-timeline.service.spec.coffee +++ b/app/modules/profile/profile-timeline/profile-timeline.service.spec.coffee @@ -40,6 +40,7 @@ describe "tgProfileTimelineService", -> valid_items = { data: [ { # valid item + event_type: "xx.tt.create", data: { values_diff: { "status": "xx", @@ -48,6 +49,7 @@ describe "tgProfileTimelineService", -> } }, { # invalid item + event_type: "xx.tt.create", data: { values_diff: { "fake": "xx" @@ -55,6 +57,7 @@ describe "tgProfileTimelineService", -> } }, { # invalid item + event_type: "xx.tt.create", data: { values_diff: { "fake2": "xx" @@ -62,6 +65,7 @@ describe "tgProfileTimelineService", -> } }, { # valid item + event_type: "xx.tt.create", data: { values_diff: { "fake2": "xx", @@ -70,6 +74,7 @@ describe "tgProfileTimelineService", -> } }, { # invalid item + event_type: "xx.tt.create", data: { values_diff: { attachments: { @@ -79,6 +84,17 @@ describe "tgProfileTimelineService", -> } }, { # valid item + event_type: "xx.tt.create", + data: { + values_diff: { + attachments: { + new: [1, 2] + } + } + } + }, + { # invalid item + event_type: "xx.tt.delete", data: { values_diff: { attachments: { @@ -104,6 +120,7 @@ describe "tgProfileTimelineService", -> .then (_items_) -> items = _items_.toJS() + expect(items).to.have.length(3) expect(items[0]).to.be.eql(valid_items.data[0]) expect(items[1]).to.be.eql(valid_items.data[3]) expect(items[2]).to.be.eql(valid_items.data[5]) From 3bf6a5ad7e25695eaff376b42a474971b5f35512 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Fri, 8 May 2015 09:15:32 +0200 Subject: [PATCH 131/366] add contact list --- .../profile-projects/profile-projects.jade | 22 ++++--------------- app/modules/services/user.service.coffee | 3 ++- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/app/modules/profile/profile-projects/profile-projects.jade b/app/modules/profile/profile-projects/profile-projects.jade index 785d128b..301d387b 100644 --- a/app/modules/profile/profile-projects/profile-projects.jade +++ b/app/modules/profile/profile-projects/profile-projects.jade @@ -11,6 +11,10 @@ section.profile-projects span.tag(style='border-left: 5px solid {{::tag.color}};', ng-repeat="tag in ::project.colorized_tags") span.tag-name {{::tag.name}} + div.project-list-single-members + a(tg-repeat="contact in ::project.contacts", tg-nav="user-profile:username=contact.username", title="{{contact.full_name}}") + img(tg-bo-src="contact.photo") + // div.project-list-single-right // div.project-list-single-stats // div.stat-comments(title="2 comments") @@ -22,21 +26,3 @@ section.profile-projects // div.stat-viewer(title="2 followers") // span.icon.icon-open-eye // span.stat-num 4 - - // div.project-list-single-members - // a(href="", title="View {{ user.nickname }} profile") - // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/mantia/128.jpg") - // a(href="", title="View {{ user.nickname }} profile") - // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/annapickard/128.jpg") - // a(href="", title="View {{ user.nickname }} profile") - // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/nexy_dre/128.jpg", alt="{{ user.nickname }}") - // a(href="", title="View {{ user.nickname }} profile") - // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/pixeliris/128.jpg", alt="{{ user.nickname }}") - // a(href="", title="View {{ user.nickname }} profile") - // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/cbillins/128.jpg", alt="{{ user.nickname }}") - // a(href="", title="View {{ user.nickname }} profile") - // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/allisongrayce/128.jpg", alt="{{ user.nickname }}") - // a(href="", title="View {{ user.nickname }} profile") - // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/uxceo/128.jpg", alt="{{ user.nickname }}") - // a(href="", title="View {{ user.nickname }} profile") - // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/syswarren/128.jpg", alt="{{ user.nickname }}") diff --git a/app/modules/services/user.service.coffee b/app/modules/services/user.service.coffee index 67b9ae65..ab1bc0f4 100644 --- a/app/modules/services/user.service.coffee +++ b/app/modules/services/user.service.coffee @@ -15,7 +15,8 @@ class UserService extends taiga.Service .then (contacts) -> projects = projects.map (project) -> project.contacts = contacts.filter (contact) -> - return project.members.indexOf(contact.id) != -1 + contactId = contact.get("id") + return project.members.indexOf(contactId) != -1 return project From a0617a97766fc3ad032825f15e7dade838d142d1 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Fri, 8 May 2015 12:54:43 +0200 Subject: [PATCH 132/366] profile contacts --- app/locales/locale-en.json | 6 +- .../profile/includes/profile-contacts.jade | 45 ------------- .../profile-contacts.controller.coffee | 17 +++++ .../profile-contacts.controller.spec.coffee | 63 +++++++++++++++++++ .../profile-contacts.directive.coffee | 13 ++++ .../profile-contacts/profile-contacts.jade | 30 +++++++++ .../profile-tab/profile-tab.directive.coffee | 4 +- .../profile/profile-tabs/profile-tabs.jade | 2 +- app/modules/profile/profile.jade | 12 ++-- app/modules/services/user.service.coffee | 7 ++- app/modules/services/user.service.spec.coffee | 21 +++++++ 11 files changed, 164 insertions(+), 56 deletions(-) delete mode 100644 app/modules/profile/includes/profile-contacts.jade create mode 100644 app/modules/profile/profile-contacts/profile-contacts.controller.coffee create mode 100644 app/modules/profile/profile-contacts/profile-contacts.controller.spec.coffee create mode 100644 app/modules/profile/profile-contacts/profile-contacts.directive.coffee create mode 100644 app/modules/profile/profile-contacts/profile-contacts.jade diff --git a/app/locales/locale-en.json b/app/locales/locale-en.json index 623326df..f5f6c32f 100644 --- a/app/locales/locale-en.json +++ b/app/locales/locale-en.json @@ -538,7 +538,11 @@ "PROJECTS": "Projects", "CLOSED_US": "Closed US", "CONTACTS": "Contacts", - "REPORT": "Report Abuse" + "REPORT": "Report Abuse", + "ACTIVITY_TAB": "Activity Tab", + "PROJECTS_TAB": "Projects Tab", + "CONTACTS_TAB": "Contacts Tab", + "FAVORITES_TAB": "Favorites Tab" } }, "PROJECT": { diff --git a/app/modules/profile/includes/profile-contacts.jade b/app/modules/profile/includes/profile-contacts.jade deleted file mode 100644 index e3ed4c1f..00000000 --- a/app/modules/profile/includes/profile-contacts.jade +++ /dev/null @@ -1,45 +0,0 @@ -section.profile-contacts - nav.profile-contact-filters - a.active(href="", title="No Filter") all - a(href="", title="Only show your team") team - a(href="", title="Only show people you follow") following - a(href="", title="Only show people follow you") followers - - for (var x = 0; x < 3; x++) - div.profile-contact-single - div.profile-contact-picture - a(href="", title="See {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/koridhandy/128.jpg", alt="{{ user.nickname }}") - div.profile-contact-data - h1 - a(href="", title="See {{ user.nickname}} profile") Sebastián Molina - span.your-contact Your contact - p Chief GIF Officer at myamazingpage.com. Tweet me at @sebas - div.extra-info - span.position Back-end developer & Stakeholder - span.location Madrid - div.profile-project-stats - div.stat-projects(title="2 projects") - span.icon.icon-project - span.stat-num 2 - div.stat-viewer(title="2 followers") - span.icon.icon-open-eye - span.stat-num 4 - - div.profile-contact-single - div.profile-contact-picture - a(href="", title="See {{ user.nickname }} profile") - img(src="https://s3.amazonaws.com/uifaces/faces/twitter/marktimemedia/128.jpg", alt="{{ user.nickname }}") - div.profile-contact-data - h1 - a(href="", title="See {{ user.nickname}} profile") Ane Moreno - p Have some experience working with sock monkeys in Orlando, FL. Crossed the country licensing bongos in Miami, FL. What gets me going now is deploying catfish in Bethesda, MD. Prior to my current job I was selling dandruff in Minneapolis, MN. In 2009 I was marketing Elvis Presley in Hanford, CA. - div.extra-info - span.position Monkey Socker & Deployer - span.location Miami - div.profile-project-stats - div.stat-comments(title="2 comments") - span.icon.icon-project - span.stat-num 2 - div.stat-viewer(title="2 followers") - span.icon.icon-open-eye - span.stat-num 4 diff --git a/app/modules/profile/profile-contacts/profile-contacts.controller.coffee b/app/modules/profile/profile-contacts/profile-contacts.controller.coffee new file mode 100644 index 00000000..e2d0e463 --- /dev/null +++ b/app/modules/profile/profile-contacts/profile-contacts.controller.coffee @@ -0,0 +1,17 @@ +class ProfileContactsController + @.$inject = [ + "tgUserService", + "$tgAuth" + ] + + constructor: (@userService, @auth) -> + + loadContacts: () -> + userId = @auth.getUser().id + + @userService.getUserContacts(userId) + .then (contacts) => + @.contacts = contacts + +angular.module("taigaProfile") + .controller("ProfileContacts", ProfileContactsController) diff --git a/app/modules/profile/profile-contacts/profile-contacts.controller.spec.coffee b/app/modules/profile/profile-contacts/profile-contacts.controller.spec.coffee new file mode 100644 index 00000000..4bfd467d --- /dev/null +++ b/app/modules/profile/profile-contacts/profile-contacts.controller.spec.coffee @@ -0,0 +1,63 @@ +describe "ProfileContacts", -> + $controller = null + $q = null + provide = null + $rootScope = null + mocks = {} + + _mockUserService = () -> + mocks.userService = { + getUserContacts: sinon.stub() + } + + provide.value "tgUserService", mocks.userService + + _mockAuthService = () -> + stub = sinon.stub() + + stub.returns({id: 2}) + + provide.value "$tgAuth", { + getUser: stub + } + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockUserService() + _mockAuthService() + + return null + + _inject = (callback) -> + inject (_$controller_, _$q_, _$rootScope_) -> + $q = _$q_ + $rootScope = _$rootScope_ + $controller = _$controller_ + + beforeEach -> + module "taigaProfile" + _mocks() + _inject() + + it "load projects with contacts attached", (done) -> + userId = 2 + contacts = [ + {id: 1}, + {id: 2}, + {id: 3} + ] + + mocks.userService.getUserContacts = (userId) -> + expect(userId).to.be.equal(userId) + + return $q (resolve, reject) -> + resolve(contacts) + + ctrl = $controller("ProfileContacts") + + ctrl.loadContacts().then () -> + expect(ctrl.contacts).to.be.equal(contacts) + done() + + $rootScope.$apply() diff --git a/app/modules/profile/profile-contacts/profile-contacts.directive.coffee b/app/modules/profile/profile-contacts/profile-contacts.directive.coffee new file mode 100644 index 00000000..70e2dd4d --- /dev/null +++ b/app/modules/profile/profile-contacts/profile-contacts.directive.coffee @@ -0,0 +1,13 @@ +ProfileContactsDirective = () -> + link = (scope, elm, attrs, ctrl) -> + ctrl.loadContacts() + + return { + templateUrl: "profile/profile-contacts/profile-contacts.html", + scope: {}, + controllerAs: "vm", + controller: "ProfileContacts", + link: link + } + +angular.module("taigaProfile").directive("tgProfileContacts", ProfileContactsDirective) diff --git a/app/modules/profile/profile-contacts/profile-contacts.jade b/app/modules/profile/profile-contacts/profile-contacts.jade new file mode 100644 index 00000000..e81fef75 --- /dev/null +++ b/app/modules/profile/profile-contacts/profile-contacts.jade @@ -0,0 +1,30 @@ +section.profile-contacts + // nav.profile-contact-filters + // a.active(href="", title="No Filter") all + // a(href="", title="Only show your team") team + // a(href="", title="Only show people you follow") following + // a(href="", title="Only show people follow you") followers + + div.profile-contact-single(tg-repeat="contact in ::vm.contacts") + div.profile-contact-picture + a(tg-nav="user-profile:username=contact.username", title="{{::contact.name }}") + img(ng-src='{{::contact.photo}}', alt='{{::contact.full_name}}') + + div.profile-contact-data + h1 + a(tg-nav="user-profile:username=contact.username", title="{{::contact.name }}") + | {{::contact.full_name}} + // span.your-contact Your contact + + p(ng-if="contact.bio") {{::contact.bio}} + + div.extra-info + span.position {{::contact.roles.join(", ")}} + // span.location todo + // div.profile-project-stats + // div.stat-projects(title="2 projects") + // span.icon.icon-project + // span.stat-num 2 + // div.stat-viewer(title="2 followers") + // span.icon.icon-open-eye + // span.stat-num 4 diff --git a/app/modules/profile/profile-tab/profile-tab.directive.coffee b/app/modules/profile/profile-tab/profile-tab.directive.coffee index ca6f3e59..50625f32 100644 --- a/app/modules/profile/profile-tab/profile-tab.directive.coffee +++ b/app/modules/profile/profile-tab/profile-tab.directive.coffee @@ -2,8 +2,10 @@ ProfileTabDirective = () -> link = (scope, element, attrs, ctrl, transclude) -> scope.tab = {} + attrs.$observe "tabTitle", (title) -> + scope.tab.title = title + scope.tab.name = attrs.tgProfileTab - scope.tab.title = attrs.tabTitle scope.tab.icon = attrs.tabIcon scope.tab.active = !!attrs.tabActive diff --git a/app/modules/profile/profile-tabs/profile-tabs.jade b/app/modules/profile/profile-tabs/profile-tabs.jade index beb2d776..00390cd8 100644 --- a/app/modules/profile/profile-tabs/profile-tabs.jade +++ b/app/modules/profile/profile-tabs/profile-tabs.jade @@ -1,6 +1,6 @@ div nav.profile-content-tabs - a.tab(ng-repeat="tab in ::vm.tabs", href="", title="{{::tab.title}}", ng-class="{active: tab.active}" ng-click="vm.toggleTab(tab)") + a.tab(ng-repeat="tab in ::vm.tabs", href="", title="{{tab.title}}", ng-class="{active: tab.active}" ng-click="vm.toggleTab(tab)") span.icon(ng-class="::tab.icon") span {{::tab.name}} diff --git a/app/modules/profile/profile.jade b/app/modules/profile/profile.jade index 8ce0ba20..f7a6bd2e 100644 --- a/app/modules/profile/profile.jade +++ b/app/modules/profile/profile.jade @@ -3,16 +3,16 @@ div.profile.centered include includes/profile-bar div.main div.timeline-wrapper(tg-profile-tabs) - div(tg-profile-tab="activity", tab-title="Activity Tab", tab-icon="icon-timeline", tab-active) + div(tg-profile-tab="activity", tab-title="{{'USER.PROFILE.ACTIVITY_TAB' | translate}}", tab-icon="icon-timeline", tab-active) div(tg-profile-timeline) - div(tg-profile-tab="projects", tab-title="Projects Tab", tab-icon="icon-project") + div(tg-profile-tab="projects", tab-title="{{'USER.PROFILE.PROJECTS_TAB' | translate}}", tab-icon="icon-project") div(tg-profile-projects) - div(tg-profile-tab="contacts", tab-title="Contacts Tab", tab-icon="icon-team") - include includes/profile-contacts + div(tg-profile-tab="contacts", tab-title="{{'USER.PROFILE.CONTACTS_TAB' | translate}}", tab-icon="icon-team") + div(tg-profile-contacts) - div(tg-profile-tab="favorites", tab-title="Favorites Tab", tab-icon="icon-star-fill") - include includes/profile-favorites + // div(tg-profile-tab="favorites", tab-title="{{'USER.PROFILE.FAVORITES_TAB' | translate}}", tab-icon="icon-star-fill") + // include includes/profile-favorites include includes/profile-sidebar diff --git a/app/modules/services/user.service.coffee b/app/modules/services/user.service.coffee index ab1bc0f4..743649e2 100644 --- a/app/modules/services/user.service.coffee +++ b/app/modules/services/user.service.coffee @@ -10,8 +10,7 @@ class UserService extends taiga.Service .then (projects) -> return Immutable.fromJS(projects) attachUserContactsToProjects: (userId, projects) -> - return @rs.users.contacts(userId) - .then (contacts) -> return Immutable.fromJS(contacts) + return @.getUserContacts(userId) .then (contacts) -> projects = projects.map (project) -> project.contacts = contacts.filter (contact) -> @@ -22,4 +21,8 @@ class UserService extends taiga.Service return projects + getUserContacts: (userId) -> + return @rs.users.contacts(userId) + .then (contacts) -> return Immutable.fromJS(contacts) + angular.module("taigaCommon").service("tgUserService", UserService) diff --git a/app/modules/services/user.service.spec.coffee b/app/modules/services/user.service.spec.coffee index e7c671ae..ec16ce41 100644 --- a/app/modules/services/user.service.spec.coffee +++ b/app/modules/services/user.service.spec.coffee @@ -91,3 +91,24 @@ describe.skip "UserService", -> done() $rootScope.$apply() + + it "get user contacts", (done) -> + userId = 2 + + contacts = [ + {id: 1}, + {id: 2}, + {id: 3} + ] + + mocks.resources.user.contacts = (userId) -> + expect(userId).to.be.equal(userId) + + return $q (resolve, reject) -> + resolve(contacts) + + userService.getUserContacts(userId).then (_contacts_) -> + expect(_contacts_.toJS()).to.be.eql(contacts) + done() + + $rootScope.$apply() From 32cc97c7f5ccad0bc9e5cb7cea32dc34ba4d5e67 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Fri, 8 May 2015 13:18:31 +0200 Subject: [PATCH 133/366] basic profile bar --- app/modules/profile/includes/profile-bar.jade | 41 ------------------- .../profile-bar/profile-bar.controller.coffee | 9 ++++ .../profile-bar/profile-bar.directive.coffee | 9 ++++ .../profile/profile-bar/profile-bar.jade | 40 ++++++++++++++++++ app/modules/profile/profile.jade | 2 +- 5 files changed, 59 insertions(+), 42 deletions(-) delete mode 100644 app/modules/profile/includes/profile-bar.jade create mode 100644 app/modules/profile/profile-bar/profile-bar.controller.coffee create mode 100644 app/modules/profile/profile-bar/profile-bar.directive.coffee create mode 100644 app/modules/profile/profile-bar/profile-bar.jade diff --git a/app/modules/profile/includes/profile-bar.jade b/app/modules/profile/includes/profile-bar.jade deleted file mode 100644 index fde3251b..00000000 --- a/app/modules/profile/includes/profile-bar.jade +++ /dev/null @@ -1,41 +0,0 @@ -section.profile-bar - div.profile-image-wrapper - img.profile-img(src="http://i.imgur.com/gPiHzHN.jpg", alt="{{ user.nickname }}") - div.edit-profile(title="{{ 'USER.PROFILE.EDIT' | translate }}") - a.profile-edition(title="{{ 'USER.PROFILE.EDIT' | translate }}", href="") - span.icon.icon-edit - span(translate="USER.PROFILE.EDIT") - a.button-green - span(translate="USER.PROFILE.FOLLOW") - div.profile-data - h1 Silvia Rodríguez - // If user has no defined role in its profile use all its project defined roles followed by an & - h2 Backend Developer & Stackeholder - div.location - include ../../../svg/location.svg - span Madrid - // Remove Abuse Flag when a user is seeing itself - a.flag(href="", title="{{ 'USER.PROFILE.REPORT' | translate }}") - include ../../../svg/flag.svg - // These values in profile stats are not defined yet in UX. Please ask - div.profile-stats - div.stat - span.stat-number 3 - span.stat-name(translate="USER.PROFILE.PROJECTS") - div.stat - span.stat-number 67 - span.stat-name(translate="USER.PROFILE.CLOSED_US") - div.stat - span.stat-number 34 - span.stat-name(translate="USER.PROFILE.CONTACTS") - // TODO Hide until organizations come - div.profile-organizations - h3 Organizations - div.profile-organizations-wrapper - div.organization - div.organization - div.organization - div.organization - - div.profile-quote - span "Small minds talk about people, average minds discuss events, great minds discuss ideas" diff --git a/app/modules/profile/profile-bar/profile-bar.controller.coffee b/app/modules/profile/profile-bar/profile-bar.controller.coffee new file mode 100644 index 00000000..25701f9a --- /dev/null +++ b/app/modules/profile/profile-bar/profile-bar.controller.coffee @@ -0,0 +1,9 @@ +class ProfileBarController + @.$inject = [ + "$tgAuth" + ] + + constructor: (@auth) -> + @.user = @auth.getUser() + +angular.module("taigaProfile").controller("ProfileBar", ProfileBarController) diff --git a/app/modules/profile/profile-bar/profile-bar.directive.coffee b/app/modules/profile/profile-bar/profile-bar.directive.coffee new file mode 100644 index 00000000..2c5e053e --- /dev/null +++ b/app/modules/profile/profile-bar/profile-bar.directive.coffee @@ -0,0 +1,9 @@ +ProfileBarDirective = () -> + return { + templateUrl: "profile/profile-bar/profile-bar.html", + controller: "ProfileBar", + controllerAs: "vm" + } + + +angular.module("taigaProfile").directive("tgProfileBar", ProfileBarDirective) diff --git a/app/modules/profile/profile-bar/profile-bar.jade b/app/modules/profile/profile-bar/profile-bar.jade new file mode 100644 index 00000000..a568a9e3 --- /dev/null +++ b/app/modules/profile/profile-bar/profile-bar.jade @@ -0,0 +1,40 @@ +section.profile-bar + div.profile-image-wrapper + img.profile-img(ng-src="{{user.big_photo}}", alt="{{ user.full_name }}") + div.edit-profile(title="{{ 'USER.PROFILE.EDIT' | translate }}") + a.profile-edition(title="{{ 'USER.PROFILE.EDIT' | translate }}", tg-nav="user-settings-user-profile") + span.icon.icon-edit + span(translate="USER.PROFILE.EDIT") + // a.button-green + // span(translate="USER.PROFILE.FOLLOW") + div.profile-data + h1 {{vm.user.full_name}} + h2 Backend Developer & Stackeholder + // div.location + // include ../../../svg/location.svg + // span Madrid + // Remove Abuse Flag when a user is seeing itself + // a.flag(href="", title="{{ 'USER.PROFILE.REPORT' | translate }}") + // include ../../../svg/flag.svg + // These values in profile stats are not defined yet in UX. Please ask + // div.profile-stats + // div.stat + // span.stat-number 3 + // span.stat-name(translate="USER.PROFILE.PROJECTS") + // div.stat + // span.stat-number 67 + // span.stat-name(translate="USER.PROFILE.CLOSED_US") + // div.stat + // span.stat-number 34 + // span.stat-name(translate="USER.PROFILE.CONTACTS") + // TODO Hide until organizations come + // div.profile-organizations + // h3 Organizations + // div.profile-organizations-wrapper + // div.organization + // div.organization + // div.organization + // div.organization + + div.profile-quote + span "Small minds talk about people, average minds discuss events, great minds discuss ideas" diff --git a/app/modules/profile/profile.jade b/app/modules/profile/profile.jade index f7a6bd2e..07059af6 100644 --- a/app/modules/profile/profile.jade +++ b/app/modules/profile/profile.jade @@ -1,6 +1,6 @@ include ../../partials/includes/components/beta div.profile.centered - include includes/profile-bar + div(tg-profile-bar) div.main div.timeline-wrapper(tg-profile-tabs) div(tg-profile-tab="activity", tab-title="{{'USER.PROFILE.ACTIVITY_TAB' | translate}}", tab-icon="icon-timeline", tab-active) From 9d551fdd5c7371295861c999da6e4790b526c7c1 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Fri, 8 May 2015 13:46:33 +0200 Subject: [PATCH 134/366] current user profile link --- app/coffee/modules/base.coffee | 2 +- app/modules/navigation-bar/dropdown-user/dropdown-user.jade | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/coffee/modules/base.coffee b/app/coffee/modules/base.coffee index b74f30bf..c7cc0839 100644 --- a/app/coffee/modules/base.coffee +++ b/app/coffee/modules/base.coffee @@ -58,7 +58,7 @@ urls = { "invitation": "/invitation/:token" "create-project": "/create-project" - "profile": "/:user" + "profile": "/profile" "user-profile": "profile/:username" "project": "/project/:project" diff --git a/app/modules/navigation-bar/dropdown-user/dropdown-user.jade b/app/modules/navigation-bar/dropdown-user/dropdown-user.jade index e7ddf281..6f718291 100644 --- a/app/modules/navigation-bar/dropdown-user/dropdown-user.jade +++ b/app/modules/navigation-bar/dropdown-user/dropdown-user.jade @@ -1,11 +1,11 @@ -a.user-avatar(href="#", title="{{ vm.user.full_name_display }}") {{ vm.user.full_name_display }} +a.user-avatar(tg-nav="profile", title="{{ vm.user.full_name_display }}") {{ vm.user.full_name_display }} img(tg-bo-src="vm.user.photo", alt="{{ vm.user.full_name_display }}") div.navbar-dropdown.dropdown-user ul li a( - href="#", + tg-nav="profile", title="{{'PROJECT.NAVIGATION.VIEW_PROFILE_TITLE' | translate}}", translate="PROJECT.NAVIGATION.VIEW_PROFILE") li From 74aa3a5d756916e8f3f5c6160762dfab8723dfad Mon Sep 17 00:00:00 2001 From: Juanfran Date: Mon, 11 May 2015 09:46:01 +0200 Subject: [PATCH 135/366] users resources --- app/coffee/app.coffee | 1 + app/coffee/modules/resources.coffee | 3 +- .../profile-contacts.controller.coffee | 6 +-- .../profile-contacts.controller.spec.coffee | 14 +++--- .../profile-projects.controller.coffee | 1 - .../profile-timeline.controller.coffee | 2 +- app/modules/resources/resources.coffee | 22 ++++++++ app/modules/resources/resources.module.coffee | 1 + .../resources/users-resource.service.coffee | 46 +++++++++++++++++ app/modules/services/user.service.coffee | 20 ++++---- app/modules/services/user.service.spec.coffee | 50 ++++++------------- 11 files changed, 110 insertions(+), 56 deletions(-) create mode 100644 app/modules/resources/resources.coffee create mode 100644 app/modules/resources/resources.module.coffee create mode 100644 app/modules/resources/users-resource.service.coffee diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 63ea2edf..96965a00 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -356,6 +356,7 @@ modules = [ "taigaBase", "taigaCommon", "taigaResources", + "taigaResources2", "taigaAuth", "taigaEvents", diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index 8d2b5e0f..4631a432 100644 --- a/app/coffee/modules/resources.coffee +++ b/app/coffee/modules/resources.coffee @@ -146,8 +146,9 @@ urls = { # locales "locales": "/locales" - # user + # users "contacts": "/users/%s/contacts" + "stats": "/users/%s/stats" } # Initialize api urls service diff --git a/app/modules/profile/profile-contacts/profile-contacts.controller.coffee b/app/modules/profile/profile-contacts/profile-contacts.controller.coffee index e2d0e463..d14882b5 100644 --- a/app/modules/profile/profile-contacts/profile-contacts.controller.coffee +++ b/app/modules/profile/profile-contacts/profile-contacts.controller.coffee @@ -1,15 +1,15 @@ class ProfileContactsController @.$inject = [ - "tgUserService", + "tgResources", "$tgAuth" ] - constructor: (@userService, @auth) -> + constructor: (@rs, @auth) -> loadContacts: () -> userId = @auth.getUser().id - @userService.getUserContacts(userId) + @rs.users.getContacts(userId) .then (contacts) => @.contacts = contacts diff --git a/app/modules/profile/profile-contacts/profile-contacts.controller.spec.coffee b/app/modules/profile/profile-contacts/profile-contacts.controller.spec.coffee index 4bfd467d..44e0ea33 100644 --- a/app/modules/profile/profile-contacts/profile-contacts.controller.spec.coffee +++ b/app/modules/profile/profile-contacts/profile-contacts.controller.spec.coffee @@ -5,12 +5,14 @@ describe "ProfileContacts", -> $rootScope = null mocks = {} - _mockUserService = () -> - mocks.userService = { - getUserContacts: sinon.stub() + _mockResources = () -> + mocks.resources = { + users: { + getContacts: sinon.stub() + } } - provide.value "tgUserService", mocks.userService + provide.value "tgResources", mocks.resources _mockAuthService = () -> stub = sinon.stub() @@ -24,7 +26,7 @@ describe "ProfileContacts", -> _mocks = () -> module ($provide) -> provide = $provide - _mockUserService() + _mockResources() _mockAuthService() return null @@ -48,7 +50,7 @@ describe "ProfileContacts", -> {id: 3} ] - mocks.userService.getUserContacts = (userId) -> + mocks.resources.users.getContacts = (userId) -> expect(userId).to.be.equal(userId) return $q (resolve, reject) -> diff --git a/app/modules/profile/profile-projects/profile-projects.controller.coffee b/app/modules/profile/profile-projects/profile-projects.controller.coffee index f18a32af..6b47fc54 100644 --- a/app/modules/profile/profile-projects/profile-projects.controller.coffee +++ b/app/modules/profile/profile-projects/profile-projects.controller.coffee @@ -15,6 +15,5 @@ class ProfileProjectsController .then (projects) => @.projects = projects - angular.module("taigaProfile") .controller("ProfileProjects", ProfileProjectsController) diff --git a/app/modules/profile/profile-timeline/profile-timeline.controller.coffee b/app/modules/profile/profile-timeline/profile-timeline.controller.coffee index db97b33a..6b9854da 100644 --- a/app/modules/profile/profile-timeline/profile-timeline.controller.coffee +++ b/app/modules/profile/profile-timeline/profile-timeline.controller.coffee @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # -# File: modules/backlog/main.coffee +# File: modules/profile/profile-timeline/profile-timeline.controller.coffee ### taiga = @.taiga diff --git a/app/modules/resources/resources.coffee b/app/modules/resources/resources.coffee new file mode 100644 index 00000000..197a95c7 --- /dev/null +++ b/app/modules/resources/resources.coffee @@ -0,0 +1,22 @@ +services = [ + "tgProjectsResources" +] + +Resources = ($injector) -> + for serviceName in services + serviceFn = $injector.get(serviceName) + + service = $injector.invoke(serviceFn) + + for serviceProperty in Object.keys(service) + if @[serviceProperty] + console.warm("repeated resource " + serviceProperty) + + @[serviceProperty] = service[serviceProperty] + + return @ + + +Resources.$inject = ["$injector"] + +angular.module("taigaResources2").service("tgResources", Resources) diff --git a/app/modules/resources/resources.module.coffee b/app/modules/resources/resources.module.coffee new file mode 100644 index 00000000..b1fdef81 --- /dev/null +++ b/app/modules/resources/resources.module.coffee @@ -0,0 +1 @@ +angular.module("taigaResources2", []) diff --git a/app/modules/resources/users-resource.service.coffee b/app/modules/resources/users-resource.service.coffee new file mode 100644 index 00000000..40abd63c --- /dev/null +++ b/app/modules/resources/users-resource.service.coffee @@ -0,0 +1,46 @@ +Resource = (urlsService, http) -> + service = {} + + service.getStats = (userId) -> + url = urlsService.resolve("stats", userId) + + httpOptions = { + headers: { + "x-disable-pagination": "1" + } + } + + return http.get(url, {}, httpOptions) + .then (result) -> + return Immutable.fromJS(result.data) + + + service.getContacts = (userId) -> + url = urlsService.resolve("contacts", userId) + + httpOptions = { + headers: { + "x-disable-pagination": "1" + } + } + + return http.get(url, {}, httpOptions) + .then (result) -> + return Immutable.fromJS(result.data) + + service.getProjects = (userId) -> + url = urlsService.resolve("projects") + + params = {"member": userId, "order_by": "memberships__user_order"} + + return http.get(url, params) + .then (result) -> + return Immutable.fromJS(result.data) + + return () -> + return {"users": service} + +Resource.$inject = ["$tgUrls", "$tgHttp"] + +module = angular.module("taigaResources2") +module.factory("tgProjectsResources", Resource) diff --git a/app/modules/services/user.service.coffee b/app/modules/services/user.service.coffee index 743649e2..a9e53e0d 100644 --- a/app/modules/services/user.service.coffee +++ b/app/modules/services/user.service.coffee @@ -1,28 +1,28 @@ taiga = @.taiga class UserService extends taiga.Service - @.$inject = ["$tgResources"] + @.$inject = ["tgResources"] constructor: (@rs) -> getProjects: (userId) -> - return @rs.projects.listByMember(userId) - .then (projects) -> return Immutable.fromJS(projects) + return @rs.users.getProjects(userId) + + getContacts: (userId) -> + return @rs.users.getContacts(userId) attachUserContactsToProjects: (userId, projects) -> - return @.getUserContacts(userId) + return @.getContacts(userId) .then (contacts) -> projects = projects.map (project) -> - project.contacts = contacts.filter (contact) -> + contactsFiltered = contacts.filter (contact) -> contactId = contact.get("id") - return project.members.indexOf(contactId) != -1 + return project.get('members').indexOf(contactId) != -1 + + project = project.set("contacts", contactsFiltered) return project return projects - getUserContacts: (userId) -> - return @rs.users.contacts(userId) - .then (contacts) -> return Immutable.fromJS(contacts) - angular.module("taigaCommon").service("tgUserService", UserService) diff --git a/app/modules/services/user.service.spec.coffee b/app/modules/services/user.service.spec.coffee index ec16ce41..4c4269f3 100644 --- a/app/modules/services/user.service.spec.coffee +++ b/app/modules/services/user.service.spec.coffee @@ -1,6 +1,4 @@ -# pending new resouercees - -describe.skip "UserService", -> +describe "UserService", -> userService = null $q = null provide = null @@ -9,15 +7,12 @@ describe.skip "UserService", -> _mockResources = () -> mocks.resources = {} - mocks.resources.projects = { - listByMember: sinon.stub() - } - mocks.resources.users = { - contacts: sinon.stub() + getProjects: sinon.stub(), + getContacts: sinon.stub() } - provide.value "$tgResources", mocks.resources + provide.value "tgResources", mocks.resources _mocks = () -> module ($provide) -> @@ -37,7 +32,7 @@ describe.skip "UserService", -> _mocks() _inject() - it "get user projects", (done) -> + it "get user projects", () -> userId = 2 projects = [ @@ -46,28 +41,17 @@ describe.skip "UserService", -> {id: 3} ] - mocks.resources.projects.listByMember = (userId) -> - expect(userId).to.be.equal(userId) + mocks.resources.users.getProjects.withArgs(userId).returns(true) - return $q (resolve, reject) -> - resolve(projects) - - userService.getProjects(userId).then (_projects_) -> - expect(_projects_.toJS()).to.be.eql(projects) - done() - - $rootScope.$apply() + expect(userService.getProjects(userId)).to.be.true it "attach user contacts to projects", (done) -> userId = 2 - class Project - constructor: (@id, @members) -> - projects = Immutable.fromJS([ - new Project(1, [1, 2, 3]), - new Project(1, [2, 3]), - new Project(1, [1]) + {id: 1, members: [1, 2, 3]}, + {id: 2, members: [2, 3]}, + {id: 3, members: [1]} ]) contacts = Immutable.fromJS([ @@ -76,18 +60,16 @@ describe.skip "UserService", -> {id: 3, name: "fake3"} ]) - mocks.resources.users.contacts = (userId) -> + mocks.resources.users.getContacts = (userId) -> expect(userId).to.be.equal(userId) return $q (resolve, reject) -> resolve(contacts) userService.attachUserContactsToProjects(userId, projects).then (_projects_) -> - contacts = _projects_.get(0).contacts + contacts = _projects_.get(0).get("contacts") - console.log _projects_.get(0) - - expect(contacts[0]).to.be.equal('fake1') + expect(contacts.get(0).get("name")).to.be.equal('fake1') done() $rootScope.$apply() @@ -101,14 +83,14 @@ describe.skip "UserService", -> {id: 3} ] - mocks.resources.user.contacts = (userId) -> + mocks.resources.users.getContacts = (userId) -> expect(userId).to.be.equal(userId) return $q (resolve, reject) -> resolve(contacts) - userService.getUserContacts(userId).then (_contacts_) -> - expect(_contacts_.toJS()).to.be.eql(contacts) + userService.getContacts(userId).then (_contacts_) -> + expect(_contacts_).to.be.eql(contacts) done() $rootScope.$apply() From 5b0eb71fb1befa04bf1251033dcbc00cd99619b3 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Mon, 11 May 2015 10:17:52 +0200 Subject: [PATCH 136/366] user stats --- .../profile-bar/profile-bar.controller.coffee | 11 +++- .../profile-bar.controller.spec.coffee | 61 +++++++++++++++++++ .../profile/profile-bar/profile-bar.jade | 26 ++++---- app/modules/services/user.service.coffee | 3 + app/modules/services/user.service.spec.coffee | 13 ++++ 5 files changed, 99 insertions(+), 15 deletions(-) create mode 100644 app/modules/profile/profile-bar/profile-bar.controller.spec.coffee diff --git a/app/modules/profile/profile-bar/profile-bar.controller.coffee b/app/modules/profile/profile-bar/profile-bar.controller.coffee index 25701f9a..b9ea7323 100644 --- a/app/modules/profile/profile-bar/profile-bar.controller.coffee +++ b/app/modules/profile/profile-bar/profile-bar.controller.coffee @@ -1,9 +1,16 @@ class ProfileBarController @.$inject = [ - "$tgAuth" + "$tgAuth", + "tgUserService" ] - constructor: (@auth) -> + constructor: (@auth, @userService) -> @.user = @auth.getUser() + @.loadStats() + + loadStats: () -> + return @userService.getStats(@.user.id).then (stats) => + @.stats = stats.toJS() + angular.module("taigaProfile").controller("ProfileBar", ProfileBarController) diff --git a/app/modules/profile/profile-bar/profile-bar.controller.spec.coffee b/app/modules/profile/profile-bar/profile-bar.controller.spec.coffee new file mode 100644 index 00000000..4a37e0ee --- /dev/null +++ b/app/modules/profile/profile-bar/profile-bar.controller.spec.coffee @@ -0,0 +1,61 @@ +describe "ProfileBar", -> + $controller = null + $q = null + provide = null + $rootScope = null + mocks = {} + + _mockUserService = () -> + mocks.userService = { + getStats: sinon.stub() + } + + provide.value "tgUserService", mocks.userService + + _mockAuthService = () -> + stub = sinon.stub() + + stub.returns({id: 2}) + + provide.value "$tgAuth", { + getUser: stub + } + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockUserService() + _mockAuthService() + + return null + + _inject = (callback) -> + inject (_$controller_, _$q_, _$rootScope_) -> + $q = _$q_ + $rootScope = _$rootScope_ + $controller = _$controller_ + + beforeEach -> + module "taigaProfile" + _mocks() + _inject() + + it "user stats filled", () -> + userId = 2 + stats = Immutable.fromJS([ + {id: 1}, + {id: 2}, + {id: 3} + ]) + + mocks.userService.getStats = (userId) -> + expect(userId).to.be.equal(userId) + + return $q (resolve, reject) -> + resolve(stats) + + ctrl = $controller("ProfileBar") + + $rootScope.$apply() + + expect(ctrl.stats).to.be.eql(stats.toJS()) diff --git a/app/modules/profile/profile-bar/profile-bar.jade b/app/modules/profile/profile-bar/profile-bar.jade index a568a9e3..40311993 100644 --- a/app/modules/profile/profile-bar/profile-bar.jade +++ b/app/modules/profile/profile-bar/profile-bar.jade @@ -1,6 +1,6 @@ section.profile-bar div.profile-image-wrapper - img.profile-img(ng-src="{{user.big_photo}}", alt="{{ user.full_name }}") + img.profile-img(ng-src="{{::vm.user.big_photo}}", alt="{{::vm.user.full_name}}") div.edit-profile(title="{{ 'USER.PROFILE.EDIT' | translate }}") a.profile-edition(title="{{ 'USER.PROFILE.EDIT' | translate }}", tg-nav="user-settings-user-profile") span.icon.icon-edit @@ -8,8 +8,8 @@ section.profile-bar // a.button-green // span(translate="USER.PROFILE.FOLLOW") div.profile-data - h1 {{vm.user.full_name}} - h2 Backend Developer & Stackeholder + h1 {{::vm.user.full_name}} + h2 {{::vm.stats.roles.join(", ")}} // div.location // include ../../../svg/location.svg // span Madrid @@ -17,16 +17,16 @@ section.profile-bar // a.flag(href="", title="{{ 'USER.PROFILE.REPORT' | translate }}") // include ../../../svg/flag.svg // These values in profile stats are not defined yet in UX. Please ask - // div.profile-stats - // div.stat - // span.stat-number 3 - // span.stat-name(translate="USER.PROFILE.PROJECTS") - // div.stat - // span.stat-number 67 - // span.stat-name(translate="USER.PROFILE.CLOSED_US") - // div.stat - // span.stat-number 34 - // span.stat-name(translate="USER.PROFILE.CONTACTS") + div.profile-stats + div.stat + span.stat-number {{::vm.stats.projects}} + span.stat-name(translate="USER.PROFILE.PROJECTS") + div.stat + span.stat-number {{::vm.stats.closed_userstories}} + span.stat-name(translate="USER.PROFILE.CLOSED_US") + div.stat + span.stat-number {{::vm.stats.contacts}} + span.stat-name(translate="USER.PROFILE.CONTACTS") // TODO Hide until organizations come // div.profile-organizations // h3 Organizations diff --git a/app/modules/services/user.service.coffee b/app/modules/services/user.service.coffee index a9e53e0d..83704a08 100644 --- a/app/modules/services/user.service.coffee +++ b/app/modules/services/user.service.coffee @@ -11,6 +11,9 @@ class UserService extends taiga.Service getContacts: (userId) -> return @rs.users.getContacts(userId) + getStats: (userId) -> + return @rs.users.getStats(userId) + attachUserContactsToProjects: (userId, projects) -> return @.getContacts(userId) .then (contacts) -> diff --git a/app/modules/services/user.service.spec.coffee b/app/modules/services/user.service.spec.coffee index 4c4269f3..22d552e3 100644 --- a/app/modules/services/user.service.spec.coffee +++ b/app/modules/services/user.service.spec.coffee @@ -45,6 +45,19 @@ describe "UserService", -> expect(userService.getProjects(userId)).to.be.true + it "get user contacts", () -> + userId = 2 + + contacts = [ + {id: 1}, + {id: 2}, + {id: 3} + ] + + mocks.resources.users.getContacts.withArgs(userId).returns(true) + + expect(userService.getContacts(userId)).to.be.true + it "attach user contacts to projects", (done) -> userId = 2 From a88a31a1b232a303ec273a5c60f4967f11b92bc5 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Mon, 11 May 2015 10:38:17 +0200 Subject: [PATCH 137/366] userService in profileContact instead of rs --- .../profile-contacts.controller.coffee | 6 +++--- .../profile-contacts.controller.spec.coffee | 14 ++++++-------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/app/modules/profile/profile-contacts/profile-contacts.controller.coffee b/app/modules/profile/profile-contacts/profile-contacts.controller.coffee index d14882b5..e82d8d3f 100644 --- a/app/modules/profile/profile-contacts/profile-contacts.controller.coffee +++ b/app/modules/profile/profile-contacts/profile-contacts.controller.coffee @@ -1,15 +1,15 @@ class ProfileContactsController @.$inject = [ - "tgResources", + "tgUserService", "$tgAuth" ] - constructor: (@rs, @auth) -> + constructor: (@userService, @auth) -> loadContacts: () -> userId = @auth.getUser().id - @rs.users.getContacts(userId) + @userService.getContacts(userId) .then (contacts) => @.contacts = contacts diff --git a/app/modules/profile/profile-contacts/profile-contacts.controller.spec.coffee b/app/modules/profile/profile-contacts/profile-contacts.controller.spec.coffee index 44e0ea33..2e60f51e 100644 --- a/app/modules/profile/profile-contacts/profile-contacts.controller.spec.coffee +++ b/app/modules/profile/profile-contacts/profile-contacts.controller.spec.coffee @@ -5,14 +5,12 @@ describe "ProfileContacts", -> $rootScope = null mocks = {} - _mockResources = () -> - mocks.resources = { - users: { - getContacts: sinon.stub() - } + _mockUserService = () -> + mocks.userServices = { + getContacts: sinon.stub() } - provide.value "tgResources", mocks.resources + provide.value "tgUserService", mocks.userServices _mockAuthService = () -> stub = sinon.stub() @@ -26,7 +24,7 @@ describe "ProfileContacts", -> _mocks = () -> module ($provide) -> provide = $provide - _mockResources() + _mockUserService() _mockAuthService() return null @@ -50,7 +48,7 @@ describe "ProfileContacts", -> {id: 3} ] - mocks.resources.users.getContacts = (userId) -> + mocks.userServices.getContacts = (userId) -> expect(userId).to.be.equal(userId) return $q (resolve, reject) -> From 307031d08526a927190292efaab0cf4432ffd2f3 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Mon, 11 May 2015 10:45:07 +0200 Subject: [PATCH 138/366] fix projects contacts list --- app/modules/profile/profile-projects/profile-projects.jade | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/modules/profile/profile-projects/profile-projects.jade b/app/modules/profile/profile-projects/profile-projects.jade index 301d387b..b7a98cec 100644 --- a/app/modules/profile/profile-projects/profile-projects.jade +++ b/app/modules/profile/profile-projects/profile-projects.jade @@ -12,8 +12,8 @@ section.profile-projects span.tag-name {{::tag.name}} div.project-list-single-members - a(tg-repeat="contact in ::project.contacts", tg-nav="user-profile:username=contact.username", title="{{contact.full_name}}") - img(tg-bo-src="contact.photo") + a(ng-repeat="contact in ::project.contacts", tg-nav="user-profile:username=contact.username", title="{{::contact.full_name}}") + img(ng-src="{{::contact.photo}}") // div.project-list-single-right // div.project-list-single-stats From 767179e21ae80db8969d5bd2d6648a8155ee9c58 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 11 May 2015 14:28:49 +0200 Subject: [PATCH 139/366] Removing unnecesary source file for projects --- app/coffee/modules/projects/main.coffee | 138 ------------------------ 1 file changed, 138 deletions(-) delete mode 100644 app/coffee/modules/projects/main.coffee diff --git a/app/coffee/modules/projects/main.coffee b/app/coffee/modules/projects/main.coffee deleted file mode 100644 index 69532457..00000000 --- a/app/coffee/modules/projects/main.coffee +++ /dev/null @@ -1,138 +0,0 @@ -### -# 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/attachments.coffee -### - -taiga = @.taiga -module = angular.module("taigaProject") -bindOnce = @.taiga.bindOnce - -class ProjectsController extends taiga.Controller - @.$inject = [ - "$scope", - "$q", - "$tgResources", - "$rootScope", - "$tgNavUrls", - "$tgAuth", - "$tgLocation", - "$appTitle", - "$projectUrl", - "$tgConfig", - "tgLoader" - ] - - constructor: (@scope, @q, @rs, @rootscope, @navUrls, @auth, @location, @appTitle, @projectUrl, @config - tgLoader) -> - @appTitle.set("Projects") - - if !@auth.isAuthenticated() - @location.path(@navUrls.resolve("login")) - - @.user = @auth.getUser() - - @.projects = [] - promise = @.loadInitialData() - - promise.then () => - @scope.$emit("projects:loaded", @.projects) - - promise.then null, @.onInitialDataError.bind(@) - - # Finally - promise.finally tgLoader.pageLoaded - - loadInitialData: -> - return @rs.projects.listByMember(@rootscope.user?.id).then (projects) => - @.projects = {'recents': projects.slice(0, 8), 'all': projects} - for project in projects - project.url = @projectUrl.get(project) - - return projects - - logout: -> - @auth.logout() - @location.path(@navUrls.resolve("login")) - - isFeedbackEnabled: -> - return @config.get("feedbackEnabled") - - sendFeedback: -> - @rootscope.$broadcast("feedback:show") - -module.controller("ProjectsController", ProjectsController) - - -class ProjectController extends taiga.Controller - @.$inject = [ - "$scope", - "$tgResources", - "$tgRepo", - "$routeParams", - "$q", - "$rootScope", - "$appTitle", - "$tgLocation", - "$tgNavUrls" - ] - - constructor: (@scope, @rs, @repo, @params, @q, @rootscope, @appTitle, @location, @navUrls) -> - promise = @.loadInitialData() - - promise.then () => - @appTitle.set(@scope.project.name) - @scope.$emit("regenerate:project-pagination") - - promise.then null, @.onInitialDataError.bind(@) - - loadInitialData: -> - promise = @.loadProject() - promise.then(=> @.loadProjectStats()) - return promise - - loadProject: -> - return @rs.projects.getBySlug(@params.pslug).then (project) => - @scope.projectId = project.id - @scope.project = project - @scope.$emit("project:loaded", @scope.project) - return project - - loadProjectStats: -> - return @rs.projects.stats(@scope.projectId).then (stats) => - @scope.stats = stats - return stats - -module.controller("ProjectController", ProjectController) - -ProjectsListDirective = ($compile, $template) -> - template = $template.get('project/project-list.html', true) - - link = ($scope, $el, $attrs, $ctrls) -> - render = (projects) -> - $el.html($compile(template({projects: projects}))($scope)) - $scope.$emit("regenerate:project-pagination") - - $scope.$on "projects:loaded", (ctx, projects) -> - render(projects.all) if projects.all? - - return { - link: link - } - -module.directive("tgProjectsList", ["$compile", "$tgTemplate", ProjectsListDirective]) From 6dc052f536d4f18aeb1fec9c6886a70709aa8e04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 7 May 2015 16:53:41 +0200 Subject: [PATCH 140/366] MInor fixes in timeline styles --- .../profile-timeline-item-title.service.coffee | 3 ++- .../profile/profile-timeline/profile-timeline.scss | 11 +++++++++-- app/modules/profile/profile.scss | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.coffee b/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.coffee index 09da8a2f..be1ecdbb 100644 --- a/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.coffee +++ b/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.coffee @@ -22,7 +22,8 @@ class ProfileTimelineItemTitle user = timeline.data.user title_attr = @translate.instant('COMMON.SEE_USER_PROFILE', {username: user.username}) url = 'user-profile:username=vm.activity.user.username' - return @._getLink(url, user.username, title_attr) + console.log(user) + return @._getLink(url, user.name, title_attr) else if param == 'field_name' field_name = Object.keys(timeline.data.values_diff)[0] diff --git a/app/modules/profile/profile-timeline/profile-timeline.scss b/app/modules/profile/profile-timeline/profile-timeline.scss index 8dc6f266..36477703 100644 --- a/app/modules/profile/profile-timeline/profile-timeline.scss +++ b/app/modules/profile/profile-timeline/profile-timeline.scss @@ -29,8 +29,15 @@ } p { margin-bottom: .5rem; - a { - color: $green-taiga; + } + a { + color: $green-taiga; + &:first-child { + @extend %bold; + color: $gray; + } + &:hover { + color: $fresh-taiga; } } } diff --git a/app/modules/profile/profile.scss b/app/modules/profile/profile.scss index 9ff15544..04029023 100644 --- a/app/modules/profile/profile.scss +++ b/app/modules/profile/profile.scss @@ -13,7 +13,7 @@ .timeline-wrapper { background: lighten($whitish, 10%); flex-basis: 768px; - margin-right: 1rem; + margin-right: 3.5rem; > div { opacity: 1; padding-top: 0; From 576d3ef5d6764fb2faf5c847bef13d2cefdb8123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 12 May 2015 08:14:39 +0200 Subject: [PATCH 141/366] Minor fix in navigation bar --- app/partials/project/project-menu.jade | 2 +- app/styles/modules/common/nav.scss | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/partials/project/project-menu.jade b/app/partials/project/project-menu.jade index d9f341da..75e3fbca 100644 --- a/app/partials/project/project-menu.jade +++ b/app/partials/project/project-menu.jade @@ -36,7 +36,7 @@ div(class="menu-container") li(id="nav-video") a(href!="<%- project.videoconferenceUrl %>" target="_blank" title="{{'PROJECT.SECTION.MEETUP' | translate}}", tabindex="1") span(class="icon icon-video") - span(translate="PROJECT.SECTION.MEETUP") + span.helper(translate="PROJECT.SECTION.MEETUP") <% } %> <% if (project.i_am_owner) { %> li(id="nav-admin") diff --git a/app/styles/modules/common/nav.scss b/app/styles/modules/common/nav.scss index f02c3d98..db1da2e4 100644 --- a/app/styles/modules/common/nav.scss +++ b/app/styles/modules/common/nav.scss @@ -36,6 +36,7 @@ $label-arrow-wh: 12px; position: absolute; top: calc(50% - 1rem); transition: all .2s; + white-space: nowrap; z-index: 99; &:after { background: rgba($blackish, 1); From f005fe0ca80da63a9108ff59c5211b2d54ac26bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 12 May 2015 08:53:49 +0200 Subject: [PATCH 142/366] Profile bar minor fixes --- app/modules/profile/profile-bar/profile-bar.jade | 4 ++-- app/modules/profile/styles/profile-bar.scss | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/modules/profile/profile-bar/profile-bar.jade b/app/modules/profile/profile-bar/profile-bar.jade index 40311993..640d3fb7 100644 --- a/app/modules/profile/profile-bar/profile-bar.jade +++ b/app/modules/profile/profile-bar/profile-bar.jade @@ -36,5 +36,5 @@ section.profile-bar // div.organization // div.organization - div.profile-quote - span "Small minds talk about people, average minds discuss events, great minds discuss ideas" + div.profile-quote(ng-if="vm.user.bio") + span {{::vm.user.bio}} diff --git a/app/modules/profile/styles/profile-bar.scss b/app/modules/profile/styles/profile-bar.scss index 80ce4f43..a0d3545f 100644 --- a/app/modules/profile/styles/profile-bar.scss +++ b/app/modules/profile/styles/profile-bar.scss @@ -1,10 +1,14 @@ .profile-bar { .profile-image-wrapper { height: 200px; - margin-bottom: .25rem; + margin-bottom: 1rem; overflow: hidden; position: relative; &:hover { + img { + filter: brightness(40%) saturate(150%) hue-rotate(60deg); + transition: all .2s cubic-bezier(.01, .7, 1, 1); + } .edit-profile { opacity: 1; transition: opacity .2s cubic-bezier(.01, .7, 1, 1); @@ -16,7 +20,6 @@ } .edit-profile { @extend %large; - background: rgba($black, .6); bottom: 0; left: 0; opacity: 0; @@ -80,17 +83,18 @@ h2 { @extend %bold; @extend %small; - margin-bottom: .5rem; } h1 { @extend %xlarge; line-height: 1; + margin-bottom: 0; text-transform: none; } h2 { @extend %large; color: $gray-light; line-height: 1.2; + margin-bottom: 1rem; } .location { color: $gray-light; @@ -110,7 +114,7 @@ color: $gray; display: flex; justify-content: space-between; - margin-bottom: 1rem; + margin-bottom: 1.25rem; padding: 1rem .5rem; .stat { padding: 0 .2rem; From 7be8c1e65808cd76832126b0246745d12ba47bb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 12 May 2015 11:50:20 +0200 Subject: [PATCH 143/366] Font fixes in sidebar. Added light option --- app/fonts/OpenSans-Light.eot | Bin 0 -> 33610 bytes app/fonts/OpenSans-Light.svg | 581 +++++++++++++++ app/fonts/OpenSans-Light.ttf | Bin 0 -> 33412 bytes app/fonts/opensans-light.ttf | 772 ++++++++++++++++++++ app/modules/profile/styles/profile-bar.scss | 6 +- app/styles/core/typography.scss | 2 +- app/styles/dependencies/helpers.scss | 1 + 7 files changed, 1358 insertions(+), 4 deletions(-) create mode 100644 app/fonts/OpenSans-Light.eot create mode 100644 app/fonts/OpenSans-Light.svg create mode 100644 app/fonts/OpenSans-Light.ttf create mode 100644 app/fonts/opensans-light.ttf diff --git a/app/fonts/OpenSans-Light.eot b/app/fonts/OpenSans-Light.eot new file mode 100644 index 0000000000000000000000000000000000000000..725db50b78876bcff3eb0b042450b9dabc7cf0b3 GIT binary patch literal 33610 zcmb5W34B!5^*Dacz4yI0`^=j?nItolVG=?TvJ4@FEdwMWge1TaFcY>UY(e%-L`6_U zSwutyH$+5=N)ZR75s{+QUBqauwXUG8TN|~ttqUYC|Id9hS@3V$?{AVf@6CP7z4x4R z&wgIjMu3}c1VA9bKSWPODlkGALJsnl>WSYHxG>EJ;r{9s01qPg_~F=sH@794`h3!0 z0xW~MumpTC8Jb`TtOOra!vdHOEwBmzOb~&&FdtUKB4~mY0N{qHFc((9N>~6()jJDd zAQV78!2jWSz=scDK>!9&QBhYj{nWQ-0B&pnfRwRiAID2u^vN1!y{Qy9?2Y{r^zG{`v)nWS@z;XZpj?7y&fARHAtulagoNx1+RxSfK zXaQ~+Vegu;l~HJ=HR|Y0i&D!&U&G0RRTKw9IWXJSSZNfMo#CuVwM7 ztG{wK4Fu0CNif4pm`+N%w7gvtaBj^YDLzSEFL*)qgBJ z&JXy$W3>XBL()nB&?FGA!cf}V*#@BXbgb^$pgE*Isqd#_=Kx7Na2NtP1VHQn*bWEC+ptN(&#S>6Q?u6<11?(p$A&KpTm24-x%yzV03pe*6Ne`EmC~XF`eOg5~TSfWV-Q{RlS*2ceB!32lr)oA4^!Ae@0V!k`T=c2zQd z&I4`Y7tkiW2yJ32+`x_`_Gh4tO^2nd0Cd6`IL-C~NC613S^!o8!1h3dY=`~b=blDa0A4}`Gi;tf_L#0a&4+eR3CRoMM&?fALR!uXsiLXMdO4skg8i0<~oVEafCbpC~ zH$kV$Cje4_!9@Tl1RpT4!u8OFSvVdiV-v2ztMPiggSnhyCeYT`EV|hgn#K5o}cNS=V#V9qfS5=5zZ}d_MJ_lK4GcfA0FI>v-2)oQHHR z>S6%O?>isvysvXw$Hb2Cg{C;OyAZxG6ySX8`6+!k-~h-`&kl%R)ALD#bjVPD#lHa- z!!@uFu7)LWEi8p)FdLS`3RU2>z)DyIYXMM2fC}ds0%BW~{ zdF|D+ufOqT+goq{{@p+R>Am^e*KfFK<(;Rt-MnV!CV2Qaa2vplcc??WD?^r&#@$PvSb4J{c`Jh-SJ zKW|`ePXEk|V81}R-|M!^7PHBy*J(A9C@@M8a(pptERWHkPp)VRln0tBb8~#I1&m_Od|K7yDX0u2GL!hWf);7Kr-&xj8;{RD86)@>s=HEnCYQ z%W`w@s9rZFFlMeUHwTXD^kV{J^tm|@>mOKl6#I`v^?>v*A9|F4#>l&k(O`MgoY=%k z_2p$AzdxFrGcINdl&Pm+jQYr!I3^~kkMu3zLx*j?qdCuQz4e3znj5nXa{_akrq{=4 zQ#3bcD=pu;^_G~N9m@)o#j>vXi#s=`d~Pf!P*xtx=1(0T>FH;DZ%Sp} zje)P*b1!RYO07lVoQF>E@~b#j|0R&i@IW@&7}q0z+DD5r*d69Di)Xr26=8A2fT8K{ziv8Y7MT%yZpm98>t2 z4c%vYZf^|ub92T|uHPDC!Eti}THa88MOH)%S)N%iyXN&>!;JpP>WKzU>0|Ed;u zY(ujzHz!t^9Vdjk`dGNkR~`#DC5WW_XntOKpuDLuhK&n2UMJPZ@&e0Z_Q2>k6^);y z4y$~@A> zN7eE3bxvX|=u31Q6M`DPU@Q4qeu@TtF&5OsNN@~q;~?~vyE)PO>H~8F(LjqY7M@to zLBzqMLOB5p73K+C)m`?h53q7`V&ETN7l&K|n6ZlN9vG4_b@j2a>hGS@l|MT@uKToa zt0pjh@>Y(CfQkyvnMiP43?|q6V&NgOhd%`Qf(}$P1$-7?MSMYTJsJ-4#oRJ9H^;X% zFmBG)z~uVj>eb^T_3J#>@V>1u9>>>>&doVWVD!-dZk}{BjGHGn)IV(j@NKTEKSq#@ zX&fCr+7CBRs(;!C5LR0WZ{>~r5+DD;pA@M-Ml|a6o~OeA8`QH*ZBT#DK7pXN#V>j~ z3<$GNka(L#z3ym++EEw?%s#>5XTsf2U@-fHCf>F|ZBsu-fkQN`6T+IXHf$h9;yH@E z>6j4KoJ0UE!jlFxqUY!aGA5!npTG@AwP8>EiVdK}aD0TDr}TC`rJ?>w0~pbxen+Fx z(fpH}Q|@jFjOSvd+&704%5~9}t&LH>*uWVh!59f*3NgC>6(5)O#x{ZuE|= zxXY`r@KliEXDTodN}rMv@=S4t98+X8PqBo|Q_SWGX5uxkH$P}@Gc&V!hq=p4MS?H| zLoj72tcNGyTVNKz4NesB1m1qMZgO_^_!ClBWPD6Jaas&F$AXjjcX(1mOxzrUDGk%= zk0Ra{-L!o>j7}XNE1X;(%TJAtkIk7}AM>Rib;9WA%9YvKGgq!!oy|Y1X0FUuf2zO! z{~z~E0MQOEs1;hl1*?TtNa|Jg_=lB(1MUOpYUjUtzZHAecL4nQj=Q>^QFe4)P#)`Q zXS2X{`ThUbPuv#(4KNd~fC$WlCYT0MXoLt%g%=?d!K4myxF)9dMOdW^owtZXQ|o_#9x7gh=Hi$uIe{EL(()l1h)$D}`L25DZLo6>fbl?Gc+4c8?DB9#;wNF#&1j&rVXYercceHxu3bh zyv)4We8^(3G+XvqzL7`EH(7nw>DJBGS8U9dVq0Z9V*9(j%)Zf-j(JW?7Go)+CADm&)w?&(A}OiAnDemQ_1zo2a;d$6nbWQHhA`V{+MD&DNLzK zX-?Uf@?GlC)S0P&NwcJ-r=_Pw(vEmV?=tV}-hcV7_8s*1^RM#9{O|ieO<$6JbNcD@ z4+HMNqQGr|Klao0v-hj$x2WHNejf*&!KuL`8HS9sjQotz8Fd-+G7e>GGVPfenL{$G zGN)%Q$=r~6N9KY4q`$p?M*kuGtNKsxzoh@(EK`;*t1zo9Yid?Y){z0p19AooA5cAD z`hX<^Ude9EUX*=Z_O01_vX5lHmi>8-CMPH7o}5Q>PUO6j^QW9Ib1vpi%srX=TJHO~ zf6eU}Xc*W$aMi#A1Kab4 zBp7%UA#eCGiB0*s@Te$!IQ$qTQ@$>Qqm(xby!n_UPVE?e40(G<_RB%P>@Ooer61m- zvCr*m$Y*3mF?ow+>o@~VuITVsZ z1^F1DAv!2j=&%RU(LN$|&Gw0r$V6PrMqIq7xw#n#Y@!DVUp;)h-mp<(KpE9)L?TfN zrJ-|${And}{An~m{WL(4`A7KiRsRUT(AxQTV&NMfV=(~xm(U6x@WR94SsHJOgW0vR z!JxI8O?sxY+3e2LG>M5WhLFVEf*{&Oou4MVs9Wdu`8CX7G(@5)X!L?bwnU;y4!P7g z!MNVoW@M%E1bK%{jj~Y|TsEo`ATLxJvX+$O<;|EOheFvh@Hr>;>Y!aA*{Y7+>MBvU z5dSVLjPDEbk@_8e8nCHbk>AF*5LGt^`fYytay8~D&rNNpSMHfwKUMh*v#XVNv3TmV zC@!v_T92t6FW|yjWf$G2teBvTz~}ik0dJd#FDXO$Hc?rDx2cx;fvzp|yl4d}*qQG-#xvXsn5fC%T?{(wS7r_hFaOQkj$L zNUe#w9Y!(R;m8(^%q6%WFVE^K$;->jlhx^GXLDf0kNI5Dr6N!cSzUbAc2RN!iUwk4 zQ5uHifuv~A;Nqf?!|9STGUYUs24!T*BN6RRmuy0M{-q}-O@A*u{;lZFTf@KIH|fRu z{M(Pdvv0}uTdvvOd1A%&Yu4h4O^-U;za&{{^ZONIM^63KQ?Gl>{@iJ8X~RgJl016P zHB*-MSF()Pk!Pna$-oyaSAzh{yS@^ca29mn1Rvx;K8z1%=I0K~OYyrh1wF`m*`Jx4 zCl(f@4H%G5O{TnjG0&~BOUeC`pfpq(D&?delKIvk_4ROm#t+48eiEe7!IhQ>3qXErwAN4vLgy8HH9x7~wl8XC`>;VpM<-@N3uRpZ@J1AD;i~KRP~q`e|H&%bz;=>EHhO{wH7lon~IFQeCS+ z;o7ct;TGW>_#hjKVNy6Z$!&KO8gry90LkcZ3nfEb$#Oqt59Uz|v+I(qx}xf+j-~ml zqiKmX9beb35|xa5keQh2puxqN12H~7hh3C#Bo>E62?LoX5=hSw3I`XXi8$;|YInLu zVv!1zYwvt@{OItr58eNpTc5%gMojfJmfnH7Th2_s{nv}%`W9c_@XmZCb@STdll>8;E9c@oiZ8(gJBf1=E*=pg$|?N2&y92?57=~9ZcW}o2kH1GU~K?t=Xb60hGR=(kP#9&?VR; zJ!aa1#R4U3g1F0}tp4G;XOFM@PPxX0J2OSQvan@irgC^JUaM>xi*=bBTaY_;5SGG4 z7GOs}4@1HMEifhsMgx^JQX~pyO`fJyGfT5xvqSTQ=2^{ojaCDBGgN*M2e!3@55Vv6 z%K_P6CpyN+M z3kjqX*^HxC{PExZ_7FL*tXFQsb$AtCkIR&e z$`0kQ@(fPIaae{El^7tr%p)yAE9k%%Hfc40XwXYqbTh5US(~*aKKoFpAm1O52Nj!8 z$ixsi)NC;Oln+X9`&5>-bB{UYv$2@P;jx^1j)k+}hQe^NWOWf@w7OYx5^m=w-tt`x9I0Ru<|7aNd8 zU2eClHfnaGp1R$-j0|s0G(*x^YNEPcfJ2EpZ`RBi*>c|@${}mF)Jj8#o%z#M!Bsf8 zXi!Er7R3(&>7tZ55<`Vf`u4AHkDXAyQZAl9SohZSk3RnMhkN(6zVrC4i;piWnDX~) zU;B{F+(nkaziawXSBU2#?6ayWAO?Zz<{GKZZ7(ldG?7f6>fZCw2sOp^YJM&--z7r!ui z(WA4T`|{J@-FVyZq2#&Fy}5hn(obLdt7m%$o^?sp3 zmm?#P4m6}9)nO-rbP!aW2hvUGu&3e1`!V&^nYH8Q|Db#=8>?SfeC~^XzVWfrh35~n z9>0Iy6D_kJBTF$AH=T0Tdz3xO;pRDiedB`CiIXqA@$93g8g|t#J$JRrhZX}cuh0r2 zWQQFhpa4Xm8VTG2p_J4{si<-!C~pH!Uu^Y*>Z?dg2yqxOSG z9=hquXTN*q(F3nMaN)##ICuN6OV`}`@NJI?ZKwAyJ3MZ}shfZEr`OJGe5s=Pv8xt5 zIQ7!KTkm;j>vi`}8GD#++qSl1;T1zm?^$^Dtz7S4+SSes!Z}EW{+t$qMkA!zL9){U zStge(`zJ&}UM3SAm1X8~d1|6AiRmUp*?*X5H;7dO#zI&ivEacLFrli;Swn?Rt2#Aj zsF3S41Cbf-R?eSmsn1^P`vKh+!?@R$oc;4pFW>&B)kw{ zd8)~bb8%|p{0nbpXZ5Brcm+E=V9lNjTl0VGO`#)tM(n z&AL3@1l>~Idfg74Xrj6vp#|OCi`!qSxa}5SvMRT41G;mjLV!YQ`y7jsTbk3%pcPypHd3SVmB%-t=^oGmEi~8eEFqT6RJrIqW7PnMf-3SmMF)bEj2~ zd}Q;4trLSwc1?Kd_x(mq9}+J2u8dpf!E5(KCycndcKz-^=EQv8i-*hRT`?(daJ0F} zq`af*Km?X|4HFLvhoK0qJ6M2eIOXE_WlFK(W4~YNQ))gRZ>Fpbz$8G z9o6XygV||?+^)|HSxfR}aD$m!P^!ie=WD9tKwWv9fFTT^pJrr=65Y{xqb%Si!`kFd@T;nc-4(n!oBmo(M_g*E`TQ)mSf1$T86Xdgwbo1WOgw_)$*FWYKlOMbLwfAZEmOJja zsdIJb7OcWtK)9i+ot+TQf)g^rc51R~Ow{GJLUq*2^kP+1-@6DB%m+CEfWI(73xibo zEoIPB@Y;A0NxwCoEO=DQ_q*D+lu$LUZm}OsXEH6Js|3JPFWxeWxo%odJysnLFIa7tFj-r z;Y?h-R5_!ZRsQ)M*+ITiZdYy*T9rGL2XH8kxTtU)x($GSBea4Z%ELiXr$YvyL25LJ z+N!8oL_HNnBt)o+5{kO2DE1DaS8MHALtL$vxv8aY^qY=PsJC-0xubIld0J?l+4=6w z&h2sij`z!LT`dd``+52TlaT84@m@`f_%gJ4x zi+E3W&5HMPLsz?yrON1`VXsYc6Jku3m?s62$43E}UT>+6>KSuZM_Dg=`OGfUBmJ`I z_dySW)rHUTF8is8c>bXLpd3_oJcUy}`u23z>DQI>%Hud1hrG63sUe?q&TYV3aNc>0 zo~oZ-uiUS^rL-zfmJpt!p~9bir^3HDoT6b&&?15W2BU^nMe{VM(Fi(`qM)u+m3j3V zXmKOWACS3e%e|-}_TBuBL-Xd*sq;x&XMxb#xrS`%fj1VZwxiq%Oh5|VR%1ave^A>z zZY!$Sc2z1n)NA$J4?%U3#76@{RTR?W^p~*H3i4@sW|1>gn1mtO;-5aOcIpI(> zpEqwm8F6FhW0W24d{k)d)a)lyJ9qb?XF(l1023voK*vY@tZ}?KM@NZPV98^B{ zd;ce>-dENSRt{hl`7Z!d$IWH#okA-Z!3mM@K%LQ`w@M~NLL|MBxm`LiL3Pw@@|sFb z6HH7?O(tp}R_d&da?c`P6`ZrEtQCGv&IS3%4S63^ovZ;2#szLRV$nRDjuvG1@5Sxc zD7PrP(Dn4|uYCF)S=YHyXnpmyJ73G`oJZEx|LUPf?ooN&(yn&;y>J%#!Jx1w)v5<@ zSp^o%(5F&|Be^>2U{q7pm*_yH@NUBs?JVF^`ORQ%`KaDsZZJz+>r$<~G(x{u{&`;c z*N(maKK^#a-g__n8u7I^eiLjxxc|9fH$VLA!xM1rQ-2&bZ_KZ5p1inWP|cIO4z$(Q zSMR=IQp@C_!iJ5L zr7A&TY9r?-r@Pt(BD8`G!LV#Wog|^nDr+b+_DUNL?w&Z4YzKcjaBdn`IAwt-A1ap= z<1(02BT~{dWkUbW z=%8yl#Mq+B{Y*`%RI3aakL~H>@xBqEfjW+m%r(aU1SjtPCTEI;WD0@Tapq6YtK+@Q zPfZXg#b5GM+c1FZNnn2G$K7sfKnN+P@O<|eP^2J&Rs($Lsn%PcF}VDyJKrC|Dy0<< zD|MfVBWC_20zy40U?l>#?K8tx!uu8hD0giM!1!d&752hL71%bu8njrCZFQ9vr?p*`2_L^e7{SR=!hQVQ!^zF1B0sI|I6@=J_Ypwc!rr4A z(GOa1hqWSOpx22T5b~1M5RmG+zyOa$hSJbQHRX*@`)^C$S;5|$@N}M>f2x|xqvo!5 zdMArO67+*f;T$xirY5CpG)Xp^5hUO+q>YQ34PFD$Q-i?)4u_>O>aYmO6;Z*NuoL2e z@n7&Zaw_k!ar>3V!(&q14=x7_2aiCBdmEj!DoGQ0{HlG&Pdt4lnwp5E|br~LgNfBGEXjnn^{u6DYK)q#WCefEm|IYv_rsST{!}zQ6o=0m z?n4yqU;{6Qk3LltwJurK`hCn`0ts{uuygpBk}9L7?g4=NXYh$z?1fI3s`kl&%zzjV z+_=(3hsDqFIVgjaU3Iuj6q`pSX|DV7lW*ETIrb#ZKXLdmRyVcf`n7n=PwfX!q8kmE zi~|}jexnrr@q4-BBOoeVzF~D>2M>%5XC`UgdOf9DnWm<=^_5Yx+v_Ggx&fl-sN}#h zT?VXQv=h5QXH7+a8rRf{9VVpdqU0ZxK^A|l{DAP@b02s7gPePI#`ZfV;LWU*EzdoOMp*=afp?@l~v_1dZcK{(3fV?Am!2u(}UZdSE5kmR8cR8s;Z>fyx zIVZN;u`+5m$A!2Ya(5dAT+2|iCiq&G19G6KSPd@6)r(a0ld3M5{n~4HKGM+OnmA!q z69WCJ!$rRu8mxQe8PC+DE%(g@3b~4%iL4H?AOxkbAw1UL9m)pv8)zGlgv^%Y?I(^3 zvrH+a(CpVrQt`MbNR86%5;amua%Pze$41R&XIAEjanVdm78w`KGCOm|MV+4B1)VU2 z``W`i1BA=>zFaMCI-x`2?vg-yrn;Pmsm^oeR}%wNx4HT?I8^9DyWc1C$dXS@BH(`X zK)T4pPa58eRizCZIbp`>qo~ z+;#JfyI5(tY1z#WJTH3d@&@Jaxb^Ted$%vU%j2jiTs%4Rkwp)`=(wc4fO2y^dHVWo zTQ_dowhIti0hmd&gB?QQB(NI|8jaSjbvle%S!NYc*`fo`I=H~@k!^9cDQ@`0a~@Qi zOO|--#FvEh@YbEr|7P}VK^k##E3P1C4(xjIqs~V~yCQFEu2)uemH>fA0g(F#C>Xi6 z$2~5HW>cd94I(3EbUA4x46D4ws`~g|bEsDbxlV;CeUg`}5yrrxZ2R0TC%pC+k zy7nq|^|=Ocay!PVGjc=P?E(jysUvJ=8Z!tLQAi-RH2K`DnccmpQ?kWP>mttRMB5-W zKq&^&gAXciJcjpqtoX2_@-P`Bw#(M7VaR_gGrHOlN`vu8{A5ouQPJ9m=cer26# z@A#MUPNH8-674|28P)=#zzC>7j6+aWmO~t9qP=^Z;yhLd=};N&m#oo3x<+faSY)l7 zPWuHscG+z;gI0DhgWFIMO>)Cg<2}2v|BLr2uz;J0fF=kQpDvGBm`u9 zNtYCV6l!o-NY4zo z;w}Y(6vhP3q;-7zb0`gPpQ@{>SDMA_`(w&^ zrCs@#@?QOq=kfeo?Y^$}b4V z3W?MQSExAQ#1S(K2g`9NI_yqT^2he}58GFZ*1Bd^hw^<{nmOnkBa%=QR7m)lGm<*VRtzSsetpy{(5- zD+pE>3-*iK+Z9oVg=#J%o}b&>TyAjxo*GnD6XyEpI^5c>|M>OyHymhv=#fqLJbcrb zb0=bZgZs8@+Osxx=gni#bH@W?7Dg6EBg+O4o3gOxn%hPY2Gb<`*-oE9bhYCYjs+_4WvrKf~_bYOGXzusQPg6T5u8YUO4|TOO!b-sk z`C*qql5{A`I=juN*I3PZhq|T{iz{Khb<;8TizKY{xWdJkGa9z;{M}Q{byo38Jg*68EIdCiEKtm4Mtgetcd7f~nu9K0Zc;4p=Qp5qXKA)!~ z4V$>Ix};+0m{4u$WCJVh+^c-id;(O}0^Oa9ys{Cw^a0NTJxf7!mTj0$Ge zzORgOIr=~Cd)mh`igmhHNXPkJguV)W_#LLODgY39XO{#o^>+os~LG01}mS z)SH&1)9LjxWmdT-Oxo)*if0pZgU9Z4Irvf=lvy+qi)2gCVM2$@Gi%e(ft%Lsc>Y(P zU$Y2Xwq94?+!Xc9n@gwd-h-3Qv5BO4HqOLsu|c@9MQOWngF|Cqf2}$$>`+8fFDAux z212R80BA}gsZBMmLvjE+rcW1>e)t*?DwR`Yx6le{d>+)U_n0+lX)e3VLw#Omx7%z6 zPqN-i^$%8U7i?~9-=Oyxq1o*tLHrmt_y>{D?lZ)4W{;TJ=uf@!3*V{Gr z4I8mw{z9SQ{@u#{IZb3DJEuHx|J~Eb-1#aEFC{H3Ku3TBa>8yCH#@8*y>$aD71xUo zic}11jg=zgWzPsDpDTR9!jmJ_+{AQID&iSvF3xJX9TZ5<%)r`$tA;FEH)7G;>t_yG zI5L0vxK;OEN4^>{Y~~VA&d?Fz#)5EVNgS_LUG1!1I0pmZhVVoqmC}=>G(m6mr1|%e%`x3Ijr*}dnQDUrOE4)pGc}8AZi`qL3B% zT%`;eIP#Y){9Yl9;Tfeo?QbXU+BRw1*uN-W4LE+7vBM`a(f5A+9pf`^5>{XOHqzoA z<&3=d&rvhelem={$rwK`f)*@H??08lh@-0u%9NjY{-!L!GRV9%1VTqa;(6q$sg>&9 zW{dAlrug1qi0`%9N(p4O%%+I1$J2_PekYuTG$;%AGZULF6*O{Es^IgcSh!#H-R>|jCJ;d~*aVkTCLP;g)Ic8R1wGO%?&so}>`s>> z!1FEwm_2e^R&&{rifd*sT3*4v8gp#S)z4YxUb$ih07$%^Ky&D_dV--)0Zrl1;9&)$ zcufJXE$FQ)7&b~%Id(`|TG{2r1%r~4N2>J&JfX8si2;{&eI;!?!pT|k|7)dz_LplA z{vYZONN#Uo!Y%jQdFz&Y{;w4Y9nf2-@Q)8a`SS1a+&%iev=R(Zz~7%B1u`;=LOd7} zvi)DeOw$fkHdi04Sg~-?@{0TaN61NPK6UMer{tN-SIoF5{`cq;*9F$$i}XNE_ zG0B3r<1Czp+vRwgh{?*nvnWTESCki(BV5jrcpOBga=p zAGco?_eiB@(4fX{Tw$%6aiIksZ|HT^+cF2M1s775!8bd5R9d;7p0O`O=d`)XD_pt4 zo@YqvGbD*807?`}c^YBD-Onos23v~})cnM-EVSB{AO6&cffYdb{e;JOG$2EI*rJnB zBWO%AvsppQj9i!L!zK#yv3o%N?gN}>NN*l6I&HLBsqHh!tI;&AS$TV2?`V7blEA|I z(pperlF`DfvXNQ%phGY9vZR9i?t%MV;?HJ)$Bg}&`}00~=VITWe1AnV=FW@jCSQMPstfKdDS(Xwsqt)!KKE%+}vuD?RJ}6V=)^HMnH<9(P9&mlO#&*79%g5 zK&Ncdsxh%pDD=XN!otGBpY!U(>g0L&MSe#BX~4xD2Oc#er8q#bWa@iF_W`N@NMw<9 zA6-~Cl}UdlpOKBVwVmro)2m7gd8>0jnc10#x3zUX3?%MP5dC1_FH-v%&+OJ`{*$YK z{Lp7zHvJd3foSRFmKXn<2LYiH*5Sz>UI{>W4lm9UP`Hd&YBAGBixE#MXGbW?2rMF^ zvL+j^#D&?)T3)NRrmJ1@vO2XMF9h?$#TJ^G?{Z}pIQ=@EGc}VH4YE+tnD45Ln)AK+ zL{IbcMPu#&xHfeRuaOB}>r%+{c|FdGVMUCAs+L7mpulJ$d-}^KAe9N6cjV z0sAc*H*D>8>fO2xT|98|`R8ijgcM!4^zY9v{Pe?5Z~pGL@AU9k+Mwe2zwlT?A6(Y< zH8=Yp@2B&bh3BxFE#Q4tV0sBFPXR;T*nd95$u zMRR5ofJoCt{Ujb)Qhjxo*ZSH*wm@(8_q!uf*VyBU-{j>>zY`Yp$@zY}tDT+`&O$28 z3J=prDJiMc;7VoQG_AQNDuOnqeNOwPmRYG*n<|0FXfHa&uckX1wwDETD&GVl*`|9zH zo0mLbh@4Kt^uK+H+5RI3i|a!FZvW(`q0@)F4ha0sZ3V)s{MCiw0Zfx(1~3R3gVB*D z`n+H?87D+jOeV=@bJs*|l7>kVtBp#$?x{~Qfcxi$iE3DL)7|gztHykwC?u<)pm;)! z?04Xvidvf0k-FsCx8Hu@sVD9aJg{~97QCMv=z>-I@*C?M$8JFq!K~LmE}J@f|6%2A zUf)JwJ?fZ+?FA2HLS5M7g`f$Dpf#8*HdnGPtH0YCHrOkz4yNPPy>5fOQfITck_}pc zXi}u~rH&nr^A76ZWqf}qbd|i6U1)hJ`!b1Smt0aNXE{=4aYoUg;>=ZNEWKs8`L>j9g^E6T5Z+| zQJ`j<*G9C|W-~Z+CbMZmR9bJ|VSdm|&1M5SM1wn?1kzKV80Qf2;_ZZ@tr|)^EyV9o z|Ke4PMgAf`hDa4p5}Amq)ubF*pge(-lqb-sL{^jgXi7(W{2usZ}fhprSs>lNQv3ONUbjE%M}2zgBA=7uAA4b6nKVy;oeBRL#O3tE=0j zP_^1Y@t~yYW>y7jJgo~I_MUp5Zilp*09M>x<1-L5O}KMz{nXmCN1k{nFzuF_n|Fk+ z-tgS*C#O#R^rh*0r?&3if2eT%`d4nBTY6V4QXjr|-=x_zck;H3yd{%XZwgOLPmYcs zTs|^8yk*|wa|bMn+_Ez*IIDK{wBk{t2aemad{k{^Y1WGH$ca^ju4Npq69pHo7M@r2 zwY0E7YZAZ&HmiYJB*^>O>jU_F_?rlV-3K_)>kEfVahI)SLBQ{kQrqJ(KmaKD{uR4M?|j zr#0o}&5%PgE;Hd&2aQ_Jz;o6U_2Dil1J&GhW_8$k-BCYMY`3ydVLxk8qUM3#aN9$> z?x2GY}XI$JUTwNOG$HZPQh{xIVOp1Ih)b~r7m%5h?9ee+rza!_^G ztC=kXlIpISwQS!*v#QIkSnOZD;xEdaiV+P9>Q`Mub1Unb8|KW{u=UitBg24 zc;uYvVL=?B+92y_uBvkwAvf%#qRvQ|UaMtBk(tZ}L6b1mLND|(thln6Fxi4Xv)r%+ z2XnPVd9AMv=ef+BQ|pyX%tSgsfKb>+kfA8?r5uTxg*>5Dm?f+ib_h=h&kE-SEmwcC z!JTk0#`!$2eP*Ue$CIQ(vErTxm&u8`Wvd9h0G;*Fos^)w+2O%@6Vn(CV6n(51sY7U zGfgZWtT&qhtVY=^du1Y7!D_J**(zKA0|BZi>WifAMbFa^Ils5)fny6KUFo2FfV-Hhq$ zHmE##DWF;(Pr(dO%nQi48J?(iUrLWFwY>%J7>STeeQMr|NEKNvoCO!peh_6RAqLsS zlH6L8mpIWE&vWnb0jYUz#fdtvM7o&WM9@!GcRu&sx|uU-7A-DVbN6+(CJ&yh{2QMo zRkdHQy}f4azC%$T`@!Hh}(r(q=eicoP_|ChJzL%3G`k&1wjnC3xZ(O-% z&6d3jXQ?dk8HkTa&6reHDe~#R?Jl6gO}&=QO46Q z*#_<9d3@Yo=P;oZz%Ao;ByaEccuNKQYQod`@}R@~HO;_d=O$JPZYT|V)fl7G>2m3k zlWcB-(WuwB&3cE+V__9hOLweM4K~K}XS)NA34Kis$HqgBs`ZemhS4!;>+SKtW8o{S zXKx7!voDK1*32j_nO@5S=X^ZNl@1mW?O=x7umgb!An2)VK|!N22_{+FoPo60g!Bp8TnThO8GaAyZU;{C=_&%V>yulL5%a zxE`h0k!Q8aJU3Gh79)s8YO@+G7PE$W%#g=t-iM*9?``P~LuVF;BqwgUWo=n<+Q{0- znHi0vA5}h@Ggn`)8!~97=g8_HAXJkoJgMf(riG0JsFvz=8tM}vjYn3yL+UE?QHwJ~ zDHCT-Z#E>iL+`JmqE{YV%TZ{1NWghO>rq?({d?v`D#XP z3nyN@B3Qa#$3ph*+p*)mJv(oEKp}6vHD~eSIc)VqPab}Bf9%Ks z1`_fmE9}r~838I0omMoNHHe0UbsKsiuUEeCG-B1n72-P@P|k91+N1 zgJA%|GBtl(1_zHG2Lp~kkzcLd4;1nDFlb2t{XM`Emfg^x%*MMLZ}>nXdX+CRrQ@BM zG`lk|4#V&M@sHu8yz|^gH_b8RDOa(p_&X?i!OGv{1j4myEs_y}{Ec>fACCTCWH78C z-bYmePs@Ih-y~AkyHR-Y+h1Tm46*=(cU7M`pKr;3m@k(|iQKvW$XA4Se+G$~_$ZJ4 zXLj?8(0T1YvmhW)rL3kwRtIU&A4 zHM_)}*Bi@^YazU9-i&H8V5qPZU5U!_kyt#Ej7U`Ka{47D6Pg;vjXbbz-J)=C>8{!n zZ}qR)6fRH8>$a_f>mHmkVZ@pV*WEMFGa=vi(xHk)jZ2HBH8q(ryW8eY*!}0EmDUXC zr`r8vx?aHuXr3e%)$CXK1Xn)#Uc=w6u3qE*&(|z~)c7?iKflJ))7MqN;@8Ao41N0I zeu(&ZHi1|5Lr~&P_#xDnda!d+sw%&|{5{Ni zr>}J9K3)<@h>*^mkf5XkB!nb_f&$HH(`k~1Bpo}QaCo4OVHX)kqk`z7vJUEs;5dpm zD2k)t=y*Hhfvb+QUX1H~0C~N?s`t9n2}E}{{K%_LRaaG4ef9c#-}n9hpVzyN{#R~m zoi?p?&eUnGWbbKkvr>yU9QH2nA6PcMwRO6?we7^kV}P{uUJy|L@h64@OG3AMs+5#u zvl-21q6dY~5tG#}IvvKu7LUb95_qGLNLIehW3>`Vv>TG7q!y3F6LUOfQ-!T)c;6_- zmZa2b3R%q0Pf?tFK2sx@MW05*ia!lkC=>o3R5xYYc?lUXsRNS2o0EVv-wO`=c7zf`w>URdJ5pC#kCoVRF9o=<^B8yrWIY9KdevtO3R2?qKUwuC6z_S)2GX zI=0OanV`1G9lxIdo>tpT7~6*457e;+uv2Z5H?|Fx5w*=A>&Li{=O(g7RUlYPI3YvW z#e5hxaDt>aN{OZwhK+{*H1GzbmC(RlO5R`~DF&i5kU|5g;hM?KbxJ4MR;L7%)wN`+ z5~$@(WIKJ?SxadSYsq$UQyq8>hj|Q1$Ur_WaF->gWakKuOb0TJ5cNijU7B!4N_I}N zkeg<)>qUXLCQ5u_ZmTCz;M3w#lf*XpQ7z~E!zi@G_ns2D;L*h2w$mfGRAKc zz>wNTy>&AgRQsX(sg5f8l^(WJP^qg3MqsJD@r{p^J>tX4 zPcdLBVHld3QVuH7;La1}0;DGvyO1cm9Ab49ln^~$5TY~P{g_BFC7q|M>%-C`7gzPm1rQNINZ)jb(ZoygYb+z8Q zW$jmQJTYB(c<=t9NJPaqNxAj{${uRdVMb(tW{`Q1E3i?Q0NYQ7fr_<}FqFkwxLAu* z`j8NKh1Hjjsuz!FV@Md-%j!!;*B^@25553)`m0z28YClVbNCI@2g<0-jH?g)Zy;6N zkEhWR9{MGim3aRcb>X<}SCCgcowG#awF^*&8eHj~U6sX|QnH20%1I@9eNtBbB+9_Z ziB8j`I24)atjuoqSSoWWxdgtlQgoJEDXJ{Z?zGB&2loe+fxh>1u4-;u{i7*k@?)p0^wcd?Nu`mZ{M-!_{k#|Z@%%?348Yv z(H%VY=lRdE(vu8~96H zg*63*HOcI|(XL}AIFGd2jAGr0L>!;P8*VwlC{Pi~}U7uWk=aCm5f0<%!BM}wv z#O_PyF}WIuP`M!SawXIqGC$e_-g+H z|77*~qu$TtQmr2}s&c6cF1;`E2okH1FKlCSnS!P6v-N`AZU%4W(^8$*d7f9C1Wv2- zT<3L8!N5DEgn2AT&?$lPVAYcqWFxs&W5Y7g_@;`A3YFh$#d7LhRW%sqP-V1OF*30c zTCpI%Z}?61kyLvy-92v}+3fRGx?IEI=yNFZ@gEgxh0ZZLR;D8pju9OzCpjfsNXr%V z7CY6k64RPIiB^f{b2Wit&rkICxH?wUCCN~s)Ey~0sBV=fiIr5>Dz9efo4o@InwPC7 z+=mZtxUi+O_vBr3xSW+Y?mNVN(>(m)^6U2fNyXxX{6v{STU{ z3~)ph676}K#?Z4dma;hveZcxq<&ffkDQ<}2`ecf4O%=247K_y?=H(ip=WMWMaOq7R z&Z>7dd-PEl(<%5?Rg2PqLir=$A)Ai6d!wi!k`_|l`{di#RP2yXNBZu5MA`N9-h26N zPe$QFDu752W&MvQ@1xM6*M7D$=n&Nl>1Sb|SN$x5o7B%cBJ*5iE5OYakASCWQ+6*N zHwK8_-MYsQ1FGL~Y8(*3%gVKk`y~};xwDPQ$)L*x(aNW%nH<&>8xNz^&goh_oW{iy z<)MkG`)sXpt&}d5l0jFErG!y-FPl22`Rh1h7>%;WIEEmG*=tqX_4&x6yT|Kv84ioZ znqke!PO_%hELLYqLX#)O8V_ZR?q1Bnp&6c{@y_afHHu+ka6E^^XUwf_JZt;V_5*t< z=&Sni-XqUks)8H&J9cf=ka!E{Kll8}Rhw>zL{v@+ro-1U-7!GwRUN)4s>9PvVGv%D zj4;54R3K^O+ix_tSzFc=^IH4j$~u0=X^b_(<*o1dZRdQvX!yocj5LHkuQML2M9g$g zVBEwsFj=seDL-)w;CaqSr@*nmW-(@w(wS(af!#z}Jrs{Y2WP0w(}j^T)OD1}Dp#sep3u0g$R2UQ z@!g{ctr&(*>}H?;9QR~Suh)@;1e2&U@fI^NHhU~2ht!j~L`dKXG4Y8Oy~)W-%^qG` zg7Ifzi>?Bl3Q6N0Aa^JSR}Rnd3H3@oIlACv3TGPrPN-*R6*|h@m5o++QJPt=x>C#z zLC!H|wneRS5)thZ-|CTgTXKuXmZ_O%<4Jd-XIDMCDxX)bt(p=GIw_2kon*U>7InhT zj*}->@vmOg^9;${s(kgsP-xSo2mh*kemuN_u9Nui@Pd@vm3N+1o>J~E^G_kg#70C? zz!H2=UzAZA6zY+Q8SXQzVltd*>0*L2UC7Sj>`fky%ScXlW~9#YWLWfxO&!Cy6n8;1P33ydSPhIRa=zyC9_u!-{#{Bxs@5pd&&pO z_sV;9?K6M;fP9)U{1gA-$sD1c>8)2p_J~i4FJTg@sNJ(7v0yUF3bK-*OG+&eYpN^q zT{53oWTAp)w7830tsWFvi#P*cRAfm`&S>={3l_Q6V;P~ZigFMjJ=OXmQwS+sK0Yds zsTst<>C`wa$^j|SNQf#H*Q_hZmzm+Dx`u^zRg~Lx3|wqYskVzdZr$Wr(^7M$*E=+| zefzV@Uw4ouQgr3qjMktckQ^cwtjGw25Bz@bqic@$4SsOd@3^}jQyyD1)44R|>`4DRW`XrC8jf+%KPH-(;%t2#cX)u<`rA-NXRW^+K=C;ncaP|ee z$)%Tt7F;v*&cNqy%yT;%&${FO6%SoLr)utIqI zhlQ=J3x6PsCzedS!W$|qDQv3s_b~mF;*zCYWHM$5h?O-_ijE2uU`%O8HAmBgc$6`r zj^!%))y40Yq=6GL%<*?G9^G-*+dCgshBV+YnVnH3^*ZLCS;Y-hKRJRzKC$;LMQK{I z=>{6O zWj!WxA8F+TOyTKsIDM}POZjzLIT1?*Q7fBqzHq%(w!$tZX=NLXVi{(##MJ?W5x@rc z(GD5y@FEN^WcXNy&-Dn!(p$^82Qtc0%F^JRC zO?~YGW(GO};lKueyWH*#du3m+cYP?((HWNSk;_ZVN{h=&%gQFpvx32nF26h@80rm% zyx~Bw$Aw=@y*;(%II;?#$B1mKGaMjsW8ku?nT$zC}e^0xcCy`eR7 za8*ZWRVXE@wjS5mTe?OK;tlM-Jr)a&vE zyJ;=L|F3StSkKbWQ@N$-huw#yCZg%>{U70q}*@*aBp$4YHET=a;v1$_RVK7YI1&kjns(=Rvl zdVQUKS*w{WN5#6_RXYC4j12B#i`{_|bd8!sA4<@M*_eSw%)xv#vilR8MEqv3!Ck>n zM@g4DwZ4+J*)tmF%x^4a6FBvxjL4c(vg8oWFZ?lkdcc#@ zaG`|VkIO+*lUAV`HJE~0)L|;l!r7RHdbrVm>6n3;Xhaicp&7H$f>yNQ9L&L7oQv}? z5A$(8F2DjT#3FdG7)!7e%di|Pa3Q>e!?U;xf5N@E63^gocn%-qN!*YB!ozq3`|uF{ z1rOjsyp7-DA9xCX#3y(Hd+>W)g+Uy^HhhXr_#L+3R@{SKgvZrHz;AE^zQMP+1~=hf zaR~3@Yutw2_#WTk2i$?ZID+SKA6CML>(Gur!;iyw0Waf4yoA5tGpxevcm=QG59q*G z7{Xh418<@eU*MnEjMWHW4LcV-*oh!6LN7w-LqEb8z*>Bcb=ZLQxEPn>5k+9Gg2>JTES9ST<85_EKJH6pxPq2@@ud=pWyH=>zhDz;PrCqDF zYmIiDqFrmvD?|PPKO4yx>|SZ@@CCcOy + + + +Created by FontForge 20110222 at Thu May 12 12:49:24 2011 + By www-data +Digitized data copyright (c) 2010-2011, Google Corporation. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/fonts/OpenSans-Light.ttf b/app/fonts/OpenSans-Light.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3d29b04b2282b362650648ab5e1293d73895c852 GIT binary patch literal 33412 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 F{{a%xj1&L> literal 0 HcmV?d00001 diff --git a/app/fonts/opensans-light.ttf b/app/fonts/opensans-light.ttf new file mode 100644 index 00000000..ce894fc4 --- /dev/null +++ b/app/fonts/opensans-light.ttf @@ -0,0 +1,772 @@ + + + + + + + + + + + + open-sans/OpenSans-Light.ttf at gh-pages · FontFaceKit/open-sans + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Skip to content +
+ + + + + + + + + + + + +
+
+
+ +
+
+
+ +
    + +
  • +
    + +
    + + + + Watch + + + + +
    + +
    +
    +
    +
  • + +
  • + +
    + +
    + + +
    +
    + + +
    + +
  • + +
  • + + + Fork + + + + +
  • + +
+ +

+ + /open-sans + + + + + +

+
+
+ +
+
+
+ + + +
+ +
+

HTTPS clone URL

+
+ + + + +
+
+ + +
+

SSH clone URL

+
+ + + + +
+
+ + +
+

Subversion checkout URL

+
+ + + + +
+
+ + + +

You can clone with + HTTPS, SSH, or Subversion. + + + +

+ + + + + + + Download ZIP + +
+
+ +
+ + + + + + + +
+ +
+ + + branch: + gh-pages + + + +
+ +
+ + + + +
+ + +
+ + +
+ Fetching contributors… +
+ +
+

+

Cannot retrieve contributors at this time

+
+
+
+
+
+ +
+ Raw + History +
+ + + +
+ +
+ +
+ 107.46 kb +
+
+ +
+
+ View Raw +
+
+ +
+ +Jump to Line + + +
+ +
+ +
+
+ + +
+ +
+ +
+ + +
+
+
+ +
+
+
+
+
+ +
+ + + + + +
+ + + Something went wrong with that request. Please try again. +
+ + + + + + + + + + diff --git a/app/modules/profile/styles/profile-bar.scss b/app/modules/profile/styles/profile-bar.scss index a0d3545f..15d5d1bb 100644 --- a/app/modules/profile/styles/profile-bar.scss +++ b/app/modules/profile/styles/profile-bar.scss @@ -156,10 +156,10 @@ } .profile-quote { - @extend %title; - @extend %larger; + @extend %light; + @extend %large; background: url('/images/quote.png') no-repeat top left; - line-height: 1.2; + line-height: 1.4; padding: .5rem; } } diff --git a/app/styles/core/typography.scss b/app/styles/core/typography.scss index 65a21a8e..44a8eff3 100755 --- a/app/styles/core/typography.scss +++ b/app/styles/core/typography.scss @@ -2,7 +2,7 @@ // Font face -@each $font-face in OpenSans-CondLight, opensans-regular, opensans-semibold, taiga { +@each $font-face in OpenSans-CondLight, OpenSans-Light, opensans-regular, opensans-semibold, taiga { @font-face { font-family: '#{$font-face}'; src: url('../fonts/#{$font-face}.eot?#iefix') format('embedded-opentype'), diff --git a/app/styles/dependencies/helpers.scss b/app/styles/dependencies/helpers.scss index 6c71c3c9..912457dd 100644 --- a/app/styles/dependencies/helpers.scss +++ b/app/styles/dependencies/helpers.scss @@ -9,6 +9,7 @@ // __Font Types__ // %title {font-family: 'OpenSans-CondLight', Arial, Helvetica, sans-serif; } +%light {font-family: 'OpenSans-Light', Arial, Helvetica, sans-serif; } %text {font-family: 'opensans-regular', Arial, Helvetica, sans-serif; } %bold {font-family: 'opensans-semibold', Arial, Helvetica, sans-serif; } %taiga {font-family: 'taiga';} From 0b5d09ca0c4bedf90335bae0f188200f0aa73044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 12 May 2015 11:50:58 +0200 Subject: [PATCH 144/366] Add Timeline style fixes for images and items --- .../profile-timeline-attachment-image.jade | 5 +- .../profile-timeline-attachment.jade | 8 +- .../profile-timeline-item.jade | 6 +- .../profile-timeline/profile-timeline.scss | 93 ++++++++----------- 4 files changed, 50 insertions(+), 62 deletions(-) diff --git a/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment-image.jade b/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment-image.jade index dbdaa186..f1ee5b4a 100644 --- a/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment-image.jade +++ b/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment-image.jade @@ -1,3 +1,4 @@ // timeline-attachment directive -div.activity-comment-attachment - img(ng-src="{{::attachment.url}}", alt="{{::attachment.filename}}") \ No newline at end of file +div.activity-image-attachment + blockquote + img(ng-src="{{::attachment.url}}", alt="{{::attachment.filename}}") diff --git a/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.jade b/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.jade index 8deca77a..f8639519 100644 --- a/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.jade +++ b/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.jade @@ -1,3 +1,5 @@ -p TODO: ATTACHMENT -p - a(ng-href="attachment.url") {{attachment.filename}} \ No newline at end of file +div.single-attachment + blockquote + a(ng-href="{{ attachment.url }}", title="Click to download {{ attachment.filename }}") + span.icon.icon-document + span {{attachment.filename}} diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item.jade b/app/modules/profile/profile-timeline-item/profile-timeline-item.jade index 44d06484..218eaf1e 100644 --- a/app/modules/profile/profile-timeline-item/profile-timeline-item.jade +++ b/app/modules/profile/profile-timeline-item/profile-timeline-item.jade @@ -1,4 +1,4 @@ -div.activity-image +div.activity-item span.activity-date {{::vm.activity.created_formated}} div.activity-info div.profile-contact-picture @@ -18,5 +18,5 @@ div.activity-image span {{::vm.activity.member.user.name}} p {{::vm.activity.member.role.name}} -div(ng-repeat="attachment in vm.activity.attachments") - div(tg-profile-timeline-attachment="attachment") + div(ng-repeat="attachment in vm.activity.attachments") + div(tg-profile-timeline-attachment="attachment") diff --git a/app/modules/profile/profile-timeline/profile-timeline.scss b/app/modules/profile/profile-timeline/profile-timeline.scss index 36477703..b5df11cb 100644 --- a/app/modules/profile/profile-timeline/profile-timeline.scss +++ b/app/modules/profile/profile-timeline/profile-timeline.scss @@ -1,9 +1,31 @@ .profile-timeline { border-top: 1px solid $whitish; - %profile-activity { + .activity-item { border-bottom: 1px solid $whitish; - padding: .8rem 1rem; + padding: 1rem .5rem; position: relative; + p { + margin-bottom: .5rem; + } + a { + color: $green-taiga; + &:first-child { + @extend %bold; + color: $gray; + } + &:hover { + color: $fresh-taiga; + } + } + blockquote { + margin-bottom: 0; + margin-left: calc(35px + 1rem); + margin-top: .5rem; + } + img { + max-height: 640px; + max-width: 100%; + } .activity-info { align-items: center; color: $gray-light; @@ -27,52 +49,6 @@ width: 100%; } } - p { - margin-bottom: .5rem; - } - a { - color: $green-taiga; - &:first-child { - @extend %bold; - color: $gray; - } - &:hover { - color: $fresh-taiga; - } - } - } - .activity-simple, - .activity-comment, - .activity-image, - .activity-notification, - .activity-member, - .activity-project { - @extend %profile-activity; - } - .activity-comment, - .activity-project, - .activity-image { - blockquote { - margin-bottom: .5rem; - margin-left: calc(35px + 1rem); - margin-top: .5rem; - img { - max-height: 640px; - max-width: 100%; - } - } - } - .activity-notification { - .activity-notification-list { - border-left: 2px solid $whitish; - margin-bottom: .5rem; - margin-left: calc(35px + 1rem); - margin-top: .5rem; - padding: .2rem 1rem; - li { - margin-bottom: .5rem; - } - } } .activity-member-view { display: flex; @@ -89,12 +65,21 @@ color: $gray-light; } } - .profile-member-picture { - img { - margin-right: 1rem; - max-width: 64px; - min-width: 32px; - width: 100%; + .single-attachment { + border: 0; + padding: 0; + span { + @extend %text; + @extend %medium; + display: inline-block; + max-width: 95%; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: middle; + white-space: nowrap; + } + .icon { + margin-right: .3rem; } } } From 042d73b771958ddbcd8698a9e3d5849f1dc673e8 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 12 May 2015 13:26:55 +0200 Subject: [PATCH 145/366] new resources return immutable objects --- app/coffee/app.coffee | 12 ++- app/js/tg-repeat.js | 18 +++- app/modules/home/duties/duty.directive.coffee | 13 ++- .../home/duties/duty.directive.spec.coffee | 5 +- app/modules/home/duties/duty.jade | 14 ++-- app/modules/home/home.jade | 2 +- app/modules/home/home.service.coffee | 73 ++++++++++++---- app/modules/home/home.service.spec.coffee | 35 ++++---- .../home/projects/home-project-list.jade | 10 +-- .../dropdown-project-list.jade | 3 +- .../profile-bar/profile-bar.controller.coffee | 2 +- .../profile-bar.controller.spec.coffee | 2 +- .../profile/profile-bar/profile-bar.jade | 10 +-- .../profile-contacts/profile-contacts.jade | 12 +-- .../profile-projects/profile-projects.jade | 12 +-- .../profile-timeline-item.controller.coffee | 2 +- ...ofile-timeline-item.controller.spec.coffee | 7 +- .../listing/projects-listing.directive.coffee | 2 +- .../projects/listing/projects-listing.jade | 14 ++-- .../projects/project/project-page.jade | 1 + .../project/project.controller.coffee | 17 ++++ .../project/project.controller.spec.coffee | 84 +++++++++++++++++++ .../projects/project/project.directive.coffee | 8 ++ app/modules/projects/project/project.jade | 24 ++++++ app/modules/projects/projects.service.coffee | 38 +++++---- .../projects/projects.service.spec.coffee | 66 ++++++++++----- .../resources/issues-resource.service.coffee | 23 +++++ .../projects-resource.service.coffee | 31 +++++++ app/modules/resources/resources.coffee | 6 +- .../resources/tasks-resource.service.coffee | 23 +++++ .../resources/users-resource.service.coffee | 2 +- .../userstories-resource.service.coffee | 23 +++++ karma.conf.js | 2 +- 33 files changed, 462 insertions(+), 134 deletions(-) create mode 100644 app/modules/projects/project/project-page.jade create mode 100644 app/modules/projects/project/project.controller.coffee create mode 100644 app/modules/projects/project/project.controller.spec.coffee create mode 100644 app/modules/projects/project/project.directive.coffee create mode 100644 app/modules/projects/project/project.jade create mode 100644 app/modules/resources/issues-resource.service.coffee create mode 100644 app/modules/resources/projects-resource.service.coffee create mode 100644 app/modules/resources/tasks-resource.service.coffee create mode 100644 app/modules/resources/userstories-resource.service.coffee diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 96965a00..748a5b4a 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -66,7 +66,17 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven ) $routeProvider.when("/project/:pslug/", - {templateUrl: "project/project.html"}) + { + templateUrl: "projects/project/project-page.html", + resolve: { + loader: tgLoaderProvider.add(true), + pageParams: -> { + "authRequired": true + } + }, + controller: "Page" + } + ) $routeProvider.when("/project/:pslug/search", {templateUrl: "search/search.html", reloadOnSearch: false}) diff --git a/app/js/tg-repeat.js b/app/js/tg-repeat.js index 8b6528a8..72c21e0e 100644 --- a/app/js/tg-repeat.js +++ b/app/js/tg-repeat.js @@ -13,6 +13,16 @@ if (immutable_collection.toJS) { collection = immutable_collection.toJS(); } + $scope[aliasAs] = collection; -> $scope[aliasAs] = immutable_collection; + +value = collection[key]; +immutable_value = immutable_collection.get(key); #x2 + + +updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); +-> (x2) +updateScope(block.scope, index, valueIdentifier, immutable_value, keyIdentifier, key, collectionLength); + --copy from angular copy angular hashKey copy angular createMap @@ -205,7 +215,7 @@ nextBlockOrder, elementsToRemove; if (aliasAs) { - $scope[aliasAs] = collection; + $scope[aliasAs] = immutable_collection; } if (isArrayLike(collection)) { collectionKeys = collection; @@ -226,6 +236,7 @@ for (index = 0; index < collectionLength; index++) { key = (collection === collectionKeys) ? index : collectionKeys[index]; value = collection[key]; + immutable_value = immutable_collection.get(key); trackById = trackByIdFn(key, value, index); if (lastBlockMap[trackById]) { // found previously seen block @@ -265,6 +276,7 @@ for (index = 0; index < collectionLength; index++) { key = (collection === collectionKeys) ? index : collectionKeys[index]; value = collection[key]; + immutable_value = immutable_collection.get(key); block = nextBlockOrder[index]; if (block.scope) { // if we have already seen this object, then we need to reuse the @@ -279,7 +291,7 @@ $animate.move(getBlockNodes(block.clone), null, jqLite(previousNode)); } previousNode = getBlockEnd(block); - updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); + updateScope(block.scope, index, valueIdentifier, immutable_value, keyIdentifier, key, collectionLength); } else { // new item which we don't know about $transclude(function ngRepeatTransclude(clone, scope) { @@ -295,7 +307,7 @@ // by a directive with templateUrl when its template arrives. block.clone = clone; nextBlockMap[block.id] = block; - updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); + updateScope(block.scope, index, valueIdentifier, immutable_value, keyIdentifier, key, collectionLength); }); } } diff --git a/app/modules/home/duties/duty.directive.coffee b/app/modules/home/duties/duty.directive.coffee index 0cb45456..d6eae86f 100644 --- a/app/modules/home/duties/duty.directive.coffee +++ b/app/modules/home/duties/duty.directive.coffee @@ -1,18 +1,18 @@ -DutyDirective = (navurls, projectsService, $translate) -> +DutyDirective = (navurls, $translate) -> link = (scope, el, attrs, ctrl) -> scope.vm = {} scope.vm.duty = scope.duty scope.vm.getDutyType = () -> if scope.vm.duty - if scope.vm.duty._name == "userstories" + if scope.vm.duty.get('_name') == "userstories" return $translate.instant("COMMON.USER_STORY") - if scope.vm.duty._name == "tasks" + if scope.vm.duty.get('_name') == "tasks" return $translate.instant("COMMON.TASK") - if scope.vm.duty._name == "issues" + if scope.vm.duty.get('_name') == "issues" return $translate.instant("COMMON.ISSUE") - directive = { + return { templateUrl: "home/duties/duty.html" scope: { "duty": "=tgDuty" @@ -20,11 +20,8 @@ DutyDirective = (navurls, projectsService, $translate) -> link: link } - return directive - DutyDirective.$inject = [ "$tgNavUrls", - "tgProjectsService", "$translate" ] diff --git a/app/modules/home/duties/duty.directive.spec.coffee b/app/modules/home/duties/duty.directive.spec.coffee index c8dbb867..d8a81aad 100644 --- a/app/modules/home/duties/duty.directive.spec.coffee +++ b/app/modules/home/duties/duty.directive.spec.coffee @@ -48,7 +48,7 @@ describe "dutyDirective", () -> compile = $compile it "duty directive scope content", () -> - scope.duty = { + scope.duty = Immutable.fromJS({ project: 1 ref: 1 _name: "userstories" @@ -56,7 +56,7 @@ describe "dutyDirective", () -> photo: "http://jstesting.taiga.io/photo" full_name_display: "Taiga testing js" } - } + }) mockTgProjectsService.projectsById.get .withArgs("1") @@ -72,4 +72,5 @@ describe "dutyDirective", () -> elm = createDirective() scope.$apply() + expect(elm.isolateScope().vm.getDutyType()).to.be.equal("User story translated") diff --git a/app/modules/home/duties/duty.jade b/app/modules/home/duties/duty.jade index b1b94e8a..6bd557d1 100644 --- a/app/modules/home/duties/duty.jade +++ b/app/modules/home/duties/duty.jade @@ -1,13 +1,13 @@ -a(href="{{ ::vm.duty.url }}", title="{{ ::duty.subject }}") +a(href="{{ ::vm.duty.get('url') }}", title="{{ ::duty.get('subject') }}") img.avatar( - src="{{ ::vm.duty.assigned_to_extra_info.photo }}" - title="{{ ::vm.duty.assigned_to_extra_info.full_name_display }}") + src="{{ ::vm.duty.get('assigned_to_extra_info').get('photo') }}" + title="{{ ::vm.duty.get('assigned_to_extra_info').get('full_name_display') }}") div.duty-data div span.duty-type {{ ::vm.getDutyType() }} - span.duty-status(ng-style="{'color': vm.duty.status_extra_info.color}") {{ ::vm.duty.status_extra_info.name }} + span.duty-status(ng-style="{'color': vm.duty.get('status_extra_info').get('color')}") {{ ::vm.duty.get('status_extra_info').get('name') }} span.duty-title - span.duty-id(tg-bo-ref="duty.ref") - span.duty-name {{ ::duty.subject }} - div.duty-project {{ ::vm.duty.projectName}} + span.duty-id(tg-bo-ref="duty.get('ref')") + span.duty-name {{ ::duty.get('subject') }} + div.duty-project {{ ::vm.duty.get('projectName')}} diff --git a/app/modules/home/home.jade b/app/modules/home/home.jade index 40f04c9d..c73a751b 100644 --- a/app/modules/home/home.jade +++ b/app/modules/home/home.jade @@ -13,7 +13,7 @@ div.home-wrapper.centered include ../../svg/hide.svg p(translate="HOME.EMPTY_WATCHING") - section.watching(ng-show="vm.watching") + section.watching(ng-show="vm.watching.size") div.duty-single(tg-duty="duty", tg-repeat="duty in vm.watching", ng-class="{blocked: duty.is_blocked}") aside.project-list(tg-home-project-list) diff --git a/app/modules/home/home.service.coffee b/app/modules/home/home.service.coffee index 0d1615ba..36207c45 100644 --- a/app/modules/home/home.service.coffee +++ b/app/modules/home/home.service.coffee @@ -1,5 +1,5 @@ class HomeService extends taiga.Service - @.$inject = ["$q", "$tgNavUrls", "$tgResources", "$rootScope", "$projectUrl", "$tgAuth"] + @.$inject = ["$q", "$tgNavUrls", "tgResources", "$rootScope", "$projectUrl", "$tgAuth"] constructor: (@q, @navurls, @rs, @rootScope, @projectUrl, @auth) -> @._workInProgress = Immutable.Map() @@ -11,27 +11,64 @@ class HomeService extends taiga.Service @.fetchWorkInProgress() attachProjectInfoToWorkInProgress: (projectsById) -> - _attachProjectInfoToDuty = (duty) => - project = projectsById.get(String(duty.project)) + _attachProjectInfoToDuty = (duty, objType) => + project = projectsById.get(String(duty.get('project'))) ctx = { - project: project.slug - ref: duty.ref + project: project.get('slug') + ref: duty.get('ref') } - duty.url = @navurls.resolve("project-#{duty._name}-detail", ctx) - duty.projectName = project.name + + url = @navurls.resolve("project-#{objType}-detail", ctx) + + duty = duty.set('url', url) + duty = duty.set('projectName', project.get('name')) + duty = duty.set("_name", objType) + return duty - @._workInProgress = Immutable.fromJS({ - assignedTo: { - userStories: _.map(_.clone(@.assignedToUserStories), _attachProjectInfoToDuty) - tasks: _.map(_.clone(@.assignedToTasks), _attachProjectInfoToDuty) - issues: _.map(_.clone(@.assignedToIssues), _attachProjectInfoToDuty) - } - watching: { - userStories: _.map(_.clone(@.watchingUserStories), _attachProjectInfoToDuty) - tasks: _.map(_.clone(@.watchingTasks), _attachProjectInfoToDuty) - issues: _.map(_.clone(@.watchingIssues), _attachProjectInfoToDuty) - } + assignedTo = Immutable.Map() + + if @.assignedToUserStories + _duties = @.assignedToUserStories.map (duty) -> + return _attachProjectInfoToDuty(duty, "userstories") + + assignedTo = assignedTo.set("userStories", _duties) + + if @.assignedToTasks + _duties = @.assignedToTasks.map (duty) -> + return _attachProjectInfoToDuty(duty, "tasks") + + assignedTo = assignedTo.set("tasks", _duties) + + if @.assignedToIssues + _duties = @.assignedToIssues.map (duty) -> + return _attachProjectInfoToDuty(duty, "issues") + + assignedTo = assignedTo.set("issues", _duties) + + watching = Immutable.Map() + + if @.watchingUserStories + _duties = @.watchingUserStories.map (duty) -> + return _attachProjectInfoToDuty(duty, "userstories") + + watching = watching.set("userStories", _duties) + + if @.watchingTasks + _duties = @.watchingTasks.map (duty) -> + return _attachProjectInfoToDuty(duty, "tasks") + + watching = watching.set("tasks", _duties) + + if @.watchingIssues + _duties = @.watchingIssues.map (duty) -> + return _attachProjectInfoToDuty(duty, "issues") + + watching = watching.set("issues", _duties) + + @._workInProgress = Immutable.Map({ + assignedTo: assignedTo, + watching: watching }) getWorkInProgress: () -> diff --git a/app/modules/home/home.service.spec.coffee b/app/modules/home/home.service.spec.coffee index 3803a6b4..167c5a61 100644 --- a/app/modules/home/home.service.spec.coffee +++ b/app/modules/home/home.service.spec.coffee @@ -58,7 +58,7 @@ describe "tgHome", -> then: mocks.thenStubWatchingIssues }) - provide.value "$tgResources", mocks.resources + provide.value "tgResources", mocks.resources _mockProjectUrl = () -> mocks.projectUrl = {get: sinon.stub()} @@ -83,7 +83,7 @@ describe "tgHome", -> provide.value "$tgNavUrls", mocks.tgNavUrls _inject = (callback) -> - inject (_$q_, _$tgResources_, _$rootScope_, _$projectUrl_, _$timeout_, _tgHomeService_) -> + inject (_$timeout_, _tgHomeService_) -> timeout = _$timeout_ homeService = _tgHomeService_ callback() if callback @@ -107,12 +107,12 @@ describe "tgHome", -> describe "fetch items", -> it "work in progress filled", () -> - mocks.thenStubAssignedToUserstories.callArg(0, [{"id": 1}]) - mocks.thenStubAssignedToTasks.callArg(0, [{"id": 2}]) - mocks.thenStubAssignedToIssues.callArg(0, [{"id": 3}]) - mocks.thenStubWatchingUserstories.callArg(0, [{"id": 4}]) - mocks.thenStubWatchingTasks.callArg(0, [{"id": 5}]) - mocks.thenStubWatchingIssues.callArg(0, [{"id": 6}]) + mocks.thenStubAssignedToUserstories.callArg(0, Immutable.fromJS([{"id": 1}])) + mocks.thenStubAssignedToTasks.callArg(0, Immutable.fromJS([{"id": 2}])) + mocks.thenStubAssignedToIssues.callArg(0, Immutable.fromJS([{"id": 3}])) + mocks.thenStubWatchingUserstories.callArg(0, Immutable.fromJS([{"id": 4}])) + mocks.thenStubWatchingTasks.callArg(0, Immutable.fromJS([{"id": 5}])) + mocks.thenStubWatchingIssues.callArg(0, Immutable.fromJS([{"id": 6}])) timeout.flush() expect(homeService.workInProgress.toJS()).to.be.eql({ @@ -136,23 +136,23 @@ describe "tgHome", -> it "project info filled", () -> duty = { id: 66 - _name: "userstories" ref: 123 project: 1 } - mocks.thenStubAssignedToUserstories.callArg(0, [duty]) - mocks.thenStubAssignedToTasks.callArg(0) - mocks.thenStubAssignedToIssues.callArg(0) - mocks.thenStubWatchingUserstories.callArg(0) - mocks.thenStubWatchingTasks.callArg(0) - mocks.thenStubWatchingIssues.callArg(0) + + mocks.thenStubAssignedToUserstories.callArg(0, Immutable.fromJS([duty])) + mocks.thenStubAssignedToTasks.callArg(0, Immutable.fromJS([])) + mocks.thenStubAssignedToIssues.callArg(0, Immutable.fromJS([])) + mocks.thenStubWatchingUserstories.callArg(0, Immutable.fromJS([])) + mocks.thenStubWatchingTasks.callArg(0, Immutable.fromJS([])) + mocks.thenStubWatchingIssues.callArg(0, Immutable.fromJS([])) timeout.flush() projectsById = { - get: () -> { + get: () -> Immutable.fromJS({ name: "Testing project" slug: "testing-project" - } + }) } mocks.tgNavUrls.resolve @@ -160,6 +160,7 @@ describe "tgHome", -> .returns("/testing-project/us/123") homeService.attachProjectInfoToWorkInProgress(projectsById) + expect(homeService.workInProgress.toJS()).to.be.eql({ assignedTo: { userStories: [ diff --git a/app/modules/home/projects/home-project-list.jade b/app/modules/home/projects/home-project-list.jade index 5fadc4d1..e9ebb4cf 100644 --- a/app/modules/home/projects/home-project-list.jade +++ b/app/modules/home/projects/home-project-list.jade @@ -1,12 +1,12 @@ ul.home-project-list(ng-show="vm.projects.size") li.home-project-list-single(tg-bind-scope, tg-repeat="project in vm.projects") - a(href="#", tg-nav="project:project=project.slug") + a(href="#", tg-nav="project:project=project.get('slug')") h2.home-project-list-single-title - span.project-name(ng-bind="::project.name", title="{{ ::project.name }}") - span.private(ng-if="project.is_private", title="{{'PROJECT.PRIVATE' | translate}}") + span.project-name(title="{{ ::project.get('name') }}") {{::project.get('name')}} + span.private(ng-if="project.get('is_private')", title="{{'PROJECT.PRIVATE' | translate}}") include ../../../svg/lock.svg - p {{ ::project.description | limitTo:150 }} - span(ng-if="::project.description.length > 150") ... + p {{ ::project.get('description') | limitTo:150 }} + span(ng-if="::project.get('description').size > 150") ... a.see-more-projects-btn.button-gray(ng-show="vm.projects.size", href="#", tg-nav="projects", title="{{'PROJECT.NAVIGATION.SEE_MORE_PROJECTS' | translate}}", translate="PROJECT.NAVIGATION.SEE_MORE_PROJECTS") section.projects-empty(ng-hide="vm.projects.size") include ../../../svg/empty-project.svg diff --git a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade index 12cdf753..9a1b78ff 100644 --- a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade +++ b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade @@ -5,8 +5,7 @@ div.navbar-dropdown.dropdown-project-list ul a(href="#", tg-repeat="project in vm.projects", - ng-bind="::project.name" - tg-nav="project:project=project.slug") + tg-nav="project:project=project.get('slug')") {{::project.get("name")}} a.see-more-projects-btn.button-gray( href="#", diff --git a/app/modules/profile/profile-bar/profile-bar.controller.coffee b/app/modules/profile/profile-bar/profile-bar.controller.coffee index b9ea7323..ba41bd15 100644 --- a/app/modules/profile/profile-bar/profile-bar.controller.coffee +++ b/app/modules/profile/profile-bar/profile-bar.controller.coffee @@ -11,6 +11,6 @@ class ProfileBarController loadStats: () -> return @userService.getStats(@.user.id).then (stats) => - @.stats = stats.toJS() + @.stats = stats angular.module("taigaProfile").controller("ProfileBar", ProfileBarController) diff --git a/app/modules/profile/profile-bar/profile-bar.controller.spec.coffee b/app/modules/profile/profile-bar/profile-bar.controller.spec.coffee index 4a37e0ee..9d2b7067 100644 --- a/app/modules/profile/profile-bar/profile-bar.controller.spec.coffee +++ b/app/modules/profile/profile-bar/profile-bar.controller.spec.coffee @@ -58,4 +58,4 @@ describe "ProfileBar", -> $rootScope.$apply() - expect(ctrl.stats).to.be.eql(stats.toJS()) + expect(ctrl.stats.toJS()).to.be.eql(stats.toJS()) diff --git a/app/modules/profile/profile-bar/profile-bar.jade b/app/modules/profile/profile-bar/profile-bar.jade index 640d3fb7..2c9c38a3 100644 --- a/app/modules/profile/profile-bar/profile-bar.jade +++ b/app/modules/profile/profile-bar/profile-bar.jade @@ -9,7 +9,7 @@ section.profile-bar // span(translate="USER.PROFILE.FOLLOW") div.profile-data h1 {{::vm.user.full_name}} - h2 {{::vm.stats.roles.join(", ")}} + h2 {{::vm.stats.get('roles').join(", ")}} // div.location // include ../../../svg/location.svg // span Madrid @@ -19,13 +19,13 @@ section.profile-bar // These values in profile stats are not defined yet in UX. Please ask div.profile-stats div.stat - span.stat-number {{::vm.stats.projects}} + span.stat-number {{::vm.stats.get('projects')}} span.stat-name(translate="USER.PROFILE.PROJECTS") div.stat - span.stat-number {{::vm.stats.closed_userstories}} + span.stat-number {{::vm.stats.get('closed_userstories')}} span.stat-name(translate="USER.PROFILE.CLOSED_US") div.stat - span.stat-number {{::vm.stats.contacts}} + span.stat-number {{::vm.stats.get('contacts')}} span.stat-name(translate="USER.PROFILE.CONTACTS") // TODO Hide until organizations come // div.profile-organizations @@ -36,5 +36,5 @@ section.profile-bar // div.organization // div.organization - div.profile-quote(ng-if="vm.user.bio") + div.profile-quote(ng-if="::vm.user.bio") span {{::vm.user.bio}} diff --git a/app/modules/profile/profile-contacts/profile-contacts.jade b/app/modules/profile/profile-contacts/profile-contacts.jade index e81fef75..bb57baa2 100644 --- a/app/modules/profile/profile-contacts/profile-contacts.jade +++ b/app/modules/profile/profile-contacts/profile-contacts.jade @@ -7,19 +7,19 @@ section.profile-contacts div.profile-contact-single(tg-repeat="contact in ::vm.contacts") div.profile-contact-picture - a(tg-nav="user-profile:username=contact.username", title="{{::contact.name }}") - img(ng-src='{{::contact.photo}}', alt='{{::contact.full_name}}') + a(tg-nav="user-profile:username=contact.get('username')", title="{{::contact.get('name') }}") + img(ng-src="{{::contact.get('photo')}}", alt="{{::contact.get('full_name')}}") div.profile-contact-data h1 - a(tg-nav="user-profile:username=contact.username", title="{{::contact.name }}") - | {{::contact.full_name}} + a(tg-nav="user-profile:username=contact.get('username')", title="{{::contact.get('name') }}") + | {{::contact.get('full_name')}} // span.your-contact Your contact - p(ng-if="contact.bio") {{::contact.bio}} + p(ng-if="contact.bio") {{::contact.get('bio')}} div.extra-info - span.position {{::contact.roles.join(", ")}} + span.position {{::contact.get('roles').join(", ")}} // span.location todo // div.profile-project-stats // div.stat-projects(title="2 projects") diff --git a/app/modules/profile/profile-projects/profile-projects.jade b/app/modules/profile/profile-projects/profile-projects.jade index b7a98cec..10774900 100644 --- a/app/modules/profile/profile-projects/profile-projects.jade +++ b/app/modules/profile/profile-projects/profile-projects.jade @@ -4,16 +4,16 @@ section.profile-projects div.project-list-single-title h1 - a(href="#", tg-nav="project:project=project.slug", title="{{ ::project.name }}") {{::project.name}} - p {{ ::project.description | limitTo:300 }} + a(href="#", tg-nav="project:project=project.get('slug')", title="{{ ::project.get('name') }}") {{::project.get('name')}} + p {{ ::project.get('description') | limitTo:300 }} div.project-list-single-tags.tags-container - span.tag(style='border-left: 5px solid {{::tag.color}};', ng-repeat="tag in ::project.colorized_tags") - span.tag-name {{::tag.name}} + span.tag(style='border-left: 5px solid {{::tag.get("color")}};', tg-repeat="tag in ::project.get('colorized_tags')") + span.tag-name {{::tag.get('name')}} div.project-list-single-members - a(ng-repeat="contact in ::project.contacts", tg-nav="user-profile:username=contact.username", title="{{::contact.full_name}}") - img(ng-src="{{::contact.photo}}") + a(tg-repeat="contact in ::project.get('contacts')", tg-nav="user-profile:username=contact.get('username')", title="{{::contact.get('full_name')}}") + img(ng-src="{{::contact.get('photo')}}") // div.project-list-single-right // div.project-list-single-stats diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.coffee b/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.coffee index 8145dd74..f17bcfeb 100644 --- a/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.coffee +++ b/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.coffee @@ -6,7 +6,7 @@ class ProfileTimelineItemController ] constructor: (@sce, @profileTimelineItemType, @profileTimelineItemTitle) -> - timeline = @.timeline + timeline = @.timeline.toJS() event = @.parseEventType(timeline.event_type) type = @profileTimelineItemType.getType(timeline, event) diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.spec.coffee b/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.spec.coffee index 32edcf26..7b127951 100644 --- a/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.spec.coffee +++ b/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.spec.coffee @@ -71,8 +71,9 @@ describe "ProfileTimelineItemController", -> it "basic activity fields filled", () -> timeline = scope.vm.timeline + timeline_immutable = Immutable.fromJS(timeline) - myCtrl = controller("ProfileTimelineItem", {$scope: scope}, {timeline: timeline}) + myCtrl = controller("ProfileTimelineItem", {$scope: scope}, {timeline: timeline_immutable}) expect(myCtrl.activity.user).to.be.equal(timeline.data.user) expect(myCtrl.activity.project).to.be.equal(timeline.data.project) @@ -94,7 +95,9 @@ describe "ProfileTimelineItemController", -> mockType.description.withArgs(timeline).returns(description) mockType.member.withArgs(timeline).returns(member) - myCtrl = controller("ProfileTimelineItem", {$scope: scope}, {timeline: timeline}) + timeline_immutable = Immutable.fromJS(timeline) + + myCtrl = controller("ProfileTimelineItem", {$scope: scope}, {timeline: timeline_immutable}) expect(myCtrl.activity.description).to.be.an('object') # $sce.trustAsHtml expect(myCtrl.activity.member).to.be.equal(member) diff --git a/app/modules/projects/listing/projects-listing.directive.coffee b/app/modules/projects/listing/projects-listing.directive.coffee index 1ae889f8..61e7ccab 100644 --- a/app/modules/projects/listing/projects-listing.directive.coffee +++ b/app/modules/projects/listing/projects-listing.directive.coffee @@ -19,7 +19,7 @@ ProjectsListingDirective = (projectsService) -> sorted_project_ids = _.map(scope.vm.projects.toArray(), (p) -> p.id) sorted_project_ids = _.without(sorted_project_ids, project.id) - sorted_project_ids.splice(index, 0, project.id) + sorted_project_ids.splice(index, 0, project.get('id')) sortData = [] for value, index in sorted_project_ids sortData.push({"project_id": value, "order":index}) diff --git a/app/modules/projects/listing/projects-listing.jade b/app/modules/projects/listing/projects-listing.jade index 452a7009..2dfaa4c7 100644 --- a/app/modules/projects/listing/projects-listing.jade +++ b/app/modules/projects/listing/projects-listing.jade @@ -15,15 +15,15 @@ div.project-list-wrapper.centered div.project-list-single-left div.project-list-single-title h1 - a(href="#", tg-nav="project:project=project.slug") - h1.project-name(ng-bind="::project.name", title="{{ ::project.name }}") - span.private(ng-if="project.is_private", title="{{'PROJECT.PRIVATE' | translate}}") + a(href="#", tg-nav="project:project=project.get('slug')") + h1.project-name(title="{{ ::project.get('name') }}") {{project.get('name')}} + span.private(ng-if="project.get('is_private')", title="{{'PROJECT.PRIVATE' | translate}}") include ../../../svg/lock.svg - p {{ ::project.description | limitTo:300 }} - span(ng-if="::project.description.length > 300") ... + p {{ ::project.get('description') | limitTo:300 }} + span(ng-if="::project.get('description').length > 300") ... - div.project-list-single-tags.tags-container(ng-if="::project.tags") - div.tags-block(tg-colorize-tags="project.tags", tg-colorize-tags-type="backlog") + div.project-list-single-tags.tags-container(ng-if="::project.get('tags').size") + div.tags-block(tg-colorize-tags="project.get('tags')", tg-colorize-tags-type="backlog") div.project-list-single-right span.drag.icon.icon-drag-v diff --git a/app/modules/projects/project/project-page.jade b/app/modules/projects/project/project-page.jade new file mode 100644 index 00000000..d86e8356 --- /dev/null +++ b/app/modules/projects/project/project-page.jade @@ -0,0 +1 @@ +div(tg-project) diff --git a/app/modules/projects/project/project.controller.coffee b/app/modules/projects/project/project.controller.coffee new file mode 100644 index 00000000..098b0727 --- /dev/null +++ b/app/modules/projects/project/project.controller.coffee @@ -0,0 +1,17 @@ +class ProjectController + @.$inject = [ + "tgProjectsService", + "$routeParams", + "$appTitle" + ] + + constructor: (@projectsService, @routeParams, @appTitle) -> + projectSlug = @routeParams.pslug + + @projectsService.getProjectBySlug(projectSlug) + .then (project) => + @appTitle.set(project.get("name")) + + @.project = project + +angular.module("taigaProjects").controller("Project", ProjectController) diff --git a/app/modules/projects/project/project.controller.spec.coffee b/app/modules/projects/project/project.controller.spec.coffee new file mode 100644 index 00000000..8e8b75c3 --- /dev/null +++ b/app/modules/projects/project/project.controller.spec.coffee @@ -0,0 +1,84 @@ +describe "ProfileBar", -> + $controller = null + $q = null + provide = null + $rootScope = null + mocks = {} + + _mockProjectsService = () -> + mocks.projectService = { + getProjectBySlug: sinon.stub() + } + + provide.value "tgProjectsService", mocks.projectService + + _mockAppTitle = () -> + mocks.appTitle = { + set: sinon.stub() + } + + provide.value "$appTitle", mocks.appTitle + + _mockRouteParams = () -> + provide.value "$routeParams", { + pslug: "project-slug" + } + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockProjectsService() + _mockRouteParams() + _mockAppTitle() + + return null + + _inject = (callback) -> + inject (_$controller_, _$q_, _$rootScope_) -> + $q = _$q_ + $rootScope = _$rootScope_ + $controller = _$controller_ + + beforeEach -> + module "taigaProjects" + _mocks() + _inject() + + it "set page title", () -> + project = Immutable.fromJS({ + name: "projectName" + }) + + thenStub = sinon.stub() + + mocks.projectService.getProjectBySlug.withArgs("project-slug").returns({ + then: thenStub + }) + + ctrl = $controller("Project") + + thenStub.callArg(0, project) + + $rootScope.$apply() + + expect(mocks.appTitle.set.withArgs("projectName")).to.be.calledOnce + + + it "set local project variable", () -> + project = Immutable.fromJS({ + name: "projectName" + }) + + thenStub = sinon.stub() + + mocks.projectService.getProjectBySlug.withArgs("project-slug").returns({ + then: thenStub + }) + + ctrl = $controller("Project") + + thenStub.callArg(0, project) + + $rootScope.$apply() + + expect(ctrl.project).to.be.equal(project) diff --git a/app/modules/projects/project/project.directive.coffee b/app/modules/projects/project/project.directive.coffee new file mode 100644 index 00000000..e3dd8298 --- /dev/null +++ b/app/modules/projects/project/project.directive.coffee @@ -0,0 +1,8 @@ +ProjectDirective = () -> + return { + templateUrl: "projects/project/project.html", + controllerAs: "vm", + controller: "Project" + } + +angular.module("taigaProjects").directive("tgProject", ProjectDirective) diff --git a/app/modules/projects/project/project.jade b/app/modules/projects/project/project.jade new file mode 100644 index 00000000..339d0880 --- /dev/null +++ b/app/modules/projects/project/project.jade @@ -0,0 +1,24 @@ +div.wrapper + div.main.centered.single-project + section.single-project-intro + h1 + span.green(class="project-name") {{::vm.project.get("name")}} + span.private(ng-if="::vm.project.get('is_private')", title="{{'PROJECT.PRIVATE' | translate}}") + include ../../../svg/lock.svg + p.description {{vm.project.get('description')}} + div.project-list-single-tags.tags-container(ng-if="::vm.project.get('tags').size") + div.tags-block(tg-colorize-tags="vm.project.get('tags')", tg-colorize-tags-type="backlog") + + div.project-data + section.timeline + span TODO. Missing the amazing timeline around!!dfdfdf + section.involved-data + h2.title Team + ul.involved-team + a(href="", title="{{::member.get('full_name')}}", tg-repeat="member in ::vm.project.get('memberships')") + img(ng-src="{{::member.get('photo')}}", alt="{{::member.get('full_name')}}") + + // h2.title Organizations + // div.involved-organization + // a(href="", title="User Name") + // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/dan_higham/48.jpg", alt="{{member.full_name}}") diff --git a/app/modules/projects/projects.service.coffee b/app/modules/projects/projects.service.coffee index e02191c0..ff1c4e40 100644 --- a/app/modules/projects/projects.service.coffee +++ b/app/modules/projects/projects.service.coffee @@ -2,7 +2,7 @@ taiga = @.taiga groupBy = @.taiga.groupBy class ProjectsService extends taiga.Service - @.$inject = ["$tgResources", "$rootScope", "$projectUrl", "tgLightboxFactory"] + @.$inject = ["tgResources", "$rootScope", "$projectUrl", "tgLightboxFactory"] constructor: (@rs, @rootScope, @projectUrl, @lightboxFactory) -> @._currentUserProjects = Immutable.Map() @@ -18,30 +18,40 @@ class ProjectsService extends taiga.Service getCurrentUserProjects: -> return @._currentUserProjectsPromise + getProjectBySlug: (projectSlug) -> + return @rs.projects.getProjectBySlug(projectSlug) + + getProjectStats: (projectId) -> + return @rs.projects.getProjectStats(projectId) + fetchProjects: -> if not @._inProgress @._inProgress = true - @._currentUserProjectsPromise = @rs.projects.listByMember(@rootScope.user?.id) + @._currentUserProjectsPromise = @rs.users.getProjects(@rootScope.user?.id) @._currentUserProjectsPromise.then (projects) => - _.map projects, (project) => - project.url = @projectUrl.get(project) + projects = projects.map (project) => + url = @projectUrl.get(project.toJS()) - project.colorized_tags = [] + project = project.set("url", url) + colorized_tags = [] - if project.tags - tags = project.tags.sort() + if project.get("tags") + tags = project.get("tags").sort() - project.colorized_tags = _.map tags, (tag) -> - color = project.tags_colors[tag] + colorized_tags = tags.map (tag) -> + color = project.get("tags_colors").get(tag) return {name: tag, color: color} - @._currentUserProjects = Immutable.fromJS({ - all: projects, - recents: projects.slice(0, 10) - }) + project = project.set("colorized_tags", colorized_tags) - @._currentUserProjectsById = Immutable.fromJS(groupBy(projects, (p) -> p.id)) + return project + + + @._currentUserProjects = @._currentUserProjects.set("all", projects) + @._currentUserProjects = @._currentUserProjects.set("recents", projects.slice(0, 10)) + + @._currentUserProjectsById = Immutable.fromJS(groupBy(projects.toJS(), (p) -> p.id)) return @.projects diff --git a/app/modules/projects/projects.service.spec.coffee b/app/modules/projects/projects.service.spec.coffee index 0ba28182..0bc3eb70 100644 --- a/app/modules/projects/projects.service.spec.coffee +++ b/app/modules/projects/projects.service.spec.coffee @@ -5,18 +5,19 @@ describe "tgProjects", -> _mockResources = () -> mocks.resources = {} - mocks.resources.projects = { - listByMember: sinon.stub() + mocks.resources.users = { + getProjects: sinon.stub() } mocks.thenStub = sinon.stub() mocks.finallyStub = sinon.stub() - mocks.resources.projects.listByMember.withArgs(10).returns({ + + mocks.resources.users.getProjects.withArgs(10).returns({ then: mocks.thenStub finally: mocks.finallyStub }) - provide.value "$tgResources", mocks.resources + provide.value "tgResources", mocks.resources _mockRootScope = () -> provide.value "$rootScope", {user: {id: 10}} @@ -64,22 +65,22 @@ describe "tgProjects", -> beforeEach -> projects = [ - {"id": 1, tags: ['xx', 'yy', 'aa'], tags_colors: {xx: "red", yy: "blue", aa: "white"}}, - {"id": 2}, - {"id": 3}, - {"id": 4}, - {"id": 5}, - {"id": 6}, - {"id": 7}, - {"id": 8}, - {"id": 9}, - {"id": 10}, - {"id": 11}, - {"id": 12}, + {"id": 1, url: 'url-1', tags: ['xx', 'yy', 'aa'], tags_colors: {xx: "red", yy: "blue", aa: "white"}, colorized_tags: [{name: 'aa', color: 'white'}, {name: 'xx', color: 'red'}, {name: 'yy', color: 'blue'}]}, + {"id": 2, url: 'url-2'}, + {"id": 3, url: 'url-3'}, + {"id": 4, url: 'url-4'}, + {"id": 5, url: 'url-5'}, + {"id": 6, url: 'url-6'}, + {"id": 7, url: 'url-7'}, + {"id": 8, url: 'url-8'}, + {"id": 9, url: 'url-9'}, + {"id": 10, url: 'url-10'}, + {"id": 11, url: 'url-11'}, + {"id": 12, url: 'url-12'} ] it "all & recents filled", () -> - mocks.thenStub.callArg(0, projects) + mocks.thenStub.callArg(0, Immutable.fromJS(projects)) expect(projectsService.currentUserProjects.get("all").toJS()).to.be.eql(projects) expect(projectsService.currentUserProjects.get("recents").toJS()).to.be.eql(projects.slice(0, 10)) @@ -87,7 +88,7 @@ describe "tgProjects", -> it "_inProgress change to false when tgResources end", () -> expect(projectsService._inProgress).to.be.true - mocks.thenStub.callArg(0, projects) + mocks.thenStub.callArg(0, Immutable.fromJS(projects)) mocks.finallyStub.callArg(0) expect(projectsService._inProgress).to.be.false @@ -96,25 +97,25 @@ describe "tgProjects", -> projectsService.fetchProjects() projectsService.fetchProjects() - mocks.thenStub.callArg(0, projects) + mocks.thenStub.callArg(0, Immutable.fromJS(projects)) - expect(mocks.resources.projects.listByMember).have.been.calledOnce + expect(mocks.resources.users.getProjects).have.been.calledOnce it "group projects by id", () -> - mocks.thenStub.callArg(0, projects) + mocks.thenStub.callArg(0, Immutable.fromJS(projects)) expect(projectsService.currentUserProjectsById.size).to.be.equal(12) expect(projectsService.currentUserProjectsById.toJS()[1].id).to.be.equal(projects[0].id) it "add urls in the project object", () -> - mocks.thenStub.callArg(0, projects) + mocks.thenStub.callArg(0, Immutable.fromJS(projects)) expect(projectsService.currentUserProjectsById.toJS()[1].url).to.be.equal("url-1") expect(projectsService.currentUserProjects.get("all").toJS()[0].url).to.be.equal("url-1") expect(projectsService.currentUserProjects.get("recents").toJS()[0].url).to.be.equal("url-1") it "add sorted colorized_tags project object", () -> - mocks.thenStub.callArg(0, projects) + mocks.thenStub.callArg(0, Immutable.fromJS(projects)) tags = [ {name: "aa", color: "white"}, @@ -152,6 +153,7 @@ describe "tgProjects", -> thenStub = sinon.stub() + mocks.resources.projects = {} mocks.resources.projects.bulkUpdateOrder = sinon.stub() mocks.resources.projects.bulkUpdateOrder.withArgs(projects_order).returns({ then: thenStub @@ -164,3 +166,21 @@ describe "tgProjects", -> thenStub.callArg(0) expect(projectsService.fetchProjects).to.have.been.calledOnce + + it "getProjectBySlug", () -> + projectSlug = "project-slug" + + mocks.resources.projects = {} + mocks.resources.projects.getProjectBySlug = sinon.stub() + mocks.resources.projects.getProjectBySlug.withArgs(projectSlug).returns(true) + + expect(projectsService.getProjectBySlug(projectSlug)).to.be.true + + it "getProjectStats", () -> + projectId = 3 + + mocks.resources.projects = {} + mocks.resources.projects.getProjectStats = sinon.stub() + mocks.resources.projects.getProjectStats.withArgs(projectId).returns(true) + + expect(projectsService.getProjectStats(projectId)).to.be.true diff --git a/app/modules/resources/issues-resource.service.coffee b/app/modules/resources/issues-resource.service.coffee new file mode 100644 index 00000000..37213c60 --- /dev/null +++ b/app/modules/resources/issues-resource.service.coffee @@ -0,0 +1,23 @@ +Resource = (urlsService, http) -> + service = {} + + service.listInAllProjects = (params) -> + url = urlsService.resolve("issues") + + httpOptions = { + headers: { + "x-disable-pagination": "1" + } + } + + return http.get(url, params, httpOptions) + .then (result) -> + return Immutable.fromJS(result.data) + + return () -> + return {"issues": service} + +Resource.$inject = ["$tgUrls", "$tgHttp"] + +module = angular.module("taigaResources2") +module.factory("tgIssuesResource", Resource) diff --git a/app/modules/resources/projects-resource.service.coffee b/app/modules/resources/projects-resource.service.coffee new file mode 100644 index 00000000..8c061b23 --- /dev/null +++ b/app/modules/resources/projects-resource.service.coffee @@ -0,0 +1,31 @@ +Resource = (urlsService, http) -> + service = {} + + service.getProjectBySlug = (projectSlug) -> + url = urlsService.resolve("projects") + + url = "#{url}/by_slug?slug=#{projectSlug}" + + return http.get(url) + .then (result) -> + return Immutable.fromJS(result.data) + + service.getProjectStats = (projectId) -> + url = urlsService.resolve("projects") + url = "#{url}/#{projectId}" + + return http.get(url) + .then (result) -> + return Immutable.fromJS(result.data) + + service.bulkUpdateOrder = (bulkData) -> + url = urlsService.resolve("bulk-update-projects-order") + return http.post(url, bulkData) + + return () -> + return {"projects": service} + +Resource.$inject = ["$tgUrls", "$tgHttp"] + +module = angular.module("taigaResources2") +module.factory("tgProjectsResources", Resource) diff --git a/app/modules/resources/resources.coffee b/app/modules/resources/resources.coffee index 197a95c7..b3fecee0 100644 --- a/app/modules/resources/resources.coffee +++ b/app/modules/resources/resources.coffee @@ -1,5 +1,9 @@ services = [ - "tgProjectsResources" + "tgProjectsResources", + "tgUsersResources", + "tgUserstoriesResource", + "tgTasksResource", + "tgIssuesResource" ] Resources = ($injector) -> diff --git a/app/modules/resources/tasks-resource.service.coffee b/app/modules/resources/tasks-resource.service.coffee new file mode 100644 index 00000000..d122cebf --- /dev/null +++ b/app/modules/resources/tasks-resource.service.coffee @@ -0,0 +1,23 @@ +Resource = (urlsService, http) -> + service = {} + + service.listInAllProjects = (params) -> + url = urlsService.resolve("tasks") + + httpOptions = { + headers: { + "x-disable-pagination": "1" + } + } + + return http.get(url, params, httpOptions) + .then (result) -> + return Immutable.fromJS(result.data) + + return () -> + return {"tasks": service} + +Resource.$inject = ["$tgUrls", "$tgHttp"] + +module = angular.module("taigaResources2") +module.factory("tgTasksResource", Resource) diff --git a/app/modules/resources/users-resource.service.coffee b/app/modules/resources/users-resource.service.coffee index 40abd63c..8587e7e1 100644 --- a/app/modules/resources/users-resource.service.coffee +++ b/app/modules/resources/users-resource.service.coffee @@ -43,4 +43,4 @@ Resource = (urlsService, http) -> Resource.$inject = ["$tgUrls", "$tgHttp"] module = angular.module("taigaResources2") -module.factory("tgProjectsResources", Resource) +module.factory("tgUsersResources", Resource) diff --git a/app/modules/resources/userstories-resource.service.coffee b/app/modules/resources/userstories-resource.service.coffee new file mode 100644 index 00000000..dccd35fd --- /dev/null +++ b/app/modules/resources/userstories-resource.service.coffee @@ -0,0 +1,23 @@ +Resource = (urlsService, http) -> + service = {} + + service.listInAllProjects = (params) -> + url = urlsService.resolve("userstories") + + httpOptions = { + headers: { + "x-disable-pagination": "1" + } + } + + return http.get(url, params, httpOptions) + .then (result) -> + return Immutable.fromJS(result.data) + + return () -> + return {"userstories": service} + +Resource.$inject = ["$tgUrls", "$tgHttp"] + +module = angular.module("taigaResources2") +module.factory("tgUserstoriesResource", Resource) diff --git a/karma.conf.js b/karma.conf.js index 3487e4cf..bc96834e 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -32,7 +32,7 @@ module.exports = function(config) { // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { - '**/*.coffee': ['coffee'], + '**/*.spec.coffee': ['coffee'], 'dist/js/app.js': ['sourcemap'] }, From 41d4625a4456ab8cf9a13a8d4c61c48de66a2dda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 12 May 2015 14:24:59 +0200 Subject: [PATCH 146/366] Add links to he timeline images --- .../profile-timeline-attachment-image.jade | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment-image.jade b/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment-image.jade index f1ee5b4a..14f30f14 100644 --- a/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment-image.jade +++ b/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment-image.jade @@ -1,4 +1,5 @@ // timeline-attachment directive div.activity-image-attachment blockquote - img(ng-src="{{::attachment.url}}", alt="{{::attachment.filename}}") + a(href="{{::attachment.url}}", title="See {{::attachment.filename}}") + img(ng-src="{{::attachment.url}}", alt="{{::attachment.filename}}") From bdf9be9b46863b873ca857934b4864fdc4148be6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 12 May 2015 14:25:27 +0200 Subject: [PATCH 147/366] Improve profile bar design hyerarchy --- app/modules/profile/styles/profile-bar.scss | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/app/modules/profile/styles/profile-bar.scss b/app/modules/profile/styles/profile-bar.scss index 15d5d1bb..e9a8d490 100644 --- a/app/modules/profile/styles/profile-bar.scss +++ b/app/modules/profile/styles/profile-bar.scss @@ -79,20 +79,17 @@ } } } - h1, - h2 { - @extend %bold; - @extend %small; - } h1 { + @extend %bold; @extend %xlarge; line-height: 1; margin-bottom: 0; text-transform: none; } h2 { - @extend %large; - color: $gray-light; + @extend %light; + @extend %larger; + color: $gray; line-height: 1.2; margin-bottom: 1rem; } From 0efc0f829e84c3d0d7a35a5d46fdf3f42859b1e1 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 12 May 2015 14:28:59 +0200 Subject: [PATCH 148/366] fix tags --- app/modules/profile/profile-projects/profile-projects.jade | 2 +- .../profile-timeline-item-title.service.coffee | 2 +- app/modules/projects/listing/projects-listing.jade | 3 ++- app/modules/projects/projects.service.coffee | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/modules/profile/profile-projects/profile-projects.jade b/app/modules/profile/profile-projects/profile-projects.jade index 10774900..b5c015a9 100644 --- a/app/modules/profile/profile-projects/profile-projects.jade +++ b/app/modules/profile/profile-projects/profile-projects.jade @@ -7,7 +7,7 @@ section.profile-projects a(href="#", tg-nav="project:project=project.get('slug')", title="{{ ::project.get('name') }}") {{::project.get('name')}} p {{ ::project.get('description') | limitTo:300 }} - div.project-list-single-tags.tags-container + div.project-list-single-tags.tags-container(ng-if="::project.get('tags').size") span.tag(style='border-left: 5px solid {{::tag.get("color")}};', tg-repeat="tag in ::project.get('colorized_tags')") span.tag-name {{::tag.get('name')}} diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.coffee b/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.coffee index be1ecdbb..b927fc1b 100644 --- a/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.coffee +++ b/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.coffee @@ -22,7 +22,7 @@ class ProfileTimelineItemTitle user = timeline.data.user title_attr = @translate.instant('COMMON.SEE_USER_PROFILE', {username: user.username}) url = 'user-profile:username=vm.activity.user.username' - console.log(user) + return @._getLink(url, user.name, title_attr) else if param == 'field_name' diff --git a/app/modules/projects/listing/projects-listing.jade b/app/modules/projects/listing/projects-listing.jade index 2dfaa4c7..68e58723 100644 --- a/app/modules/projects/listing/projects-listing.jade +++ b/app/modules/projects/listing/projects-listing.jade @@ -23,7 +23,8 @@ div.project-list-wrapper.centered span(ng-if="::project.get('description').length > 300") ... div.project-list-single-tags.tags-container(ng-if="::project.get('tags').size") - div.tags-block(tg-colorize-tags="project.get('tags')", tg-colorize-tags-type="backlog") + span.tag(style='border-left: 5px solid {{::tag.get("color")}};', tg-repeat="tag in ::project.get('colorized_tags')") + span.tag-name {{::tag.get('name')}} div.project-list-single-right span.drag.icon.icon-drag-v diff --git a/app/modules/projects/projects.service.coffee b/app/modules/projects/projects.service.coffee index ff1c4e40..a097ca9a 100644 --- a/app/modules/projects/projects.service.coffee +++ b/app/modules/projects/projects.service.coffee @@ -41,7 +41,7 @@ class ProjectsService extends taiga.Service colorized_tags = tags.map (tag) -> color = project.get("tags_colors").get(tag) - return {name: tag, color: color} + return Immutable.fromJS({name: tag, color: color}) project = project.set("colorized_tags", colorized_tags) From 8f3e512f6e8bec072d751e64fe217287739a7056 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 12 May 2015 14:36:06 +0200 Subject: [PATCH 149/366] fix timeline item title service test --- .../profile-timeline-item-title.service.spec.coffee | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.spec.coffee b/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.spec.coffee index f9def537..fe9586f8 100644 --- a/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.spec.coffee +++ b/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.spec.coffee @@ -35,7 +35,8 @@ describe "tgProfileTimelineItemTitle", -> timeline = { data: { user: { - username: 'xx' + username: 'xx', + name: 'oo' } } } @@ -52,7 +53,7 @@ describe "tgProfileTimelineItemTitle", -> .returns('user-param') usernamelink = sinon.match ((value) -> - return value.username == 'xx' + return value.username == 'oo' ), "usernamelink" mockTranslate.instant From 08b731baa069903b4b209a7998b323bd202d3829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 12 May 2015 15:18:28 +0200 Subject: [PATCH 150/366] Minor fixes on timeline --- .../profile/profile-bar/profile-bar.jade | 5 +-- .../profile-timeline-attachment-image.jade | 2 +- .../profile-timeline-attachment.jade | 2 +- app/modules/profile/styles/profile-bar.scss | 45 ++++++------------- 4 files changed, 17 insertions(+), 37 deletions(-) diff --git a/app/modules/profile/profile-bar/profile-bar.jade b/app/modules/profile/profile-bar/profile-bar.jade index 2c9c38a3..55ba3153 100644 --- a/app/modules/profile/profile-bar/profile-bar.jade +++ b/app/modules/profile/profile-bar/profile-bar.jade @@ -1,10 +1,7 @@ section.profile-bar div.profile-image-wrapper img.profile-img(ng-src="{{::vm.user.big_photo}}", alt="{{::vm.user.full_name}}") - div.edit-profile(title="{{ 'USER.PROFILE.EDIT' | translate }}") - a.profile-edition(title="{{ 'USER.PROFILE.EDIT' | translate }}", tg-nav="user-settings-user-profile") - span.icon.icon-edit - span(translate="USER.PROFILE.EDIT") + a.profile-edition(title="{{ 'USER.PROFILE.EDIT' | translate }}", tg-nav="user-settings-user-profile", translate="USER.PROFILE.EDIT") // a.button-green // span(translate="USER.PROFILE.FOLLOW") div.profile-data diff --git a/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment-image.jade b/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment-image.jade index 14f30f14..6a5c2c16 100644 --- a/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment-image.jade +++ b/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment-image.jade @@ -1,5 +1,5 @@ // timeline-attachment directive div.activity-image-attachment blockquote - a(href="{{::attachment.url}}", title="See {{::attachment.filename}}") + a(href="{{::attachment.url}}", title="See {{::attachment.filename}}", target="_blank") img(ng-src="{{::attachment.url}}", alt="{{::attachment.filename}}") diff --git a/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.jade b/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.jade index f8639519..cdac755e 100644 --- a/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.jade +++ b/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.jade @@ -1,5 +1,5 @@ div.single-attachment blockquote - a(ng-href="{{ attachment.url }}", title="Click to download {{ attachment.filename }}") + a(ng-href="{{ attachment.url }}", title="Click to download {{ attachment.filename }}", target="_blank") span.icon.icon-document span {{attachment.filename}} diff --git a/app/modules/profile/styles/profile-bar.scss b/app/modules/profile/styles/profile-bar.scss index e9a8d490..713dae40 100644 --- a/app/modules/profile/styles/profile-bar.scss +++ b/app/modules/profile/styles/profile-bar.scss @@ -9,53 +9,36 @@ filter: brightness(40%) saturate(150%) hue-rotate(60deg); transition: all .2s cubic-bezier(.01, .7, 1, 1); } - .edit-profile { + .profile-edition { opacity: 1; - transition: opacity .2s cubic-bezier(.01, .7, 1, 1); + transform: translateY(0); + transition: all .2s cubic-bezier(.01, .7, 1, 1); + transition-delay: .3s; } } } .profile-img { max-width: 100%; } - .edit-profile { + .profile-edition { @extend %large; + @extend %light; + background: rgba($black, .4); bottom: 0; + color: $white; left: 0; opacity: 0; + overflow: hidden; + padding: 1rem; position: absolute; - right: 0; - top: 0; + transform: translateY(100%); + width: 100%; &:hover { - .profile-edition { - opacity: 1; - transition: all .2s cubic-bezier(.01, .7, 1, 1); - transition-delay: .2s; - } + background: rgba($black, .8); + transition: all .3s cubic-bezier(.01, .7, 1, 1); } } - .profile-edition { - @extend %small; - background: rgba($white, .1); - color: $white; - height: 30px; - left: calc(50% - 55px); - opacity: 0; - padding: .4rem; - position: absolute; - top: calc(50% - 5px); - width: 110px; - .icon-edit { - color: $white; - margin-right: .3rem; - } - &:hover { - background: rgba($white, .4); - color: $white; - transition: all .2s cubic-bezier(.77, .01, .97, .85); - } - } .button-green { display: block; margin-bottom: 1rem; From 280a209008b3d3960cdd4f9e06aa07aa9bf05dbf Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 12 May 2015 15:25:05 +0200 Subject: [PATCH 151/366] Refreshing avatar image --- app/coffee/modules/auth.coffee | 6 ++++++ .../dropdown-user/dropdown-user.directive.coffee | 3 ++- app/modules/navigation-bar/dropdown-user/dropdown-user.jade | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/coffee/modules/auth.coffee b/app/coffee/modules/auth.coffee index 7735eb04..4e9e884b 100644 --- a/app/coffee/modules/auth.coffee +++ b/app/coffee/modules/auth.coffee @@ -40,6 +40,11 @@ class AuthService extends taiga.Service constructor: (@rootscope, @storage, @model, @rs, @http, @urls, @config, @translate) -> super() + userModel = @.getUser() + @.setUserdata(userModel) + + setUserdata: (userModel) -> + @.userData = Immutable.fromJS(userModel.getAttrs()) _setLocales: -> lang = @rootscope.user.lang || @config.get("defaultLanguage") || "en" @@ -62,6 +67,7 @@ class AuthService extends taiga.Service @rootscope.auth = user @storage.set("userInfo", user.getAttrs()) @rootscope.user = user + @.setUserdata(user) @._setLocales() diff --git a/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee b/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee index 18f46138..82da69f9 100644 --- a/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee +++ b/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee @@ -3,8 +3,9 @@ DropdownUserDirective = (authService, configService, locationService, link = (scope, el, attrs, ctrl) -> scope.vm = {} - scope.vm.user = authService.getUser() + #scope.vm.user = authService.user scope.vm.isFeedbackEnabled = configService.get("feedbackEnabled") + taiga.defineImmutableProperty(scope.vm, "user", () -> authService.userData) scope.vm.logout = -> authService.logout() diff --git a/app/modules/navigation-bar/dropdown-user/dropdown-user.jade b/app/modules/navigation-bar/dropdown-user/dropdown-user.jade index 6f718291..2eab692b 100644 --- a/app/modules/navigation-bar/dropdown-user/dropdown-user.jade +++ b/app/modules/navigation-bar/dropdown-user/dropdown-user.jade @@ -1,5 +1,5 @@ -a.user-avatar(tg-nav="profile", title="{{ vm.user.full_name_display }}") {{ vm.user.full_name_display }} - img(tg-bo-src="vm.user.photo", alt="{{ vm.user.full_name_display }}") +a.user-avatar(tg-nav="profile", title="{{ vm.user.get('full_name_display') }}") {{ vm.user.get('full_name_display') }} + img(ng-src="{{ vm.user.get('photo') }}", alt="{{ vm.user.get('full_name_display') }}") div.navbar-dropdown.dropdown-user ul From b6d04a70b799b1169e30a40603c62c85d43f66e6 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 12 May 2015 16:03:23 +0200 Subject: [PATCH 152/366] rename profile-timeline to user-timeline --- app/coffee/app.coffee | 2 + app/coffee/modules/resources.coffee | 1 - app/coffee/modules/resources/timeline.coffee | 39 ------------- .../profile-timeline-item.directive.coffee | 13 ----- .../profile-timeline.directive.coffee | 9 --- .../profile-timeline.service.coffee | 57 ------------------ app/modules/profile/profile.jade | 2 +- .../resources/users-resource.service.coffee | 11 ++++ .../user-timeline-attachment-image.jade} | 0 ...user-timeline-attachment.directive.coffee} | 14 ++--- ...timeline-attachment.directive.spec.coffee} | 10 ++-- .../user-timeline-attachment.jade} | 0 .../user-timeline-item-title.service.coffee} | 6 +- ...r-timeline-item-title.service.spec.coffee} | 8 +-- .../user-timeline-item-type.service.coffee} | 6 +- ...er-timeline-item-type.service.spec.coffee} | 8 +-- .../user-timeline-item.controller.coffee} | 16 ++--- ...user-timeline-item.controller.spec.coffee} | 32 +++++----- .../user-timeline-item.directive.coffee | 13 +++++ .../user-timeline-item.jade} | 2 +- .../user-timeline/user-timeline.module.coffee | 1 + .../user-timeline.controller.coffee} | 12 ++-- .../user-timeline.controller.spec.coffee} | 18 +++--- .../user-timeline.directive.coffee | 9 +++ .../user-timeline/user-timeline.jade} | 2 +- .../user-timeline/user-timeline.scss} | 0 .../user-timeline.service.coffee | 58 +++++++++++++++++++ .../user-timeline.service.spec.coffee} | 32 +++++----- 28 files changed, 176 insertions(+), 205 deletions(-) delete mode 100644 app/coffee/modules/resources/timeline.coffee delete mode 100644 app/modules/profile/profile-timeline-item/profile-timeline-item.directive.coffee delete mode 100644 app/modules/profile/profile-timeline/profile-timeline.directive.coffee delete mode 100644 app/modules/profile/profile-timeline/profile-timeline.service.coffee rename app/modules/{profile/profile-timeline-attachment/profile-timeline-attachment-image.jade => user-timeline/user-timeline-attachment/user-timeline-attachment-image.jade} (100%) rename app/modules/{profile/profile-timeline-attachment/profile-timeline-attachment.directive.coffee => user-timeline/user-timeline-attachment/user-timeline-attachment.directive.coffee} (53%) rename app/modules/{profile/profile-timeline-attachment/profile-timeline-attachment.directive.spec.coffee => user-timeline/user-timeline-attachment/user-timeline-attachment.directive.spec.coffee} (76%) rename app/modules/{profile/profile-timeline-attachment/profile-timeline-attachment.jade => user-timeline/user-timeline-attachment/user-timeline-attachment.jade} (100%) rename app/modules/{profile/profile-timeline-item/profile-timeline-item-title.service.coffee => user-timeline/user-timeline-item/user-timeline-item-title.service.coffee} (95%) rename app/modules/{profile/profile-timeline-item/profile-timeline-item-title.service.spec.coffee => user-timeline/user-timeline-item/user-timeline-item-title.service.spec.coffee} (97%) rename app/modules/{profile/profile-timeline-item/profile-timeline-item-type.service.coffee => user-timeline/user-timeline-item/user-timeline-item-type.service.coffee} (98%) rename app/modules/{profile/profile-timeline-item/profile-timeline-item-type.service.spec.coffee => user-timeline/user-timeline-item/user-timeline-item-type.service.spec.coffee} (74%) rename app/modules/{profile/profile-timeline-item/profile-timeline-item.controller.coffee => user-timeline/user-timeline-item/user-timeline-item.controller.coffee} (70%) rename app/modules/{profile/profile-timeline-item/profile-timeline-item.controller.spec.coffee => user-timeline/user-timeline-item/user-timeline-item.controller.spec.coffee} (69%) create mode 100644 app/modules/user-timeline/user-timeline-item/user-timeline-item.directive.coffee rename app/modules/{profile/profile-timeline-item/profile-timeline-item.jade => user-timeline/user-timeline-item/user-timeline-item.jade} (94%) create mode 100644 app/modules/user-timeline/user-timeline.module.coffee rename app/modules/{profile/profile-timeline/profile-timeline.controller.coffee => user-timeline/user-timeline/user-timeline.controller.coffee} (81%) rename app/modules/{profile/profile-timeline/profile-timeline.controller.spec.coffee => user-timeline/user-timeline/user-timeline.controller.spec.coffee} (82%) create mode 100644 app/modules/user-timeline/user-timeline/user-timeline.directive.coffee rename app/modules/{profile/profile-timeline/profile-timeline.jade => user-timeline/user-timeline/user-timeline.jade} (61%) rename app/modules/{profile/profile-timeline/profile-timeline.scss => user-timeline/user-timeline/user-timeline.scss} (100%) create mode 100644 app/modules/user-timeline/user-timeline/user-timeline.service.coffee rename app/modules/{profile/profile-timeline/profile-timeline.service.spec.coffee => user-timeline/user-timeline/user-timeline.service.spec.coffee} (79%) diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 748a5b4a..6e83343a 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -392,9 +392,11 @@ modules = [ "taigaPlugins", "taigaIntegrations", "taigaComponents", + # new modules "taigaProfile", "taigaHome", "taigaPage", + "taigaUserTimeline", # template cache "templates", diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index 4631a432..69730a00 100644 --- a/app/coffee/modules/resources.coffee +++ b/app/coffee/modules/resources.coffee @@ -195,7 +195,6 @@ module.run([ "$tgWebhooksResourcesProvider", "$tgWebhookLogsResourcesProvider", "$tgLocalesResourcesProvider", - "$tgTimelineResourcesProvider", "$tgUsersResourcesProvider", initResources ]) diff --git a/app/coffee/modules/resources/timeline.coffee b/app/coffee/modules/resources/timeline.coffee deleted file mode 100644 index 31529120..00000000 --- a/app/coffee/modules/resources/timeline.coffee +++ /dev/null @@ -1,39 +0,0 @@ -### -# 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/resources/timeline.coffee -### - -taiga = @.taiga - -resourceProvider = ($repo) -> - service = {} - - service.profile = (userId, page) -> - params = { - page: page - } - - return $repo.queryOnePaginatedRaw("timeline-profile", userId, params) - - return (instance) -> - instance.timeline = service - - -module = angular.module("taigaResources") -module.factory("$tgTimelineResourcesProvider", ["$tgRepo", resourceProvider]) diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item.directive.coffee b/app/modules/profile/profile-timeline-item/profile-timeline-item.directive.coffee deleted file mode 100644 index d15cf6b9..00000000 --- a/app/modules/profile/profile-timeline-item/profile-timeline-item.directive.coffee +++ /dev/null @@ -1,13 +0,0 @@ -ProfileTimelineItemDirective = () -> - return { - controllerAs: "vm" - controller: "ProfileTimelineItem" - bindToController: true - templateUrl: "profile/profile-timeline-item/profile-timeline-item.html" - scope: { - timeline: "=tgProfileTimelineItem" - } - } - -angular.module("taigaProfile") - .directive("tgProfileTimelineItem", ProfileTimelineItemDirective) diff --git a/app/modules/profile/profile-timeline/profile-timeline.directive.coffee b/app/modules/profile/profile-timeline/profile-timeline.directive.coffee deleted file mode 100644 index ae4ba91d..00000000 --- a/app/modules/profile/profile-timeline/profile-timeline.directive.coffee +++ /dev/null @@ -1,9 +0,0 @@ -ProfileTimelineDirective = -> - return { - templateUrl: "profile/profile-timeline/profile-timeline.html", - controller: "ProfileTimeline", - controllerAs: "vm", - scope: {} - } - -angular.module("taigaProfile").directive("tgProfileTimeline", ProfileTimelineDirective) diff --git a/app/modules/profile/profile-timeline/profile-timeline.service.coffee b/app/modules/profile/profile-timeline/profile-timeline.service.coffee deleted file mode 100644 index b6c99236..00000000 --- a/app/modules/profile/profile-timeline/profile-timeline.service.coffee +++ /dev/null @@ -1,57 +0,0 @@ -taiga = @.taiga - -class ProfileTimelineService extends taiga.Service - @.$inject = ["$tgResources"] - - constructor: (@rs) -> - - _valid_fields: [ - 'status', - 'subject', - 'description', - 'assigned_to', - 'points', - 'severity', - 'priority', - 'type', - 'attachments', - 'milestone', - 'is_blocked', - 'is_iocaine', - 'content_diff', - 'name', - 'estimated_finish', - 'estimated_start' - ] - - _isValidField: (values) -> - return _.some values, (value) => @._valid_fields.indexOf(value) != -1 - - _isValidEvent: (event) -> - return event.split(".").slice(-1)[0] != 'delete' - - _filterValidTimelineItems: (timeline) => - if timeline.data.values_diff - values = Object.keys(timeline.data.values_diff) - - if values && values.length - if !@._isValidField(values) - return false - else if values[0] == 'attachments' && - timeline.data.values_diff.attachments.new.length == 0 - return false - - if !@._isValidEvent(timeline.event_type) - return false - - return true - - getTimeline: (userId, page) -> - return @rs.timeline.profile(userId, page) - .then (result) => - newTimelineList = _.filter result.data, @._filterValidTimelineItems - - return Immutable.fromJS(newTimelineList) - - -angular.module("taigaProjects").service("tgProfileTimelineService", ProfileTimelineService) diff --git a/app/modules/profile/profile.jade b/app/modules/profile/profile.jade index 07059af6..5183265d 100644 --- a/app/modules/profile/profile.jade +++ b/app/modules/profile/profile.jade @@ -4,7 +4,7 @@ div.profile.centered div.main div.timeline-wrapper(tg-profile-tabs) div(tg-profile-tab="activity", tab-title="{{'USER.PROFILE.ACTIVITY_TAB' | translate}}", tab-icon="icon-timeline", tab-active) - div(tg-profile-timeline) + div(tg-user-timeline) div(tg-profile-tab="projects", tab-title="{{'USER.PROFILE.PROJECTS_TAB' | translate}}", tab-icon="icon-project") div(tg-profile-projects) diff --git a/app/modules/resources/users-resource.service.coffee b/app/modules/resources/users-resource.service.coffee index 8587e7e1..8badf44f 100644 --- a/app/modules/resources/users-resource.service.coffee +++ b/app/modules/resources/users-resource.service.coffee @@ -37,6 +37,17 @@ Resource = (urlsService, http) -> .then (result) -> return Immutable.fromJS(result.data) + service.getTimeline = (userId, page) -> + params = { + page: page + } + + url = urlsService.resolve("timeline-profile") + url = "#{url}/#{userId}" + + return http.get(url, params).then (result) => + return Immutable.fromJS(result.data) + return () -> return {"users": service} diff --git a/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment-image.jade b/app/modules/user-timeline/user-timeline-attachment/user-timeline-attachment-image.jade similarity index 100% rename from app/modules/profile/profile-timeline-attachment/profile-timeline-attachment-image.jade rename to app/modules/user-timeline/user-timeline-attachment/user-timeline-attachment-image.jade diff --git a/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.directive.coffee b/app/modules/user-timeline/user-timeline-attachment/user-timeline-attachment.directive.coffee similarity index 53% rename from app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.directive.coffee rename to app/modules/user-timeline/user-timeline-attachment/user-timeline-attachment.directive.coffee index e733a089..10e438a4 100644 --- a/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.directive.coffee +++ b/app/modules/user-timeline/user-timeline-attachment/user-timeline-attachment.directive.coffee @@ -1,4 +1,4 @@ -ProfileTimelineAttachmentDirective = (template, $compile) -> +UserTimelineAttachmentDirective = (template, $compile) -> validFileExtensions = [".jpg", ".jpeg", ".bmp", ".gif", ".png"] isImage = (url) -> @@ -11,9 +11,9 @@ ProfileTimelineAttachmentDirective = (template, $compile) -> is_image = isImage(scope.attachment.url) if is_image - templateHtml = template.get("profile/profile-timeline-attachment/profile-timeline-attachment-image.html") + templateHtml = template.get("user-timeline/user-timeline-attachment/user-timeline-attachment-image.html") else - templateHtml = template.get("profile/profile-timeline-attachment/profile-timeline-attachment.html") + templateHtml = template.get("user-timeline/user-timeline-attachment/user-timeline-attachment.html") el.html(templateHtml) $compile(el.contents())(scope) @@ -23,14 +23,14 @@ ProfileTimelineAttachmentDirective = (template, $compile) -> return { link: link scope: { - attachment: "=tgProfileTimelineAttachment" + attachment: "=tgUserTimelineAttachment" } } -ProfileTimelineAttachmentDirective.$inject = [ +UserTimelineAttachmentDirective.$inject = [ "$tgTemplate", "$compile" ] -angular.module("taigaProfile") - .directive("tgProfileTimelineAttachment", ProfileTimelineAttachmentDirective) +angular.module("taigaUserTimeline") + .directive("tgUserTimelineAttachment", UserTimelineAttachmentDirective) diff --git a/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.directive.spec.coffee b/app/modules/user-timeline/user-timeline-attachment/user-timeline-attachment.directive.spec.coffee similarity index 76% rename from app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.directive.spec.coffee rename to app/modules/user-timeline/user-timeline-attachment/user-timeline-attachment.directive.spec.coffee index c2e24a9d..37a3f927 100644 --- a/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.directive.spec.coffee +++ b/app/modules/user-timeline/user-timeline-attachment/user-timeline-attachment.directive.spec.coffee @@ -1,7 +1,7 @@ -describe "profileTimelineAttachmentDirective", () -> +describe "userTimelineAttachmentDirective", () -> element = scope = compile = provide = null mockTgTemplate = null - template = "
" + template = "
" _mockTgTemplate= () -> mockTgTemplate = { @@ -23,7 +23,7 @@ describe "profileTimelineAttachmentDirective", () -> return elm beforeEach -> - module "taigaProfile" + module "taigaUserTimeline" _mocks() @@ -37,7 +37,7 @@ describe "profileTimelineAttachmentDirective", () -> } mockTgTemplate.get - .withArgs("profile/profile-timeline-attachment/profile-timeline-attachment-image.html") + .withArgs("user-timeline/user-timeline-attachment/user-timeline-attachment-image.html") .returns("
") elm = createDirective() @@ -50,7 +50,7 @@ describe "profileTimelineAttachmentDirective", () -> } mockTgTemplate.get - .withArgs("profile/profile-timeline-attachment/profile-timeline-attachment.html") + .withArgs("user-timeline/user-timeline-attachment/user-timeline-attachment.html") .returns("
") elm = createDirective() diff --git a/app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.jade b/app/modules/user-timeline/user-timeline-attachment/user-timeline-attachment.jade similarity index 100% rename from app/modules/profile/profile-timeline-attachment/profile-timeline-attachment.jade rename to app/modules/user-timeline/user-timeline-attachment/user-timeline-attachment.jade diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.coffee b/app/modules/user-timeline/user-timeline-item/user-timeline-item-title.service.coffee similarity index 95% rename from app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.coffee rename to app/modules/user-timeline/user-timeline-item/user-timeline-item-title.service.coffee index b927fc1b..9de3e72d 100644 --- a/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.coffee +++ b/app/modules/user-timeline/user-timeline-item/user-timeline-item-title.service.coffee @@ -1,4 +1,4 @@ -class ProfileTimelineItemTitle +class UserTimelineItemTitle @.$inject = [ "$translate" ] @@ -87,5 +87,5 @@ class ProfileTimelineItemTitle getTitle: (timeline, event, type) -> return @translate.instant(type.key, @._getParams(timeline, event, type)) -angular.module("taigaProfile") - .service("tgProfileTimelineItemTitle", ProfileTimelineItemTitle) +angular.module("taigaUserTimeline") + .service("tgUserTimelineItemTitle", UserTimelineItemTitle) diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.spec.coffee b/app/modules/user-timeline/user-timeline-item/user-timeline-item-title.service.spec.coffee similarity index 97% rename from app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.spec.coffee rename to app/modules/user-timeline/user-timeline-item/user-timeline-item-title.service.spec.coffee index fe9586f8..a28fdcd6 100644 --- a/app/modules/profile/profile-timeline-item/profile-timeline-item-title.service.spec.coffee +++ b/app/modules/user-timeline/user-timeline-item/user-timeline-item-title.service.spec.coffee @@ -1,4 +1,4 @@ -describe "tgProfileTimelineItemTitle", -> +describe "tgUserTimelineItemTitle", -> mySvc = null mockTranslate = null timeline = event = type = null @@ -20,15 +20,15 @@ describe "tgProfileTimelineItemTitle", -> _mockTranslate() _inject = -> - inject (_tgProfileTimelineItemTitle_) -> - mySvc = _tgProfileTimelineItemTitle_ + inject (_tgUserTimelineItemTitle_) -> + mySvc = _tgUserTimelineItemTitle_ _setup = -> _mocks() _inject() beforeEach -> - module "taigaProfile" + module "taigaUserTimeline" _setup() it "title with username", () -> diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item-type.service.coffee b/app/modules/user-timeline/user-timeline-item/user-timeline-item-type.service.coffee similarity index 98% rename from app/modules/profile/profile-timeline-item/profile-timeline-item-type.service.coffee rename to app/modules/user-timeline/user-timeline-item/user-timeline-item-type.service.coffee index 29987438..ba7f60b3 100644 --- a/app/modules/profile/profile-timeline-item/profile-timeline-item-type.service.coffee +++ b/app/modules/user-timeline/user-timeline-item/user-timeline-item-type.service.coffee @@ -150,8 +150,8 @@ timelineType = (timeline, event) -> return _.find types, (obj) -> return obj.check(timeline, event, field_name) -class ProfileTimelineType +class UserTimelineType getType: (timeline, event) -> timelineType(timeline, event) -angular.module("taigaProfile") - .service("tgProfileTimelineItemType", ProfileTimelineType) +angular.module("taigaUserTimeline") + .service("tgUserTimelineItemType", UserTimelineType) diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item-type.service.spec.coffee b/app/modules/user-timeline/user-timeline-item/user-timeline-item-type.service.spec.coffee similarity index 74% rename from app/modules/profile/profile-timeline-item/profile-timeline-item-type.service.spec.coffee rename to app/modules/user-timeline/user-timeline-item/user-timeline-item-type.service.spec.coffee index ecc15084..5198f07c 100644 --- a/app/modules/profile/profile-timeline-item/profile-timeline-item-type.service.spec.coffee +++ b/app/modules/user-timeline/user-timeline-item/user-timeline-item-type.service.spec.coffee @@ -1,4 +1,4 @@ -describe "tgProfileTimelineItemType", -> +describe "tgUserTimelineItemType", -> mySvc = null _provide = (callback) -> @@ -7,14 +7,14 @@ describe "tgProfileTimelineItemType", -> return null _inject = -> - inject (_tgProfileTimelineItemType_) -> - mySvc = _tgProfileTimelineItemType_ + inject (_tgUserTimelineItemType_) -> + mySvc = _tgUserTimelineItemType_ _setup = -> _inject() beforeEach -> - module "taigaProfile" + module "taigaUserTimeline" _setup() it "get the timeline type", () -> diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.coffee b/app/modules/user-timeline/user-timeline-item/user-timeline-item.controller.coffee similarity index 70% rename from app/modules/profile/profile-timeline-item/profile-timeline-item.controller.coffee rename to app/modules/user-timeline/user-timeline-item/user-timeline-item.controller.coffee index f17bcfeb..8da84bc5 100644 --- a/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.coffee +++ b/app/modules/user-timeline/user-timeline-item/user-timeline-item.controller.coffee @@ -1,21 +1,21 @@ -class ProfileTimelineItemController +class UserTimelineItemController @.$inject = [ "$sce", - "tgProfileTimelineItemType", - "tgProfileTimelineItemTitle" + "tgUserTimelineItemType", + "tgUserTimelineItemTitle" ] - constructor: (@sce, @profileTimelineItemType, @profileTimelineItemTitle) -> + constructor: (@sce, @userTimelineItemType, @userTimelineItemTitle) -> timeline = @.timeline.toJS() event = @.parseEventType(timeline.event_type) - type = @profileTimelineItemType.getType(timeline, event) + type = @userTimelineItemType.getType(timeline, event) @.activity = {} @.activity.user = timeline.data.user @.activity.project = timeline.data.project @.activity.sprint = timeline.data.milestone - @.activity.title = @profileTimelineItemTitle.getTitle(timeline, event, type) + @.activity.title = @userTimelineItemTitle.getTitle(timeline, event, type) @.activity.created_formated = moment(timeline.created).fromNow() @.activity.obj = @.getObject(timeline, event) @@ -41,5 +41,5 @@ class ProfileTimelineItemController if timeline.data[event.obj] return timeline.data[event.obj] -angular.module("taigaProfile") - .controller("ProfileTimelineItem", ProfileTimelineItemController) +angular.module("taigaUserTimeline") + .controller("UserTimelineItem", UserTimelineItemController) diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.spec.coffee b/app/modules/user-timeline/user-timeline-item/user-timeline-item.controller.spec.coffee similarity index 69% rename from app/modules/profile/profile-timeline-item/profile-timeline-item.controller.spec.coffee rename to app/modules/user-timeline/user-timeline-item/user-timeline-item.controller.spec.coffee index 7b127951..1ef9b7c3 100644 --- a/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.spec.coffee +++ b/app/modules/user-timeline/user-timeline-item/user-timeline-item.controller.spec.coffee @@ -1,12 +1,12 @@ -describe "ProfileTimelineItemController", -> +describe "UserTimelineItemController", -> controller = scope = provide = null timeline = event = null - mockTgProfileTimelineItemType = null - mockTgProfileTimelineItemTitle = null + mockTgUserTimelineItemType = null + mockTgUserTimelineItemTitle = null mockType = null - _mockTgProfileTimelineItemType = () -> - mockTgProfileTimelineItemType = { + _mockTgUserTimelineItemType = () -> + mockTgUserTimelineItemType = { getType: sinon.stub() } @@ -15,24 +15,24 @@ describe "ProfileTimelineItemController", -> member: sinon.stub() } - mockTgProfileTimelineItemType.getType.withArgs(timeline).returns(mockType) + mockTgUserTimelineItemType.getType.withArgs(timeline).returns(mockType) - provide.value "tgProfileTimelineItemType", mockTgProfileTimelineItemType + provide.value "tgUserTimelineItemType", mockTgUserTimelineItemType - _mockTgProfileTimelineItemTitle = () -> - mockTgProfileTimelineItemTitle = { + _mockTgUserTimelineItemTitle = () -> + mockTgUserTimelineItemTitle = { getTitle: sinon.stub() } - mockTgProfileTimelineItemTitle.getTitle.withArgs(timeline, event, mockType).returns("fakeTitle") + mockTgUserTimelineItemTitle.getTitle.withArgs(timeline, event, mockType).returns("fakeTitle") - provide.value "tgProfileTimelineItemTitle", mockTgProfileTimelineItemTitle + provide.value "tgUserTimelineItemTitle", mockTgUserTimelineItemTitle _mocks = () -> module ($provide) -> provide = $provide - _mockTgProfileTimelineItemType() - _mockTgProfileTimelineItemTitle() + _mockTgUserTimelineItemType() + _mockTgUserTimelineItemTitle() return null @@ -61,7 +61,7 @@ describe "ProfileTimelineItemController", -> } beforeEach -> - module "taigaProfile" + module "taigaUserTimeline" _setup() _mocks() @@ -73,7 +73,7 @@ describe "ProfileTimelineItemController", -> timeline = scope.vm.timeline timeline_immutable = Immutable.fromJS(timeline) - myCtrl = controller("ProfileTimelineItem", {$scope: scope}, {timeline: timeline_immutable}) + myCtrl = controller("UserTimelineItem", {$scope: scope}, {timeline: timeline_immutable}) expect(myCtrl.activity.user).to.be.equal(timeline.data.user) expect(myCtrl.activity.project).to.be.equal(timeline.data.project) @@ -97,7 +97,7 @@ describe "ProfileTimelineItemController", -> timeline_immutable = Immutable.fromJS(timeline) - myCtrl = controller("ProfileTimelineItem", {$scope: scope}, {timeline: timeline_immutable}) + myCtrl = controller("UserTimelineItem", {$scope: scope}, {timeline: timeline_immutable}) expect(myCtrl.activity.description).to.be.an('object') # $sce.trustAsHtml expect(myCtrl.activity.member).to.be.equal(member) diff --git a/app/modules/user-timeline/user-timeline-item/user-timeline-item.directive.coffee b/app/modules/user-timeline/user-timeline-item/user-timeline-item.directive.coffee new file mode 100644 index 00000000..15daf833 --- /dev/null +++ b/app/modules/user-timeline/user-timeline-item/user-timeline-item.directive.coffee @@ -0,0 +1,13 @@ +UserTimelineItemDirective = () -> + return { + controllerAs: "vm" + controller: "UserTimelineItem" + bindToController: true + templateUrl: "user-timeline/user-timeline-item/user-timeline-item.html" + scope: { + timeline: "=tgUserTimelineItem" + } + } + +angular.module("taigaUserTimeline") + .directive("tgUserTimelineItem", UserTimelineItemDirective) diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item.jade b/app/modules/user-timeline/user-timeline-item/user-timeline-item.jade similarity index 94% rename from app/modules/profile/profile-timeline-item/profile-timeline-item.jade rename to app/modules/user-timeline/user-timeline-item/user-timeline-item.jade index 218eaf1e..1d941d60 100644 --- a/app/modules/profile/profile-timeline-item/profile-timeline-item.jade +++ b/app/modules/user-timeline/user-timeline-item/user-timeline-item.jade @@ -19,4 +19,4 @@ div.activity-item p {{::vm.activity.member.role.name}} div(ng-repeat="attachment in vm.activity.attachments") - div(tg-profile-timeline-attachment="attachment") + div(tg-user-timeline-attachment="attachment") diff --git a/app/modules/user-timeline/user-timeline.module.coffee b/app/modules/user-timeline/user-timeline.module.coffee new file mode 100644 index 00000000..728a1bbf --- /dev/null +++ b/app/modules/user-timeline/user-timeline.module.coffee @@ -0,0 +1 @@ +angular.module("taigaUserTimeline", []) diff --git a/app/modules/profile/profile-timeline/profile-timeline.controller.coffee b/app/modules/user-timeline/user-timeline/user-timeline.controller.coffee similarity index 81% rename from app/modules/profile/profile-timeline/profile-timeline.controller.coffee rename to app/modules/user-timeline/user-timeline/user-timeline.controller.coffee index 6b9854da..dffa696d 100644 --- a/app/modules/profile/profile-timeline/profile-timeline.controller.coffee +++ b/app/modules/user-timeline/user-timeline/user-timeline.controller.coffee @@ -23,13 +23,13 @@ taiga = @.taiga mixOf = @.taiga.mixOf -class ProfileTimelineController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.FiltersMixin) +class UserTimelineController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.FiltersMixin) @.$inject = [ "$tgAuth", - "tgProfileTimelineService" + "tgUserTimelineService" ] - constructor: (@auth, @profileTimelineService) -> + constructor: (@auth, @userTimelineService) -> @.timelineList = Immutable.List() @.page = 1 @.loadingData = false @@ -39,12 +39,12 @@ class ProfileTimelineController extends mixOf(taiga.Controller, taiga.PageMixin, @.loadingData = true - @profileTimelineService + @userTimelineService .getTimeline(user.id, @.page) .then (newTimelineList) => @.timelineList = @.timelineList.concat(newTimelineList) @.page++ @.loadingData = false -angular.module("taigaProfile") - .controller("ProfileTimeline", ProfileTimelineController) +angular.module("taigaUserTimeline") + .controller("UserTimeline", UserTimelineController) diff --git a/app/modules/profile/profile-timeline/profile-timeline.controller.spec.coffee b/app/modules/user-timeline/user-timeline/user-timeline.controller.spec.coffee similarity index 82% rename from app/modules/profile/profile-timeline/profile-timeline.controller.spec.coffee rename to app/modules/user-timeline/user-timeline/user-timeline.controller.spec.coffee index 954560cb..3979a071 100644 --- a/app/modules/profile/profile-timeline/profile-timeline.controller.spec.coffee +++ b/app/modules/user-timeline/user-timeline/user-timeline.controller.spec.coffee @@ -1,16 +1,16 @@ -describe "ProfileTimelineController", -> +describe "UserTimelineController", -> myCtrl = scope = $q = provide = null mocks = {} mockUser = {id: 3} - _mockProfileTimeline = () -> - mocks.profileTimelineService = { + _mockUserTimeline = () -> + mocks.userTimelineService = { getTimeline: sinon.stub() } - provide.value "tgProfileTimelineService", mocks.profileTimelineService + provide.value "tgUserTimelineService", mocks.userTimelineService _mockTgAuth = () -> provide.value "$tgAuth", { @@ -21,19 +21,19 @@ describe "ProfileTimelineController", -> _mocks = () -> module ($provide) -> provide = $provide - _mockProfileTimeline() + _mockUserTimeline() _mockTgAuth() return null beforeEach -> - module "taigaProfile" + module "taigaUserTimeline" _mocks() inject ($controller, _$q_) -> $q = _$q_ - myCtrl = $controller "ProfileTimeline" + myCtrl = $controller "UserTimeline" it "timelineList should be an array", () -> expect(myCtrl.timelineList.toJS()).is.an("array") @@ -54,14 +54,12 @@ describe "ProfileTimelineController", -> thenStub = sinon.stub() - profileStub = sinon.stub() + mocks.userTimelineService.getTimeline = sinon.stub() .withArgs(mockUser.id, myCtrl.page) .returns({ then: thenStub }) - mocks.profileTimelineService.getTimeline = profileStub - it "the loadingData variable must be true during the timeline load", () -> expect(myCtrl.loadingData).to.be.false diff --git a/app/modules/user-timeline/user-timeline/user-timeline.directive.coffee b/app/modules/user-timeline/user-timeline/user-timeline.directive.coffee new file mode 100644 index 00000000..dda6e07a --- /dev/null +++ b/app/modules/user-timeline/user-timeline/user-timeline.directive.coffee @@ -0,0 +1,9 @@ +UserTimelineDirective = -> + return { + templateUrl: "user-timeline/user-timeline/user-timeline.html", + controller: "UserTimeline", + controllerAs: "vm", + scope: {} + } + +angular.module("taigaProfile").directive("tgUserTimeline", UserTimelineDirective) diff --git a/app/modules/profile/profile-timeline/profile-timeline.jade b/app/modules/user-timeline/user-timeline/user-timeline.jade similarity index 61% rename from app/modules/profile/profile-timeline/profile-timeline.jade rename to app/modules/user-timeline/user-timeline/user-timeline.jade index 6901f624..4c57d15a 100644 --- a/app/modules/profile/profile-timeline/profile-timeline.jade +++ b/app/modules/user-timeline/user-timeline/user-timeline.jade @@ -1,3 +1,3 @@ section.profile-timeline div(infinite-scroll="vm.loadTimeline()", infinite-scroll-distance="3", infinite-scroll-disabled="vm.loadingData") - div(tg-repeat="timeline in vm.timelineList", tg-profile-timeline-item="timeline") + div(tg-repeat="timeline in vm.timelineList", tg-user-timeline-item="timeline") diff --git a/app/modules/profile/profile-timeline/profile-timeline.scss b/app/modules/user-timeline/user-timeline/user-timeline.scss similarity index 100% rename from app/modules/profile/profile-timeline/profile-timeline.scss rename to app/modules/user-timeline/user-timeline/user-timeline.scss diff --git a/app/modules/user-timeline/user-timeline/user-timeline.service.coffee b/app/modules/user-timeline/user-timeline/user-timeline.service.coffee new file mode 100644 index 00000000..8c4381e0 --- /dev/null +++ b/app/modules/user-timeline/user-timeline/user-timeline.service.coffee @@ -0,0 +1,58 @@ +taiga = @.taiga + +class UserTimelineService extends taiga.Service + @.$inject = ["tgResources"] + + constructor: (@rs) -> + + _valid_fields: [ + 'status', + 'subject', + 'description', + 'assigned_to', + 'points', + 'severity', + 'priority', + 'type', + 'attachments', + 'milestone', + 'is_blocked', + 'is_iocaine', + 'content_diff', + 'name', + 'estimated_finish', + 'estimated_start' + ] + + _isValidField: (values) -> + return _.some values, (value) => @._valid_fields.indexOf(value) != -1 + + _isValidEvent: (event) -> + return event.split(".").slice(-1)[0] != 'delete' + + _filterValidTimelineItems: (timeline) -> + if timeline.get("data") + values = [] + values_diff = timeline.get("data").get("values_diff") + + if values_diff + values = Object.keys(values_diff.toJS()) + + if values && values.length + if !@._isValidField(values) + return false + else if values[0] == 'attachments' && + values_diff.get('attachments').get('new').size == 0 + return false + + if !@._isValidEvent(timeline.get('event_type')) + return false + + return true + + getTimeline: (userId, page) -> + return @rs.users.getTimeline(userId, page) + .then (result) => + return result.filter (timeline) => @._filterValidTimelineItems(timeline) + +angular.module("taigaUserTimeline").service("tgUserTimelineService", UserTimelineService) diff --git a/app/modules/profile/profile-timeline/profile-timeline.service.spec.coffee b/app/modules/user-timeline/user-timeline/user-timeline.service.spec.coffee similarity index 79% rename from app/modules/profile/profile-timeline/profile-timeline.service.spec.coffee rename to app/modules/user-timeline/user-timeline/user-timeline.service.spec.coffee index 03766f40..9cc2f2c9 100644 --- a/app/modules/profile/profile-timeline/profile-timeline.service.spec.coffee +++ b/app/modules/user-timeline/user-timeline/user-timeline.service.spec.coffee @@ -1,18 +1,18 @@ -describe "tgProfileTimelineService", -> +describe "tgUserTimelineService", -> provide = null $q = null $rootScope = null - profileTimelineService = null + userTimelineService = null mocks = {} _mockResources = () -> mocks.resources = {} - mocks.resources.timeline = { - profile: sinon.stub() + mocks.resources.users = { + getTimeline: sinon.stub() } - provide.value "$tgResources", mocks.resources + provide.value "tgResources", mocks.resources _mocks = () -> module ($provide) -> @@ -25,20 +25,19 @@ describe "tgProfileTimelineService", -> _mocks() _inject = (callback) -> - inject (_tgProfileTimelineService_, _$q_, _$rootScope_) -> - profileTimelineService = _tgProfileTimelineService_ + inject (_tgUserTimelineService_, _$q_, _$rootScope_) -> + userTimelineService = _tgUserTimelineService_ $q = _$q_ $rootScope = _$rootScope_ callback() if callback beforeEach -> - module "taigaProjects" + module "taigaUserTimeline" _setup() _inject() it "filter invalid timeline items", (done) -> - valid_items = { - data: [ + valid_items = [ { # valid item event_type: "xx.tt.create", data: { @@ -104,26 +103,25 @@ describe "tgProfileTimelineService", -> } } ] - } userId = 3 page = 2 - mocks.resources.timeline.profile = (_userId_, _page_) -> + mocks.resources.users.getTimeline = (_userId_, _page_) -> expect(_userId_).to.be.equal(userId) expect(_page_).to.be.equal(page) return $q (resolve, reject) -> - resolve(valid_items) + resolve(Immutable.fromJS(valid_items)) - profileTimelineService.getTimeline(userId, page) + userTimelineService.getTimeline(userId, page) .then (_items_) -> items = _items_.toJS() expect(items).to.have.length(3) - expect(items[0]).to.be.eql(valid_items.data[0]) - expect(items[1]).to.be.eql(valid_items.data[3]) - expect(items[2]).to.be.eql(valid_items.data[5]) + expect(items[0]).to.be.eql(valid_items[0]) + expect(items[1]).to.be.eql(valid_items[3]) + expect(items[2]).to.be.eql(valid_items[5]) done() From 77133463fb7aca314d1bd0be584d6427576e8a93 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 13 May 2015 07:26:29 +0200 Subject: [PATCH 153/366] Fixing tests --- .../dropdown-user/dropdown-user.directive.coffee | 1 - .../dropdown-user/dropdown-user.directive.spec.coffee | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee b/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee index 82da69f9..fd9e69d6 100644 --- a/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee +++ b/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.coffee @@ -3,7 +3,6 @@ DropdownUserDirective = (authService, configService, locationService, link = (scope, el, attrs, ctrl) -> scope.vm = {} - #scope.vm.user = authService.user scope.vm.isFeedbackEnabled = configService.get("feedbackEnabled") taiga.defineImmutableProperty(scope.vm, "user", () -> authService.userData) diff --git a/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.spec.coffee b/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.spec.coffee index f4994de2..b8eeb527 100644 --- a/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.spec.coffee +++ b/app/modules/navigation-bar/dropdown-user/dropdown-user.directive.spec.coffee @@ -18,7 +18,7 @@ describe "dropdownUserDirective", () -> _mockTgAuth = () -> mockTgAuth = { - getUser: sinon.stub() + userData: Immutable.fromJS({id: 66}) logout: sinon.stub() } provide.value "$tgAuth", mockTgAuth @@ -69,13 +69,12 @@ describe "dropdownUserDirective", () -> compile = $compile it "dropdown user directive scope content", () -> - mockTgAuth.getUser.withArgs().returns({id: 66}) mockTgConfig.get.withArgs("feedbackEnabled").returns(true) elm = createDirective() scope.$apply() vm = elm.isolateScope().vm - expect(vm.user.id).to.be.equal(66) + expect(vm.user.get("id")).to.be.equal(66) expect(vm.isFeedbackEnabled).to.be.equal(true) it "dropdown user log out", () -> From 08be65d587ab20040dddaceae72edc221ff3b957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 13 May 2015 09:16:30 +0200 Subject: [PATCH 154/366] User profile bio limit --- app/locales/locale-en.json | 2 +- app/modules/profile/profile-bar/profile-bar.jade | 2 +- app/partials/user/user-profile.jade | 3 ++- app/styles/modules/user-settings/user-profile.scss | 3 +++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/locales/locale-en.json b/app/locales/locale-en.json index f5f6c32f..e01a2924 100644 --- a/app/locales/locale-en.json +++ b/app/locales/locale-en.json @@ -1116,7 +1116,7 @@ "EMAIL": "Email", "FULL_NAME": "Full name", "PLACEHOLDER_FULL_NAME": "Set your full name (ex. Íñigo Montoya)", - "BIO": "Bio", + "BIO": "Bio (max. 210 chars)", "PLACEHOLDER_BIO": "Tell us something about you", "LANGUAGE": "Language", "LANGUAGE_DEFAULT": "-- use default language --" diff --git a/app/modules/profile/profile-bar/profile-bar.jade b/app/modules/profile/profile-bar/profile-bar.jade index 55ba3153..44c3ba2b 100644 --- a/app/modules/profile/profile-bar/profile-bar.jade +++ b/app/modules/profile/profile-bar/profile-bar.jade @@ -34,4 +34,4 @@ section.profile-bar // div.organization div.profile-quote(ng-if="::vm.user.bio") - span {{::vm.user.bio}} + span {{::vm.user.bio | limitTo:210 }}{{vm.user.bio.length < 210 ? '' : '...'}} diff --git a/app/partials/user/user-profile.jade b/app/partials/user/user-profile.jade index bc88f297..d4d3ced0 100644 --- a/app/partials/user/user-profile.jade +++ b/app/partials/user/user-profile.jade @@ -58,8 +58,9 @@ div.wrapper(tg-user-profile, ng-controller="UserSettingsController as ctrl", fieldset label(for="bio", translate="USER_PROFILE.FIELD.BIO") + textarea(name="bio", id="bio", ng-model="user.bio", - ng-attr-placeholder="{{'USER_PROFILE.FIELD.PLACEHOLDER_BIO' | translate}}") + ng-attr-placeholder="{{'USER_PROFILE.FIELD.PLACEHOLDER_BIO' | translate}}", ng-maxlength="210", maxlength="210") fieldset.submit button.button-green.submit-button(type="submit", title="{{'COMMON.SAVE' | translate}}", diff --git a/app/styles/modules/user-settings/user-profile.scss b/app/styles/modules/user-settings/user-profile.scss index 2deea46a..38eff6c2 100644 --- a/app/styles/modules/user-settings/user-profile.scss +++ b/app/styles/modules/user-settings/user-profile.scss @@ -70,6 +70,9 @@ display: block; margin-bottom: .5rem; } + textarea { + min-height: 7rem; + } .button-green { color: $white; cursor: pointer; From 1e92388cdc211466fd856506237da7fea893ec61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 13 May 2015 09:49:35 +0200 Subject: [PATCH 155/366] Projects page user profile --- .../profile-projects/profile-projects.jade | 2 ++ .../projects/listing/projects-listing.scss | 24 +++++++------------ 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/app/modules/profile/profile-projects/profile-projects.jade b/app/modules/profile/profile-projects/profile-projects.jade index b5c015a9..3c3b0d76 100644 --- a/app/modules/profile/profile-projects/profile-projects.jade +++ b/app/modules/profile/profile-projects/profile-projects.jade @@ -11,6 +11,8 @@ section.profile-projects span.tag(style='border-left: 5px solid {{::tag.get("color")}};', tg-repeat="tag in ::project.get('colorized_tags')") span.tag-name {{::tag.get('name')}} + div.project-list-single-right + div.project-list-single-members a(tg-repeat="contact in ::project.get('contacts')", tg-nav="user-profile:username=contact.get('username')", title="{{::contact.get('full_name')}}") img(ng-src="{{::contact.get('photo')}}") diff --git a/app/modules/projects/listing/projects-listing.scss b/app/modules/projects/listing/projects-listing.scss index 5354288c..5041b59c 100644 --- a/app/modules/projects/listing/projects-listing.scss +++ b/app/modules/projects/listing/projects-listing.scss @@ -1,7 +1,7 @@ .project-list-single { border-bottom: 1px solid $whitish; display: flex; - justify-content: center; + justify-content: space-between; padding: .5rem; position: relative; } @@ -9,23 +9,18 @@ .project-list-single-left, .project-list-single-right { display: flex; - flex-direction: column; } .project-list-single-left { align-content: space-between; - flex: 4; + padding-right: 1rem; h1 { - @extend %text; - @extend %large; - display: inline-block; - margin-bottom: 0; - text-transform: none; - } - .project-name { @extend %text; @extend %larger; color: $gray; + display: inline-block; + margin-bottom: 0; + text-transform: none; vertical-align: middle; white-space: nowrap; } @@ -34,7 +29,6 @@ @extend %small; color: $gray; margin-bottom: 0; - max-width: 95%; } .project-list-single-tags { align-content: flex-end; @@ -73,13 +67,13 @@ .project-list-single-members { align-self: flex-end; display: flex; + flex-direction: row-reverse; flex-grow: 0; - flex-wrap: wrap; + flex-wrap: wrap-reverse; margin-top: 1rem; - max-width: 160px; + max-width: 200px; a { - display: block; - } + display: block; } img { margin-right: .3rem; width: 34px; From 4e41ea50154c26a6bc05419e24bb84908a8dfc49 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 13 May 2015 08:20:40 +0200 Subject: [PATCH 156/366] new route auth --- app/coffee/app.coffee | 42 ++++++++++---- app/modules/page/page.controller.coffee | 8 +-- app/modules/page/page.controller.spec.coffee | 61 -------------------- 3 files changed, 31 insertions(+), 80 deletions(-) diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 6e83343a..a3942d4c 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -41,11 +41,13 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven $routeProvider.when("/", { templateUrl: "home/home-page.html", + access: { + requiresLogin: true + }, resolve: { loader: tgLoaderProvider.add(true), pageParams: -> { "title": "PROJECT.WELCOME" - "authRequired": true } } } @@ -54,11 +56,13 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven $routeProvider.when("/projects/", { templateUrl: "projects/projects-page.html", + access: { + requiresLogin: true + }, resolve: { loader: tgLoaderProvider.add(true), pageParams: -> { "title": "PROJECT.SECTION_PROJECTS" - "authRequired": true } }, controller: "Page" @@ -68,13 +72,12 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven $routeProvider.when("/project/:pslug/", { templateUrl: "projects/project/project-page.html", - resolve: { - loader: tgLoaderProvider.add(true), - pageParams: -> { - "authRequired": true - } + access: { + requiresLogin: true }, - controller: "Page" + resolve: { + loader: tgLoaderProvider.add(true) + } } ) @@ -172,7 +175,17 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven # User profile $routeProvider.when("/profile", - {templateUrl: "profile/profile.html"}) + { + templateUrl: "profile/profile-page.html", + resolve: { + loader: tgLoaderProvider.add(true) + }, + access: { + requiresLogin: true + } + controller: "ProfilePage" + } + ) # Auth $routeProvider.when("/login", @@ -341,7 +354,7 @@ i18nInit = (lang, $translate) -> checksley.updateMessages('default', messages) -init = ($log, $config, $rootscope, $auth, $events, $analytics, $translate) -> +init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $navUrls) -> $log.debug("Initialize application") # Taiga Plugins @@ -360,6 +373,10 @@ init = ($log, $config, $rootscope, $auth, $events, $analytics, $translate) -> # Analytics $analytics.initialize() + $rootscope.$on '$routeChangeStart', (event, next) -> + if next.access && next.access.requiresLogin + if !$auth.isAuthenticated() + $location.path($navUrls.resolve("login")) modules = [ # Main Global Modules @@ -426,11 +443,12 @@ module.config([ module.run([ "$log", - "$tgConfig", "$rootScope", "$tgAuth", "$tgEvents", "$tgAnalytics", - "$translate" + "$translate", + "$tgLocation", + "$tgNavUrls", init ]) diff --git a/app/modules/page/page.controller.coffee b/app/modules/page/page.controller.coffee index 5a317157..3bdaf835 100644 --- a/app/modules/page/page.controller.coffee +++ b/app/modules/page/page.controller.coffee @@ -1,17 +1,11 @@ class PageController extends taiga.Controller @.$inject = [ - "$tgAuth", "$appTitle", "$translate", - "$tgLocation", - "$tgNavUrls", "pageParams" ] - constructor: (@auth, @appTitle, @translate, @location, @navUrls, @pageParams) -> - if @pageParams.authRequired && !@auth.isAuthenticated() - @location.path(@navUrls.resolve("login")) - + constructor: (@appTitle, @translate, @pageParams) -> if @pageParams.title @translate(@pageParams.title).then (text) => @appTitle.set(text) diff --git a/app/modules/page/page.controller.spec.coffee b/app/modules/page/page.controller.spec.coffee index 8163c668..dac7d66c 100644 --- a/app/modules/page/page.controller.spec.coffee +++ b/app/modules/page/page.controller.spec.coffee @@ -9,13 +9,6 @@ describe "PageController", -> provide.value "pageParams", mocks.pageParams - _mockAuth = () -> - mocks.auth = { - isAuthenticated: sinon.stub() - } - - provide.value "$tgAuth", mocks.auth - _mockAppTitle = () -> mocks.appTitle = { set: sinon.spy() @@ -23,20 +16,6 @@ describe "PageController", -> provide.value "$appTitle", mocks.appTitle - _mockLocation = () -> - mocks.location = { - path: sinon.spy() - } - - provide.value "$tgLocation", mocks.location - - _mockNavUrls = () -> - mocks.navUrls = { - resolve: sinon.stub() - } - - provide.value "$tgNavUrls", mocks.navUrls - _mockTranslate = () -> mocks.translate = sinon.stub() @@ -47,9 +26,6 @@ describe "PageController", -> provide = $provide _mockAppTitle() _mockPageParams() - _mockAuth() - _mockLocation() - _mockNavUrls() _mockTranslate() return null @@ -62,43 +38,6 @@ describe "PageController", -> inject ($controller) -> controller = $controller - describe "auth", () -> - it "if auth is required and the user is not logged redirect to login page", () -> - locationPath = "location-path" - - mocks.pageParams.authRequired = true - mocks.auth.isAuthenticated.returns(false) - mocks.navUrls.resolve.withArgs("login").returns(locationPath) - - pageCtrl = controller "Page", - $scope: {} - - expect(mocks.location.path.withArgs(locationPath)).have.been.calledOnce - - it "if auth is not required no redirect to login page", () -> - locationPath = "location-path" - - mocks.pageParams.authRequired = false - mocks.auth.isAuthenticated.returns(false) - mocks.navUrls.resolve.withArgs("login").returns(locationPath) - - pageCtrl = controller "Page", - $scope: {} - - expect(mocks.location.path).have.callCount(0) - - it "if auth is required and the user is logged no redirect", () -> - locationPath = "location-path" - - mocks.pageParams.authRequired = true - mocks.auth.isAuthenticated.returns(true) - mocks.navUrls.resolve.withArgs("login").returns(locationPath) - - pageCtrl = controller "Page", - $scope: {} - - expect(mocks.location.path).have.callCount(0) - describe "page title", () -> it "if title is defined set it", () -> thenStub = sinon.stub() From 408fc1b8e335e4bca6d22aa55855237d14146339 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 13 May 2015 08:58:18 +0200 Subject: [PATCH 157/366] new custom route page title --- app/coffee/app.coffee | 24 +++---- app/modules/page/page.controller.coffee | 12 ---- app/modules/page/page.controller.spec.coffee | 62 ------------------- app/modules/page/page.module.coffee | 1 - .../profile/profile-page.controller.coffee | 12 ++++ .../profile-page.controller.spec.coffee | 47 ++++++++++++++ .../{profile.jade => profile-page.jade} | 0 app/modules/projects/project/project.jade | 2 +- 8 files changed, 73 insertions(+), 87 deletions(-) delete mode 100644 app/modules/page/page.controller.coffee delete mode 100644 app/modules/page/page.controller.spec.coffee delete mode 100644 app/modules/page/page.module.coffee create mode 100644 app/modules/profile/profile-page.controller.coffee create mode 100644 app/modules/profile/profile-page.controller.spec.coffee rename app/modules/profile/{profile.jade => profile-page.jade} (100%) diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index a3942d4c..259d97a9 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -44,10 +44,10 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven access: { requiresLogin: true }, + title: "PROJECT.WELCOME", resolve: { loader: tgLoaderProvider.add(true), pageParams: -> { - "title": "PROJECT.WELCOME" } } } @@ -59,25 +59,24 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven access: { requiresLogin: true }, + title: "PROJECT.SECTION_PROJECTS", resolve: { - loader: tgLoaderProvider.add(true), - pageParams: -> { - "title": "PROJECT.SECTION_PROJECTS" - } - }, - controller: "Page" + loader: tgLoaderProvider.add(true) + } } ) $routeProvider.when("/project/:pslug/", { - templateUrl: "projects/project/project-page.html", + templateUrl: "projects/project/project.html", access: { requiresLogin: true }, resolve: { loader: tgLoaderProvider.add(true) - } + }, + controller: "Project", + controllerAs: "vm" } ) @@ -354,7 +353,7 @@ i18nInit = (lang, $translate) -> checksley.updateMessages('default', messages) -init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $navUrls) -> +init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $navUrls, $appTitle) -> $log.debug("Initialize application") # Taiga Plugins @@ -378,6 +377,9 @@ init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $na if !$auth.isAuthenticated() $location.path($navUrls.resolve("login")) + if next.title + $translate(next.title).then (text) => $appTitle.set(text) + modules = [ # Main Global Modules "taigaBase", @@ -412,7 +414,6 @@ modules = [ # new modules "taigaProfile", "taigaHome", - "taigaPage", "taigaUserTimeline", # template cache @@ -450,5 +451,6 @@ module.run([ "$translate", "$tgLocation", "$tgNavUrls", + "$appTitle", init ]) diff --git a/app/modules/page/page.controller.coffee b/app/modules/page/page.controller.coffee deleted file mode 100644 index 3bdaf835..00000000 --- a/app/modules/page/page.controller.coffee +++ /dev/null @@ -1,12 +0,0 @@ -class PageController extends taiga.Controller - @.$inject = [ - "$appTitle", - "$translate", - "pageParams" - ] - - constructor: (@appTitle, @translate, @pageParams) -> - if @pageParams.title - @translate(@pageParams.title).then (text) => @appTitle.set(text) - -angular.module("taigaPage").controller("Page", PageController) diff --git a/app/modules/page/page.controller.spec.coffee b/app/modules/page/page.controller.spec.coffee deleted file mode 100644 index dac7d66c..00000000 --- a/app/modules/page/page.controller.spec.coffee +++ /dev/null @@ -1,62 +0,0 @@ -describe "PageController", -> - pageCtrl = null - provide = null - controller = null - mocks = {} - - _mockPageParams = () -> - mocks.pageParams = {} - - provide.value "pageParams", mocks.pageParams - - _mockAppTitle = () -> - mocks.appTitle = { - set: sinon.spy() - } - - provide.value "$appTitle", mocks.appTitle - - _mockTranslate = () -> - mocks.translate = sinon.stub() - - provide.value "$translate", mocks.translate - - _mocks = () -> - module ($provide) -> - provide = $provide - _mockAppTitle() - _mockPageParams() - _mockTranslate() - - return null - - beforeEach -> - module "taigaPage" - - _mocks() - - inject ($controller) -> - controller = $controller - - describe "page title", () -> - it "if title is defined set it", () -> - thenStub = sinon.stub() - - mocks.pageParams.title = "TITLE" - mocks.translate.withArgs("TITLE").returns({ - then: thenStub - }) - - pageCtrl = controller "Page", - $scope: {} - - thenStub.callArg(0, "TITLE") - - expect(mocks.appTitle.set.withArgs("TITLE")).have.been.calledOnce - - it "if title is not defined not call appTitle", () -> - pageCtrl = controller "Page", - $scope: {} - - expect(mocks.translate).have.callCount(0) - expect(mocks.appTitle.set.withArgs("TITLE")).have.callCount(0) diff --git a/app/modules/page/page.module.coffee b/app/modules/page/page.module.coffee deleted file mode 100644 index 82f679b0..00000000 --- a/app/modules/page/page.module.coffee +++ /dev/null @@ -1 +0,0 @@ -module = angular.module("taigaPage", []) diff --git a/app/modules/profile/profile-page.controller.coffee b/app/modules/profile/profile-page.controller.coffee new file mode 100644 index 00000000..891b08b3 --- /dev/null +++ b/app/modules/profile/profile-page.controller.coffee @@ -0,0 +1,12 @@ +class ProfilePageController extends taiga.Controller + @.$inject = [ + "$appTitle", + "$tgAuth" + ] + + constructor: (@appTitle, @auth) -> + user = @auth.getUser() + + @appTitle.set(user.username) + +angular.module("taigaProfile").controller("ProfilePage", ProfilePageController) diff --git a/app/modules/profile/profile-page.controller.spec.coffee b/app/modules/profile/profile-page.controller.spec.coffee new file mode 100644 index 00000000..cb17556e --- /dev/null +++ b/app/modules/profile/profile-page.controller.spec.coffee @@ -0,0 +1,47 @@ +describe "PageController", -> + pageCtrl = null + provide = null + controller = null + mocks = {} + + _mockAuth = () -> + mocks.authService = { + getUser: sinon.stub() + } + + provide.value "$tgAuth", mocks.authService + + _mockAppTitle = () -> + mocks.appTitle = { + set: sinon.spy() + } + + provide.value "$appTitle", mocks.appTitle + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockAppTitle() + _mockAuth() + + return null + + beforeEach -> + module "taigaProfile" + + _mocks() + + inject ($controller) -> + controller = $controller + + it "set the username as title", () -> + user = { + username: 'UserName' + } + + mocks.authService.getUser.returns(user) + + pageCtrl = controller "ProfilePage", + $scope: {} + + expect(mocks.appTitle.set.withArgs(user.username)).have.been.calledOnce diff --git a/app/modules/profile/profile.jade b/app/modules/profile/profile-page.jade similarity index 100% rename from app/modules/profile/profile.jade rename to app/modules/profile/profile-page.jade diff --git a/app/modules/projects/project/project.jade b/app/modules/projects/project/project.jade index 339d0880..0551d025 100644 --- a/app/modules/projects/project/project.jade +++ b/app/modules/projects/project/project.jade @@ -11,7 +11,7 @@ div.wrapper div.project-data section.timeline - span TODO. Missing the amazing timeline around!!dfdfdf + div(tg-user-timeline) section.involved-data h2.title Team ul.involved-team From ad6b7fcf1d29bebff228baace3621bd469eabcbe Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 13 May 2015 10:09:47 +0200 Subject: [PATCH 158/366] new route controller page system --- app/coffee/app.coffee | 14 ++--- ...oller.coffee => profile.controller.coffee} | 2 +- ....coffee => profile.controller.spec.coffee} | 36 ++++++++----- .../{profile-page.jade => profile.jade} | 0 .../projects-listing.controller.coffee | 12 +++++ .../projects-listing.controller.spec.coffee | 54 +++++++++++++++++++ .../listing/projects-listing.directive.coffee | 24 ++++----- .../projects/listing/projects-listing.jade | 2 +- .../projects/project/project-page.jade | 1 - .../projects/project/project.directive.coffee | 8 --- app/modules/projects/project/project.jade | 2 +- app/modules/projects/projects-page.jade | 1 - 12 files changed, 109 insertions(+), 47 deletions(-) rename app/modules/profile/{profile-page.controller.coffee => profile.controller.coffee} (73%) rename app/modules/profile/{profile-page.controller.spec.coffee => profile.controller.spec.coffee} (54%) rename app/modules/profile/{profile-page.jade => profile.jade} (100%) create mode 100644 app/modules/projects/listing/projects-listing.controller.coffee create mode 100644 app/modules/projects/listing/projects-listing.controller.spec.coffee delete mode 100644 app/modules/projects/project/project-page.jade delete mode 100644 app/modules/projects/project/project.directive.coffee delete mode 100644 app/modules/projects/projects-page.jade diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 259d97a9..ab483e4b 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -46,23 +46,23 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven }, title: "PROJECT.WELCOME", resolve: { - loader: tgLoaderProvider.add(true), - pageParams: -> { - } + loader: tgLoaderProvider.add(true) } } ) $routeProvider.when("/projects/", { - templateUrl: "projects/projects-page.html", + templateUrl: "projects/listing/projects-listing.html", access: { requiresLogin: true }, title: "PROJECT.SECTION_PROJECTS", resolve: { loader: tgLoaderProvider.add(true) - } + }, + controller: "ProjectsListing", + controllerAs: "vm" } ) @@ -175,14 +175,14 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven # User profile $routeProvider.when("/profile", { - templateUrl: "profile/profile-page.html", + templateUrl: "profile/profile.html", resolve: { loader: tgLoaderProvider.add(true) }, access: { requiresLogin: true } - controller: "ProfilePage" + controller: "Profile" } ) diff --git a/app/modules/profile/profile-page.controller.coffee b/app/modules/profile/profile.controller.coffee similarity index 73% rename from app/modules/profile/profile-page.controller.coffee rename to app/modules/profile/profile.controller.coffee index 891b08b3..04c6ddc4 100644 --- a/app/modules/profile/profile-page.controller.coffee +++ b/app/modules/profile/profile.controller.coffee @@ -9,4 +9,4 @@ class ProfilePageController extends taiga.Controller @appTitle.set(user.username) -angular.module("taigaProfile").controller("ProfilePage", ProfilePageController) +angular.module("taigaProfile").controller("Profile", ProfilePageController) diff --git a/app/modules/profile/profile-page.controller.spec.coffee b/app/modules/profile/profile.controller.spec.coffee similarity index 54% rename from app/modules/profile/profile-page.controller.spec.coffee rename to app/modules/profile/profile.controller.spec.coffee index cb17556e..6637d7e4 100644 --- a/app/modules/profile/profile-page.controller.spec.coffee +++ b/app/modules/profile/profile.controller.spec.coffee @@ -1,23 +1,33 @@ -describe "PageController", -> +describe "ProfileController", -> pageCtrl = null provide = null controller = null mocks = {} - _mockAuth = () -> - mocks.authService = { - getUser: sinon.stub() - } - - provide.value "$tgAuth", mocks.authService + projects = Immutable.fromJS([ + {id: 1}, + {id: 2}, + {id: 3} + ]) _mockAppTitle = () -> + stub = sinon.stub() + mocks.appTitle = { - set: sinon.spy() + set: sinon.stub() } provide.value "$appTitle", mocks.appTitle + _mockAuth = () -> + stub = sinon.stub() + + mocks.auth = { + getUser: sinon.stub() + } + + provide.value "$tgAuth", mocks.auth + _mocks = () -> module ($provide) -> provide = $provide @@ -34,14 +44,14 @@ describe "PageController", -> inject ($controller) -> controller = $controller - it "set the username as title", () -> + it "define projects", () -> user = { - username: 'UserName' + username: "UserName" } - mocks.authService.getUser.returns(user) + mocks.auth.getUser.returns(user) - pageCtrl = controller "ProfilePage", + ctrl = controller "Profile", $scope: {} - expect(mocks.appTitle.set.withArgs(user.username)).have.been.calledOnce + expect(mocks.appTitle.set.withArgs(user.username)).to.be.calledOnce diff --git a/app/modules/profile/profile-page.jade b/app/modules/profile/profile.jade similarity index 100% rename from app/modules/profile/profile-page.jade rename to app/modules/profile/profile.jade diff --git a/app/modules/projects/listing/projects-listing.controller.coffee b/app/modules/projects/listing/projects-listing.controller.coffee new file mode 100644 index 00000000..3206782c --- /dev/null +++ b/app/modules/projects/listing/projects-listing.controller.coffee @@ -0,0 +1,12 @@ +class ProjectsListingController + @.$inject = [ + "tgProjectsService" + ] + + constructor: (@projectsService) -> + taiga.defineImmutableProperty(@, "projects", () => @projectsService.currentUserProjects.get("all")) + + newProject: -> + @projectsService.newProject() + +angular.module("taigaProjects").controller("ProjectsListing", ProjectsListingController) diff --git a/app/modules/projects/listing/projects-listing.controller.spec.coffee b/app/modules/projects/listing/projects-listing.controller.spec.coffee new file mode 100644 index 00000000..a976da33 --- /dev/null +++ b/app/modules/projects/listing/projects-listing.controller.spec.coffee @@ -0,0 +1,54 @@ +describe "ProjectsListingController", -> + pageCtrl = null + provide = null + controller = null + mocks = {} + + projects = Immutable.fromJS([ + {id: 1}, + {id: 2}, + {id: 3} + ]) + + _mockProjectsService = () -> + stub = sinon.stub() + + mocks.projectsService = { + currentUserProjects: { + get: stub + }, + newProject: sinon.stub() + } + + stub.withArgs("all").returns(projects) + + provide.value "tgProjectsService", mocks.projectsService + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockProjectsService() + + return null + + beforeEach -> + module "taigaProjects" + + _mocks() + + inject ($controller) -> + controller = $controller + + it "define projects", () -> + pageCtrl = controller "ProjectsListing", + $scope: {} + + expect(pageCtrl.projects).to.be.equal(projects) + + it "new project", () -> + pageCtrl = controller "ProjectsListing", + $scope: {} + + pageCtrl.newProject() + + expect(mocks.projectsService.newProject).to.be.calledOnce; diff --git a/app/modules/projects/listing/projects-listing.directive.coffee b/app/modules/projects/listing/projects-listing.directive.coffee index 61e7ccab..cd5f90fa 100644 --- a/app/modules/projects/listing/projects-listing.directive.coffee +++ b/app/modules/projects/listing/projects-listing.directive.coffee @@ -1,10 +1,8 @@ -ProjectsListingDirective = (projectsService) -> +SortProjectsDirective = (projectsService) -> link = (scope, el, attrs, ctrl) -> - scope.vm = {} itemEl = null - tdom = el.find(".js-sortable") - tdom.sortable({ + el.sortable({ dropOnEmpty: true revert: 200 axis: "y" @@ -12,31 +10,29 @@ ProjectsListingDirective = (projectsService) -> placeholder: 'placeholder' }) - tdom.on "sortstop", (event, ui) -> + el.on "sortstop", (event, ui) -> itemEl = ui.item project = itemEl.scope().project index = itemEl.index() - sorted_project_ids = _.map(scope.vm.projects.toArray(), (p) -> p.id) + sorted_project_ids = _.map(scope.projects.toJS(), (p) -> p.id) sorted_project_ids = _.without(sorted_project_ids, project.id) sorted_project_ids.splice(index, 0, project.get('id')) + sortData = [] + for value, index in sorted_project_ids sortData.push({"project_id": value, "order":index}) projectsService.bulkUpdateProjectsOrder(sortData) - taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.currentUserProjects.get("all")) - - scope.vm.newProject = -> - projectsService.newProject() - directive = { - templateUrl: "projects/listing/projects-listing.html" - scope: {} + scope: { + projects: "=tgSortProjects" + }, link: link } return directive -angular.module("taigaProjects").directive("tgProjectsListing", ["tgProjectsService", ProjectsListingDirective]) +angular.module("taigaProjects").directive("tgSortProjects", ["tgProjectsService", SortProjectsDirective]) diff --git a/app/modules/projects/listing/projects-listing.jade b/app/modules/projects/listing/projects-listing.jade index 68e58723..0acc92f3 100644 --- a/app/modules/projects/listing/projects-listing.jade +++ b/app/modules/projects/listing/projects-listing.jade @@ -10,7 +10,7 @@ div.project-list-wrapper.centered section.project-list-section div.project-list - ul.js-sortable + ul(tg-sort-projects="vm.projects") li.project-list-single(tg-bind-scope, tg-repeat="project in vm.projects") div.project-list-single-left div.project-list-single-title diff --git a/app/modules/projects/project/project-page.jade b/app/modules/projects/project/project-page.jade deleted file mode 100644 index d86e8356..00000000 --- a/app/modules/projects/project/project-page.jade +++ /dev/null @@ -1 +0,0 @@ -div(tg-project) diff --git a/app/modules/projects/project/project.directive.coffee b/app/modules/projects/project/project.directive.coffee deleted file mode 100644 index e3dd8298..00000000 --- a/app/modules/projects/project/project.directive.coffee +++ /dev/null @@ -1,8 +0,0 @@ -ProjectDirective = () -> - return { - templateUrl: "projects/project/project.html", - controllerAs: "vm", - controller: "Project" - } - -angular.module("taigaProjects").directive("tgProject", ProjectDirective) diff --git a/app/modules/projects/project/project.jade b/app/modules/projects/project/project.jade index 0551d025..a8f0be4d 100644 --- a/app/modules/projects/project/project.jade +++ b/app/modules/projects/project/project.jade @@ -13,7 +13,7 @@ div.wrapper section.timeline div(tg-user-timeline) section.involved-data - h2.title Team + h2.title {{"PROJECT.SECTION.TEAM" | translate}} ul.involved-team a(href="", title="{{::member.get('full_name')}}", tg-repeat="member in ::vm.project.get('memberships')") img(ng-src="{{::member.get('photo')}}", alt="{{::member.get('full_name')}}") diff --git a/app/modules/projects/projects-page.jade b/app/modules/projects/projects-page.jade deleted file mode 100644 index 21f045f0..00000000 --- a/app/modules/projects/projects-page.jade +++ /dev/null @@ -1 +0,0 @@ -div(tg-projects-listing) From 9352a7f0fc6f0d04978de3f7bba69197b813cf2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 13 May 2015 10:14:28 +0200 Subject: [PATCH 159/366] Contacts Tab --- .../profile-contacts/profile-contacts.jade | 21 +++++++++---------- .../profile/styles/profile-contacts.scss | 4 ++-- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/app/modules/profile/profile-contacts/profile-contacts.jade b/app/modules/profile/profile-contacts/profile-contacts.jade index bb57baa2..4d1de5f0 100644 --- a/app/modules/profile/profile-contacts/profile-contacts.jade +++ b/app/modules/profile/profile-contacts/profile-contacts.jade @@ -6,20 +6,19 @@ section.profile-contacts // a(href="", title="Only show people follow you") followers div.profile-contact-single(tg-repeat="contact in ::vm.contacts") - div.profile-contact-picture - a(tg-nav="user-profile:username=contact.get('username')", title="{{::contact.get('name') }}") - img(ng-src="{{::contact.get('photo')}}", alt="{{::contact.get('full_name')}}") - - div.profile-contact-data - h1 + div.profile-contact-picture a(tg-nav="user-profile:username=contact.get('username')", title="{{::contact.get('name') }}") - | {{::contact.get('full_name')}} - // span.your-contact Your contact + img(ng-src="{{::contact.get('photo')}}", alt="{{::contact.get('full_name')}}") - p(ng-if="contact.bio") {{::contact.get('bio')}} + div.profile-contact-data + h1 + a(tg-nav="user-profile:username=contact.get('username')", title="{{::contact.get('name') }}") + | {{::contact.get('full_name')}} - div.extra-info - span.position {{::contact.get('roles').join(", ")}} + p(ng-if="contact.bio") {{::contact.get('bio')}} + + div.extra-info + span.position {{::contact.get('roles').join(", ")}} // span.location todo // div.profile-project-stats // div.stat-projects(title="2 projects") diff --git a/app/modules/profile/styles/profile-contacts.scss b/app/modules/profile/styles/profile-contacts.scss index bd76eee4..6ffd5a31 100644 --- a/app/modules/profile/styles/profile-contacts.scss +++ b/app/modules/profile/styles/profile-contacts.scss @@ -30,6 +30,7 @@ margin-right: 1rem; max-width: 100px; img { + border-radius: .2rem; width: 100%; } } @@ -37,7 +38,7 @@ flex: 1; h1 { @extend %text; - @extend %xlarge; + @extend %larger; align-items: center; display: flex; margin-bottom: 0; @@ -59,7 +60,6 @@ color: $gray-light; } .position { - @extend %bold; margin-right: .3rem; } } From dcc3e79f20443c82ea4ee005325954dad2074dc0 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 14 May 2015 08:13:48 +0200 Subject: [PATCH 160/366] Fixing timeline avatar if it doesn't exist --- .../user-timeline/user-timeline-item/user-timeline-item.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/user-timeline/user-timeline-item/user-timeline-item.jade b/app/modules/user-timeline/user-timeline-item/user-timeline-item.jade index 1d941d60..c51e721b 100644 --- a/app/modules/user-timeline/user-timeline-item/user-timeline-item.jade +++ b/app/modules/user-timeline/user-timeline-item/user-timeline-item.jade @@ -3,7 +3,7 @@ div.activity-item div.activity-info div.profile-contact-picture a(tg-nav="user-profile:username=vm.activity.user.username", title="{{::vm.activity.user.name }}") - img(ng-src="{{::vm.activity.user.photo}}", alt="{{::vm.activity.user.name}}") + img(ng-src="{{::vm.activity.user.photo || '/images/unnamed.png'}}", alt="{{::vm.activity.user.name}}") p(tg-compile-html="vm.activity.title") From 25d3cdadcef504324c00905b811981aa4af6a884 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 13 May 2015 15:00:01 +0200 Subject: [PATCH 161/366] project timeline --- app/coffee/app.coffee | 5 +- app/locales/locale-en.json | 1 + .../profile-bar/profile-bar.directive.coffee | 3 +- .../profile-projects.controller.coffee | 1 + .../profile-tab/profile-tab.directive.coffee | 18 +-- .../profile/profile-tab/profile-tab.jade | 2 + app/modules/profile/profile.controller.coffee | 4 +- .../profile/profile.controller.spec.coffee | 18 +-- app/modules/profile/profile.jade | 2 +- .../project/project.controller.coffee | 6 +- .../project/project.controller.spec.coffee | 26 ++- app/modules/projects/project/project.jade | 4 +- .../projects-resource.service.coffee | 11 ++ .../resources/users-resource.service.coffee | 2 +- .../user-timeline-item-type.service.coffee | 6 + .../user-timeline.controller.coffee | 26 +-- .../user-timeline.controller.spec.coffee | 66 ++++++-- .../user-timeline.directive.coffee | 6 +- .../user-timeline.service.coffee | 6 + .../user-timeline.service.spec.coffee | 148 +++++++++++------- 20 files changed, 239 insertions(+), 122 deletions(-) create mode 100644 app/modules/profile/profile-tab/profile-tab.jade diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index ab483e4b..201e2bf5 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -181,8 +181,9 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven }, access: { requiresLogin: true - } - controller: "Profile" + }, + controller: "Profile", + controllerAs: "vm" } ) diff --git a/app/locales/locale-en.json b/app/locales/locale-en.json index e01a2924..af4e1029 100644 --- a/app/locales/locale-en.json +++ b/app/locales/locale-en.json @@ -1151,6 +1151,7 @@ "ISSUE_CREATED": "{{username}} has created a new Issue in {{project_name}} {{obj_name}}", "TASK_CREATED": "{{username}} has created a new Task in {{project_name}} {{obj_name}}", "WIKI_CREATED": "{{username}} has created a new Wiki page in {{project_name}} {{obj_name}}", + "MILESTONE_CREATED": "{{username}} has created a new Milestone in {{project_name}} {{obj_name}}", "NEW_PROJECT": "{{username}} has a new project {{project_name}}", "MILESTONE_UPDATED": "{{username}} has updated the milestone {{obj_name}}", "US_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the US {{obj_name}}", diff --git a/app/modules/profile/profile-bar/profile-bar.directive.coffee b/app/modules/profile/profile-bar/profile-bar.directive.coffee index 2c5e053e..a236ba61 100644 --- a/app/modules/profile/profile-bar/profile-bar.directive.coffee +++ b/app/modules/profile/profile-bar/profile-bar.directive.coffee @@ -2,7 +2,8 @@ ProfileBarDirective = () -> return { templateUrl: "profile/profile-bar/profile-bar.html", controller: "ProfileBar", - controllerAs: "vm" + controllerAs: "vm", + scope: {} } diff --git a/app/modules/profile/profile-projects/profile-projects.controller.coffee b/app/modules/profile/profile-projects/profile-projects.controller.coffee index 6b47fc54..fe691826 100644 --- a/app/modules/profile/profile-projects/profile-projects.controller.coffee +++ b/app/modules/profile/profile-projects/profile-projects.controller.coffee @@ -14,6 +14,7 @@ class ProfileProjectsController return @userService.attachUserContactsToProjects(userId, projects) .then (projects) => @.projects = projects + console.log @.projects.toJS() angular.module("taigaProfile") .controller("ProfileProjects", ProfileProjectsController) diff --git a/app/modules/profile/profile-tab/profile-tab.directive.coffee b/app/modules/profile/profile-tab/profile-tab.directive.coffee index 50625f32..a15c0f30 100644 --- a/app/modules/profile/profile-tab/profile-tab.directive.coffee +++ b/app/modules/profile/profile-tab/profile-tab.directive.coffee @@ -11,20 +11,12 @@ ProfileTabDirective = () -> ctrl.addTab(scope.tab) - scope.$watch "tab.active", (active) -> - if active - transclude scope, (clone, scope) -> - element.append(clone) - else - element.children().each (idx, elm) -> - scope.$$childHead.$destroy() - elm.remove() - return { - scope: {} - require: "^tgProfileTabs" - link: link - transclude: true + templateUrl: "profile/profile-tab/profile-tab.html", + scope: {}, + require: "^tgProfileTabs", + link: link, + transclude: true, replace: true } diff --git a/app/modules/profile/profile-tab/profile-tab.jade b/app/modules/profile/profile-tab/profile-tab.jade new file mode 100644 index 00000000..ff646ec6 --- /dev/null +++ b/app/modules/profile/profile-tab/profile-tab.jade @@ -0,0 +1,2 @@ +div(ng-if="tab.active") + ng-transclude diff --git a/app/modules/profile/profile.controller.coffee b/app/modules/profile/profile.controller.coffee index 04c6ddc4..141bcda1 100644 --- a/app/modules/profile/profile.controller.coffee +++ b/app/modules/profile/profile.controller.coffee @@ -5,8 +5,8 @@ class ProfilePageController extends taiga.Controller ] constructor: (@appTitle, @auth) -> - user = @auth.getUser() + @.user = @auth.userData - @appTitle.set(user.username) + @appTitle.set(@.user.get('username')) angular.module("taigaProfile").controller("Profile", ProfilePageController) diff --git a/app/modules/profile/profile.controller.spec.coffee b/app/modules/profile/profile.controller.spec.coffee index 6637d7e4..f054c617 100644 --- a/app/modules/profile/profile.controller.spec.coffee +++ b/app/modules/profile/profile.controller.spec.coffee @@ -23,7 +23,7 @@ describe "ProfileController", -> stub = sinon.stub() mocks.auth = { - getUser: sinon.stub() + userData: Immutable.fromJS({username: "UserName"}) } provide.value "$tgAuth", mocks.auth @@ -44,14 +44,14 @@ describe "ProfileController", -> inject ($controller) -> controller = $controller - it "define projects", () -> - user = { - username: "UserName" - } - - mocks.auth.getUser.returns(user) - + it "define user", () -> ctrl = controller "Profile", $scope: {} - expect(mocks.appTitle.set.withArgs(user.username)).to.be.calledOnce + expect(ctrl.user).to.be.equal(mocks.auth.userData) + + it "define projects", () -> + ctrl = controller "Profile", + $scope: {} + + expect(mocks.appTitle.set.withArgs("UserName")).to.be.calledOnce diff --git a/app/modules/profile/profile.jade b/app/modules/profile/profile.jade index 5183265d..6a315e06 100644 --- a/app/modules/profile/profile.jade +++ b/app/modules/profile/profile.jade @@ -4,7 +4,7 @@ div.profile.centered div.main div.timeline-wrapper(tg-profile-tabs) div(tg-profile-tab="activity", tab-title="{{'USER.PROFILE.ACTIVITY_TAB' | translate}}", tab-icon="icon-timeline", tab-active) - div(tg-user-timeline) + div(tg-user-timeline, userId="vm.user.get('id')") div(tg-profile-tab="projects", tab-title="{{'USER.PROFILE.PROJECTS_TAB' | translate}}", tab-icon="icon-project") div(tg-profile-projects) diff --git a/app/modules/projects/project/project.controller.coffee b/app/modules/projects/project/project.controller.coffee index 098b0727..f130a358 100644 --- a/app/modules/projects/project/project.controller.coffee +++ b/app/modules/projects/project/project.controller.coffee @@ -2,11 +2,13 @@ class ProjectController @.$inject = [ "tgProjectsService", "$routeParams", - "$appTitle" + "$appTitle", + "$tgAuth" ] - constructor: (@projectsService, @routeParams, @appTitle) -> + constructor: (@projectsService, @routeParams, @appTitle, @auth) -> projectSlug = @routeParams.pslug + @.user = @auth.userData @projectsService.getProjectBySlug(projectSlug) .then (project) => diff --git a/app/modules/projects/project/project.controller.spec.coffee b/app/modules/projects/project/project.controller.spec.coffee index 8e8b75c3..43528df7 100644 --- a/app/modules/projects/project/project.controller.spec.coffee +++ b/app/modules/projects/project/project.controller.spec.coffee @@ -1,4 +1,4 @@ -describe "ProfileBar", -> +describe "ProjectController", -> $controller = null $q = null provide = null @@ -19,6 +19,13 @@ describe "ProfileBar", -> provide.value "$appTitle", mocks.appTitle + _mockAuth = () -> + mocks.auth = { + userData: Immutable.fromJS({username: "UserName"}) + } + + provide.value "$tgAuth", mocks.auth + _mockRouteParams = () -> provide.value "$routeParams", { pslug: "project-slug" @@ -30,6 +37,7 @@ describe "ProfileBar", -> _mockProjectsService() _mockRouteParams() _mockAppTitle() + _mockAuth() return null @@ -44,6 +52,18 @@ describe "ProfileBar", -> _mocks() _inject() + it "set local user", () -> + thenStub = sinon.stub() + + mocks.projectService.getProjectBySlug.withArgs("project-slug").returns({ + then: thenStub + }) + + ctrl = $controller "Project", + $scope: {} + + expect(ctrl.user).to.be.equal(mocks.auth.userData) + it "set page title", () -> project = Immutable.fromJS({ name: "projectName" @@ -59,8 +79,6 @@ describe "ProfileBar", -> thenStub.callArg(0, project) - $rootScope.$apply() - expect(mocks.appTitle.set.withArgs("projectName")).to.be.calledOnce @@ -79,6 +97,4 @@ describe "ProfileBar", -> thenStub.callArg(0, project) - $rootScope.$apply() - expect(ctrl.project).to.be.equal(project) diff --git a/app/modules/projects/project/project.jade b/app/modules/projects/project/project.jade index a8f0be4d..29dbcb30 100644 --- a/app/modules/projects/project/project.jade +++ b/app/modules/projects/project/project.jade @@ -10,8 +10,8 @@ div.wrapper div.tags-block(tg-colorize-tags="vm.project.get('tags')", tg-colorize-tags-type="backlog") div.project-data - section.timeline - div(tg-user-timeline) + section.timeline(ng-if="vm.project") + div(tg-user-timeline, projectId="vm.project.get('id')", userId="vm.user.get('id')") section.involved-data h2.title {{"PROJECT.SECTION.TEAM" | translate}} ul.involved-team diff --git a/app/modules/resources/projects-resource.service.coffee b/app/modules/resources/projects-resource.service.coffee index 8c061b23..14f59b0d 100644 --- a/app/modules/resources/projects-resource.service.coffee +++ b/app/modules/resources/projects-resource.service.coffee @@ -22,6 +22,17 @@ Resource = (urlsService, http) -> url = urlsService.resolve("bulk-update-projects-order") return http.post(url, bulkData) + service.getTimeline = (projectId, page) -> + params = { + page: page + } + + url = urlsService.resolve("timeline-project") + url = "#{url}/#{projectId}" + + return http.get(url, params).then (result) -> + return Immutable.fromJS(result.data) + return () -> return {"projects": service} diff --git a/app/modules/resources/users-resource.service.coffee b/app/modules/resources/users-resource.service.coffee index 8badf44f..e75490ee 100644 --- a/app/modules/resources/users-resource.service.coffee +++ b/app/modules/resources/users-resource.service.coffee @@ -45,7 +45,7 @@ Resource = (urlsService, http) -> url = urlsService.resolve("timeline-profile") url = "#{url}/#{userId}" - return http.get(url, params).then (result) => + return http.get(url, params).then (result) -> return Immutable.fromJS(result.data) return () -> diff --git a/app/modules/user-timeline/user-timeline-item/user-timeline-item-type.service.coffee b/app/modules/user-timeline/user-timeline-item/user-timeline-item-type.service.coffee index ba7f60b3..6e15edbf 100644 --- a/app/modules/user-timeline/user-timeline-item/user-timeline-item-type.service.coffee +++ b/app/modules/user-timeline/user-timeline-item/user-timeline-item-type.service.coffee @@ -49,6 +49,12 @@ timelineType = (timeline, event) -> key: 'TIMELINE.TASK_CREATED', translate_params: ['username', 'project_name', 'obj_name'] }, + { # NewMilestone + check: (timeline, event) -> + return event.obj == 'milestone' && event.type == 'create' + key: 'TIMELINE.MILESTONE_CREATED', + translate_params: ['username', 'project_name', 'obj_name'] + }, { # NewUsComment check: (timeline, event) -> return timeline.data.comment && event.obj == 'userstory' diff --git a/app/modules/user-timeline/user-timeline/user-timeline.controller.coffee b/app/modules/user-timeline/user-timeline/user-timeline.controller.coffee index dffa696d..8dbcb903 100644 --- a/app/modules/user-timeline/user-timeline/user-timeline.controller.coffee +++ b/app/modules/user-timeline/user-timeline/user-timeline.controller.coffee @@ -25,26 +25,32 @@ mixOf = @.taiga.mixOf class UserTimelineController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.FiltersMixin) @.$inject = [ - "$tgAuth", "tgUserTimelineService" ] - constructor: (@auth, @userTimelineService) -> + constructor: (@userTimelineService) -> @.timelineList = Immutable.List() @.page = 1 @.loadingData = false loadTimeline: () -> - user = @auth.getUser() - @.loadingData = true - @userTimelineService - .getTimeline(user.id, @.page) - .then (newTimelineList) => - @.timelineList = @.timelineList.concat(newTimelineList) - @.page++ - @.loadingData = false + if @.projectId + @userTimelineService + .getProjectTimeline(@.projectId, @.page) + .then (newTimelineList) => + @._timelineLoaded(newTimelineList) + else + @userTimelineService + .getTimeline(@.userId, @.page) + .then (newTimelineList) => + @._timelineLoaded(newTimelineList) + + _timelineLoaded: (newTimelineList) -> + @.timelineList = @.timelineList.concat(newTimelineList) + @.page++ + @.loadingData = false angular.module("taigaUserTimeline") .controller("UserTimeline", UserTimelineController) diff --git a/app/modules/user-timeline/user-timeline/user-timeline.controller.spec.coffee b/app/modules/user-timeline/user-timeline/user-timeline.controller.spec.coffee index 3979a071..c0dc090c 100644 --- a/app/modules/user-timeline/user-timeline/user-timeline.controller.spec.coffee +++ b/app/modules/user-timeline/user-timeline/user-timeline.controller.spec.coffee @@ -1,5 +1,5 @@ describe "UserTimelineController", -> - myCtrl = scope = $q = provide = null + controller = scope = $q = provide = null mocks = {} @@ -7,42 +7,37 @@ describe "UserTimelineController", -> _mockUserTimeline = () -> mocks.userTimelineService = { - getTimeline: sinon.stub() + getTimeline: sinon.stub(), + getProjectTimeline: sinon.stub() } provide.value "tgUserTimelineService", mocks.userTimelineService - _mockTgAuth = () -> - provide.value "$tgAuth", { - getUser: () -> - return mockUser - } - _mocks = () -> module ($provide) -> provide = $provide _mockUserTimeline() - _mockTgAuth() return null - beforeEach -> module "taigaUserTimeline" _mocks() inject ($controller, _$q_) -> $q = _$q_ - myCtrl = $controller "UserTimeline" + controller = $controller it "timelineList should be an array", () -> + myCtrl = controller "UserTimeline" expect(myCtrl.timelineList.toJS()).is.an("array") it "pagination starts at 1", () -> + myCtrl = controller "UserTimeline" expect(myCtrl.page).to.be.equal(1) describe "load timeline", () -> - thenStub = timelineList = null + timelineList = null beforeEach () -> timelineList = Immutable.fromJS([ @@ -52,6 +47,10 @@ describe "UserTimelineController", -> { fake: "fake"} ]) + it "the loadingData variable must be true during the timeline load", () -> + myCtrl = controller "UserTimeline" + myCtrl.userId = mockUser.id + thenStub = sinon.stub() mocks.userTimelineService.getTimeline = sinon.stub() @@ -60,7 +59,6 @@ describe "UserTimelineController", -> then: thenStub }) - it "the loadingData variable must be true during the timeline load", () -> expect(myCtrl.loadingData).to.be.false myCtrl.loadTimeline() @@ -72,6 +70,17 @@ describe "UserTimelineController", -> expect(myCtrl.loadingData).to.be.false it "pagiantion increase one every call to loadTimeline", () -> + myCtrl = controller "UserTimeline" + myCtrl.userId = mockUser.id + + thenStub = sinon.stub() + + mocks.userTimelineService.getTimeline = sinon.stub() + .withArgs(mockUser.id, myCtrl.page) + .returns({ + then: thenStub + }) + expect(myCtrl.page).to.equal(1) myCtrl.loadTimeline() @@ -81,8 +90,39 @@ describe "UserTimelineController", -> expect(myCtrl.page).to.equal(2) it "timeline items", () -> + myCtrl = controller "UserTimeline" + myCtrl.userId = mockUser.id + + thenStub = sinon.stub() + + mocks.userTimelineService.getTimeline = sinon.stub() + .withArgs(mockUser.id, myCtrl.page) + .returns({ + then: thenStub + }) + myCtrl.loadTimeline() thenStub.callArgWith(0, timelineList) expect(myCtrl.timelineList.size).to.be.eql(4) + + it "project timeline items", () -> + myCtrl = controller "UserTimeline" + myCtrl.userId = mockUser.id + myCtrl.projectId = 4 + + thenStub = sinon.stub() + + mocks.userTimelineService.getProjectTimeline = sinon.stub() + .withArgs(4, myCtrl.page) + .returns({ + then: thenStub + }) + + myCtrl.loadTimeline() + + thenStub.callArgWith(0, timelineList) + + expect(myCtrl.timelineList.size).to.be.eql(4) + expect(myCtrl.page).to.equal(2) diff --git a/app/modules/user-timeline/user-timeline/user-timeline.directive.coffee b/app/modules/user-timeline/user-timeline/user-timeline.directive.coffee index dda6e07a..9c15161e 100644 --- a/app/modules/user-timeline/user-timeline/user-timeline.directive.coffee +++ b/app/modules/user-timeline/user-timeline/user-timeline.directive.coffee @@ -3,7 +3,11 @@ UserTimelineDirective = -> templateUrl: "user-timeline/user-timeline/user-timeline.html", controller: "UserTimeline", controllerAs: "vm", - scope: {} + scope: { + projectId: "=projectid", + userId: "=userid" + }, + bindToController: true } angular.module("taigaProfile").directive("tgUserTimeline", UserTimelineDirective) diff --git a/app/modules/user-timeline/user-timeline/user-timeline.service.coffee b/app/modules/user-timeline/user-timeline/user-timeline.service.coffee index 8c4381e0..b0deddda 100644 --- a/app/modules/user-timeline/user-timeline/user-timeline.service.coffee +++ b/app/modules/user-timeline/user-timeline/user-timeline.service.coffee @@ -55,4 +55,10 @@ class UserTimelineService extends taiga.Service .then (result) => return result.filter (timeline) => @._filterValidTimelineItems(timeline) + + getProjectTimeline: (projectId, page) -> + return @rs.projects.getTimeline(projectId, page) + .then (result) => + return result.filter (timeline) => @._filterValidTimelineItems(timeline) + angular.module("taigaUserTimeline").service("tgUserTimelineService", UserTimelineService) diff --git a/app/modules/user-timeline/user-timeline/user-timeline.service.spec.coffee b/app/modules/user-timeline/user-timeline/user-timeline.service.spec.coffee index 9cc2f2c9..bec9174d 100644 --- a/app/modules/user-timeline/user-timeline/user-timeline.service.spec.coffee +++ b/app/modules/user-timeline/user-timeline/user-timeline.service.spec.coffee @@ -12,6 +12,10 @@ describe "tgUserTimelineService", -> getTimeline: sinon.stub() } + mocks.resources.projects = { + getTimeline: sinon.stub() + } + provide.value "tgResources", mocks.resources _mocks = () -> @@ -36,74 +40,74 @@ describe "tgUserTimelineService", -> _setup() _inject() - it "filter invalid timeline items", (done) -> - valid_items = [ - { # valid item - event_type: "xx.tt.create", - data: { - values_diff: { - "status": "xx", - "subject": "xx" - } + valid_items = [ + { # valid item + event_type: "xx.tt.create", + data: { + values_diff: { + "status": "xx", + "subject": "xx" } - }, - { # invalid item - event_type: "xx.tt.create", - data: { - values_diff: { - "fake": "xx" - } + } + }, + { # invalid item + event_type: "xx.tt.create", + data: { + values_diff: { + "fake": "xx" } - }, - { # invalid item - event_type: "xx.tt.create", - data: { - values_diff: { - "fake2": "xx" - } + } + }, + { # invalid item + event_type: "xx.tt.create", + data: { + values_diff: { + "fake2": "xx" } - }, - { # valid item - event_type: "xx.tt.create", - data: { - values_diff: { - "fake2": "xx", - "milestone": "xx" - } + } + }, + { # valid item + event_type: "xx.tt.create", + data: { + values_diff: { + "fake2": "xx", + "milestone": "xx" } - }, - { # invalid item - event_type: "xx.tt.create", - data: { - values_diff: { - attachments: { - new: [] - } - } - } - }, - { # valid item - event_type: "xx.tt.create", - data: { - values_diff: { - attachments: { - new: [1, 2] - } - } - } - }, - { # invalid item - event_type: "xx.tt.delete", - data: { - values_diff: { - attachments: { - new: [1, 2] - } + } + }, + { # invalid item + event_type: "xx.tt.create", + data: { + values_diff: { + attachments: { + new: [] } } } - ] + }, + { # valid item + event_type: "xx.tt.create", + data: { + values_diff: { + attachments: { + new: [1, 2] + } + } + } + }, + { # invalid item + event_type: "xx.tt.delete", + data: { + values_diff: { + attachments: { + new: [1, 2] + } + } + } + } + ] + it "filter invalid user timeline items", (done) -> userId = 3 page = 2 @@ -126,3 +130,27 @@ describe "tgUserTimelineService", -> done() $rootScope.$apply() + + it "filter invalid project timeline items", (done) -> + projectId = 3 + page = 2 + + mocks.resources.projects.getTimeline = (_projectId_, _page_) -> + expect(_projectId_).to.be.equal(projectId) + expect(_page_).to.be.equal(page) + + return $q (resolve, reject) -> + resolve(Immutable.fromJS(valid_items)) + + userTimelineService.getProjectTimeline(projectId, page) + .then (_items_) -> + items = _items_.toJS() + + expect(items).to.have.length(3) + expect(items[0]).to.be.eql(valid_items[0]) + expect(items[1]).to.be.eql(valid_items[3]) + expect(items[2]).to.be.eql(valid_items[5]) + + done() + + $rootScope.$apply() From 623b562192f6d901cc4c98cad18ed16768081a8a Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 13 May 2015 16:36:19 +0200 Subject: [PATCH 162/366] fix profile projects tags --- .../profile-projects.controller.coffee | 6 +- .../profile-projects.controller.spec.coffee | 11 ++- app/modules/projects/projects.service.coffee | 50 +++++++------ .../projects/projects.service.spec.coffee | 70 ++++++++++--------- .../projects-resource.service.coffee | 9 +++ .../resources/users-resource.service.coffee | 9 --- app/modules/services/user.service.coffee | 3 - app/modules/services/user.service.spec.coffee | 13 ---- 8 files changed, 87 insertions(+), 84 deletions(-) diff --git a/app/modules/profile/profile-projects/profile-projects.controller.coffee b/app/modules/profile/profile-projects/profile-projects.controller.coffee index fe691826..a5c8c59c 100644 --- a/app/modules/profile/profile-projects/profile-projects.controller.coffee +++ b/app/modules/profile/profile-projects/profile-projects.controller.coffee @@ -1,20 +1,20 @@ class ProfileProjectsController @.$inject = [ + "tgProjectsService", "tgUserService", "$tgAuth" ] - constructor: (@userService, @auth) -> + constructor: (@projectsService, @userService, @auth) -> loadProjects: () -> userId = @auth.getUser().id - @userService.getProjects(userId) + @projectsService.getProjectsByUserId(userId) .then (projects) => return @userService.attachUserContactsToProjects(userId, projects) .then (projects) => @.projects = projects - console.log @.projects.toJS() angular.module("taigaProfile") .controller("ProfileProjects", ProfileProjectsController) diff --git a/app/modules/profile/profile-projects/profile-projects.controller.spec.coffee b/app/modules/profile/profile-projects/profile-projects.controller.spec.coffee index ab8988c4..f9062661 100644 --- a/app/modules/profile/profile-projects/profile-projects.controller.spec.coffee +++ b/app/modules/profile/profile-projects/profile-projects.controller.spec.coffee @@ -7,12 +7,18 @@ describe "ProfileProjects", -> _mockUserService = () -> mocks.userService = { - getProjects: sinon.stub(), attachUserContactsToProjects: sinon.stub() } provide.value "tgUserService", mocks.userService + _mockProjectsService = () -> + mocks.projectsService = { + getProjectsByUserId: sinon.stub() + } + + provide.value "tgProjectsService", mocks.projectsService + _mockAuthService = () -> stub = sinon.stub() @@ -27,6 +33,7 @@ describe "ProfileProjects", -> provide = $provide _mockUserService() _mockAuthService() + _mockProjectsService() return null @@ -55,7 +62,7 @@ describe "ProfileProjects", -> {id: 3, contacts: "fake"} ] - mocks.userService.getProjects = (userId) -> + mocks.projectsService.getProjectsByUserId = (userId) -> expect(userId).to.be.equal(userId) return $q (resolve, reject) -> diff --git a/app/modules/projects/projects.service.coffee b/app/modules/projects/projects.service.coffee index a097ca9a..996944dd 100644 --- a/app/modules/projects/projects.service.coffee +++ b/app/modules/projects/projects.service.coffee @@ -2,9 +2,9 @@ taiga = @.taiga groupBy = @.taiga.groupBy class ProjectsService extends taiga.Service - @.$inject = ["tgResources", "$rootScope", "$projectUrl", "tgLightboxFactory"] + @.$inject = ["tgResources", "$tgAuth", "$projectUrl", "tgLightboxFactory"] - constructor: (@rs, @rootScope, @projectUrl, @lightboxFactory) -> + constructor: (@rs, @auth, @projectUrl, @lightboxFactory) -> @._currentUserProjects = Immutable.Map() @._currentUserProjectsById = Immutable.Map() @._inProgress = false @@ -24,30 +24,35 @@ class ProjectsService extends taiga.Service getProjectStats: (projectId) -> return @rs.projects.getProjectStats(projectId) + getProjectsByUserId: (userId) -> + return @rs.projects.getProjectsByUserId(userId) + .then (projects) => + return @._decorate(projects) + + _decorate: (projects) -> + return projects.map (project) => + url = @projectUrl.get(project.toJS()) + + project = project.set("url", url) + colorized_tags = [] + + if project.get("tags") + tags = project.get("tags").sort() + + colorized_tags = tags.map (tag) -> + color = project.get("tags_colors").get(tag) + return Immutable.fromJS({name: tag, color: color}) + + project = project.set("colorized_tags", colorized_tags) + + return project + fetchProjects: -> if not @._inProgress @._inProgress = true - @._currentUserProjectsPromise = @rs.users.getProjects(@rootScope.user?.id) + @._currentUserProjectsPromise = @.getProjectsByUserId(@auth.userData.get("id")) @._currentUserProjectsPromise.then (projects) => - projects = projects.map (project) => - url = @projectUrl.get(project.toJS()) - - project = project.set("url", url) - colorized_tags = [] - - if project.get("tags") - tags = project.get("tags").sort() - - colorized_tags = tags.map (tag) -> - color = project.get("tags_colors").get(tag) - return Immutable.fromJS({name: tag, color: color}) - - project = project.set("colorized_tags", colorized_tags) - - return project - - @._currentUserProjects = @._currentUserProjects.set("all", projects) @._currentUserProjects = @._currentUserProjects.set("recents", projects.slice(0, 10)) @@ -69,4 +74,5 @@ class ProjectsService extends taiga.Service @rs.projects.bulkUpdateOrder(sortData).then => @.fetchProjects() -angular.module("taigaProjects").service("tgProjectsService", ProjectsService) +angular.module("taigaProjects").service("tgProjectsService +", ProjectsService) diff --git a/app/modules/projects/projects.service.spec.coffee b/app/modules/projects/projects.service.spec.coffee index 0bc3eb70..e57fc68b 100644 --- a/app/modules/projects/projects.service.spec.coffee +++ b/app/modules/projects/projects.service.spec.coffee @@ -1,26 +1,23 @@ describe "tgProjects", -> - projectsService = provide = null + projectsService = provide = $rootScope = null + $q = null mocks = {} _mockResources = () -> mocks.resources = {} - mocks.resources.users = { - getProjects: sinon.stub() - } + mocks.resources.projects = {} - mocks.thenStub = sinon.stub() - mocks.finallyStub = sinon.stub() - - mocks.resources.users.getProjects.withArgs(10).returns({ - then: mocks.thenStub - finally: mocks.finallyStub - }) + mocks.resources.projects.getProjectsByUserId = () -> + return $q (resolve) -> + resolve(Immutable.fromJS([])) provide.value "tgResources", mocks.resources - _mockRootScope = () -> - provide.value "$rootScope", {user: {id: 10}} + _mockAuthService = () -> + mocks.auth = {userData: Immutable.fromJS({id: 10})} + + provide.value "$tgAuth", mocks.auth _mockProjectUrl = () -> mocks.projectUrl = {get: sinon.stub()} @@ -38,6 +35,12 @@ describe "tgProjects", -> provide.value "tgLightboxFactory", mocks.lightboxFactory _inject = (callback) -> + inject (_$q_, _$rootScope_) -> + $q = _$q_ + $rootScope = _$rootScope_ + callback() if callback + + _injectService = (callback) -> inject (_tgProjectsService_) -> projectsService = _tgProjectsService_ callback() if callback @@ -46,18 +49,15 @@ describe "tgProjects", -> module ($provide) -> provide = $provide _mockResources() - _mockRootScope() _mockProjectUrl() _mockLightboxFactory() + _mockAuthService() return null - _setup = -> - _mocks() - beforeEach -> module "taigaProjects" - _setup() + _mocks() _inject() describe "fetch items", -> @@ -79,8 +79,15 @@ describe "tgProjects", -> {"id": 12, url: 'url-12'} ] + mocks.resources.projects.getProjectsByUserId = () -> + return $q (resolve) -> + resolve(Immutable.fromJS(projects)) + + _injectService() + it "all & recents filled", () -> - mocks.thenStub.callArg(0, Immutable.fromJS(projects)) + projectsService.fetchProjects() + $rootScope.$digest() expect(projectsService.currentUserProjects.get("all").toJS()).to.be.eql(projects) expect(projectsService.currentUserProjects.get("recents").toJS()).to.be.eql(projects.slice(0, 10)) @@ -88,34 +95,25 @@ describe "tgProjects", -> it "_inProgress change to false when tgResources end", () -> expect(projectsService._inProgress).to.be.true - mocks.thenStub.callArg(0, Immutable.fromJS(projects)) - mocks.finallyStub.callArg(0) + $rootScope.$digest() expect(projectsService._inProgress).to.be.false - it "_inProgress prevent new ajax calls", () -> - projectsService.fetchProjects() - projectsService.fetchProjects() - - mocks.thenStub.callArg(0, Immutable.fromJS(projects)) - - expect(mocks.resources.users.getProjects).have.been.calledOnce - it "group projects by id", () -> - mocks.thenStub.callArg(0, Immutable.fromJS(projects)) + $rootScope.$digest() expect(projectsService.currentUserProjectsById.size).to.be.equal(12) expect(projectsService.currentUserProjectsById.toJS()[1].id).to.be.equal(projects[0].id) it "add urls in the project object", () -> - mocks.thenStub.callArg(0, Immutable.fromJS(projects)) + $rootScope.$digest() expect(projectsService.currentUserProjectsById.toJS()[1].url).to.be.equal("url-1") expect(projectsService.currentUserProjects.get("all").toJS()[0].url).to.be.equal("url-1") expect(projectsService.currentUserProjects.get("recents").toJS()[0].url).to.be.equal("url-1") it "add sorted colorized_tags project object", () -> - mocks.thenStub.callArg(0, Immutable.fromJS(projects)) + $rootScope.$digest() tags = [ {name: "aa", color: "white"}, @@ -129,6 +127,8 @@ describe "tgProjects", -> expect(colorized_tags).to.be.eql(tags) it "newProject, create the wizard lightbox", () -> + _injectService() + projectsService.newProject() expect(mocks.lightboxFactory.create).to.have.been.calledWith("tg-lb-create-project", { @@ -136,6 +136,8 @@ describe "tgProjects", -> }) it "bulkUpdateProjectsOrder and then fetch projects again", () -> + _injectService() + projects_order = [ {"id": 8}, {"id": 2}, @@ -168,6 +170,8 @@ describe "tgProjects", -> expect(projectsService.fetchProjects).to.have.been.calledOnce it "getProjectBySlug", () -> + _injectService() + projectSlug = "project-slug" mocks.resources.projects = {} @@ -177,6 +181,8 @@ describe "tgProjects", -> expect(projectsService.getProjectBySlug(projectSlug)).to.be.true it "getProjectStats", () -> + _injectService() + projectId = 3 mocks.resources.projects = {} diff --git a/app/modules/resources/projects-resource.service.coffee b/app/modules/resources/projects-resource.service.coffee index 14f59b0d..2a9d847f 100644 --- a/app/modules/resources/projects-resource.service.coffee +++ b/app/modules/resources/projects-resource.service.coffee @@ -10,6 +10,15 @@ Resource = (urlsService, http) -> .then (result) -> return Immutable.fromJS(result.data) + service.getProjectsByUserId = (userId) -> + url = urlsService.resolve("projects") + + params = {"member": userId, "order_by": "memberships__user_order"} + + return http.get(url, params) + .then (result) -> + return Immutable.fromJS(result.data) + service.getProjectStats = (projectId) -> url = urlsService.resolve("projects") url = "#{url}/#{projectId}" diff --git a/app/modules/resources/users-resource.service.coffee b/app/modules/resources/users-resource.service.coffee index e75490ee..c587e916 100644 --- a/app/modules/resources/users-resource.service.coffee +++ b/app/modules/resources/users-resource.service.coffee @@ -28,15 +28,6 @@ Resource = (urlsService, http) -> .then (result) -> return Immutable.fromJS(result.data) - service.getProjects = (userId) -> - url = urlsService.resolve("projects") - - params = {"member": userId, "order_by": "memberships__user_order"} - - return http.get(url, params) - .then (result) -> - return Immutable.fromJS(result.data) - service.getTimeline = (userId, page) -> params = { page: page diff --git a/app/modules/services/user.service.coffee b/app/modules/services/user.service.coffee index 83704a08..26ec4d1e 100644 --- a/app/modules/services/user.service.coffee +++ b/app/modules/services/user.service.coffee @@ -5,9 +5,6 @@ class UserService extends taiga.Service constructor: (@rs) -> - getProjects: (userId) -> - return @rs.users.getProjects(userId) - getContacts: (userId) -> return @rs.users.getContacts(userId) diff --git a/app/modules/services/user.service.spec.coffee b/app/modules/services/user.service.spec.coffee index 22d552e3..c1ef0cdb 100644 --- a/app/modules/services/user.service.spec.coffee +++ b/app/modules/services/user.service.spec.coffee @@ -32,19 +32,6 @@ describe "UserService", -> _mocks() _inject() - it "get user projects", () -> - userId = 2 - - projects = [ - {id: 1}, - {id: 2}, - {id: 3} - ] - - mocks.resources.users.getProjects.withArgs(userId).returns(true) - - expect(userService.getProjects(userId)).to.be.true - it "get user contacts", () -> userId = 2 From f90cde89283fc8617c3dc8f4a6fb2aa7225d7362 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Thu, 14 May 2015 09:04:20 +0200 Subject: [PATCH 163/366] sinon promise util --- app/modules/services/user.service.spec.coffee | 17 +++------- karma.conf.js | 2 ++ test-utils.js | 33 +++++++++++++++++++ 3 files changed, 40 insertions(+), 12 deletions(-) create mode 100644 test-utils.js diff --git a/app/modules/services/user.service.spec.coffee b/app/modules/services/user.service.spec.coffee index c1ef0cdb..d48b0fe5 100644 --- a/app/modules/services/user.service.spec.coffee +++ b/app/modules/services/user.service.spec.coffee @@ -60,19 +60,15 @@ describe "UserService", -> {id: 3, name: "fake3"} ]) - mocks.resources.users.getContacts = (userId) -> - expect(userId).to.be.equal(userId) - - return $q (resolve, reject) -> - resolve(contacts) + mocks.resources.users.getContacts = sinon.stub().promise() + mocks.resources.users.getContacts.withArgs(userId).resolve(contacts) userService.attachUserContactsToProjects(userId, projects).then (_projects_) -> contacts = _projects_.get(0).get("contacts") expect(contacts.get(0).get("name")).to.be.equal('fake1') - done() - $rootScope.$apply() + done() it "get user contacts", (done) -> userId = 2 @@ -83,11 +79,8 @@ describe "UserService", -> {id: 3} ] - mocks.resources.users.getContacts = (userId) -> - expect(userId).to.be.equal(userId) - - return $q (resolve, reject) -> - resolve(contacts) + mocks.resources.users.getContacts = sinon.stub().promise() + mocks.resources.users.getContacts.withArgs(userId).resolve(contacts) userService.getContacts(userId).then (_contacts_) -> expect(_contacts_).to.be.eql(contacts) diff --git a/karma.conf.js b/karma.conf.js index bc96834e..ae7b045e 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -18,6 +18,8 @@ module.exports = function(config) { 'karma.app.conf.js', 'dist/js/libs.js', 'node_modules/angular-mocks/angular-mocks.js', + 'node_modules/bluebird/js/browser/bluebird.js', + 'test-utils.js', 'dist/js/app.js', 'dist/js/templates.js', 'app/**/*spec.coffee' diff --git a/test-utils.js b/test-utils.js new file mode 100644 index 00000000..4b33b6ad --- /dev/null +++ b/test-utils.js @@ -0,0 +1,33 @@ +(function() { + var searchOriginal = function(obj) { + if (obj._promise) { + return obj; + } else { + return searchOriginal(obj.parent); + } + }; + + sinon.stub.promise = function() { + var obj = this; + + var returnedPromise = new Promise(function(_resolve_, _reject_){ + obj._resolvefn = _resolve_; + obj._rejectfn = _reject_; + }); + + this.returns(returnedPromise); + this._promise = true; + + return this; + }; + + sinon.stub.resolve = function() { + var original = searchOriginal(this); + original._resolvefn.apply(this, arguments); + }; + + sinon.stub.reject = function() { + var original = searchOriginal(this); + original._rejectfn.apply(this, arguments); + }; +}()); From 56385e6ad79ecf9e1c578ba81afeb63056deecd8 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Thu, 14 May 2015 09:21:31 +0200 Subject: [PATCH 164/366] fix duty avatar --- app/modules/home/duties/duty.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/home/duties/duty.jade b/app/modules/home/duties/duty.jade index 6bd557d1..8c26a45b 100644 --- a/app/modules/home/duties/duty.jade +++ b/app/modules/home/duties/duty.jade @@ -1,6 +1,6 @@ a(href="{{ ::vm.duty.get('url') }}", title="{{ ::duty.get('subject') }}") img.avatar( - src="{{ ::vm.duty.get('assigned_to_extra_info').get('photo') }}" + ng-src="{{ ::vm.duty.get('assigned_to_extra_info').get('photo') }}" title="{{ ::vm.duty.get('assigned_to_extra_info').get('full_name_display') }}") div.duty-data From 5ffc33fe0c07910f005f7e76134bf30077a1e251 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 19 May 2015 09:02:32 +0200 Subject: [PATCH 165/366] currentUserService + refactor --- app/coffee/app.coffee | 6 +- app/coffee/modules/auth.coffee | 13 +- app/coffee/modules/base/http.coffee | 12 +- app/coffee/modules/common/loader.coffee | 2 +- app/modules/home/home-page.jade | 1 - app/modules/home/home.directive.coffee | 37 --- app/modules/home/home.directive.spec.coffee | 91 ------- app/modules/home/home.jade | 13 +- app/modules/home/home.service.coffee | 144 +++++------ app/modules/home/home.service.spec.coffee | 243 ++++++++---------- .../home-project-list-directive.spec.coffee | 40 +-- .../home-project-list.directive.coffee | 9 +- .../working-on/working-on.controller.coffee | 29 +++ .../working-on.controller.spec.coffee | 59 +++++ .../working-on/working-on.directive.coffee | 20 ++ app/modules/home/working-on/working-on.jade | 12 + .../dropdown-project-list.directive.coffee | 5 +- ...ropdown-project-list.directive.spec.coffee | 54 ++-- .../navigation-bar.directive.coffee | 6 +- .../navigation-bar.directive.spec.coffee | 32 +-- .../projects-listing.controller.coffee | 7 +- .../projects-listing.controller.spec.coffee | 29 ++- app/modules/projects/projects.service.coffee | 37 +-- .../projects/projects.service.spec.coffee | 108 +++----- .../services/current-user.service.coffee | 45 ++++ .../services/current-user.service.spec.coffee | 77 ++++++ bower.json | 3 +- gulpfile.js | 1 + karma.conf.js | 2 +- package.json | 1 - 30 files changed, 579 insertions(+), 559 deletions(-) delete mode 100644 app/modules/home/home-page.jade delete mode 100644 app/modules/home/home.directive.coffee delete mode 100644 app/modules/home/home.directive.spec.coffee create mode 100644 app/modules/home/working-on/working-on.controller.coffee create mode 100644 app/modules/home/working-on/working-on.controller.spec.coffee create mode 100644 app/modules/home/working-on/working-on.directive.coffee create mode 100644 app/modules/home/working-on/working-on.jade create mode 100644 app/modules/services/current-user.service.coffee create mode 100644 app/modules/services/current-user.service.spec.coffee diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 201e2bf5..8b3b7f84 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -40,7 +40,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven $compileProvider, $translateProvider) -> $routeProvider.when("/", { - templateUrl: "home/home-page.html", + templateUrl: "home/home.html", access: { requiresLogin: true }, @@ -365,6 +365,10 @@ init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $na lang = ctx.language i18nInit(lang, $translate) + # bluebird + Promise.setScheduler (cb) -> + $rootscope.$evalAsync(cb) + # Load user if $auth.isAuthenticated() $events.setupConnection() diff --git a/app/coffee/modules/auth.coffee b/app/coffee/modules/auth.coffee index 4e9e884b..a675ca65 100644 --- a/app/coffee/modules/auth.coffee +++ b/app/coffee/modules/auth.coffee @@ -36,15 +36,21 @@ class AuthService extends taiga.Service "$tgHttp", "$tgUrls", "$tgConfig", - "$translate"] + "$translate", + "tgCurrentUserService"] - constructor: (@rootscope, @storage, @model, @rs, @http, @urls, @config, @translate) -> + constructor: (@rootscope, @storage, @model, @rs, @http, @urls, @config, @translate, @currentUserService) -> super() userModel = @.getUser() @.setUserdata(userModel) setUserdata: (userModel) -> - @.userData = Immutable.fromJS(userModel.getAttrs()) + if userModel + @.userData = Immutable.fromJS(userModel.getAttrs()) + @currentUserService.setUser(@.userData) + else + @.userData = null + _setLocales: -> lang = @rootscope.user.lang || @config.get("defaultLanguage") || "en" @@ -67,6 +73,7 @@ class AuthService extends taiga.Service @rootscope.auth = user @storage.set("userInfo", user.getAttrs()) @rootscope.user = user + @.setUserdata(user) @._setLocales() diff --git a/app/coffee/modules/base/http.coffee b/app/coffee/modules/base/http.coffee index ddf2c7a9..e8847601 100644 --- a/app/coffee/modules/base/http.coffee +++ b/app/coffee/modules/base/http.coffee @@ -22,11 +22,12 @@ taiga = @.taiga class HttpService extends taiga.Service - @.$inject = ["$http", "$q", "$tgStorage", "$rootScope"] + @.$inject = ["$http", "$q", "$tgStorage", "$rootScope", "$cacheFactory"] - constructor: (@http, @q, @storage, @rootScope) -> + constructor: (@http, @q, @storage, @rootScope, @cacheFactory) -> super() + @.cache = @cacheFactory("httpget"); headers: -> headers = {} @@ -52,7 +53,12 @@ class HttpService extends taiga.Service get: (url, params, options) -> options = _.merge({method: "GET", url: url}, options) options.params = params if params - return @.request(options) + + # prevent duplicated http request + options.cache = @.cache + + return @.request(options).finally (data) => + @.cache.removeAll() post: (url, data, params, options) -> options = _.merge({method: "POST", url: url}, options) diff --git a/app/coffee/modules/common/loader.coffee b/app/coffee/modules/common/loader.coffee index 01e48791..d6886971 100644 --- a/app/coffee/modules/common/loader.coffee +++ b/app/coffee/modules/common/loader.coffee @@ -105,7 +105,7 @@ Loader = () -> clearInterval(intervalAuto) clearTimeout(timeoutAuto) - ), 200 + ), 50 start = () -> startLoadTime = new Date().getTime() diff --git a/app/modules/home/home-page.jade b/app/modules/home/home-page.jade deleted file mode 100644 index 9e2a5c4a..00000000 --- a/app/modules/home/home-page.jade +++ /dev/null @@ -1 +0,0 @@ -div(tg-home) diff --git a/app/modules/home/home.directive.coffee b/app/modules/home/home.directive.coffee deleted file mode 100644 index 47d8f99c..00000000 --- a/app/modules/home/home.directive.coffee +++ /dev/null @@ -1,37 +0,0 @@ -HomeDirective = ($q, homeService, projectsService) -> - link = (scope, el, attrs, ctrl) -> - scope.vm = {} - - projectsPromise = projectsService.getCurrentUserProjects() - workInProgresPromise = homeService.getWorkInProgress() - - $q.all([projectsPromise, workInProgresPromise]).then -> - homeService.attachProjectInfoToWorkInProgress(projectsService.currentUserProjectsById) - - taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.currentUserProjects) - taiga.defineImmutableProperty(scope.vm, "workInProgress", () -> homeService.workInProgress) - - if scope.vm.workInProgress.size > 0 - userStories = scope.vm.workInProgress.get("assignedTo").get("userStories") - tasks = scope.vm.workInProgress.get("assignedTo").get("tasks") - issues = scope.vm.workInProgress.get("assignedTo").get("issues") - scope.vm.assignedTo = userStories.concat(tasks).concat(issues) - - userStories = scope.vm.workInProgress.get("watching").get("userStories") - tasks = scope.vm.workInProgress.get("watching").get("tasks") - issues = scope.vm.workInProgress.get("watching").get("issues") - scope.vm.watching = userStories.concat(tasks).concat(issues) - - return { - templateUrl: "home/home.html" - scope: {} - link: link - } - -HomeDirective.$inject = [ - "$q", - "tgHomeService", - "tgProjectsService" -] - -angular.module("taigaHome").directive("tgHome", HomeDirective) diff --git a/app/modules/home/home.directive.spec.coffee b/app/modules/home/home.directive.spec.coffee deleted file mode 100644 index e91aa9cb..00000000 --- a/app/modules/home/home.directive.spec.coffee +++ /dev/null @@ -1,91 +0,0 @@ -describe "homeDirective", () -> - scope = compile = provide = timeout = null - mockTgHomeService = mockTgProjectsService = null - thenStubGetCurrentUserProjectsById = thenStubGetWorkInProgress = null - template = "
" - - createDirective = () -> - elm = compile(template)(scope) - return elm - - _mockTgHomeService = () -> - thenStubGetWorkInProgress = sinon.stub() - - mockTgHomeService = { - getWorkInProgress: sinon.stub() - workInProgress: Immutable.fromJS({ - assignedTo: { - userStories: [{"id": 1}] - tasks: [{"id": 2}] - issues: [{"id": 3}] - } - watching: { - userStories: [{"id": 4}] - tasks: [{"id": 5}] - issues: [{"id": 6}] - } - }) - attachProjectInfoToWorkInProgress: sinon.stub() - } - - mockTgHomeService.getWorkInProgress.returns({ - then: thenStubGetWorkInProgress - }) - provide.value "tgHomeService", mockTgHomeService - - _mockTranslateFilter = () -> - mockTranslateFilter = (value) -> - return value - provide.value "translateFilter", mockTranslateFilter - - _mockTgDuty = () -> - provide.factory 'tgDutyDirective', () -> {} - - _mockHomeProjectList = () -> - provide.factory 'tgHomeProjectListDirective', () -> {} - - _mockTgProjectsService = () -> - thenStubGetCurrentUserProjectsById = sinon.stub() - mockTgProjectsService = { - getCurrentUserProjects: sinon.stub() - currentUserProjectsById: { - get: sinon.stub() - } - } - - mockTgProjectsService.getCurrentUserProjects.returns({ - then: thenStubGetCurrentUserProjectsById - }) - provide.value "tgProjectsService", mockTgProjectsService - - _mocks = () -> - module ($provide) -> - provide = $provide - _mockTgDuty() - _mockHomeProjectList() - _mockTgHomeService() - _mockTranslateFilter() - _mockTgProjectsService() - return null - - beforeEach -> - module "templates" - module "taigaHome" - - _mocks() - - inject ($rootScope, $compile, $timeout) -> - scope = $rootScope.$new() - compile = $compile - timeout = $timeout - - it "home directive content", () -> - elm = createDirective() - scope.$apply() - - thenStubGetCurrentUserProjectsById.callArg(0) - thenStubGetWorkInProgress.callArg(0) - timeout.flush() - - expect(elm.isolateScope().vm.assignedTo.size).to.be.equal(3) - expect(elm.isolateScope().vm.watching.size).to.be.equal(3) diff --git a/app/modules/home/home.jade b/app/modules/home/home.jade index c73a751b..eb815985 100644 --- a/app/modules/home/home.jade +++ b/app/modules/home/home.jade @@ -3,17 +3,6 @@ doctype html include ../../partials/includes/components/beta div.home-wrapper.centered div.duty-summary - div.title-bar.working-on-title(ng-show="vm.assignedTo.size", translate="HOME.WORKING_ON_SECTION") - section.working-on(ng-show="vm.assignedTo.size") - div.duty-single(tg-duty="duty", tg-repeat="duty in vm.assignedTo", ng-class="{blocked: duty.is_blocked}") - - div.title-bar.watching-title(translate="HOME.WATCHING_SECTION") - - section.watching-empty(ng-show="!vm.watching.size") - include ../../svg/hide.svg - p(translate="HOME.EMPTY_WATCHING") - - section.watching(ng-show="vm.watching.size") - div.duty-single(tg-duty="duty", tg-repeat="duty in vm.watching", ng-class="{blocked: duty.is_blocked}") + div(tg-working-on) aside.project-list(tg-home-project-list) diff --git a/app/modules/home/home.service.coffee b/app/modules/home/home.service.coffee index 36207c45..08cc5e03 100644 --- a/app/modules/home/home.service.coffee +++ b/app/modules/home/home.service.coffee @@ -1,18 +1,18 @@ +groupBy = @.taiga.groupBy + class HomeService extends taiga.Service - @.$inject = ["$q", "$tgNavUrls", "tgResources", "$rootScope", "$projectUrl", "$tgAuth"] + @.$inject = [ + "$tgNavUrls", + "tgResources", + "tgProjectsService" + ] - constructor: (@q, @navurls, @rs, @rootScope, @projectUrl, @auth) -> - @._workInProgress = Immutable.Map() - @._projectPromise = null - @._inProgress = false + constructor: (@navurls, @rs, @projectsService) -> - taiga.defineImmutableProperty @, "workInProgress", () => return @._workInProgress - - @.fetchWorkInProgress() - - attachProjectInfoToWorkInProgress: (projectsById) -> + _attachProjectInfoToWorkInProgress: (workInProgress, projectsById) -> _attachProjectInfoToDuty = (duty, objType) => project = projectsById.get(String(duty.get('project'))) + ctx = { project: project.get('slug') ref: duty.get('ref') @@ -26,105 +26,105 @@ class HomeService extends taiga.Service return duty - assignedTo = Immutable.Map() + assignedTo = workInProgress.get("assignedTo") - if @.assignedToUserStories - _duties = @.assignedToUserStories.map (duty) -> + if assignedTo.get("userStories") + _duties = assignedTo.get("userStories").map (duty) -> return _attachProjectInfoToDuty(duty, "userstories") assignedTo = assignedTo.set("userStories", _duties) - if @.assignedToTasks - _duties = @.assignedToTasks.map (duty) -> + if assignedTo.get("tasks") + _duties = assignedTo.get("tasks").map (duty) -> return _attachProjectInfoToDuty(duty, "tasks") assignedTo = assignedTo.set("tasks", _duties) - if @.assignedToIssues - _duties = @.assignedToIssues.map (duty) -> + if assignedTo.get("issues") + _duties = assignedTo.get("issues").map (duty) -> return _attachProjectInfoToDuty(duty, "issues") assignedTo = assignedTo.set("issues", _duties) - watching = Immutable.Map() + watching = workInProgress.get("watching") - if @.watchingUserStories - _duties = @.watchingUserStories.map (duty) -> + if watching.get("userStories") + _duties = watching.get("userStories").map (duty) -> return _attachProjectInfoToDuty(duty, "userstories") watching = watching.set("userStories", _duties) - if @.watchingTasks - _duties = @.watchingTasks.map (duty) -> + if watching.get("tasks") + _duties = watching.get("tasks").map (duty) -> return _attachProjectInfoToDuty(duty, "tasks") watching = watching.set("tasks", _duties) - if @.watchingIssues - _duties = @.watchingIssues.map (duty) -> + if watching.get("issues") + _duties = watching.get("issues").map (duty) -> return _attachProjectInfoToDuty(duty, "issues") watching = watching.set("issues", _duties) - @._workInProgress = Immutable.Map({ - assignedTo: assignedTo, - watching: watching - }) - getWorkInProgress: () -> - return @._projectPromise + workInProgress = workInProgress.set("assignedTo", assignedTo) + workInProgress = workInProgress.set("watching", watching) - fetchWorkInProgress: () -> - userId = @auth.getUser().id - if not @._inProgress - @._inProgress = true - params = { - status__is_closed: false - assigned_to: userId - } - assignedUserStoriesPromise = @rs.userstories.listInAllProjects(params).then (userstories) => - @.assignedToUserStories = userstories + getWorkInProgress: (userId) -> + projectsById = Immutable.Map() - assignedTasksPromise = @rs.tasks.listInAllProjects(params).then (tasks) => - @.assignedToTasks = tasks + projectsPromise = @projectsService.getProjectsByUserId(userId).then (projects) -> + projectsById = Immutable.fromJS(groupBy(projects.toJS(), (p) -> p.id)) - assignedIssuesPromise = @rs.issues.listInAllProjects(params).then (issues) => - @.assignedToIssues = issues + assignedTo = Immutable.Map() - params = { - status__is_closed: false - watchers: userId - } - watchingUserStoriesPromise = @rs.userstories.listInAllProjects(params).then (userstories) => - @.watchingUserStories = userstories + params = { + status__is_closed: false + assigned_to: userId + } - watchingTasksPromise = @rs.tasks.listInAllProjects(params).then (tasks) => - @.watchingTasks = tasks + assignedUserStoriesPromise = @rs.userstories.listInAllProjects(params).then (userstories) -> + assignedTo = assignedTo.set("userStories", userstories) - watchingIssuesPromise = @rs.issues.listInAllProjects(params).then (issues) => - @.watchingIssues = issues + assignedTasksPromise = @rs.tasks.listInAllProjects(params).then (tasks) -> + assignedTo = assignedTo.set("tasks", tasks) - @._projectPromise = @q.all([assignedUserStoriesPromise, assignedTasksPromise, - assignedIssuesPromise, watchingUserStoriesPromise, - watchingUserStoriesPromise, watchingIssuesPromise]) + assignedIssuesPromise = @rs.issues.listInAllProjects(params).then (issues) -> + assignedTo = assignedTo.set("issues", issues) - @._projectPromise.then => - @._workInProgress = Immutable.fromJS({ - assignedTo: { - userStories: @.assignedToUserStories - tasks: @.assignedToTasks - issues: @.assignedToIssues - } - watching: { - userStories: @.watchingUserStories - tasks: @.watchingTasks - issues: @.watchingIssues - } - }) + params = { + status__is_closed: false + watchers: userId + } - @._inProgress = false + watching = Immutable.Map() - return @._projectPromise + watchingUserStoriesPromise = @rs.userstories.listInAllProjects(params).then (userstories) -> + watching = watching.set("userStories", userstories) + + watchingTasksPromise = @rs.tasks.listInAllProjects(params).then (tasks) -> + watching = watching.set("tasks", tasks) + + watchingIssuesPromise = @rs.issues.listInAllProjects(params).then (issues) -> + watching = watching.set("issues", issues) + + workInProgress = Immutable.Map() + + Promise.all([ + projectsPromise + assignedUserStoriesPromise, + assignedTasksPromise, + assignedIssuesPromise, + watchingUserStoriesPromise, + watchingTasksPromise, + watchingIssuesPromise + ]).then => + workInProgress = workInProgress.set("assignedTo", assignedTo) + workInProgress = workInProgress.set("watching", watching) + + workInProgress = @._attachProjectInfoToWorkInProgress(workInProgress, projectsById) + + return workInProgress angular.module("taigaHome").service("tgHomeService", HomeService) diff --git a/app/modules/home/home.service.spec.coffee b/app/modules/home/home.service.spec.coffee index 167c5a61..e174608f 100644 --- a/app/modules/home/home.service.spec.coffee +++ b/app/modules/home/home.service.spec.coffee @@ -1,90 +1,36 @@ describe "tgHome", -> - homeService = provide = timeout = null + homeService = provide = null mocks = {} _mockResources = () -> mocks.resources = {} - mocks.resources.userstories = { - listInAllProjects: sinon.stub() - } + mocks.resources.userstories = {} + mocks.resources.tasks = {} + mocks.resources.issues = {} - mocks.resources.tasks = { - listInAllProjects: sinon.stub() - } - - mocks.resources.issues = { - listInAllProjects: sinon.stub() - } - - paramsAssignedTo = { - status__is_closed: false - assigned_to: 1 - } - - paramsWatching = { - status__is_closed: false - watchers: 1 - } - - mocks.thenStubAssignedToUserstories = sinon.stub() - mocks.resources.userstories.listInAllProjects.withArgs(paramsAssignedTo).returns({ - then: mocks.thenStubAssignedToUserstories - }) - - mocks.thenStubAssignedToTasks = sinon.stub() - mocks.resources.tasks.listInAllProjects.withArgs(paramsAssignedTo).returns({ - then: mocks.thenStubAssignedToTasks - }) - - mocks.thenStubAssignedToIssues = sinon.stub() - mocks.resources.issues.listInAllProjects.withArgs(paramsAssignedTo).returns({ - then: mocks.thenStubAssignedToIssues - }) - - - mocks.thenStubWatchingUserstories = sinon.stub() - mocks.resources.userstories.listInAllProjects.withArgs(paramsWatching).returns({ - then: mocks.thenStubWatchingUserstories - }) - - mocks.thenStubWatchingTasks = sinon.stub() - mocks.resources.tasks.listInAllProjects.withArgs(paramsWatching).returns({ - then: mocks.thenStubWatchingTasks - }) - - mocks.thenStubWatchingIssues = sinon.stub() - mocks.resources.issues.listInAllProjects.withArgs(paramsWatching).returns({ - then: mocks.thenStubWatchingIssues - }) + mocks.resources.userstories.listInAllProjects = sinon.stub().promise() + mocks.resources.tasks.listInAllProjects = sinon.stub().promise() + mocks.resources.issues.listInAllProjects = sinon.stub().promise() provide.value "tgResources", mocks.resources - _mockProjectUrl = () -> - mocks.projectUrl = {get: sinon.stub()} - mocks.projectUrl.get = (project) -> - return "url-" + project.id - - provide.value "$projectUrl", mocks.projectUrl - - _mockAuth = () -> - mocks.auth = { - getUser: sinon.stub() - } - - mocks.auth.getUser.returns(id: 1) - - provide.value "$tgAuth", mocks.auth - _mockTgNavUrls = () -> mocks.tgNavUrls = { resolve: sinon.stub() } + provide.value "$tgNavUrls", mocks.tgNavUrls + _mockProjectsService = () -> + mocks.projectsService = { + getProjectsByUserId: sinon.stub().promise() + } + + provide.value "tgProjectsService", mocks.projectsService + _inject = (callback) -> - inject (_$timeout_, _tgHomeService_) -> - timeout = _$timeout_ + inject (_tgHomeService_) -> homeService = _tgHomeService_ callback() if callback @@ -92,9 +38,9 @@ describe "tgHome", -> module ($provide) -> provide = $provide _mockResources() - _mockProjectUrl() - _mockAuth() _mockTgNavUrls() + _mockProjectsService() + return null _setup = -> @@ -105,80 +51,93 @@ describe "tgHome", -> _setup() _inject() - describe "fetch items", -> - it "work in progress filled", () -> - mocks.thenStubAssignedToUserstories.callArg(0, Immutable.fromJS([{"id": 1}])) - mocks.thenStubAssignedToTasks.callArg(0, Immutable.fromJS([{"id": 2}])) - mocks.thenStubAssignedToIssues.callArg(0, Immutable.fromJS([{"id": 3}])) - mocks.thenStubWatchingUserstories.callArg(0, Immutable.fromJS([{"id": 4}])) - mocks.thenStubWatchingTasks.callArg(0, Immutable.fromJS([{"id": 5}])) - mocks.thenStubWatchingIssues.callArg(0, Immutable.fromJS([{"id": 6}])) + it "get work in progress by user", (done) -> + userId = 3 - timeout.flush() - expect(homeService.workInProgress.toJS()).to.be.eql({ - assignedTo: { - userStories: [{"id": 1}] - tasks: [{"id": 2}] - issues: [{"id": 3}] - } - watching: { - userStories: [{"id": 4}] - tasks: [{"id": 5}] - issues: [{"id": 6}] - } - }) + mocks.projectsService.getProjectsByUserId + .withArgs(userId) + .resolve(Immutable.fromJS([ + {id: 1, name: "fake1", slug: "project-1"}, + {id: 2, name: "fake2", slug: "project-2"} + ])) - it "_inProgress change to false when tgResources end", () -> - expect(homeService._inProgress).to.be.true - timeout.flush() - expect(homeService._inProgress).to.be.false + mocks.resources.userstories.listInAllProjects + .resolve(Immutable.fromJS([{id: 1, ref: 1, project: "1"}])) - it "project info filled", () -> - duty = { - id: 66 - ref: 123 - project: 1 - } + mocks.resources.tasks.listInAllProjects + .resolve(Immutable.fromJS([{id: 2, ref: 2, project: "1"}])) - mocks.thenStubAssignedToUserstories.callArg(0, Immutable.fromJS([duty])) - mocks.thenStubAssignedToTasks.callArg(0, Immutable.fromJS([])) - mocks.thenStubAssignedToIssues.callArg(0, Immutable.fromJS([])) - mocks.thenStubWatchingUserstories.callArg(0, Immutable.fromJS([])) - mocks.thenStubWatchingTasks.callArg(0, Immutable.fromJS([])) - mocks.thenStubWatchingIssues.callArg(0, Immutable.fromJS([])) - timeout.flush() + mocks.resources.issues.listInAllProjects + .resolve(Immutable.fromJS([{id: 3, ref: 3, project: "1"}])) - projectsById = { - get: () -> Immutable.fromJS({ - name: "Testing project" - slug: "testing-project" + # mock urls + mocks.tgNavUrls.resolve + .withArgs("project-userstories-detail", {project: "project-1", ref: 1}) + .returns("/testing-project/us/1") + + mocks.tgNavUrls.resolve + .withArgs("project-tasks-detail", {project: "project-1", ref: 2}) + .returns("/testing-project/tasks/1") + + mocks.tgNavUrls.resolve + .withArgs("project-issues-detail", {project: "project-1", ref: 3}) + .returns("/testing-project/issues/1") + + homeService.getWorkInProgress(userId) + .then (workInProgress) -> + expect(workInProgress.toJS()).to.be.eql({ + assignedTo: { + userStories: [{ + id: 1, + ref: 1, + project: '1', + url: '/testing-project/us/1', + projectName: 'fake1', + _name: 'userstories' + }] + tasks: [{ + id: 2, + ref: 2, + project: '1', + url: '/testing-project/tasks/1', + projectName: 'fake1', + _name: 'tasks' + }] + issues: [{ + id: 3, + ref: 3, + project: '1', + url: '/testing-project/issues/1', + projectName: 'fake1', + _name: 'issues' + }] + } + watching: { + userStories: [{ + id: 1, + ref: 1, + project: '1', + url: '/testing-project/us/1', + projectName: 'fake1', + _name: 'userstories' + }] + tasks: [{ + id: 2, + ref: 2, + project: '1', + url: '/testing-project/tasks/1', + projectName: 'fake1', + _name: 'tasks' + }] + issues: [{ + id: 3, + ref: 3, + project: '1', + url: '/testing-project/issues/1', + projectName: 'fake1', + _name: 'issues' + }] + } }) - } - mocks.tgNavUrls.resolve - .withArgs("project-userstories-detail", {project: "testing-project", ref: 123}) - .returns("/testing-project/us/123") - - homeService.attachProjectInfoToWorkInProgress(projectsById) - - expect(homeService.workInProgress.toJS()).to.be.eql({ - assignedTo: { - userStories: [ - { - id: 66 - _name: "userstories" - ref: 123 - project: 1 - url: "/testing-project/us/123" - projectName: "Testing project" - } - ] - tasks: [] - issues: [] - } - watching: { - userStories: [] - tasks: [] - issues: [] - } - }) + done() diff --git a/app/modules/home/projects/home-project-list-directive.spec.coffee b/app/modules/home/projects/home-project-list-directive.spec.coffee index 2ad8b7b6..eccd78ec 100644 --- a/app/modules/home/projects/home-project-list-directive.spec.coffee +++ b/app/modules/home/projects/home-project-list-directive.spec.coffee @@ -1,21 +1,31 @@ describe "homeProjectListDirective", () -> scope = compile = provide = null - mockTgProjectsService = null + mocks = {} template = "
" - recents = [] + projects = Immutable.fromJS({ + recents: [ + {id: 1}, + {id: 2}, + {id: 3} + ] + }) createDirective = () -> elm = compile(template)(scope) return elm - _mockTgProjectsService = () -> - mockTgProjectsService = { - newProject: sinon.stub() - currentUserProjects: { - get: sinon.stub() - } + _mockTgCurrentUserService = () -> + mocks.currentUserService = { + projects: projects } - provide.value "tgProjectsService", mockTgProjectsService + + provide.value "tgCurrentUserService", mocks.currentUserService + + _mockTgProjectsService = () -> + mocks.projectsService = { + newProject: sinon.stub() + } + provide.value "tgProjectsService", mocks.projectsService _mockTranslateFilter = () -> mockTranslateFilter = (value) -> @@ -25,6 +35,7 @@ describe "homeProjectListDirective", () -> _mocks = () -> module ($provide) -> provide = $provide + _mockTgCurrentUserService() _mockTgProjectsService() _mockTranslateFilter() return null @@ -49,17 +60,14 @@ describe "homeProjectListDirective", () -> ]) it "home project list directive scope content", () -> - mockTgProjectsService.currentUserProjects.get - .withArgs("recents") - .returns(recents) - elm = createDirective() scope.$apply() - expect(elm.isolateScope().vm.projects.size).to.be.equal(2) + expect(elm.isolateScope().vm.projects.size).to.be.equal(3) it "home project list directive newProject", () -> elm = createDirective() scope.$apply() - expect(mockTgProjectsService.newProject.callCount).to.be.equal(0) + + expect(mocks.projectsService.newProject.callCount).to.be.equal(0) elm.isolateScope().vm.newProject() - expect(mockTgProjectsService.newProject.callCount).to.be.equal(1) + expect(mocks.projectsService.newProject.callCount).to.be.equal(1) diff --git a/app/modules/home/projects/home-project-list.directive.coffee b/app/modules/home/projects/home-project-list.directive.coffee index 17947490..4af582e0 100644 --- a/app/modules/home/projects/home-project-list.directive.coffee +++ b/app/modules/home/projects/home-project-list.directive.coffee @@ -1,8 +1,8 @@ -HomeProjectListDirective = (projectsService) -> +HomeProjectListDirective = (currentUserService, projectsService) -> link = (scope, el, attrs, ctrl) -> scope.vm = {} - taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.currentUserProjects.get("recents")) + taiga.defineImmutableProperty(scope.vm, "projects", () -> currentUserService.projects.get("recents")) scope.vm.newProject = -> projectsService.newProject() @@ -15,6 +15,9 @@ HomeProjectListDirective = (projectsService) -> return directive -HomeProjectListDirective.$inject = ["tgProjectsService"] +HomeProjectListDirective.$inject = [ + "tgCurrentUserService", + "tgProjectsService" +] angular.module("taigaHome").directive("tgHomeProjectList", HomeProjectListDirective) diff --git a/app/modules/home/working-on/working-on.controller.coffee b/app/modules/home/working-on/working-on.controller.coffee new file mode 100644 index 00000000..9d101bcf --- /dev/null +++ b/app/modules/home/working-on/working-on.controller.coffee @@ -0,0 +1,29 @@ +class WorkingOnController + @.$inject = [ + "tgHomeService" + ] + + constructor: (@homeService) -> + @.assignedTo = Immutable.Map() + @.watching = Immutable.Map() + + _setAssignedTo: (workInProgress) -> + userStories = workInProgress.get("assignedTo").get("userStories") + tasks = workInProgress.get("assignedTo").get("tasks") + issues = workInProgress.get("assignedTo").get("issues") + + @.assignedTo = userStories.concat(tasks).concat(issues) + + _setWatching: (workInProgress) -> + userStories = workInProgress.get("watching").get("userStories") + tasks = workInProgress.get("watching").get("tasks") + issues = workInProgress.get("watching").get("issues") + + @.watching = userStories.concat(tasks).concat(issues) + + getWorkInProgress: (userId) -> + return @homeService.getWorkInProgress(userId).then (workInProgress) => + @._setAssignedTo(workInProgress) + @._setWatching(workInProgress) + +angular.module("taigaHome").controller("WorkingOn", WorkingOnController) diff --git a/app/modules/home/working-on/working-on.controller.spec.coffee b/app/modules/home/working-on/working-on.controller.spec.coffee new file mode 100644 index 00000000..9a77d8b2 --- /dev/null +++ b/app/modules/home/working-on/working-on.controller.spec.coffee @@ -0,0 +1,59 @@ +describe "WorkingOn", -> + $controller = null + $provide = null + mocks = {} + + _mockHomeService = () -> + mocks.homeService = { + getWorkInProgress: sinon.stub().promise() + } + + $provide.value("tgHomeService", mocks.homeService) + + _mocks = () -> + module (_$provide_) -> + $provide = _$provide_ + + _mockHomeService() + + return null + + _inject = () -> + inject (_$controller_) -> + $controller = _$controller_ + + beforeEach -> + module "taigaHome" + _mocks() + _inject() + + it "get work in progress items", (done) -> + userId = 3 + + workInProgress = Immutable.fromJS({ + assignedTo: { + userStories: [{id: 1}, {id: 2}], + tasks: [{id: 3}, {id: 4}], + issues: [{id: 5}, {id: 6}] + }, + watching: { + userStories: [{id: 7}, {id: 8}], + tasks: [{id: 9}, {id: 10}], + issues: [{id: 11}, {id: 12}] + } + }) + + mocks.homeService.getWorkInProgress.withArgs(userId).resolve(workInProgress) + + ctrl = $controller("WorkingOn") + + ctrl.getWorkInProgress(userId).then () -> + expect(ctrl.assignedTo.toJS()).to.be.eql([ + {id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6} + ]) + + expect(ctrl.watching.toJS()).to.be.eql([ + {id: 7}, {id: 8}, {id: 9}, {id: 10}, {id: 11}, {id: 12} + ]) + + done() diff --git a/app/modules/home/working-on/working-on.directive.coffee b/app/modules/home/working-on/working-on.directive.coffee new file mode 100644 index 00000000..2a806655 --- /dev/null +++ b/app/modules/home/working-on/working-on.directive.coffee @@ -0,0 +1,20 @@ +WorkingOnDirective = (homeService, currentUserService) -> + link = (scope, el, attrs, ctrl) -> + userId = currentUserService.getUser().get("id") + + ctrl.getWorkInProgress(userId) + + return { + controller: "WorkingOn", + controllerAs: "vm", + templateUrl: "home/working-on/working-on.html", + scope: {}, + link: link + } + +WorkingOnDirective.$inject = [ + "tgHomeService", + "tgCurrentUserService" +] + +angular.module("taigaHome").directive("tgWorkingOn", WorkingOnDirective) diff --git a/app/modules/home/working-on/working-on.jade b/app/modules/home/working-on/working-on.jade new file mode 100644 index 00000000..34e287ef --- /dev/null +++ b/app/modules/home/working-on/working-on.jade @@ -0,0 +1,12 @@ +div.title-bar.working-on-title(ng-show="vm.assignedTo.size", translate="HOME.WORKING_ON_SECTION") +section.working-on(ng-show="vm.assignedTo.size") + div.duty-single(tg-duty="duty", tg-repeat="duty in vm.assignedTo", ng-class="{blocked: duty.is_blocked}") + +div.title-bar.watching-title(translate="HOME.WATCHING_SECTION") + +section.watching-empty(ng-show="!vm.watching.size") + include ../../../svg/hide.svg + p(translate="HOME.EMPTY_WATCHING") + +section.watching(ng-show="vm.watching.size") + div.duty-single(tg-duty="duty", tg-repeat="duty in vm.watching", ng-class="{blocked: duty.is_blocked}") \ No newline at end of file diff --git a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee index be019c11..b125c86c 100644 --- a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee +++ b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.coffee @@ -1,8 +1,8 @@ -DropdownProjectListDirective = (projectsService) -> +DropdownProjectListDirective = (currentUserService, projectsService) -> link = (scope, el, attrs, ctrl) -> scope.vm = {} - taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.currentUserProjects.get("recents")) + taiga.defineImmutableProperty(scope.vm, "projects", () -> currentUserService.projects.get("recents")) scope.vm.newProject = -> projectsService.newProject() @@ -16,6 +16,7 @@ DropdownProjectListDirective = (projectsService) -> return directive DropdownProjectListDirective.$inject = [ + "tgCurrentUserService", "tgProjectsService" ] diff --git a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.spec.coffee b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.spec.coffee index 46fe9b75..4f144067 100644 --- a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.spec.coffee +++ b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.directive.spec.coffee @@ -1,32 +1,45 @@ describe "dropdownProjectListDirective", () -> scope = compile = provide = null - mockTgProjectsService = null + mocks = {} template = "
" recents = [] - createDirective = () -> - elm = compile(template)(scope) - return elm - - _mockTgProjectsService = () -> - mockTgProjectsService = { - newProject: sinon.stub() - currentUserProjects: { - get: sinon.stub() - } - } - provide.value "tgProjectsService", mockTgProjectsService + projects = Immutable.fromJS({ + recents: [ + {id: 1}, + {id: 2}, + {id: 3} + ] + }) _mockTranslateFilter = () -> mockTranslateFilter = (value) -> return value provide.value "translateFilter", mockTranslateFilter + createDirective = () -> + elm = compile(template)(scope) + return elm + + _mockTgProjectsService = () -> + mocks.projectsService = { + newProject: sinon.stub() + } + provide.value "tgProjectsService", mocks.projectsService + + _mockTgCurrentUserService = () -> + mocks.currentUserService = { + projects: projects + } + provide.value "tgCurrentUserService", mocks.currentUserService + _mocks = () -> module ($provide) -> provide = $provide _mockTgProjectsService() + _mockTgCurrentUserService() _mockTranslateFilter() + return null beforeEach -> @@ -39,20 +52,7 @@ describe "dropdownProjectListDirective", () -> scope = $rootScope.$new() compile = $compile - recents = Immutable.fromJS([ - { - id:1 - }, - { - id: 2 - } - ]) - it "dropdown project list directive scope content", () -> - mockTgProjectsService.currentUserProjects.get - .withArgs("recents") - .returns(recents) - elm = createDirective() scope.$apply() - expect(elm.isolateScope().vm.projects.size).to.be.equal(2) + expect(elm.isolateScope().vm.projects.size).to.be.equal(3) diff --git a/app/modules/navigation-bar/navigation-bar.directive.coffee b/app/modules/navigation-bar/navigation-bar.directive.coffee index cdda7d28..ba8e0fe6 100644 --- a/app/modules/navigation-bar/navigation-bar.directive.coffee +++ b/app/modules/navigation-bar/navigation-bar.directive.coffee @@ -1,8 +1,8 @@ -NavigationBarDirective = (projectsService) -> +NavigationBarDirective = (currentUserService) -> link = (scope, el, attrs, ctrl) -> scope.vm = {} - taiga.defineImmutableProperty(scope.vm, "projects", () -> projectsService.currentUserProjects.get("recents")) + taiga.defineImmutableProperty(scope.vm, "projects", () -> currentUserService.projects.get("recents")) directive = { templateUrl: "navigation-bar/navigation-bar.html" @@ -13,7 +13,7 @@ NavigationBarDirective = (projectsService) -> return directive NavigationBarDirective.$inject = [ - "tgProjectsService" + "tgCurrentUserService" ] angular.module("taigaNavigationBar").directive("tgNavigationBar", NavigationBarDirective) diff --git a/app/modules/navigation-bar/navigation-bar.directive.spec.coffee b/app/modules/navigation-bar/navigation-bar.directive.spec.coffee index 31f2b13e..62d9aa42 100644 --- a/app/modules/navigation-bar/navigation-bar.directive.spec.coffee +++ b/app/modules/navigation-bar/navigation-bar.directive.spec.coffee @@ -1,21 +1,26 @@ describe "navigationBarDirective", () -> scope = compile = provide = null - mockTgProjectsService = null + mocks = {} template = "
" - recents = [] + projects = Immutable.fromJS({ + recents: [ + {id: 1}, + {id: 2}, + {id: 3} + ] + }) createDirective = () -> elm = compile(template)(scope) return elm - _mockTgProjectsService = () -> - mockTgProjectsService = { - newProject: sinon.stub() - currentUserProjects: { - get: sinon.stub() - } + _mocksCurrentUserService = () -> + mocks.currentUserService = { + projects: projects } - provide.value "tgProjectsService", mockTgProjectsService + + provide.value "tgCurrentUserService", mocks.currentUserService + _mockTranslateFilter = () -> mockTranslateFilter = (value) -> @@ -31,7 +36,8 @@ describe "navigationBarDirective", () -> _mocks = () -> module ($provide) -> provide = $provide - _mockTgProjectsService() + + _mocksCurrentUserService() _mockTranslateFilter() _mockTgDropdownProjectListDirective() _mockTgDropdownUserDirective() @@ -57,10 +63,6 @@ describe "navigationBarDirective", () -> ]) it "navigation bar directive scope content", () -> - mockTgProjectsService.currentUserProjects.get - .withArgs("recents") - .returns(recents) - elm = createDirective() scope.$apply() - expect(elm.isolateScope().vm.projects.size).to.be.equal(2) + expect(elm.isolateScope().vm.projects.size).to.be.equal(3) diff --git a/app/modules/projects/listing/projects-listing.controller.coffee b/app/modules/projects/listing/projects-listing.controller.coffee index 3206782c..d815fa5c 100644 --- a/app/modules/projects/listing/projects-listing.controller.coffee +++ b/app/modules/projects/listing/projects-listing.controller.coffee @@ -1,10 +1,11 @@ class ProjectsListingController @.$inject = [ - "tgProjectsService" + "tgCurrentUserService", + "tgProjectsService", ] - constructor: (@projectsService) -> - taiga.defineImmutableProperty(@, "projects", () => @projectsService.currentUserProjects.get("all")) + constructor: (@currentUserService, @projectsService) -> + taiga.defineImmutableProperty(@, "projects", () => @currentUserService.projects.get("all")) newProject: -> @projectsService.newProject() diff --git a/app/modules/projects/listing/projects-listing.controller.spec.coffee b/app/modules/projects/listing/projects-listing.controller.spec.coffee index a976da33..58b61c79 100644 --- a/app/modules/projects/listing/projects-listing.controller.spec.coffee +++ b/app/modules/projects/listing/projects-listing.controller.spec.coffee @@ -4,30 +4,37 @@ describe "ProjectsListingController", -> controller = null mocks = {} - projects = Immutable.fromJS([ - {id: 1}, - {id: 2}, - {id: 3} - ]) + projects = Immutable.fromJS({ + all: [ + {id: 1}, + {id: 2}, + {id: 3} + ] + }) + + _mockCurrentUserService = () -> + stub = sinon.stub() + + mocks.currentUserService = { + projects: projects + } + + provide.value "tgCurrentUserService", mocks.currentUserService _mockProjectsService = () -> stub = sinon.stub() mocks.projectsService = { - currentUserProjects: { - get: stub - }, newProject: sinon.stub() } - stub.withArgs("all").returns(projects) - provide.value "tgProjectsService", mocks.projectsService _mocks = () -> module ($provide) -> provide = $provide _mockProjectsService() + _mockCurrentUserService() return null @@ -43,7 +50,7 @@ describe "ProjectsListingController", -> pageCtrl = controller "ProjectsListing", $scope: {} - expect(pageCtrl.projects).to.be.equal(projects) + expect(pageCtrl.projects).to.be.equal(projects.get('all')) it "new project", () -> pageCtrl = controller "ProjectsListing", diff --git a/app/modules/projects/projects.service.coffee b/app/modules/projects/projects.service.coffee index 996944dd..22cd4cc2 100644 --- a/app/modules/projects/projects.service.coffee +++ b/app/modules/projects/projects.service.coffee @@ -2,21 +2,9 @@ taiga = @.taiga groupBy = @.taiga.groupBy class ProjectsService extends taiga.Service - @.$inject = ["tgResources", "$tgAuth", "$projectUrl", "tgLightboxFactory"] + @.$inject = ["tgResources", "$projectUrl", "tgLightboxFactory"] - constructor: (@rs, @auth, @projectUrl, @lightboxFactory) -> - @._currentUserProjects = Immutable.Map() - @._currentUserProjectsById = Immutable.Map() - @._inProgress = false - @._currentUserProjectsPromise = null - - taiga.defineImmutableProperty @, "currentUserProjects", () => return @._currentUserProjects - taiga.defineImmutableProperty @, "currentUserProjectsById", () => return @._currentUserProjectsById - - @.fetchProjects() - - getCurrentUserProjects: -> - return @._currentUserProjectsPromise + constructor: (@rs, @projectUrl, @lightboxFactory) -> getProjectBySlug: (projectSlug) -> return @rs.projects.getProjectBySlug(projectSlug) @@ -47,24 +35,6 @@ class ProjectsService extends taiga.Service return project - fetchProjects: -> - if not @._inProgress - @._inProgress = true - - @._currentUserProjectsPromise = @.getProjectsByUserId(@auth.userData.get("id")) - @._currentUserProjectsPromise.then (projects) => - @._currentUserProjects = @._currentUserProjects.set("all", projects) - @._currentUserProjects = @._currentUserProjects.set("recents", projects.slice(0, 10)) - - @._currentUserProjectsById = Immutable.fromJS(groupBy(projects.toJS(), (p) -> p.id)) - - return @.projects - - @._currentUserProjectsPromise.finally => - @._inProgress = false - - return @._currentUserProjectsPromise - newProject: -> @lightboxFactory.create("tg-lb-create-project", { "class": "wizard-create-project" @@ -74,5 +44,4 @@ class ProjectsService extends taiga.Service @rs.projects.bulkUpdateOrder(sortData).then => @.fetchProjects() -angular.module("taigaProjects").service("tgProjectsService -", ProjectsService) +angular.module("taigaProjects").service("tgProjectsService", ProjectsService) diff --git a/app/modules/projects/projects.service.spec.coffee b/app/modules/projects/projects.service.spec.coffee index e57fc68b..e861574a 100644 --- a/app/modules/projects/projects.service.spec.coffee +++ b/app/modules/projects/projects.service.spec.coffee @@ -35,13 +35,9 @@ describe "tgProjects", -> provide.value "tgLightboxFactory", mocks.lightboxFactory _inject = (callback) -> - inject (_$q_, _$rootScope_) -> + inject (_$q_, _$rootScope_, _tgProjectsService_) -> $q = _$q_ $rootScope = _$rootScope_ - callback() if callback - - _injectService = (callback) -> - inject (_tgProjectsService_) -> projectsService = _tgProjectsService_ callback() if callback @@ -60,75 +56,7 @@ describe "tgProjects", -> _mocks() _inject() - describe "fetch items", -> - projects = [] - - beforeEach -> - projects = [ - {"id": 1, url: 'url-1', tags: ['xx', 'yy', 'aa'], tags_colors: {xx: "red", yy: "blue", aa: "white"}, colorized_tags: [{name: 'aa', color: 'white'}, {name: 'xx', color: 'red'}, {name: 'yy', color: 'blue'}]}, - {"id": 2, url: 'url-2'}, - {"id": 3, url: 'url-3'}, - {"id": 4, url: 'url-4'}, - {"id": 5, url: 'url-5'}, - {"id": 6, url: 'url-6'}, - {"id": 7, url: 'url-7'}, - {"id": 8, url: 'url-8'}, - {"id": 9, url: 'url-9'}, - {"id": 10, url: 'url-10'}, - {"id": 11, url: 'url-11'}, - {"id": 12, url: 'url-12'} - ] - - mocks.resources.projects.getProjectsByUserId = () -> - return $q (resolve) -> - resolve(Immutable.fromJS(projects)) - - _injectService() - - it "all & recents filled", () -> - projectsService.fetchProjects() - $rootScope.$digest() - - expect(projectsService.currentUserProjects.get("all").toJS()).to.be.eql(projects) - expect(projectsService.currentUserProjects.get("recents").toJS()).to.be.eql(projects.slice(0, 10)) - - it "_inProgress change to false when tgResources end", () -> - expect(projectsService._inProgress).to.be.true - - $rootScope.$digest() - - expect(projectsService._inProgress).to.be.false - - it "group projects by id", () -> - $rootScope.$digest() - - expect(projectsService.currentUserProjectsById.size).to.be.equal(12) - expect(projectsService.currentUserProjectsById.toJS()[1].id).to.be.equal(projects[0].id) - - it "add urls in the project object", () -> - $rootScope.$digest() - - expect(projectsService.currentUserProjectsById.toJS()[1].url).to.be.equal("url-1") - expect(projectsService.currentUserProjects.get("all").toJS()[0].url).to.be.equal("url-1") - expect(projectsService.currentUserProjects.get("recents").toJS()[0].url).to.be.equal("url-1") - - it "add sorted colorized_tags project object", () -> - $rootScope.$digest() - - tags = [ - {name: "aa", color: "white"}, - {name: "xx", color: "red"}, - {name: "yy", color: "blue"} - ]; - - - colorized_tags = projectsService.currentUserProjects.get("all").toJS()[0].colorized_tags - - expect(colorized_tags).to.be.eql(tags) - it "newProject, create the wizard lightbox", () -> - _injectService() - projectsService.newProject() expect(mocks.lightboxFactory.create).to.have.been.calledWith("tg-lb-create-project", { @@ -136,8 +64,6 @@ describe "tgProjects", -> }) it "bulkUpdateProjectsOrder and then fetch projects again", () -> - _injectService() - projects_order = [ {"id": 8}, {"id": 2}, @@ -170,8 +96,6 @@ describe "tgProjects", -> expect(projectsService.fetchProjects).to.have.been.calledOnce it "getProjectBySlug", () -> - _injectService() - projectSlug = "project-slug" mocks.resources.projects = {} @@ -181,8 +105,6 @@ describe "tgProjects", -> expect(projectsService.getProjectBySlug(projectSlug)).to.be.true it "getProjectStats", () -> - _injectService() - projectId = 3 mocks.resources.projects = {} @@ -190,3 +112,31 @@ describe "tgProjects", -> mocks.resources.projects.getProjectStats.withArgs(projectId).returns(true) expect(projectsService.getProjectStats(projectId)).to.be.true + + it "getProjectsByUserId", (done) -> + projectId = 3 + + projects = Immutable.fromJS([ + {id: 1, url: 'url-1'}, + {id: 2, url: 'url-2', tags: ['xx', 'yy', 'aa'], tags_colors: {xx: "red", yy: "blue", aa: "white"}} + ]) + + mocks.resources.projects = {} + mocks.resources.projects.getProjectsByUserId = sinon.stub().promise() + mocks.resources.projects.getProjectsByUserId.withArgs(projectId).resolve(projects) + + projectsService.getProjectsByUserId(projectId).then (projects) -> + expect(projects.toJS()).to.be.eql([{ + id: 1, + url: 'url-1' + }, + { + id: 2, + url: 'url-2', + tags: ['xx', 'yy', 'aa'], + tags_colors: {xx: "red", yy: "blue", aa: "white"}, + colorized_tags: [{name: 'aa', color: 'white'}, {name: 'xx', color: 'red'}, {name: 'yy', color: 'blue'}] + } + ]) + + done() diff --git a/app/modules/services/current-user.service.coffee b/app/modules/services/current-user.service.coffee new file mode 100644 index 00000000..a25110db --- /dev/null +++ b/app/modules/services/current-user.service.coffee @@ -0,0 +1,45 @@ +taiga = @.taiga + +groupBy = @.taiga.groupBy + +class CurrentUserService + @.$inject = [ + "tgProjectsService", + "$tgStorage" + ] + + constructor: (@projectsService, @storageService) -> + @._user = Immutable.Map() + @._projects = Immutable.Map() + @._projectsById = Immutable.Map() + + taiga.defineImmutableProperty @, "projects", () => return @._projects + taiga.defineImmutableProperty @, "projectsById", () => return @._projectsById + + getUser: () -> + if !@._user.size + userData = @storageService.get("userInfo") + userData = Immutable.fromJS(userData) + @.setUser(userData) if userData + + return @._user + + setUser: (user) -> + @._user = user + + return @._loadUserInfo() + + _loadProjects: () -> + return @projectsService.getProjectsByUserId(@._user.get("id")) + .then (projects) => + @._projects = @._projects.set("all", projects) + @._projects = @._projects.set("recents", projects.slice(0, 10)) + + @._projectsById = Immutable.fromJS(groupBy(projects.toJS(), (p) -> p.id)) + + return @.projects + + _loadUserInfo: () -> + return @._loadProjects() + +angular.module("taigaCommon").service("tgCurrentUserService", CurrentUserService) diff --git a/app/modules/services/current-user.service.spec.coffee b/app/modules/services/current-user.service.spec.coffee new file mode 100644 index 00000000..a5fb23b4 --- /dev/null +++ b/app/modules/services/current-user.service.spec.coffee @@ -0,0 +1,77 @@ +describe "tgCurrentUserService", -> + currentUserService = provide = null + mocks = {} + + _mockTgStorage = () -> + mocks.storageService = { + get: sinon.stub() + } + + provide.value "$tgStorage", mocks.storageService + + _mockProjectsService = () -> + mocks.projectsService = { + getProjectsByUserId: sinon.stub().promise() + } + + provide.value "tgProjectsService", mocks.projectsService + + _inject = (callback) -> + inject (_tgCurrentUserService_) -> + currentUserService = _tgCurrentUserService_ + callback() if callback + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgStorage() + _mockProjectsService() + + return null + + _setup = -> + _mocks() + + beforeEach -> + module "taigaCommon" + _setup() + _inject() + + describe "get user", () -> + it "return the user if it is defined", () -> + currentUserService._user = 123 + + expect(currentUserService.getUser()).to.be.equal(123) + + it "get user form storage if it is not defined", () -> + user = {id: 1, name: "fake1"} + + currentUserService.setUser = sinon.spy() + mocks.storageService.get.withArgs("userInfo").returns(user) + + _user = currentUserService.getUser() + + expect(currentUserService.setUser).to.be.calledOnce + + it "set user and load user info", (done) -> + user = Immutable.fromJS({id: 1, name: "fake1"}) + + projects = Immutable.fromJS([ + {id: 1, name: "fake1"}, + {id: 2, name: "fake2"}, + {id: 3, name: "fake3"}, + {id: 4, name: "fake4"}, + {id: 5, name: "fake5"} + ]) + + mocks.projectsService.getProjectsByUserId = sinon.stub().promise() + mocks.projectsService.getProjectsByUserId.withArgs(user.get("id")).resolve(projects) + + currentUserService.setUser(user).then () -> + expect(currentUserService._user).to.be.equal(user) + expect(currentUserService.projects.get("all").size).to.be.equal(5) + expect(currentUserService.projects.get("recents").size).to.be.equal(5) + expect(currentUserService.projectsById.size).to.be.equal(5) + expect(currentUserService.projectsById.get("3").get("name")).to.be.equal("fake3") + + done() diff --git a/bower.json b/bower.json index 7989c254..c16cfedc 100644 --- a/bower.json +++ b/bower.json @@ -79,7 +79,8 @@ "angular-translate-interpolation-messageformat": "~2.6.1", "ngInfiniteScroll": "1.0.0", "eventemitter2": "~0.4.14", - "immutable": "~3.7.2" + "immutable": "~3.7.2", + "bluebird": "~2.9.25" }, "resolutions": { "lodash": "~2.4.1", diff --git a/gulpfile.js b/gulpfile.js index b1017fb8..bb22d5d7 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -116,6 +116,7 @@ paths.coffee_order = [ ]; paths.libs = [ + paths.vendor + "bluebird/js/browser/bluebird.js", paths.vendor + "jquery/dist/jquery.js", paths.vendor + "lodash/dist/lodash.js", paths.vendor + "emoticons/lib/emoticons.js", diff --git a/karma.conf.js b/karma.conf.js index ae7b045e..209da9ec 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -18,7 +18,7 @@ module.exports = function(config) { 'karma.app.conf.js', 'dist/js/libs.js', 'node_modules/angular-mocks/angular-mocks.js', - 'node_modules/bluebird/js/browser/bluebird.js', + 'vendor/bluebird/js/browser/bluebird.js', 'test-utils.js', 'dist/js/app.js', 'dist/js/templates.js', diff --git a/package.json b/package.json index 46b73b46..ad9f67d3 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ }, "devDependencies": { "angular-mocks": "^1.3.15", - "bluebird": "^2.9.21", "chai": "^2.2.0", "cli-color": "^0.3.3", "coffee-script": "^1.9.1", From 87e69334ac43aa5cb88a159549ca307d5623a2d3 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 19 May 2015 09:25:30 +0200 Subject: [PATCH 166/366] profile bar image 100% --- app/modules/profile/styles/profile-bar.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/modules/profile/styles/profile-bar.scss b/app/modules/profile/styles/profile-bar.scss index 713dae40..4149c612 100644 --- a/app/modules/profile/styles/profile-bar.scss +++ b/app/modules/profile/styles/profile-bar.scss @@ -19,6 +19,7 @@ } .profile-img { max-width: 100%; + width: 100%; } .profile-edition { @extend %large; From 739b40603f14246eeef3a982a9e3a40f507ad693 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 19 May 2015 09:44:44 +0200 Subject: [PATCH 167/366] rename projects-listing directive to sort-projects --- .../sort-projects.controller.coffee} | 0 .../sort-projects.controller.spec.coffee} | 0 .../sort-projects.directive.coffee} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename app/modules/projects/{listing/projects-listing.controller.coffee => sort-projects/sort-projects.controller.coffee} (100%) rename app/modules/projects/{listing/projects-listing.controller.spec.coffee => sort-projects/sort-projects.controller.spec.coffee} (100%) rename app/modules/projects/{listing/projects-listing.directive.coffee => sort-projects/sort-projects.directive.coffee} (100%) diff --git a/app/modules/projects/listing/projects-listing.controller.coffee b/app/modules/projects/sort-projects/sort-projects.controller.coffee similarity index 100% rename from app/modules/projects/listing/projects-listing.controller.coffee rename to app/modules/projects/sort-projects/sort-projects.controller.coffee diff --git a/app/modules/projects/listing/projects-listing.controller.spec.coffee b/app/modules/projects/sort-projects/sort-projects.controller.spec.coffee similarity index 100% rename from app/modules/projects/listing/projects-listing.controller.spec.coffee rename to app/modules/projects/sort-projects/sort-projects.controller.spec.coffee diff --git a/app/modules/projects/listing/projects-listing.directive.coffee b/app/modules/projects/sort-projects/sort-projects.directive.coffee similarity index 100% rename from app/modules/projects/listing/projects-listing.directive.coffee rename to app/modules/projects/sort-projects/sort-projects.directive.coffee From 680d46a40efe8112d179ebdf5fe6ce6ce6ebb8bb Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 19 May 2015 11:45:21 +0200 Subject: [PATCH 168/366] fix bulk projects update --- .../sort-projects.directive.coffee | 5 +++-- .../projects-listing.coffee} | 0 .../projects-listing.controller.spec.coffee} | 0 app/modules/projects/projects.service.coffee | 3 +-- .../projects/projects.service.spec.coffee | 16 ++++------------ app/modules/services/current-user.service.coffee | 4 ++++ .../services/current-user.service.spec.coffee | 15 ++++++++++++++- 7 files changed, 26 insertions(+), 17 deletions(-) rename app/modules/projects/{sort-projects => components}/sort-projects.directive.coffee (86%) rename app/modules/projects/{sort-projects/sort-projects.controller.coffee => listing/projects-listing.coffee} (100%) rename app/modules/projects/{sort-projects/sort-projects.controller.spec.coffee => listing/projects-listing.controller.spec.coffee} (100%) diff --git a/app/modules/projects/sort-projects/sort-projects.directive.coffee b/app/modules/projects/components/sort-projects.directive.coffee similarity index 86% rename from app/modules/projects/sort-projects/sort-projects.directive.coffee rename to app/modules/projects/components/sort-projects.directive.coffee index cd5f90fa..17ed9261 100644 --- a/app/modules/projects/sort-projects/sort-projects.directive.coffee +++ b/app/modules/projects/components/sort-projects.directive.coffee @@ -1,4 +1,4 @@ -SortProjectsDirective = (projectsService) -> +SortProjectsDirective = (currentUserService) -> link = (scope, el, attrs, ctrl) -> itemEl = null @@ -8,6 +8,7 @@ SortProjectsDirective = (projectsService) -> axis: "y" opacity: .95 placeholder: 'placeholder' + containment: $(".master") }) el.on "sortstop", (event, ui) -> @@ -24,7 +25,7 @@ SortProjectsDirective = (projectsService) -> for value, index in sorted_project_ids sortData.push({"project_id": value, "order":index}) - projectsService.bulkUpdateProjectsOrder(sortData) + currentUserService.bulkUpdateProjectsOrder(sortData) directive = { scope: { diff --git a/app/modules/projects/sort-projects/sort-projects.controller.coffee b/app/modules/projects/listing/projects-listing.coffee similarity index 100% rename from app/modules/projects/sort-projects/sort-projects.controller.coffee rename to app/modules/projects/listing/projects-listing.coffee diff --git a/app/modules/projects/sort-projects/sort-projects.controller.spec.coffee b/app/modules/projects/listing/projects-listing.controller.spec.coffee similarity index 100% rename from app/modules/projects/sort-projects/sort-projects.controller.spec.coffee rename to app/modules/projects/listing/projects-listing.controller.spec.coffee diff --git a/app/modules/projects/projects.service.coffee b/app/modules/projects/projects.service.coffee index 22cd4cc2..d9887f18 100644 --- a/app/modules/projects/projects.service.coffee +++ b/app/modules/projects/projects.service.coffee @@ -41,7 +41,6 @@ class ProjectsService extends taiga.Service }) bulkUpdateProjectsOrder: (sortData) -> - @rs.projects.bulkUpdateOrder(sortData).then => - @.fetchProjects() + return @rs.projects.bulkUpdateOrder(sortData) angular.module("taigaProjects").service("tgProjectsService", ProjectsService) diff --git a/app/modules/projects/projects.service.spec.coffee b/app/modules/projects/projects.service.spec.coffee index e861574a..56911cd9 100644 --- a/app/modules/projects/projects.service.spec.coffee +++ b/app/modules/projects/projects.service.spec.coffee @@ -1,4 +1,4 @@ -describe "tgProjects", -> +describe "tgProjectsService", -> projectsService = provide = $rootScope = null $q = null mocks = {} @@ -79,21 +79,13 @@ describe "tgProjects", -> {"id": 12}, ] - thenStub = sinon.stub() - mocks.resources.projects = {} mocks.resources.projects.bulkUpdateOrder = sinon.stub() - mocks.resources.projects.bulkUpdateOrder.withArgs(projects_order).returns({ - then: thenStub - }) + mocks.resources.projects.bulkUpdateOrder.withArgs(projects_order).returns(true) - projectsService.fetchProjects = sinon.stub() + result = projectsService.bulkUpdateProjectsOrder(projects_order) - projectsService.bulkUpdateProjectsOrder(projects_order) - - thenStub.callArg(0) - - expect(projectsService.fetchProjects).to.have.been.calledOnce + expect(result).to.be.true it "getProjectBySlug", () -> projectSlug = "project-slug" diff --git a/app/modules/services/current-user.service.coffee b/app/modules/services/current-user.service.coffee index a25110db..652e7d68 100644 --- a/app/modules/services/current-user.service.coffee +++ b/app/modules/services/current-user.service.coffee @@ -29,6 +29,10 @@ class CurrentUserService return @._loadUserInfo() + bulkUpdateProjectsOrder: (sortData) -> + @projectsService.bulkUpdateProjectsOrder(sortData).then () => + @._loadProjects() + _loadProjects: () -> return @projectsService.getProjectsByUserId(@._user.get("id")) .then (projects) => diff --git a/app/modules/services/current-user.service.spec.coffee b/app/modules/services/current-user.service.spec.coffee index a5fb23b4..200e1e12 100644 --- a/app/modules/services/current-user.service.spec.coffee +++ b/app/modules/services/current-user.service.spec.coffee @@ -11,7 +11,8 @@ describe "tgCurrentUserService", -> _mockProjectsService = () -> mocks.projectsService = { - getProjectsByUserId: sinon.stub().promise() + getProjectsByUserId: sinon.stub().promise(), + bulkUpdateProjectsOrder: sinon.stub().promise() } provide.value "tgProjectsService", mocks.projectsService @@ -75,3 +76,15 @@ describe "tgCurrentUserService", -> expect(currentUserService.projectsById.get("3").get("name")).to.be.equal("fake3") done() + + it "bulkUpdateProjectsOrder and reload projects", (done) -> + fakeData = [{id: 1, id: 2}] + + currentUserService._loadProjects = sinon.spy() + + mocks.projectsService.bulkUpdateProjectsOrder.withArgs(fakeData).resolve() + + currentUserService.bulkUpdateProjectsOrder(fakeData).then () -> + expect(currentUserService._loadProjects).to.be.callOnce + + done() From 220fa4132e984cc126e2e147f711f3e3299fa145 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 19 May 2015 13:27:17 +0200 Subject: [PATCH 169/366] fix draggable items position --- app/js/jquery.ui.git-custom.js | 20161 +++++----------- .../components/sort-projects.directive.coffee | 1 - 2 files changed, 5411 insertions(+), 14751 deletions(-) diff --git a/app/js/jquery.ui.git-custom.js b/app/js/jquery.ui.git-custom.js index 327fb2a1..9ad0133c 100644 --- a/app/js/jquery.ui.git-custom.js +++ b/app/js/jquery.ui.git-custom.js @@ -1,24 +1,36 @@ -/*! jQuery UI - v1.11.1-pre - 2014-07-24 +/*! jQuery UI - v1.11.4 - 2015-05-19 * http://jqueryui.com -* Includes: core.js, widget.js, mouse.js, draggable.js, droppable.js, resizable.js, selectable.js, sortable.js, effect.js, accordion.js, autocomplete.js, button.js, datepicker.js, dialog.js, effect-blind.js, effect-bounce.js, effect-clip.js, effect-drop.js, effect-explode.js, effect-fade.js, effect-fold.js, effect-highlight.js, effect-puff.js, effect-pulsate.js, effect-scale.js, effect-shake.js, effect-size.js, effect-slide.js, effect-transfer.js, menu.js, position.js, progressbar.js, selectmenu.js, slider.js, spinner.js, tabs.js, tooltip.js -* Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */ +* Includes: core.js, widget.js, mouse.js, position.js, draggable.js, droppable.js, resizable.js, selectable.js, sortable.js, autocomplete.js, menu.js +* Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */ + (function( factory ) { if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. - define( [ "jquery" ], factory ); + define([ "jquery" ], factory ); } else { // Browser globals factory( jQuery ); } }(function( $ ) { +/*! + * jQuery UI Core 1.11.4 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/category/ui-core/ + */ + // $.ui might exist from components with no dependencies, e.g., $.ui.position $.ui = $.ui || {}; $.extend( $.ui, { - version: "@VERSION", + version: "1.11.4", keyCode: { BACKSPACE: 8, @@ -42,15 +54,16 @@ $.extend( $.ui, { // plugins $.fn.extend({ - scrollParent: function() { + scrollParent: function( includeHidden ) { var position = this.css( "position" ), excludeStaticParent = position === "absolute", + overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/, scrollParent = this.parents().filter( function() { var parent = $( this ); if ( excludeStaticParent && parent.css( "position" ) === "static" ) { return false; } - return (/(auto|scroll)/).test( parent.css( "overflow" ) + parent.css( "overflow-y" ) + parent.css( "overflow-x" ) ); + return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) + parent.css( "overflow-x" ) ); }).eq( 0 ); return position === "fixed" || !scrollParent.length ? $( this[ 0 ].ownerDocument || document ) : scrollParent; @@ -87,10 +100,10 @@ function focusable( element, isTabIndexNotNaN ) { if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) { return false; } - img = $( "img[usemap=#" + mapName + "]" )[0]; + img = $( "img[usemap='#" + mapName + "']" )[ 0 ]; return !!img && visible( img ); } - return ( /input|select|textarea|button|object/.test( nodeName ) ? + return ( /^(input|select|textarea|button|object)$/.test( nodeName ) ? !element.disabled : "a" === nodeName ? element.href || isTabIndexNotNaN : @@ -294,19 +307,18 @@ $.ui.plugin = { } }; -})); -(function( factory ) { - if ( typeof define === "function" && define.amd ) { +/*! + * jQuery UI Widget 1.11.4 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/jQuery.widget/ + */ - // AMD. Register as an anonymous module. - define( [ "jquery" ], factory ); - } else { - - // Browser globals - factory( jQuery ); - } -}(function( $ ) { var widget_uuid = 0, widget_slice = Array.prototype.slice; @@ -324,7 +336,7 @@ $.cleanData = (function( orig ) { } // http://bugs.jquery.com/ticket/8235 - } catch( e ) {} + } catch ( e ) {} } orig( elems ); }; @@ -478,11 +490,6 @@ $.widget.bridge = function( name, object ) { args = widget_slice.call( arguments, 1 ), returnValue = this; - // allow multiple hashes to be passed on init - options = !isMethodCall && args.length ? - $.widget.extend.apply( null, [ options ].concat(args) ) : - options; - if ( isMethodCall ) { this.each(function() { var methodValue, @@ -507,6 +514,12 @@ $.widget.bridge = function( name, object ) { } }); } else { + + // Allow multiple hashes to be passed on init + if ( args.length ) { + options = $.widget.extend.apply( null, [ options ].concat(args) ); + } + this.each(function() { var instance = $.data( this, fullName ); if ( instance ) { @@ -542,10 +555,6 @@ $.Widget.prototype = { this.element = $( element ); this.uuid = widget_uuid++; this.eventNamespace = "." + this.widgetName + this.uuid; - this.options = $.widget.extend( {}, - this.options, - this._getCreateOptions(), - options ); this.bindings = $(); this.hoverable = $(); @@ -568,6 +577,11 @@ $.Widget.prototype = { this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); } + this.options = $.widget.extend( {}, + this.options, + this._getCreateOptions(), + options ); + this._create(); this._trigger( "create", null, this._getCreateEventData() ); this._init(); @@ -730,8 +744,14 @@ $.Widget.prototype = { }, _off: function( element, eventName ) { - eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace; + eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + + this.eventNamespace; element.unbind( eventName ).undelegate( eventName ); + + // Clear the stack to avoid memory leaks (#10056) + this.bindings = $( this.bindings.not( element ).get() ); + this.focusable = $( this.focusable.not( element ).get() ); + this.hoverable = $( this.hoverable.not( element ).get() ); }, _delay: function( handler, delay ) { @@ -789,6 +809,7 @@ $.Widget.prototype = { } } } + this.element.trigger( event, data ); return !( $.isFunction( callback ) && callback.apply( this.element[0], [ event ].concat( data ) ) === false || @@ -832,32 +853,28 @@ $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { }; }); -return $.widget; +var widget = $.widget; -})); -(function( factory ) { - if ( typeof define === "function" && define.amd ) { +/*! + * jQuery UI Mouse 1.11.4 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/mouse/ + */ - // AMD. Register as an anonymous module. - define([ - "jquery", - "./widget" - ], factory ); - } else { - - // Browser globals - factory( jQuery ); - } -}(function( $ ) { var mouseHandled = false; $( document ).mouseup( function() { mouseHandled = false; }); -return $.widget("ui.mouse", { - version: "@VERSION", +var mouse = $.widget("ui.mouse", { + version: "1.11.4", options: { cancel: "input,textarea,button,select,option", distance: 1, @@ -898,6 +915,8 @@ return $.widget("ui.mouse", { return; } + this._mouseMoved = false; + // we may have missed mouseup (out of window) (this._mouseStarted && this._mouseUp(event)); @@ -951,13 +970,23 @@ return $.widget("ui.mouse", { }, _mouseMove: function(event) { - // IE mouseup check - mouseup happened when mouse was out of window - if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) { - return this._mouseUp(event); + // Only check for mouseups outside the document if you've moved inside the document + // at least once. This prevents the firing of mouseup in the case of IE<9, which will + // fire a mousemove event if content is placed under the cursor. See #7778 + // Support: IE <9 + if ( this._mouseMoved ) { + // IE mouseup check - mouseup happened when mouse was out of window + if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) { + return this._mouseUp(event); - // Iframe mouseup check - mouseup occurred in another document - } else if ( !event.which ) { - return this._mouseUp( event ); + // Iframe mouseup check - mouseup occurred in another document + } else if ( !event.which ) { + return this._mouseUp( event ); + } + } + + if ( event.which || event.button ) { + this._mouseMoved = true; } if (this._mouseStarted) { @@ -1012,11685 +1041,18 @@ return $.widget("ui.mouse", { _mouseCapture: function(/* event */) { return true; } }); -})); - -(function( factory ) { - if ( typeof define === "function" && define.amd ) { - - // AMD. Register as an anonymous module. - define([ - "jquery", - "./core", - "./mouse", - "./widget" - ], factory ); - } else { - - // Browser globals - factory( jQuery ); - } -}(function( $ ) { - -$.widget("ui.draggable", $.ui.mouse, { - version: "@VERSION", - widgetEventPrefix: "drag", - options: { - addClasses: true, - appendTo: "parent", - axis: false, - connectToSortable: false, - containment: false, - cursor: "auto", - cursorAt: false, - grid: false, - handle: false, - helper: "original", - iframeFix: false, - opacity: false, - refreshPositions: false, - revert: false, - revertDuration: 500, - scope: "default", - scroll: true, - scrollSensitivity: 20, - scrollSpeed: 20, - snap: false, - snapMode: "both", - snapTolerance: 20, - stack: false, - zIndex: false, - - // callbacks - drag: null, - start: null, - stop: null - }, - _create: function() { - - if (this.options.helper === "original" && !(/^(?:r|a|f)/).test(this.element.css("position"))) { - this.element[0].style.position = "relative"; - } - if (this.options.addClasses){ - this.element.addClass("ui-draggable"); - } - if (this.options.disabled){ - this.element.addClass("ui-draggable-disabled"); - } - this._setHandleClassName(); - - this._mouseInit(); - }, - - _setOption: function( key, value ) { - this._super( key, value ); - if ( key === "handle" ) { - this._removeHandleClassName(); - this._setHandleClassName(); - } - }, - - _destroy: function() { - if ( ( this.helper || this.element ).is( ".ui-draggable-dragging" ) ) { - this.destroyOnClear = true; - return; - } - this.element.removeClass( "ui-draggable ui-draggable-dragging ui-draggable-disabled" ); - this._removeHandleClassName(); - this._mouseDestroy(); - }, - - _mouseCapture: function(event) { - - var document = this.document[ 0 ], - o = this.options; - - // support: IE9 - // IE9 throws an "Unspecified error" accessing document.activeElement from an